mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-19 18:14:44 +00:00
Log-Related Fixes (2023-12-5) (#1149)
* tighened up the iterative processing aspects of kill assist calculations; wrong database query for assists; assumption of flag being acquired when it really wasn't; assumption of facility capture start when no longer represented * mainly, the spacing * augmented the calculations for bep * adjustment to calculations for the long life bonus experience and to the lifespan experience limits
This commit is contained in:
parent
ba7adee547
commit
85957670ba
|
|
@ -231,7 +231,7 @@ game {
|
|||
# Battle experience points
|
||||
# BEP is to be calculated in relation to how valuable a kill is worth.
|
||||
bep = {
|
||||
# After all calculations are complete, multiple the result by this value
|
||||
# After all calculations are complete, multiply the result by this value
|
||||
rate = 1.0
|
||||
# These numbers are to determine the starting value for a particular kill
|
||||
base = {
|
||||
|
|
@ -240,12 +240,103 @@ game {
|
|||
# If the player who died ever utilized a mechanized assault exo-suit
|
||||
as-max = 250
|
||||
# The player who died got at least one kill
|
||||
with-kills = 100
|
||||
with-kills = 150
|
||||
# The player who died was mounted in a vehicle at the time of death
|
||||
as-mounted = 100
|
||||
# The player who died after having been in the game world for a while after spawning.
|
||||
# Dying before this is often called a "spawn kill".
|
||||
mature = 50
|
||||
# How long it normally takes for a player who has respawned to naturally lose the status of "green" when being inactive.
|
||||
# See `base.nature`.
|
||||
maturity-time = 30000
|
||||
}
|
||||
life-span = {
|
||||
# The experience value of a player's lifespan is measured in intervals.
|
||||
# Per interval, after all calculations are complete, multiply the result by this value
|
||||
life-span-threat-rate = 1.0
|
||||
# The experience value of using certain equipment per interval of time during playtime (consider to be per second).
|
||||
# (key, value) where key is technically the index of an ExoSuitType or an object class id and value is the growth
|
||||
threat-assessment-of = [
|
||||
{
|
||||
id = 0
|
||||
value = 1.25
|
||||
},
|
||||
{
|
||||
id = 1
|
||||
value = 1.5
|
||||
},
|
||||
{
|
||||
id = 2
|
||||
value = 2.15
|
||||
},
|
||||
{
|
||||
id = 3
|
||||
value = 1.25
|
||||
},
|
||||
{
|
||||
id = 4
|
||||
value = 1.0
|
||||
},
|
||||
{
|
||||
id = 258
|
||||
value = 10.0
|
||||
},
|
||||
{
|
||||
id = 410
|
||||
value = 0
|
||||
},
|
||||
{
|
||||
id = 608
|
||||
value = 0
|
||||
}
|
||||
]
|
||||
# The maximum experience ceiling during playtime based on the use of certain equipment.
|
||||
# (key, value) where key is technically the index of an ExoSuitType or an object class id and value is the maximum
|
||||
max-threat-level = [
|
||||
{
|
||||
id = 0
|
||||
level = 2000
|
||||
},
|
||||
{
|
||||
id = 1
|
||||
level = 2000
|
||||
},
|
||||
{
|
||||
id = 2
|
||||
level = 5000
|
||||
},
|
||||
{
|
||||
id = 3
|
||||
level = 2000
|
||||
},
|
||||
{
|
||||
id = 4
|
||||
level = 900
|
||||
},
|
||||
{
|
||||
id = 258
|
||||
level = 0
|
||||
},
|
||||
{
|
||||
id = 410
|
||||
level = 0
|
||||
},
|
||||
{
|
||||
id = 608
|
||||
level = 0
|
||||
}
|
||||
]
|
||||
}
|
||||
revenge = {
|
||||
# If player A kills another player B who killed player A just previously, this is the percentage of experience to deposit.
|
||||
# The kill event must have been the exact previous life's death after revive or respawn.
|
||||
# This only applies if experience is set to 0.
|
||||
# Set to zero and experience = 0 to ignore revenge.
|
||||
rate = 0.15
|
||||
# If player A kills another player B who killed player A just previously, deposit this experience.
|
||||
# The kill event must have been the exact previous life's death after revive or respawn.
|
||||
# Set to zero to reuse the experience value from the previous kill event.
|
||||
experience = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -253,7 +344,7 @@ game {
|
|||
# The events from which support experience rises are numerous.
|
||||
# Calculation is determined by the selection of an "event" that decides how the values are combined.
|
||||
sep = {
|
||||
# After all calculations are complete, multiple the result by this value
|
||||
# After all calculations are complete, multiply the result by this value
|
||||
rate = 1.0
|
||||
# When using an advanced nanite transport to deposit into the resource silo of a major facility,
|
||||
# for reaching the maximum amount of a single deposit,
|
||||
|
|
@ -376,6 +467,8 @@ game {
|
|||
# When the cep has to be capped for squad size, calculate a small amount to add to the capped value
|
||||
squad-size-limit-overflow-multiplier = 0.2
|
||||
}
|
||||
# When summing bep to produce facility capture base rewards, multiply the result by this value
|
||||
facility-capture-rate = 0.5
|
||||
}
|
||||
|
||||
# The game's official maximum battle rank is 40.
|
||||
|
|
|
|||
|
|
@ -643,7 +643,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
player.ExoSuit = exosuit //changes the value of MaxArmor to reflect the new exo-suit
|
||||
val toMaxArmor = player.MaxArmor
|
||||
val toArmor = toMaxArmor
|
||||
if (originalArmor != toMaxArmor) {
|
||||
if (originalSuit != exosuit || originalArmor != toMaxArmor) {
|
||||
player.LogActivity(RepairFromExoSuitChange(exosuit, toMaxArmor - originalArmor))
|
||||
}
|
||||
player.Armor = toMaxArmor
|
||||
|
|
|
|||
|
|
@ -191,6 +191,7 @@ object ExoSuitDefinition {
|
|||
case PlanetSideEmpire.TR => GlobalDefinitions.TRMAX.Use
|
||||
case PlanetSideEmpire.NC => GlobalDefinitions.NCMAX.Use
|
||||
case PlanetSideEmpire.VS => GlobalDefinitions.VSMAX.Use
|
||||
case _ => GlobalDefinitions.Standard.Use
|
||||
}
|
||||
case _ => GlobalDefinitions.Standard.Use
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,10 +110,10 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
|||
)
|
||||
//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(
|
||||
val baseExperienceFromFacilityCapture: Long = (FacilityHackParticipation.calculateExperienceFromKills(
|
||||
killsByPlayersNotInTower,
|
||||
contributionOpposingSize
|
||||
)
|
||||
) * Config.app.game.experience.facilityCaptureRate).toLong
|
||||
val events = building.Zone.AvatarEvents
|
||||
val buildingId = building.GUID.guid
|
||||
val zoneNumber = building.Zone.Number
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
|
|||
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(
|
||||
val baseExperienceFromFacilityCapture: Long = (FacilityHackParticipation.calculateExperienceFromKills(
|
||||
FacilityHackParticipation.allocateKillsByPlayers(
|
||||
building.Position,
|
||||
building.Definition.SOIRadius.toFloat,
|
||||
|
|
@ -60,7 +60,7 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
|
|||
contributionVictor
|
||||
),
|
||||
contributionOpposingSize
|
||||
)
|
||||
) * Config.app.game.experience.facilityCaptureRate).toLong
|
||||
//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) {
|
||||
|
|
|
|||
|
|
@ -50,13 +50,28 @@ trait SupportActivityCausedByAnother {
|
|||
def amount: Int
|
||||
}
|
||||
|
||||
trait ExoSuitChange {
|
||||
def exosuit: ExoSuitType.Value
|
||||
}
|
||||
|
||||
trait CommonExoSuitChange extends ExoSuitChange {
|
||||
def src: SourceEntry
|
||||
|
||||
def exosuit: ExoSuitType.Value = {
|
||||
src match {
|
||||
case p: PlayerSource => p.ExoSuit
|
||||
case _ => ExoSuitType.Standard
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait IncarnationActivity extends GeneralActivity
|
||||
|
||||
final case class SpawningActivity(src: SourceEntry, zoneNumber: Int, unit: Option[SourceEntry])
|
||||
extends IncarnationActivity
|
||||
extends IncarnationActivity with CommonExoSuitChange
|
||||
|
||||
final case class ReconstructionActivity(src: SourceEntry, zoneNumber: Int, unit: Option[SourceEntry])
|
||||
extends IncarnationActivity
|
||||
extends IncarnationActivity with CommonExoSuitChange
|
||||
|
||||
final case class RevivingActivity(target: SourceEntry, user: PlayerSource, amount: Int, equipment: EquipmentDefinition)
|
||||
extends IncarnationActivity with SupportActivityCausedByAnother
|
||||
|
|
@ -154,7 +169,7 @@ final case class HealFromImplant(implant: ImplantType, amount: Int)
|
|||
extends HealingActivity
|
||||
|
||||
final case class RepairFromExoSuitChange(exosuit: ExoSuitType.Value, amount: Int)
|
||||
extends RepairingActivity
|
||||
extends RepairingActivity with ExoSuitChange
|
||||
|
||||
final case class RepairFromKit(kit_def: KitDefinition, amount: Int)
|
||||
extends RepairingActivity()
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import net.psforever.objects.vital.interaction.{Adversarial, DamageResult}
|
|||
import net.psforever.objects.vital.{DamagingActivity, HealingActivity, InGameActivity, RepairingActivity, RevivingActivity, SpawningActivity}
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
import net.psforever.util.Config
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration._
|
||||
|
||||
|
|
@ -145,161 +145,11 @@ object KillAssists {
|
|||
.orElse {
|
||||
limitHistoryToThisLife(history)
|
||||
.lastOption
|
||||
.collect { case dam: DamagingActivity =>
|
||||
val res = dam.data
|
||||
(res, res.adversarial.get.attacker)
|
||||
}
|
||||
.collect { case dam: DamagingActivity if dam.data.adversarial.nonEmpty => dam.data }
|
||||
.map { data => (data, data.adversarial.get.attacker) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* "Menace" is a crude measurement of how much consistent destructive power a player has been demonstrating.
|
||||
* Within the last ten kills, the rate of the player's killing speed is measured.
|
||||
* The measurement - a "streak" in modern lingo - is transformed into the form of an `Integer` for simplicity.
|
||||
* @param player the player
|
||||
* @param mercy a time value that can be used to continue a missed streak
|
||||
* @return an integer between 0 and 7;
|
||||
* 0 is no kills,
|
||||
* 1 is some kills,
|
||||
* 2-7 is a menace score;
|
||||
* there is no particular meaning behind different menace scores ascribed by this function
|
||||
* but the range allows for progressive distinction
|
||||
* @see `qualifiedTimeDifferences`
|
||||
* @see `takeWhileLess`
|
||||
*/
|
||||
private[exp] def calculateMenace(player: PlayerSource, mercy: Long = 5000L): Int = {
|
||||
val maxDelayDiff: Long = 45000L
|
||||
val minDelayDiff: Long = 20000L
|
||||
val allKills = player.progress.kills
|
||||
//the very first kill must have been within the max delay (but does not count towards menace)
|
||||
if (allKills.headOption.exists { System.currentTimeMillis() - _.time.toDate.getTime < maxDelayDiff}) {
|
||||
allKills match {
|
||||
case _ :: kills if kills.size > 3 =>
|
||||
val (continuations, restsBetweenKills) =
|
||||
qualifiedTimeDifferences(
|
||||
kills.map(_.time.toDate.getTime).iterator,
|
||||
maxValidDiffCount = 10,
|
||||
maxDelayDiff,
|
||||
minDelayDiff
|
||||
)
|
||||
.partition(_ > minDelayDiff)
|
||||
math.max(
|
||||
1,
|
||||
math.floor(math.sqrt(
|
||||
math.max(0, takeWhileLess(restsBetweenKills, testValue = 20000L, mercy).size - 1) + /*max=8*/
|
||||
math.max(0, takeWhileLess(restsBetweenKills, testValue = 10000L, mercy).size - 5) * 3 + /*max=12*/
|
||||
math.max(0, takeWhileLess(restsBetweenKills, testValue = 5000L, mercy = 1000L).size - 4) * 7 /*max=35*/
|
||||
) - continuations.size)
|
||||
).toInt
|
||||
case _ =>
|
||||
1
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a list of times
|
||||
* and produce a list of delays between those entries less than a maximum time delay.
|
||||
* These are considered "qualifying".
|
||||
* Count a certain number of time delays that fall within a minimum threshold
|
||||
* and stop when that minimum count is achieved.
|
||||
* These are considered "valid".
|
||||
* The final product should be a new list of the successive delays from the first list
|
||||
* containing both qualified and valid entries,
|
||||
* stopping at either the first unqualified delay or the last valid delay or at exhaustion of the original list.
|
||||
* @param iter unfiltered list of times (ms)
|
||||
* @param maxValidDiffCount maximum number of valid entries in the final list of time differences;
|
||||
* see `validTimeEntryCount`
|
||||
* @param maxDiff exclusive amount of time allowed between qualifying entries;
|
||||
* include any time difference within this delay;
|
||||
* these entries are "qualifying" but are not "valid"
|
||||
* @param minDiff inclusive amount of time difference allowed between valid entries;
|
||||
* include time differences in this delay
|
||||
* these entries are "valid" and should increment the counter `validTimeEntryCount`
|
||||
* @return list of qualifying time differences (ms)
|
||||
*/
|
||||
/*
|
||||
Parameters governed by recursion:
|
||||
@param diffList ongoing list of qualifying time differences (ms)
|
||||
@param diffExtensionList accumulation of entries greater than the `minTimeEntryDiff`
|
||||
but less that the `minTimeEntryDiff`;
|
||||
holds qualifying time differences
|
||||
that will be included before the next valid time difference
|
||||
@param validDiffCount currently number of valid time entries in the qualified time list;
|
||||
see `maxValidTimeEntryCount`
|
||||
@param previousTime previous qualifying entry time;
|
||||
by default, current time (ms)
|
||||
*/
|
||||
@tailrec
|
||||
private def qualifiedTimeDifferences(
|
||||
iter: Iterator[Long],
|
||||
maxValidDiffCount: Int,
|
||||
maxDiff: Long,
|
||||
minDiff: Long,
|
||||
diffList: Seq[Long] = Nil,
|
||||
diffExtensionList: Seq[Long] = Nil,
|
||||
validDiffCount: Int = 0,
|
||||
previousTime: Long = System.currentTimeMillis()
|
||||
): Iterable[Long] = {
|
||||
if (iter.hasNext && validDiffCount < maxValidDiffCount) {
|
||||
val nextTime = iter.next()
|
||||
val delay = previousTime - nextTime
|
||||
if (delay < maxDiff) {
|
||||
if (delay <= minDiff) {
|
||||
qualifiedTimeDifferences(
|
||||
iter,
|
||||
maxValidDiffCount,
|
||||
maxDiff,
|
||||
minDiff,
|
||||
diffList ++ (diffExtensionList :+ delay),
|
||||
Nil,
|
||||
validDiffCount + 1,
|
||||
nextTime
|
||||
)
|
||||
} else {
|
||||
qualifiedTimeDifferences(
|
||||
iter,
|
||||
maxValidDiffCount,
|
||||
maxDiff,
|
||||
minDiff,
|
||||
diffList,
|
||||
diffExtensionList :+ delay,
|
||||
validDiffCount,
|
||||
nextTime
|
||||
)
|
||||
}
|
||||
} else {
|
||||
diffList
|
||||
}
|
||||
} else {
|
||||
diffList
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From a list of values, isolate all values less than than a test value.
|
||||
* @param list list of values
|
||||
* @param testValue test value that all valid values must be less than
|
||||
* @param mercy initial mercy value that values may be tested for being less than the test value
|
||||
* @return list of values less than the test value, including mercy
|
||||
*/
|
||||
private def takeWhileLess(list: Iterable[Long], testValue: Long, mercy: Long): Iterable[Long] = {
|
||||
var onGoingMercy: Long = mercy
|
||||
list.filter { value =>
|
||||
if (value < testValue) {
|
||||
true
|
||||
} else if (value - onGoingMercy < testValue) {
|
||||
//mercy is reduced every time it is utilized to find a valid value
|
||||
onGoingMercy = math.ceil(onGoingMercy * 0.8f).toLong
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify a base experience value to consider additional reasons for points.
|
||||
* @param killer player that delivers the interaction that reduces health to zero
|
||||
|
|
@ -308,39 +158,50 @@ object KillAssists {
|
|||
* @return the value of the kill in what the game called "battle experience points"
|
||||
* @see `BattleRank.withExperience`
|
||||
* @see `Support.baseExperience`
|
||||
* @see `Support.calculateMenace`
|
||||
*/
|
||||
private def calculateExperience(
|
||||
killer: PlayerSource,
|
||||
victim: PlayerSource,
|
||||
history: Iterable[InGameActivity]
|
||||
): Long = {
|
||||
//base value (the kill experience before modifiers)
|
||||
lazy val base = Support.baseExperience(victim, history)
|
||||
if (killer.Faction == victim.Faction || killer.unique == victim.unique) {
|
||||
val killerUnique = killer.unique
|
||||
if (killer.Faction == victim.Faction || killerUnique == victim.unique) {
|
||||
0L
|
||||
} else if (base > 1) {
|
||||
//include battle rank disparity modifier
|
||||
val battleRankDisparity: Long = {
|
||||
import net.psforever.objects.avatar.BattleRank
|
||||
val killerLevel = BattleRank.withExperience(killer.bep).value
|
||||
val victimLevel = BattleRank.withExperience(victim.bep).value
|
||||
val victimMinusKiller = victimLevel - killerLevel
|
||||
if (victimMinusKiller > -1) {
|
||||
victimMinusKiller * 10 + victimLevel
|
||||
} else {
|
||||
val bothLevels = killerLevel + victimLevel
|
||||
val pointFive = (base.toFloat * 0.25f).toInt
|
||||
-1 * (if (bothLevels >= base) {
|
||||
pointFive
|
||||
} else {
|
||||
math.min(bothLevels, pointFive)
|
||||
})
|
||||
}
|
||||
}.toLong
|
||||
//include menace modifier
|
||||
base + battleRankDisparity + (victim.progress.kills.size.toFloat * (1f + calculateMenace(victim).toFloat / 10f)).toLong
|
||||
} else {
|
||||
base
|
||||
val base = Support.baseExperience(victim, history)
|
||||
if (base > Support.TheShortestLifeIsWorth) {
|
||||
val bep = Config.app.game.experience.bep
|
||||
//battle rank disparity modifier
|
||||
val battleRankDisparity: Long = {
|
||||
import net.psforever.objects.avatar.BattleRank
|
||||
val killerLevel = BattleRank.withExperience(killer.bep).value
|
||||
val victimLevel = BattleRank.withExperience(victim.bep).value
|
||||
val victimMinusKiller = victimLevel - killerLevel
|
||||
if (victimMinusKiller > -1) {
|
||||
victimMinusKiller * 5 + victimLevel // 5 x (y - x) + y -> min = 1 @ (1, 1), max = 235 @ (40, 1)
|
||||
} else {
|
||||
victimMinusKiller //min = -39, max = -1
|
||||
}
|
||||
}.toLong
|
||||
//revenge modifier
|
||||
val revengeBonus: Long = {
|
||||
val revenge = bep.revenge
|
||||
val victimUnique = victim.unique
|
||||
val lastDeath = killer.progress.prior.flatMap(_.death)
|
||||
val sameLastKiller = lastDeath.map(_.assailant.map(_.unique)).flatMap(_.headOption).contains(victimUnique)
|
||||
if (revenge.experience != 0 && sameLastKiller) {
|
||||
revenge.experience
|
||||
} else if (sameLastKiller) {
|
||||
(lastDeath.map(_.experienceEarned).get * revenge.rate).toLong
|
||||
} else {
|
||||
0L
|
||||
}
|
||||
}
|
||||
base + battleRankDisparity + revengeBonus
|
||||
} else {
|
||||
base
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -600,37 +461,49 @@ object KillAssists {
|
|||
): Seq[(Long, Int)] = {
|
||||
var amt = amount
|
||||
var count = 0
|
||||
var newOrder: Seq[(Long, Int)] = Nil
|
||||
var newOrderPos: Seq[(Long, Int)] = Nil
|
||||
var newOrderNeg: Seq[(Long, Int)] = Nil
|
||||
order.takeWhile { entry =>
|
||||
val (id, total) = entry
|
||||
if (id > 0 && total > 0) {
|
||||
val part = participants(id)
|
||||
if (amount > total) {
|
||||
//drop this entry
|
||||
participants.put(id, part.copy(amount = 0, weapons = Nil)) //just in case
|
||||
amt = amt - total
|
||||
} else {
|
||||
//edit around the inclusion of this entry
|
||||
val newTotal = total - amt
|
||||
val trimmedWeapons = {
|
||||
var index = -1
|
||||
var weaponSum = 0
|
||||
val pweapons = part.weapons
|
||||
while (weaponSum < amt) {
|
||||
index += 1
|
||||
weaponSum = weaponSum + pweapons(index).amount
|
||||
participants.get(id) match {
|
||||
case Some(part) =>
|
||||
val reduceByValue = math.min(amt, total)
|
||||
val trimmedWeapons = {
|
||||
var index = 0
|
||||
var weaponSum = 0
|
||||
val pweapons = part.weapons
|
||||
val pwepiter = pweapons.iterator
|
||||
while (pwepiter.hasNext && weaponSum < reduceByValue) {
|
||||
weaponSum = weaponSum + pwepiter.next().amount
|
||||
index += 1
|
||||
}
|
||||
//output list(s)
|
||||
(if (weaponSum == reduceByValue) {
|
||||
newOrderNeg = newOrderNeg :+ (id, 0)
|
||||
index += 1
|
||||
pweapons.drop(index)
|
||||
} else if (weaponSum > reduceByValue) {
|
||||
newOrderPos = (id, total - amt) +: newOrderPos
|
||||
val remainder = pweapons.drop(index)
|
||||
remainder.headOption.map(_.copy(amount = weaponSum - reduceByValue)) ++ remainder.tail
|
||||
} else {
|
||||
newOrderPos = (id, total - amt) +: newOrderPos
|
||||
val remainder = pweapons.drop(index)
|
||||
remainder.headOption.map(_.copy(amount = reduceByValue - weaponSum)) ++ remainder.tail
|
||||
}) ++ pweapons.take(index).map(_.copy(amount = 0))
|
||||
}
|
||||
(pweapons(index).copy(amount = weaponSum - amt) +: pweapons.slice(index+1, pweapons.size)) ++
|
||||
pweapons.slice(0, index).map(_.copy(amount = 0))
|
||||
}
|
||||
newOrder = (id, newTotal) +: newOrder
|
||||
participants.put(id, part.copy(amount = part.amount - amount, weapons = trimmedWeapons))
|
||||
amt = 0
|
||||
participants.put(id, part.copy(amount = part.amount - reduceByValue, weapons = trimmedWeapons.toSeq))
|
||||
amt = amt - reduceByValue
|
||||
case _ =>
|
||||
//we do not have contribution stat data for this id
|
||||
//perform no calculations; devalue the entry
|
||||
newOrderNeg = newOrderNeg :+ (id, 0)
|
||||
}
|
||||
}
|
||||
count += 1
|
||||
amt > 0
|
||||
}
|
||||
newOrder ++ order.drop(count)
|
||||
newOrderPos ++ order.drop(count) ++ newOrderNeg//.reverse
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ object KillContributions {
|
|||
* @see `CombinedHealthAndArmorContributionProcess`
|
||||
* @see `composeContributionOutput`
|
||||
* @see `initialScoring`
|
||||
* @see `KillAssists.calculateMenace`
|
||||
* @see `Support.calculateMenace`
|
||||
* @see `limitHistoryToThisLife`
|
||||
* @see `rewardTheseSupporters`
|
||||
* @see `SupportActivity`
|
||||
|
|
@ -140,7 +140,7 @@ object KillContributions {
|
|||
val empty = mutable.ListBuffer[SourceUniqueness]()
|
||||
empty.addOne(target.unique)
|
||||
val otherContributionCalculations = additionalContributionSources(faction, kill, contributions)(_, _, _)
|
||||
if (longHistory.nonEmpty && KillAssists.calculateMenace(target) > 3) {
|
||||
if (longHistory.nonEmpty && Support.calculateMenace(target) > 3) {
|
||||
//long and short history
|
||||
val longContributionProcess = new CombinedHealthAndArmorContributionProcess(faction, contributions, Nil)
|
||||
val shortContributionProcess = new CombinedHealthAndArmorContributionProcess(faction, contributions, Seq(longContributionProcess))
|
||||
|
|
|
|||
|
|
@ -2,49 +2,510 @@
|
|||
package net.psforever.objects.zones.exp
|
||||
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.objects.vital.{InGameActivity, ReconstructionActivity, RepairFromExoSuitChange, SpawningActivity}
|
||||
import net.psforever.objects.vital.{ExoSuitChange, InGameActivity, RevivingActivity, TerminalUsedActivity, VehicleDismountActivity, VehicleMountActivity, VehicleMountChange, VitalityDefinition}
|
||||
import net.psforever.types.{ExoSuitType, PlanetSideEmpire}
|
||||
import net.psforever.util.Config
|
||||
import net.psforever.util.{Config, DefinitionUtil, ThreatAssessment, ThreatLevel}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.mutable
|
||||
|
||||
/**
|
||||
* Functions to assist experience calculation and history manipulation and analysis.
|
||||
*/
|
||||
object Support {
|
||||
private val sep = Config.app.game.experience.sep
|
||||
/** Almost nothing! */
|
||||
final val TheShortestLifeIsWorth: Long = 1L
|
||||
|
||||
/**
|
||||
* Calculate a base experience value to consider additional reasons for points.
|
||||
* Calculate the experience value to reflect the value of a player's lifespan.
|
||||
* @param victim player to which a final interaction has reduced health to zero
|
||||
* @param history chronology of activity the game considers noteworthy
|
||||
* @return the value of the kill in what the game called "battle experience points"
|
||||
* @see `Support.wasEverAMax`
|
||||
*/
|
||||
private[exp] def baseExperience(
|
||||
victim: PlayerSource,
|
||||
history: Iterable[InGameActivity]
|
||||
): Long = {
|
||||
val lifespan = (history.headOption, history.lastOption) match {
|
||||
//setup
|
||||
val historyList = history.toList
|
||||
val withKills = victim.progress.kills.nonEmpty
|
||||
val fullLifespan = (historyList.headOption, historyList.lastOption) match {
|
||||
case (Some(spawn), Some(death)) => death.time - spawn.time
|
||||
case _ => 0L
|
||||
}
|
||||
val base = if (Support.wasEverAMax(victim, history)) {
|
||||
Config.app.game.experience.bep.base.asMax
|
||||
} else if (victim.progress.kills.nonEmpty) {
|
||||
Config.app.game.experience.bep.base.withKills
|
||||
} else if (victim.Seated) {
|
||||
Config.app.game.experience.bep.base.asMounted
|
||||
} else if (lifespan > 15000L) {
|
||||
Config.app.game.experience.bep.base.mature
|
||||
val recordOfWornTimes = countTimeWhileExoSuitOrMounted(historyList)
|
||||
.map { case (id, time) => (id, (time * 0.001f).toLong) } // turn milliseconds into seconds
|
||||
//short life factors
|
||||
val shortLifeBonus = baseExperienceShortLifeFactors(
|
||||
victim,
|
||||
historyList,
|
||||
recordOfWornTimes,
|
||||
withKills,
|
||||
fullLifespan
|
||||
)
|
||||
if (shortLifeBonus > TheShortestLifeIsWorth) {
|
||||
val longLifeBonus: Long = {
|
||||
val threat = baseExperienceLongLifeFactors(victim, recordOfWornTimes, defaultValue = 100f * shortLifeBonus.toFloat)
|
||||
if (withKills) {
|
||||
threat
|
||||
} else {
|
||||
(threat * 0.85f).toLong
|
||||
}
|
||||
}
|
||||
//long life factors
|
||||
shortLifeBonus + longLifeBonus
|
||||
} else {
|
||||
1L
|
||||
//the shortest life is afforded no additional bonuses
|
||||
shortLifeBonus
|
||||
}
|
||||
if (base > 1) {
|
||||
//black ops modifier
|
||||
base// * Config.app.game.experience.bep.base.bopsMultiplier
|
||||
}
|
||||
|
||||
/**
|
||||
* Assuming a chronological history of player actions and interactions,
|
||||
* allocate every exo-suit use and mountable use to a time interval
|
||||
* and accumulates the sum of those time intervals.
|
||||
* The end result is a map association between exo-suits and vehicles and time that equipment has been used.
|
||||
* @param history chronology of activity the game considers noteworthy
|
||||
* @param initialExosuit start with this exo-suit type
|
||||
* @return mapping between equipment (object class ids) and the time that equipment has been used (ms);
|
||||
* the "equipment" includes exo-suits and all noted mountable entities
|
||||
*/
|
||||
private def countTimeWhileExoSuitOrMounted(
|
||||
history: List[InGameActivity],
|
||||
initialExosuit: ExoSuitType.Value = ExoSuitType.Standard
|
||||
): Map[Int, Long] = {
|
||||
val wornTime: mutable.HashMap[Int, Long] = mutable.HashMap[Int, Long]()
|
||||
var currentSuit: Int = initialExosuit.id
|
||||
var lastActTime: Long = history.head.time
|
||||
var lastMountAct: Option[VehicleMountChange] = None
|
||||
//collect history events that encompass changes to exo-suits and to mounting conditions
|
||||
history.collect {
|
||||
case suitChange: ExoSuitChange =>
|
||||
updateEquippedEntry(
|
||||
currentSuit,
|
||||
suitChange.time - lastActTime,
|
||||
wornTime
|
||||
)
|
||||
currentSuit = suitChange.exosuit.id
|
||||
lastActTime = suitChange.time
|
||||
case mount: VehicleMountActivity =>
|
||||
updateEquippedEntry(
|
||||
currentSuit,
|
||||
mount.time - lastActTime,
|
||||
wornTime
|
||||
)
|
||||
lastActTime = mount.time
|
||||
lastMountAct = Some(mount)
|
||||
case dismount: VehicleDismountActivity
|
||||
if dismount.pairedEvent.isEmpty =>
|
||||
updateEquippedEntry(
|
||||
dismount.vehicle.Definition.ObjectId,
|
||||
dismount.time - lastActTime,
|
||||
wornTime
|
||||
)
|
||||
lastActTime = dismount.time
|
||||
lastMountAct = None
|
||||
case dismount: VehicleDismountActivity =>
|
||||
updateEquippedEntry(
|
||||
dismount.vehicle.Definition.ObjectId,
|
||||
dismount.time - dismount.pairedEvent.get.time,
|
||||
wornTime
|
||||
)
|
||||
lastActTime = dismount.time
|
||||
lastMountAct = None
|
||||
}
|
||||
//no more changes; add remaining time from unresolved activity
|
||||
val lastTime = history.last.time
|
||||
lastMountAct
|
||||
.collect { mount =>
|
||||
//dying in a vehicle is a reason to care about the last mount activity
|
||||
updateEquippedEntry(
|
||||
mount.vehicle.Definition.ObjectId,
|
||||
lastTime - mount.time,
|
||||
wornTime
|
||||
)
|
||||
Some(mount)
|
||||
}
|
||||
.orElse {
|
||||
//dying while on foot
|
||||
updateEquippedEntry(
|
||||
currentSuit,
|
||||
lastTime - lastActTime,
|
||||
wornTime
|
||||
)
|
||||
None
|
||||
}
|
||||
wornTime.toMap
|
||||
}
|
||||
|
||||
/**
|
||||
* ...
|
||||
* @param equipmentId the equipment
|
||||
* @param timePassed how long it was in use
|
||||
* @param wornTime mapping between equipment (object class ids) and the time that equipment has been used (ms)
|
||||
* @return the length of time the equipment was used
|
||||
*/
|
||||
private def updateEquippedEntry(
|
||||
equipmentId: Int,
|
||||
timePassed: Long,
|
||||
wornTime: mutable.HashMap[Int, Long]
|
||||
): Long = {
|
||||
wornTime
|
||||
.get(equipmentId)
|
||||
.collect {
|
||||
oldTime =>
|
||||
val time = oldTime + timePassed
|
||||
wornTime.update(equipmentId, time)
|
||||
time
|
||||
}
|
||||
.orElse {
|
||||
wornTime.update(equipmentId, timePassed)
|
||||
Some(timePassed)
|
||||
}
|
||||
.get
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the experience value to reflect the value of a player's short term lifespan.
|
||||
* In effect, determine a token experience value for short unproductive lives.
|
||||
* Four main conditions are outlined.
|
||||
* In order of elimination traversal:
|
||||
* was the player ever using a mechanized assault exo-suit,
|
||||
* did the player kill anything,
|
||||
* was the player mounted in a vehicle of turret for long enough for it to be considered,
|
||||
* and has the player been alive long enough?
|
||||
* @param player player to which a final interaction has reduced health to zero
|
||||
* @param history chronology of activity the game considers noteworthy
|
||||
* @param recordOfWornTimes between equipment (object class ids) and the time that equipment has been used (ms)
|
||||
* @param withKills consider that the victim killed an opponent in this past life
|
||||
* @param fullLifespan for how long this last life spanned
|
||||
* @return the value of the kill in what the game called "battle experience points"
|
||||
* @see `Config.app.game.experience.bep.base`
|
||||
*/
|
||||
private def baseExperienceShortLifeFactors(
|
||||
player: PlayerSource,
|
||||
history: List[InGameActivity],
|
||||
recordOfWornTimes: Map[Int, Long],
|
||||
withKills: Boolean,
|
||||
fullLifespan: Long
|
||||
): Long = {
|
||||
val bep = Config.app.game.experience.bep.base
|
||||
//TODO bops
|
||||
if (recordOfWornTimes.getOrElse(ExoSuitType.MAX.id, 0L) > 0L) { //see: Support.wasEverAMax
|
||||
bep.asMax
|
||||
} else if (withKills) {
|
||||
bep.withKills
|
||||
} else if (player.Seated || {
|
||||
val mountTime = recordOfWornTimes.collect { case (id, value) if id > 10 => value }.sum
|
||||
mountTime * 3L >= fullLifespan
|
||||
}) {
|
||||
bep.asMounted
|
||||
} else {
|
||||
base
|
||||
val validMaturityTime = if (
|
||||
!history.head.isInstanceOf[RevivingActivity] ||
|
||||
history.exists(_.isInstanceOf[TerminalUsedActivity])
|
||||
) {
|
||||
bep.maturityTime
|
||||
} else {
|
||||
0L
|
||||
}
|
||||
if (fullLifespan > validMaturityTime) {
|
||||
bep.mature
|
||||
} else {
|
||||
TheShortestLifeIsWorth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the experience value to reflect the value of a player's full lifespan.
|
||||
* A lifespan is associated with conditions and states that can each be assigned a weight or value.
|
||||
* Summing up all of these conditions and states produces a reward value.
|
||||
* @param player player to which a final interaction has reduced health to zero
|
||||
* @param recordOfWornTimes between equipment (object class ids) and the time that equipment has been used (ms)
|
||||
* @return the value of the kill in what the game called "battle experience points"
|
||||
* @see `Config.app.game.experience.bep.lifeSpanThreatRate`
|
||||
* @see `Config.app.game.experience.bep.threatAssessmentOf`
|
||||
*/
|
||||
private def baseExperienceLongLifeFactors(
|
||||
player: PlayerSource,
|
||||
recordOfWornTimes: Map[Int, Long],
|
||||
defaultValue: Float
|
||||
): Long = {
|
||||
//awarded values for a target's lifespan based on the distribution of their tactical choices
|
||||
val individualThreatEstimates: Map[Int, Float] = calculateThreatEstimatesPerEntry(recordOfWornTimes)
|
||||
val totalThreatEstimate: Float = individualThreatEstimates.values.sum
|
||||
val maxThreatCapacity: Float = {
|
||||
val (exosuitTimes, otherTimes) = recordOfWornTimes.partition(_._1 < 10)
|
||||
calculateMaxThreatCapacityPerEntry(
|
||||
(if (exosuitTimes.values.sum > otherTimes.values.sum) {
|
||||
individualThreatEstimates.filter(_._1 < 10)
|
||||
} else {
|
||||
individualThreatEstimates.filter(_._1 > 10)
|
||||
}).maxBy(_._2)._1,
|
||||
defaultValue
|
||||
)
|
||||
}
|
||||
//menace modifier -> min = kills, max = 8 x kills
|
||||
val menace = (player.progress.kills.size.toFloat * (1f + Support.calculateMenace(player).toFloat)).toLong
|
||||
//last kill experience
|
||||
val lastKillExperience = player.progress.kills
|
||||
.lastOption
|
||||
.collect { kill =>
|
||||
val reduce = ((System.currentTimeMillis() - kill.time.toDate.getTime).toFloat * 0.001f).toLong
|
||||
math.max(0L, kill.experienceEarned - reduce)
|
||||
}
|
||||
.getOrElse(0L)
|
||||
//cap lifespan then add extra
|
||||
math.min(totalThreatEstimate, maxThreatCapacity).toLong + menace + lastKillExperience
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the reward available based on a tactical option by id.
|
||||
* @param recordOfWornTimes between equipment (object class ids) and the time that equipment has been used (ms)
|
||||
* @return value of the equipment
|
||||
*/
|
||||
private def calculateThreatEstimatesPerEntry(recordOfWornTimes: Map[Int, Long]): Map[Int, Float] = {
|
||||
recordOfWornTimes.map {
|
||||
case (key, amount) => (key, amount * calculateThreatEstimatesPerEntry(key))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the reward available based on a tactical option by id.
|
||||
* If not listed in a previous table of values,
|
||||
* obtain the definition associated with the equipment id and test use the mass of the entity.
|
||||
* The default value is 0.
|
||||
* @param key equipment id used to collect the ceiling value
|
||||
* @return value of the equipment
|
||||
* @see `Config.app.game.experience.bep.threatAssessmentOf`
|
||||
* @see `VitalityDefinition.mass`
|
||||
*/
|
||||
private def calculateThreatEstimatesPerEntry(key: Int): Float = {
|
||||
Config.app.game.experience.bep.lifeSpan.threatAssessmentOf
|
||||
.find { case ThreatAssessment(a, _) => a == key }
|
||||
.map(_.value)
|
||||
.getOrElse {
|
||||
getDefinitionById(key)
|
||||
.map(o => 2f + math.log10(o.mass.toDouble).toFloat)
|
||||
.getOrElse(0f)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the maximum possible reward available based on tactical options.
|
||||
* If not listed in a previous table of values,
|
||||
* obtain the definition associated with the equipment id and test use the maximum health of the entity.
|
||||
* @param key equipment id used to estimate one sample for the ceiling value
|
||||
* @param defaultValue what to use for an unresolved ceiling value;
|
||||
* defaults to 0
|
||||
* @return maximum value for this equipment
|
||||
* @see `Config.app.game.experience.bep.maxThreatLevel`
|
||||
* @see `VitalityDefinition.MaxHealth`
|
||||
*/
|
||||
private def calculateMaxThreatCapacityPerEntry(
|
||||
key: Int,
|
||||
defaultValue: Float
|
||||
): Float = {
|
||||
Config.app.game.experience.bep.lifeSpan.maxThreatLevel
|
||||
.find { case ThreatLevel(a, _) => a == key }
|
||||
.map(_.level.toFloat)
|
||||
.getOrElse {
|
||||
getDefinitionById(key)
|
||||
.map(_.MaxHealth.toFloat * 1.2f)
|
||||
.getOrElse(defaultValue)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ...
|
||||
* @param key equipment id
|
||||
* @return the definition if the definition can be found;
|
||||
* `None`, otherwise
|
||||
* @see `DefinitionUtil.idToDefinition`
|
||||
* @see `GlobalDefinitions`
|
||||
* @see `VitalityDefinition`
|
||||
*/
|
||||
private def getDefinitionById(key: Int): Option[VitalityDefinition] = {
|
||||
try {
|
||||
DefinitionUtil.idToDefinition(key) match {
|
||||
case o: VitalityDefinition => Some(o)
|
||||
case _ => None
|
||||
}
|
||||
} catch {
|
||||
case _: Exception => None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* "Menace" is a crude measurement of how much consistent destructive power a player has been demonstrating.
|
||||
* Within the last few kills, the rate of the player's killing speed is measured.
|
||||
* The measurement - a "streak" in modern lingo - is transformed into the form of an `Integer` for simplicity.
|
||||
* @param player the player
|
||||
* @param minimumKills number of kills needed before menace is considered
|
||||
* @param testValues time values to determine allowable delay between kills to qualify for a score rating;
|
||||
* three score ratings, so three values;
|
||||
* defaults to 20s, 10s, 5s (in ms)
|
||||
* @param maxDelayDiff time until the previous kill disqualifies menace;
|
||||
* exclusive amount of time allowed between qualifying entries;
|
||||
* default is 45s (in ms)
|
||||
* @param minDelayDiff inclusive amount of time difference allowed between valid entries;
|
||||
* default is 20s (in ms)
|
||||
* @param mercy a time value that can be used to continue a missed streak;
|
||||
* defaults to 5s (in ms)
|
||||
* @return an integer between 0 and 7;
|
||||
* 0 is no kills,
|
||||
* 1 is some kills,
|
||||
* 2-7 is a menace score;
|
||||
* there is no particular meaning behind different menace scores ascribed by this function
|
||||
* but the range allows for progressive distinction
|
||||
* @see `qualifiedTimeDifferences`
|
||||
* @see `takeWhileLess`
|
||||
*/
|
||||
private[exp] def calculateMenace(
|
||||
player: PlayerSource,
|
||||
minimumKills: Int = 3,
|
||||
testValues: Seq[Long] = Seq(20000L, 10000L, 5000L),
|
||||
maxDelayDiff: Long = 45000L,
|
||||
minDelayDiff: Long = 20000L,
|
||||
mercy: Long = 5000L
|
||||
): Int = {
|
||||
//init
|
||||
val (minDiff, maxDiff) = (math.min(maxDelayDiff, maxDelayDiff), math.max(maxDelayDiff, maxDelayDiff))
|
||||
val valuesForTesting = testValues.padTo(3, ((maxDiff + minDiff) * 0.5f).toLong)
|
||||
//func
|
||||
val allKills = player.progress.kills
|
||||
//the very first kill must have been within the max delay (but does not count towards menace)
|
||||
if (allKills.headOption.exists { System.currentTimeMillis() - _.time.toDate.getTime < maxDiff}) {
|
||||
allKills match {
|
||||
case _ :: kills if kills.size > minimumKills =>
|
||||
val (continuations, restsBetweenKills) =
|
||||
qualifiedTimeDifferences(
|
||||
kills.map(_.time.toDate.getTime).iterator,
|
||||
maxValidDiffCount = 10,
|
||||
maxDiff,
|
||||
minDiff
|
||||
)
|
||||
.partition(_ > minDiff)
|
||||
math.max(
|
||||
1,
|
||||
math.floor(math.sqrt(
|
||||
math.max(0, takeWhileLess(restsBetweenKills, valuesForTesting.head, mercy).size - 1) + /*max=8*/
|
||||
math.max(0, takeWhileLess(restsBetweenKills, valuesForTesting(1), mercy).size - 5) * 3 + /*max=12*/
|
||||
math.max(0, takeWhileLess(restsBetweenKills, valuesForTesting(2), mercy = 1000L).size - 4) * 7 /*max=35*/
|
||||
) - continuations.size)
|
||||
).toInt
|
||||
case _ =>
|
||||
1
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a list of times
|
||||
* and produce a list of delays between those entries less than a maximum time delay.
|
||||
* These are considered "qualifying".
|
||||
* Count a certain number of time delays that fall within a minimum threshold
|
||||
* and stop when that minimum count is achieved.
|
||||
* These are considered "valid".
|
||||
* The final product should be a new list of the successive delays from the first list
|
||||
* containing both qualified and valid entries,
|
||||
* stopping at either the first unqualified delay or the last valid delay or at exhaustion of the original list.
|
||||
* @param iter unfiltered list of times (ms)
|
||||
* @param maxValidDiffCount maximum number of valid entries in the final list of time differences;
|
||||
* see `validTimeEntryCount`
|
||||
* @param maxDiff exclusive amount of time allowed between qualifying entries;
|
||||
* include any time difference within this delay;
|
||||
* these entries are "qualifying" but are not "valid"
|
||||
* @param minDiff inclusive amount of time difference allowed between valid entries;
|
||||
* include time differences in this delay
|
||||
* these entries are "valid" and should increment the counter `validTimeEntryCount`
|
||||
* @return list of qualifying time differences (ms)
|
||||
*/
|
||||
/*
|
||||
Parameters governed by recursion:
|
||||
@param diffList ongoing list of qualifying time differences (ms)
|
||||
@param diffExtensionList accumulation of entries greater than the `minTimeEntryDiff`
|
||||
but less that the `minTimeEntryDiff`;
|
||||
holds qualifying time differences
|
||||
that will be included before the next valid time difference
|
||||
@param validDiffCount currently number of valid time entries in the qualified time list;
|
||||
see `maxValidTimeEntryCount`
|
||||
@param previousTime previous qualifying entry time;
|
||||
by default, current time (ms)
|
||||
*/
|
||||
@tailrec
|
||||
private def qualifiedTimeDifferences(
|
||||
iter: Iterator[Long],
|
||||
maxValidDiffCount: Int,
|
||||
maxDiff: Long,
|
||||
minDiff: Long,
|
||||
diffList: Seq[Long] = Nil,
|
||||
diffExtensionList: Seq[Long] = Nil,
|
||||
validDiffCount: Int = 0,
|
||||
previousTime: Long = System.currentTimeMillis()
|
||||
): Iterable[Long] = {
|
||||
if (iter.hasNext && validDiffCount < maxValidDiffCount) {
|
||||
val nextTime = iter.next()
|
||||
val delay = previousTime - nextTime
|
||||
if (delay < maxDiff) {
|
||||
if (delay <= minDiff) {
|
||||
qualifiedTimeDifferences(
|
||||
iter,
|
||||
maxValidDiffCount,
|
||||
maxDiff,
|
||||
minDiff,
|
||||
diffList ++ (diffExtensionList :+ delay),
|
||||
Nil,
|
||||
validDiffCount + 1,
|
||||
nextTime
|
||||
)
|
||||
} else {
|
||||
qualifiedTimeDifferences(
|
||||
iter,
|
||||
maxValidDiffCount,
|
||||
maxDiff,
|
||||
minDiff,
|
||||
diffList,
|
||||
diffExtensionList :+ delay,
|
||||
validDiffCount,
|
||||
nextTime
|
||||
)
|
||||
}
|
||||
} else {
|
||||
diffList
|
||||
}
|
||||
} else {
|
||||
diffList
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From a list of values, isolate all values less than than a test value.
|
||||
* @param list list of values
|
||||
* @param testValue test value that all valid values must be less than
|
||||
* @param mercy initial mercy value that values may be tested against being less than the test value
|
||||
* @return list of values less than the test value, including mercy
|
||||
*/
|
||||
private def takeWhileLess(
|
||||
list: Iterable[Long],
|
||||
testValue: Long,
|
||||
mercy: Long
|
||||
): Iterable[Long] = {
|
||||
var onGoingMercy: Long = mercy
|
||||
list.filter { value =>
|
||||
if (value < testValue) {
|
||||
true
|
||||
} else if (value - onGoingMercy < testValue) {
|
||||
//mercy is reduced every time it is utilized to find a valid value
|
||||
onGoingMercy = math.ceil(onGoingMercy * 0.8f).toLong
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -185,10 +646,8 @@ object Support {
|
|||
*/
|
||||
private[exp] def wasEverAMax(player: PlayerSource, history: Iterable[InGameActivity]): Boolean = {
|
||||
player.ExoSuit == ExoSuitType.MAX || history.exists {
|
||||
case SpawningActivity(p: PlayerSource, _, _) => p.ExoSuit == ExoSuitType.MAX
|
||||
case ReconstructionActivity(p: PlayerSource, _, _) => p.ExoSuit == ExoSuitType.MAX
|
||||
case RepairFromExoSuitChange(suit, _) => suit == ExoSuitType.MAX
|
||||
case _ => false
|
||||
case suitChange: ExoSuitChange => suitChange.exosuit == ExoSuitType.MAX
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -211,7 +670,7 @@ object Support {
|
|||
weaponStat: WeaponStats,
|
||||
canNotFindEventDefaultValue: Option[Float] = None
|
||||
): WeaponStats = {
|
||||
val rewards: Float = sep.events
|
||||
val rewards: Float = Config.app.game.experience.sep.events
|
||||
.find(evt => event.equals(evt.name))
|
||||
.map { event =>
|
||||
val shots = weaponStat.shots
|
||||
|
|
@ -228,7 +687,7 @@ object Support {
|
|||
}
|
||||
}
|
||||
.getOrElse(
|
||||
canNotFindEventDefaultValue.getOrElse(sep.canNotFindEventDefaultValue.toFloat)
|
||||
canNotFindEventDefaultValue.getOrElse(Config.app.game.experience.sep.canNotFindEventDefaultValue.toFloat)
|
||||
)
|
||||
weaponStat.copy(contributions = rewards)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ object ToDatabase {
|
|||
position: Vector3,
|
||||
exp: Long
|
||||
): Unit = {
|
||||
ctx.run(query[persistence.Killactivity]
|
||||
ctx.run(query[persistence.Assistactivity]
|
||||
.insert(
|
||||
_.killerId -> lift(avatarId),
|
||||
_.victimId -> lift(victimId),
|
||||
|
|
@ -227,6 +227,12 @@ object ToDatabase {
|
|||
avatarIdAndExp.map { case (avatarId, exp, expType) =>
|
||||
persistence.Buildingcapture(-1, avatarId, zoneId, buildingId, exp, expType)
|
||||
}
|
||||
)}.foreach(e => query[persistence.Buildingcapture].insertValue(e)))
|
||||
)}.foreach(e => query[persistence.Buildingcapture].insert(
|
||||
_.avatarId -> e.avatarId,
|
||||
_.zoneId -> e.zoneId,
|
||||
_.buildingId -> e.buildingId,
|
||||
_.exp -> e.exp,
|
||||
_.expType -> e.expType
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,10 @@ class HackCaptureActor extends Actor {
|
|||
val faction = GetHackingFaction(target).getOrElse(target.Faction)
|
||||
target.HackedBy = None
|
||||
hackedObjects = remainder
|
||||
val now: Long = System.currentTimeMillis()
|
||||
val facilityHackTime: Long = target.Definition.FacilityHackTime.toMillis
|
||||
val building = target.Owner.asInstanceOf[Building]
|
||||
val hackTime = results.headOption.map { now - _.hack_timestamp }.getOrElse(facilityHackTime)
|
||||
// If LLU exists it was not delivered. Send resecured notifications
|
||||
building.GetFlag.collect {
|
||||
case flag: CaptureFlag => target.Zone.LocalEvents ! CaptureFlagManager.Lost(flag, CaptureFlagLostReasonEnum.Resecured)
|
||||
|
|
@ -99,15 +102,15 @@ class HackCaptureActor extends Actor {
|
|||
target.Faction,
|
||||
faction,
|
||||
hacker,
|
||||
target.Definition.FacilityHackTime.toMillis,
|
||||
System.currentTimeMillis() - results.head.hack_timestamp,
|
||||
facilityHackTime,
|
||||
hackTime,
|
||||
isResecured = true
|
||||
)
|
||||
// Restart the timer in case the object we just removed was the next one scheduled
|
||||
RestartTimer()
|
||||
|
||||
case HackCaptureActor.FlagCaptured(flag) =>
|
||||
log.warn(hackedObjects.toString())
|
||||
log.debug(hackedObjects.toString())
|
||||
val building = flag.Owner.asInstanceOf[Building]
|
||||
val bguid = building.CaptureTerminal.map { _.GUID }
|
||||
hackedObjects.find(entry => bguid.contains(entry.target.GUID)) match {
|
||||
|
|
@ -160,13 +163,16 @@ class HackCaptureActor extends Actor {
|
|||
RestartTimer()
|
||||
spawnCaptureFlag(neighbours, terminal, hackingFaction)
|
||||
true
|
||||
case Some((owner, Some(flag), _)) if hackingFaction == flag.Faction =>
|
||||
case Some((_, Some(flag), _)) if hackingFaction == flag.Faction =>
|
||||
log.error(s"TrySpawnCaptureFlag: flag hacked facility can not be hacked twice by $hackingFaction")
|
||||
false
|
||||
case Some((owner, _, _)) if hackingFaction == terminal.Faction =>
|
||||
log.error(s"TrySpawnCaptureFlag: owning faction and hacking faction match for facility ${owner.Name}; should we be resecuring instead?")
|
||||
false
|
||||
case Some((owner, _, _)) =>
|
||||
log.error(s"TrySpawnCaptureFlag: couldn't find any neighbouring $hackingFaction facilities of ${owner.Name} for LLU hack")
|
||||
case Some((owner, Some(flag), _)) =>
|
||||
log.warn(s"TrySpawnCaptureFlag: couldn't find any neighbouring $hackingFaction facilities of ${owner.Name} for LLU hack")
|
||||
owner.GetFlagSocket.foreach { _.clearOldFlagData() }
|
||||
terminal.Zone.LocalEvents ! CaptureFlagManager.Lost(owner.GetFlag.get, CaptureFlagLostReasonEnum.Ended)
|
||||
terminal.Zone.LocalEvents ! CaptureFlagManager.Lost(flag, CaptureFlagLostReasonEnum.Ended)
|
||||
false
|
||||
case _ =>
|
||||
log.error(s"TrySpawnCaptureFlag: expecting a terminal ${terminal.GUID.guid} with the ctf owning facility")
|
||||
|
|
|
|||
|
|
@ -244,14 +244,27 @@ case class Experience(
|
|||
longContributionTime: Long,
|
||||
bep: BattleExperiencePoints,
|
||||
sep: SupportExperiencePoints,
|
||||
cep: CommandExperiencePoints
|
||||
cep: CommandExperiencePoints,
|
||||
facilityCaptureRate: Float
|
||||
) {
|
||||
assert(shortContributionTime < longContributionTime)
|
||||
}
|
||||
|
||||
case class ThreatAssessment(
|
||||
id: Int,
|
||||
value: Float
|
||||
)
|
||||
|
||||
case class ThreatLevel(
|
||||
id: Int,
|
||||
level: Long
|
||||
)
|
||||
|
||||
case class BattleExperiencePoints(
|
||||
base: BattleExperiencePointsBase,
|
||||
rate: Float
|
||||
rate: Float,
|
||||
base: BattleExperiencePointsBase,
|
||||
lifeSpan: BattleExperiencePointsLifespan,
|
||||
revenge: BattleExperiencePointsRevenge
|
||||
)
|
||||
|
||||
case class BattleExperiencePointsBase(
|
||||
|
|
@ -259,7 +272,19 @@ case class BattleExperiencePointsBase(
|
|||
asMax: Long,
|
||||
withKills: Long,
|
||||
asMounted: Long,
|
||||
mature: Long
|
||||
mature: Long,
|
||||
maturityTime: Long
|
||||
)
|
||||
|
||||
case class BattleExperiencePointsLifespan(
|
||||
lifeSpanThreatRate: Float,
|
||||
threatAssessmentOf: List[ThreatAssessment],
|
||||
maxThreatLevel: List[ThreatLevel]
|
||||
)
|
||||
|
||||
case class BattleExperiencePointsRevenge(
|
||||
rate: Float,
|
||||
experience: Long
|
||||
)
|
||||
|
||||
case class SupportExperiencePoints(
|
||||
|
|
@ -289,11 +314,11 @@ case class CommandExperiencePoints(
|
|||
)
|
||||
|
||||
case class PromotionSystem(
|
||||
active: Boolean,
|
||||
broadcastBattleRank: Int,
|
||||
resetBattleRank: Int,
|
||||
maxBattleRank: Int,
|
||||
battleExperiencePointsModifier: Float,
|
||||
supportExperiencePointsModifier: Float,
|
||||
captureExperiencePointsModifier: Float
|
||||
active: Boolean,
|
||||
broadcastBattleRank: Int,
|
||||
resetBattleRank: Int,
|
||||
maxBattleRank: Int,
|
||||
battleExperiencePointsModifier: Float,
|
||||
supportExperiencePointsModifier: Float,
|
||||
captureExperiencePointsModifier: Float
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue