adjustment to calculations for the long life bonus experience and to the lifespan experience limits

This commit is contained in:
Fate-JH 2024-01-08 01:54:26 -05:00
parent 5b0ffdb5aa
commit d86580b21f
2 changed files with 170 additions and 104 deletions

View file

@ -259,27 +259,27 @@ game {
threat-assessment-of = [
{
id = 0
value = 1.0
},
{
id = 1
value = 1.25
},
{
id = 1
value = 1.5
},
{
id = 2
value = 2.0
value = 2.15
},
{
id = 3
value = 1.0
value = 1.25
},
{
id = 4
value = 0.75
value = 1.0
},
{
id = 258
value = 0
value = 10.0
},
{
id = 410
@ -295,11 +295,11 @@ game {
max-threat-level = [
{
id = 0
level = 900
level = 2000
},
{
id = 1
level = 1200
level = 2000
},
{
id = 2
@ -307,11 +307,11 @@ game {
},
{
id = 3
level = 1000
level = 2000
},
{
id = 4
level = 20000
level = 900
},
{
id = 258

View file

@ -45,7 +45,7 @@ object Support {
)
if (shortLifeBonus > TheShortestLifeIsWorth) {
val longLifeBonus: Long = {
val threat = baseExperienceLongLifeFactors(victim, recordOfWornTimes)
val threat = baseExperienceLongLifeFactors(victim, recordOfWornTimes, defaultValue = 100f * shortLifeBonus.toFloat)
if (withKills) {
threat
} else {
@ -74,41 +74,44 @@ object Support {
history: List[InGameActivity],
initialExosuit: ExoSuitType.Value = ExoSuitType.Standard
): Map[Int, Long] = {
val wornTime: mutable.HashMap[Int, Long] = mutable.HashMap[Int, Long]()
val wornTime: mutable.HashMap[Int, Long] = mutable.HashMap[Int, Long]()
var currentSuit: Int = initialExosuit.id
var lastSuitAct: Long = history.head.time
var lastDismountAct: Option[VehicleMountChange] = None
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 =>
//use previous vehicle dismount to distinguish between infantry exo-suit use and pilot exo-suit use
val timePassed = lastDismountAct.map(_.time).getOrElse(lastSuitAct) - suitChange.time
wornTime.get(currentSuit) match {
case None => wornTime.update(currentSuit, timePassed)
case Some(oldTime) => wornTime.update(currentSuit, oldTime + timePassed)
}
updateEquippedEntry(
currentSuit,
suitChange.time - lastActTime,
wornTime
)
currentSuit = suitChange.exosuit.id
lastSuitAct = suitChange.time
lastDismountAct = None
lastActTime = suitChange.time
case mount: VehicleMountActivity =>
wornTime.getOrElseUpdate(mount.vehicle.Definition.ObjectId, 0L)
lastDismountAct = None
updateEquippedEntry(
currentSuit,
mount.time - lastActTime,
wornTime
)
lastActTime = mount.time
lastMountAct = Some(mount)
case dismount: VehicleDismountActivity
if dismount.pairedEvent.isEmpty =>
//though we have reference to a previous mount activity, only care about the dismount activity's knowledge
wornTime.getOrElseUpdate(dismount.vehicle.Definition.ObjectId, 0L)
lastDismountAct = Some(dismount)
updateEquippedEntry(
dismount.vehicle.Definition.ObjectId,
dismount.time - lastActTime,
wornTime
)
lastActTime = dismount.time
lastMountAct = None
case dismount: VehicleDismountActivity =>
val timePassed = dismount.time - dismount.pairedEvent.get.time
val input = dismount.vehicle.Definition.ObjectId
wornTime.get(input) match {
case None => wornTime.update(input, timePassed)
case Some(oldTime) => wornTime.update(input, oldTime + timePassed)
}
lastDismountAct = Some(dismount)
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
@ -116,26 +119,52 @@ object Support {
lastMountAct
.collect { mount =>
//dying in a vehicle is a reason to care about the last mount activity
val input = mount.vehicle.Definition.ObjectId
val lastMountTime = lastTime - mount.time
wornTime.get(input) match {
case None => wornTime.update(input, lastMountTime)
case Some(oldTime) => wornTime.update(input, oldTime + lastMountTime)
}
updateEquippedEntry(
mount.vehicle.Definition.ObjectId,
lastTime - mount.time,
wornTime
)
Some(mount)
}
.orElse {
//dying while on foot
val lastSuitTime = lastTime - lastDismountAct.map(_.time).getOrElse(lastSuitAct)
wornTime.get(currentSuit) match {
case None => wornTime.update(currentSuit, lastSuitTime)
case Some(oldTime) => wornTime.update(currentSuit, oldTime + lastSuitTime)
}
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.
@ -200,76 +229,113 @@ object Support {
*/
private def baseExperienceLongLifeFactors(
player: PlayerSource,
recordOfWornTimes: Map[Int, Long]
recordOfWornTimes: Map[Int, Long],
defaultValue: Float
): Long = {
val bep = Config.app.game.experience.bep
//awarded value for a target's lifespan based on the distribution of their tactical choices
val threatEstimate = (recordOfWornTimes.foldLeft(0L) {
case (sum, (key, amount)) =>
if (key > 10) sum + amount
else sum + (amount * bep.lifeSpan.threatAssessmentOf.find { case ThreatAssessment(a, _) => a == key }.map(_.value).getOrElse(1.0f)).toLong
} * bep.lifeSpan.lifeSpanThreatRate).toLong
//maximum award for a target's lifespan based on the greatest potential of their tactical choices
val maxThreatLevel : Long = estimateMaxThreatLevel(
recordOfWornTimes,
recordOfWornTimes.maxBy { case (_, value) => value }._1
)
//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
//cap
math.min(threatEstimate + menace, maxThreatLevel)
//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.
* @param recordOfWornTimes between equipment (object class ids) and the time that equipment has been used (ms)
* @param testKey equipment id used to estimate one sample for the ceiling value
* @param defaultThreatLevel what to use for an unresolved ceiling value;
* defaults to 0
* @return maximum value of the kill in what the game called "battle experience points"
* 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 estimateMaxThreatLevel(
recordOfWornTimes: Map[Int, Long],
testKey: Int,
defaultThreatLevel: Long = 0L
): Long = {
val exoSuitMaxThreatId = recordOfWornTimes
.filter { case (key, _) => key < 10 }
.maxBy { case (_, value) => value }
._1
val estimatedExosuitThreatLevel = estimateMaxThreatLevelBasedOnKey(exoSuitMaxThreatId, defaultThreatLevel)
math.max(
estimateMaxThreatLevelBasedOnKey(testKey, estimatedExosuitThreatLevel),
estimatedExosuitThreatLevel
)
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)
}
}
/**
* Calculate the maximum possible reward available based on a tactical option by id.
* @param refId equipment id used to collect the ceiling value
* @param defaultThreatLevel what to use for an unresolved ceiling value
* @return maximum value of the kill in what the game called "battle experience points"
* @see `Config.app.game.experience.bep.maxThreatLevel`
* ...
* @param key equipment id
* @return the definition if the definition can be found;
* `None`, otherwise
* @see `DefinitionUtil.idToDefinition`
* @see `VitalityDefinition.MaxHealth`
* @see `GlobalDefinitions`
* @see `VitalityDefinition`
*/
private def estimateMaxThreatLevelBasedOnKey(
refId: Int,
defaultThreatLevel: Long
): Long = {
Config.app.game.experience.bep.lifeSpan.maxThreatLevel
.find { case ThreatLevel(key, _) => key == refId }
.map(_.level)
.getOrElse(
try {
DefinitionUtil.idToDefinition(refId) match {
case o: VitalityDefinition => (o.MaxHealth * 1.5f).toLong
case _ => defaultThreatLevel
}
} catch {
case _: Exception => defaultThreatLevel
}
)
private def getDefinitionById(key: Int): Option[VitalityDefinition] = {
try {
DefinitionUtil.idToDefinition(key) match {
case o: VitalityDefinition => Some(o)
case _ => None
}
} catch {
case _: Exception => None
}
}
/**