Repair Value Arpeggio (#807)

* repair capability is now reflected based on progression in the engineering certification tree; uses correct values and modifiers

* 12 works better than 12.5

* fixed tests
This commit is contained in:
Fate-JH 2021-05-06 07:31:05 -04:00 committed by GitHub
parent 7fca0a5582
commit cbb48d1442
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 137 additions and 42 deletions

View file

@ -1911,6 +1911,7 @@ object GlobalDefinitions {
armor_canister.Name = "armor_canister"
armor_canister.Capacity = 100
armor_canister.repairAmount = 12f //ADB says 12.5, but 12 is better for the math
armor_canister.Tile = InventoryTile.Tile23
upgrade_canister.Name = "upgrade_canister"
@ -4938,6 +4939,9 @@ object GlobalDefinitions {
nano_dispenser.FireModes.head.Add.Damage2 = 0
nano_dispenser.FireModes.head.Add.Damage3 = 0
nano_dispenser.FireModes.head.Add.Damage4 = 20
nano_dispenser.AddRepairMultiplier(level = 3, value = 2.0f)
nano_dispenser.AddRepairMultiplier(level = 2, value = 1.5f)
nano_dispenser.AddRepairMultiplier(level = 1, value = 1.0f)
nano_dispenser.Tile = InventoryTile.Tile63
bank.Name = "bank"
@ -4952,6 +4956,9 @@ object GlobalDefinitions {
bank.FireModes(1).AmmoTypeIndices += 0
bank.FireModes(1).AmmoSlotIndex = 0
bank.FireModes(1).Magazine = 100
bank.AddRepairMultiplier(level = 3, value = 1.5f)
bank.AddRepairMultiplier(level = 2, value = 1.2f)
bank.AddRepairMultiplier(level = 1, value = 1.0f)
bank.Tile = InventoryTile.Tile33
remote_electronics_kit.Name = "remote_electronics_kit"

View file

@ -1,6 +1,7 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects
import net.psforever.objects.avatar.Certification
import net.psforever.objects.definition.ExoSuitDefinition
import net.psforever.objects.equipment.EquipmentSlot
import net.psforever.objects.inventory.InventoryItem
@ -134,4 +135,29 @@ object Players {
player.avatar.certifications.intersect(permissions.toSet).nonEmpty
}
}
/**
* For a given selection of certification, find the player's engineer level.
* The level of a player with no engineering certification is 0.
* The certifications that matter are all related to the ability to repair damaged equipment and
* the ability to place combat support utilities - combat engineering (ce) deployables - in the game world.
* @see `Avatar.certifications`
* @see `Certification`
* @param player the player whose certifications are to be tested
* @return the engineering level
*/
def repairModifierLevel(player: Player): Int = {
val certs = player.avatar.certifications
if(certs.contains(Certification.AdvancedEngineering) ||
certs.contains(Certification.AssaultEngineering) ||
certs.contains(Certification.FortificationEngineering)) {
3
} else if (certs.contains(Certification.CombatEngineering)) {
2
} else if (certs.contains(Certification.Engineering)) {
1
} else {
0
}
}
}

View file

@ -14,7 +14,6 @@ import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.damage.{AggravatedBehavior, Damageable, DamageableEntity}
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.repair.Repairable
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.vital._
import net.psforever.objects.vital.resolution.ResolutionCalculations.Output
@ -27,6 +26,7 @@ import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.objects.locker.LockerContainerControl
import net.psforever.objects.serverobject.environment._
import net.psforever.objects.serverobject.repair.Repairable
import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad
import net.psforever.objects.vital.environment.EnvironmentReason
import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason}
@ -185,7 +185,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
val guid = player.GUID
if (!(player.isMoving || user.isMoving)) { //only allow stationary repairs
val newArmor = player.Armor =
originalArmor + Repairable.Quality + RepairValue(item) + definition.RepairMod
originalArmor + Repairable.applyLevelModifier(user, item, RepairToolValue(item)).toInt + definition.RepairMod
val magazine = item.Discharge()
events ! AvatarServiceMessage(
uname,
@ -863,12 +863,15 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
case _ => ;
}
def RepairValue(item: Tool): Int =
if (player.ExoSuit != ExoSuitType.MAX) {
def RepairToolValue(item: Tool): Float = {
item.AmmoSlot.Box.Definition.repairAmount +
(if (player.ExoSuit != ExoSuitType.MAX) {
item.FireMode.Add.Damage0
} else {
item.FireMode.Add.Damage3
}
else {
item.FireMode.Add.Damage3
})
}
def MessageDeferredCallback(msg: Any): Unit = {
msg match {

View file

@ -8,6 +8,8 @@ class AmmoBoxDefinition(objectId: Int) extends EquipmentDefinition(objectId) {
import net.psforever.objects.equipment.EquipmentSize
private val ammoType: Ammo.Value = Ammo(objectId) //let throw NoSuchElementException
private var capacity: Int = 1
var repairAmount: Float = 0
Name = "ammo box"
Size = EquipmentSize.Inventory
Packet = AmmoBoxDefinition.converter

View file

@ -10,6 +10,11 @@ class ToolDefinition(objectId: Int) extends EquipmentDefinition(objectId) {
private val ammoTypes: mutable.ListBuffer[AmmoBoxDefinition] = new mutable.ListBuffer[AmmoBoxDefinition]
private val projectileTypes: mutable.ListBuffer[ProjectileDefinition] = new mutable.ListBuffer[ProjectileDefinition]
private val fireModes: mutable.ListBuffer[FireModeDefinition] = new mutable.ListBuffer[FireModeDefinition]
/**
* the multiplier value at which the base repair amount of an ammunition operates;
* by default, it always has a single "no repair" multiplier
*/
private val repairMultipliers: mutable.ListBuffer[Float] = new mutable.ListBuffer[Float].addOne(0f)
private var defaultFireModeIndex: Option[Int] = None
Name = "tool"
Packet = ToolDefinition.converter
@ -22,6 +27,26 @@ class ToolDefinition(objectId: Int) extends EquipmentDefinition(objectId) {
def NextFireModeIndex(index: Int): Int = index + 1
def RepairMultipliers: Seq[Float] = repairMultipliers.toSeq
def RepairMultiplier(level: Int): Float = {
if (level > -1 && level < repairMultipliers.size) {
repairMultipliers(level)
} else {
0f
}
}
def AddRepairMultiplier(level: Int, value: Float): Seq[Float] = {
if (level > 0) {
while(repairMultipliers.size <= level) {
repairMultipliers.addOne(0f)
}
repairMultipliers.update(level, value)
}
RepairMultipliers
}
def DefaultFireModeIndex: Int = defaultFireModeIndex.getOrElse(0)
def DefaultFireModeIndex_=(index: Int): Int = DefaultFireModeIndex_=(Some(index))

View file

@ -3,7 +3,7 @@ package net.psforever.objects.serverobject.repair
import akka.actor.Actor.Receive
import net.psforever.objects.equipment.Ammo
import net.psforever.objects.{GlobalDefinitions, Player, Tool}
import net.psforever.objects.{GlobalDefinitions, Player, Players, Tool}
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.vital.Vitality
@ -51,7 +51,7 @@ trait Repairable {
* @param item the tool in question
* @return an amount to add to the repair attempt progress
*/
def RepairValue(item: Tool): Int = 0
def RepairToolValue(item: Tool): Float = item.AmmoSlot.Box.Definition.repairAmount
/**
* The entity is no longer destroyed.
@ -65,8 +65,19 @@ trait Repairable {
object Repairable {
/* the type of all entities governed by this mixin; see Damageable.Target */
final type Target = PlanetSideServerObject with Vitality
/* the basic repair value; originally found on the `armor_canister` object definition */
final val Quality: Int = 12
/**
* Apply the player's engineering modifier to a repairing tool's base repair value.
* @see `AmmoBoxDefinition.RepairMultiplier`
* @see `Players.repairModifierLevel`
* @param user the player using the tool used for repairing
* @param item the tool used for repairing
* @param amount the base amount of repairing
* @return a modified amount of repairing
*/
def applyLevelModifier(user: Player, item: Tool, amount: Float): Float = {
item.Definition.RepairMultiplier(Players.repairModifierLevel(user)) * amount
}
/**
* The entity is no longer destroyed.

View file

@ -1,6 +1,7 @@
//Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.repair
import net.psforever.objects.Tool
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
@ -15,6 +16,8 @@ trait RepairableAmenity extends RepairableEntity {
super.Restoration(obj)
RepairableAmenity.Restoration(obj)
}
override def RepairToolValue(item: Tool): Float = super.RepairToolValue(item) + item.FireMode.Add.Damage1
}
object RepairableAmenity {

View file

@ -82,8 +82,8 @@ trait RepairableEntity extends Repairable {
val name = player.Name
val originalHealth = target.Health
val updatedHealth =
if (!(player.isMoving(1f) || target.isMoving(1f))) { //only allow stationary repairs within margin of error
val repairValue = Repairable.Quality + RepairValue(item) + target.Definition.RepairMod
if (!(player.isMoving(test = 1f) || target.isMoving(test = 1f))) { //only allow stationary repairs within margin of error
val repairValue = Repairable.applyLevelModifier(player, item, RepairToolValue(item)).toInt + target.Definition.RepairMod
val magazine = item.Discharge()
events ! AvatarServiceMessage(
player.Name,
@ -107,11 +107,11 @@ trait RepairableEntity extends Repairable {
}
protected def PerformRepairs(target: Repairable.Target, amount: Int): Int = {
val zone = target.Zone
val zoneId = zone.id
val events = zone.AvatarEvents
val tguid = target.GUID
val newHealth = target.Health = target.Health + amount
val zone = target.Zone
val zoneId = zone.id
val events = zone.AvatarEvents
val tguid = target.GUID
val newHealth = target.Health = target.Health + amount
if (target.Destroyed) {
if (newHealth >= target.Definition.RepairRestoresAt) {
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 0, newHealth))
@ -122,7 +122,4 @@ trait RepairableEntity extends Repairable {
}
newHealth
}
/* random object repair modifier */
override def RepairValue(item: Tool): Int = item.FireMode.Add.Damage1
}

View file

@ -1,11 +1,14 @@
//Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.repair
import net.psforever.objects.{Tool, Vehicle}
/**
* The "control" `Actor` mixin for repair-handling code for `Vehicle` objects.
*/
trait RepairableVehicle extends RepairableEntity {
def RepairableObject: Vehicle
override def Restoration(obj: Repairable.Target): Unit = {
obj.Health = 0
obj.Destroyed = true
@ -13,4 +16,13 @@ trait RepairableVehicle extends RepairableEntity {
/* if you wanted to properly restore a destroyed vehicle, the quickest way is an ObjectCreateMessage packet */
/* additionally, the vehicle deconstruction task must be cancelled */
}
override def RepairToolValue(item: Tool): Float = {
super.RepairToolValue(item) +
(if (RepairableObject.Definition.CanFly) {
item.FireMode.Add.Damage2
} else {
item.FireMode.Add.Damage1
})
}
}

View file

@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.repair
import net.psforever.objects.Tool
import net.psforever.objects.equipment.EquipmentSlot
import net.psforever.objects.serverobject.turret.WeaponTurret
import net.psforever.objects.vehicles.MountedWeapons
import net.psforever.services.Service
@ -41,9 +42,9 @@ object RepairableWeaponTurret {
val tguid = target.GUID
val events = zone.VehicleEvents
target.Weapons
.map({ case (index, slot) => (index, slot.Equipment) })
.map({ case (index, slot: EquipmentSlot) => (index, slot.Equipment) })
.collect {
case (index, Some(tool: Tool)) =>
case (index: Int, Some(tool: Tool)) =>
events ! VehicleServiceMessage(
zoneId,
VehicleAction.EquipmentInSlot(Service.defaultPlayerGUID, tguid, index, tool)

View file

@ -4,7 +4,7 @@ package objects
import akka.actor.Props
import akka.testkit.TestProbe
import base.ActorTest
import net.psforever.objects.avatar.Avatar
import net.psforever.objects.avatar.{Avatar, Certification}
import net.psforever.objects.{Default, GlobalDefinitions, Player, Tool}
import net.psforever.objects.definition.ToolDefinition
import net.psforever.objects.guid.NumberPoolHub
@ -200,8 +200,9 @@ class FacilityTurretControlRestorationTest extends ActorTest {
turret.Position = Vector3(1, 0, 0)
val turretWeapon = turret.Weapons.values.head.Equipment.get.asInstanceOf[Tool]
val player1 =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=3
val avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
.copy(certifications = Set(Certification.Engineering))
val player1 = Player(avatar) //guid=3
player1.Spawn()
player1.Position = Vector3(2, 2, 2)
val player1Probe = TestProbe()

View file

@ -5,7 +5,7 @@ import akka.actor.{ActorRef, Props}
import akka.testkit.TestProbe
import base.ActorTest
import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.avatar.Avatar
import net.psforever.objects.avatar.{Avatar, Certification}
import net.psforever.objects.ballistics._
import net.psforever.objects.{GlobalDefinitions, Player, Tool}
import net.psforever.objects.guid.NumberPoolHub
@ -773,8 +773,9 @@ class GeneratorControlRepairPastRestorePoint extends ActorTest {
gen.Position = Vector3(1, 0, 0)
gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control")
val player1 =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=3
val avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
.copy(certifications = Set(Certification.Engineering))
val player1 = Player(avatar) //guid=3
player1.Position = Vector3(14, 0, 0) //<14m from generator; dies
player1.Spawn()
val player1Probe = TestProbe()

View file

@ -6,7 +6,7 @@ import akka.actor.{ActorSystem, Props}
import akka.testkit.TestProbe
import base.ActorTest
import net.psforever.actors.session.AvatarActor
import net.psforever.objects.avatar.{Avatar, PlayerControl}
import net.psforever.objects.avatar.{Avatar, Certification, PlayerControl}
import net.psforever.objects.ballistics._
import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource
@ -185,8 +185,9 @@ class PlayerControlHealSelfTest extends ActorTest {
}
class PlayerControlRepairTest extends ActorTest {
val player1 =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
val avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
.copy(certifications = Set(Certification.Engineering))
val player1 = Player(avatar) //guid=1
val player2 =
Player(Avatar(1, "TestCharacter2", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=2
val avatarProbe = TestProbe()
@ -281,8 +282,9 @@ class PlayerControlRepairTest extends ActorTest {
}
class PlayerControlRepairSelfTest extends ActorTest {
val player1 =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
val avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
.copy(certifications = Set(Certification.Engineering))
val player1 = Player(avatar) //guid=1
val avatarProbe = TestProbe()
val guid = new NumberPoolHub(new MaxNumberSource(15))
val zone = new Zone("test", new ZoneMap("test"), 0) {

View file

@ -5,7 +5,7 @@ import akka.actor.Props
import akka.testkit.TestProbe
import base.ActorTest
import net.psforever.objects._
import net.psforever.objects.avatar.Avatar
import net.psforever.objects.avatar.{Avatar, Certification}
import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.CommonMessages
@ -36,8 +36,9 @@ class RepairableEntityRepairTest extends ActorTest {
}
val building = Building("test-building", 1, 1, zone, StructureType.Facility) //guid=1
val gen = Generator(GlobalDefinitions.generator) //guid=2
val player1 =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=3
val avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
.copy(certifications = Set(Certification.Engineering))
val player1 = Player(avatar) //guid=3
player1.Spawn()
guid.register(building, 1)
guid.register(gen, 2)
@ -149,8 +150,9 @@ class RepairableAmenityTest extends ActorTest {
}
val building = Building("test-building", 1, 1, zone, StructureType.Facility) //guid=1
val term = Terminal(GlobalDefinitions.order_terminal) //guid=2
val player1 =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=3
val avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
.copy(certifications = Set(Certification.Engineering))
val player1 = Player(avatar) //guid=3
player1.Spawn()
guid.register(building, 1)
guid.register(term, 2)
@ -250,8 +252,9 @@ class RepairableTurretWeapon extends ActorTest {
turret.Position = Vector3(1, 0, 0)
val turretWeapon = turret.Weapons.values.head.Equipment.get.asInstanceOf[Tool]
val player1 =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=3
val avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
.copy(certifications = Set(Certification.Engineering))
val player1 = Player(avatar) //guid=3
player1.Spawn()
player1.Position = Vector3(2, 2, 2)
val player1Probe = TestProbe()
@ -336,8 +339,9 @@ class RepairableVehicleRepair extends ActorTest {
atv.Position = Vector3(1, 0, 0)
val atvWeapon = atv.Weapons(1).Equipment.get.asInstanceOf[Tool]
val player1 =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=4
val avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
.copy(certifications = Set(Certification.Engineering))
val player1 = Player(avatar) //guid=4
player1.Spawn()
player1.Position = Vector3(2, 2, 2)
val player1Probe = TestProbe()