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

View file

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