accounted for collapsing cert trees, exclusive certifications, and a method of keeping track of certification costs (#227)

This commit is contained in:
Fate-JH 2018-07-30 10:20:23 -04:00 committed by GitHub
parent 2f0629d83a
commit 8b5073dcbc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 449 additions and 61 deletions

View file

@ -0,0 +1,233 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.avatar
import net.psforever.types.CertificationType
import scala.collection.mutable
object Certification {
object Dependencies {
/**
* Find the certifications that are immediately dependent on the target certification.
* (For `A`, find all `B` that are `B ⇒ A`.)
* @param certification the target certification
* @return all connected certifications
*/
def From(certification : CertificationType.Value) : Set[CertificationType.Value] = dependencies(certification).toSet
/**
* Find all certifications that are dependent on the target certification.
* (For `A`, find all `B...C` where `C ⇒ B` and `B ⇒ A`.)
* @param certification the target certification
* @return all connected certifications
*/
def FromAll(certification : CertificationType.Value) : Set[CertificationType.Value] = {
var available : List[CertificationType.Value] = List(certification)
var allocated : mutable.ListBuffer[CertificationType.Value] = mutable.ListBuffer.empty[CertificationType.Value]
do {
available = available.flatMap(cert => dependencies(cert))
allocated ++= available
}
while(available.nonEmpty)
allocated.toSet
}
/**
* Find the certifications that are immediate dependencies of the target certification.
* (For `A`, find all `B` where `A ⇒ B`.)
* @param certification the target certification
* @return all connected certifications
*/
def For(certification : CertificationType.Value) : Set[CertificationType.Value] = {
(for {
(cert, certs) <- dependencies
if certs contains certification
} yield cert).toSet
}
/**
* Find all certifications that are dependencies of the target certification.
* (For `A`, find all `B...C` where `A ⇒ B` and `B ⇒ C`.)
* @param certification the target certification
* @return all connected certifications
*/
def ForAll(certification : CertificationType.Value) : Set[CertificationType.Value] = {
var available : List[CertificationType.Value] = List(certification)
var allocated : mutable.ListBuffer[CertificationType.Value] = mutable.ListBuffer.empty[CertificationType.Value]
do {
available = available.flatMap {
For
}
allocated ++= available
}
while(available.nonEmpty)
allocated.toSet
}
import CertificationType._
/**
* Find all certifications that are related but mutually exclusive with the target certification.
* (For `A`, find all `B` that `B ⊃ A` but `A XOR B`.)
* @param certification the target certification
* @return all connected certifications
*/
def Like(certification : CertificationType.Value) : Set[CertificationType.Value] = certification match {
case AssaultBuggy =>
Set(Harasser)
case LightScout =>
Set(AirCavalryScout, AssaultBuggy, Harasser)
case UniMAX =>
Set(AAMAX, AIMAX, AVMAX)
case AdvancedEngineering =>
Set(AssaultEngineering, FortificationEngineering)
case ElectronicsExpert =>
Set(DataCorruption, ExpertHacking)
case _ =>
Set.empty[CertificationType.Value]
}
private val dependencies : Map[CertificationType.Value, List[CertificationType.Value]] = Map(
StandardAssault -> List(),
AgileExoSuit -> List(),
ReinforcedExoSuit -> List(),
InfiltrationSuit -> List(Phantasm),
AIMAX -> List(),
AVMAX -> List(),
AAMAX -> List(),
UniMAX -> List(),
StandardAssault -> List(),
MediumAssault -> List(AntiVehicular, HeavyAssault, Sniping, SpecialAssault),
AntiVehicular -> List(),
HeavyAssault -> List(),
Sniping -> List(),
SpecialAssault -> List(EliteAssault),
EliteAssault -> List(),
ATV -> List(Switchblade),
Switchblade -> List(),
Harasser -> List(),
AssaultBuggy -> List(),
LightScout -> List(AirCavalryAssault),
GroundSupport -> List(),
GroundTransport -> List(),
ArmoredAssault1 -> List(ArmoredAssault2),
ArmoredAssault2 -> List(BattleFrameRobotics, Flail),
Flail -> List(),
AirCavalryScout -> List(AirCavalryAssault),
AirCavalryAssault -> List(AirCavalryInterceptor),
AirCavalryInterceptor -> List(),
AirSupport -> List(GalaxyGunship),
GalaxyGunship -> List(),
Phantasm -> List(),
BattleFrameRobotics -> List(BFRAntiInfantry, BFRAntiAircraft),
BFRAntiInfantry -> List(),
BFRAntiAircraft -> List(),
Medical -> List(AdvancedMedical),
AdvancedMedical -> List(),
Engineering -> List(CombatEngineering),
CombatEngineering -> List(AdvancedEngineering, AssaultEngineering, FortificationEngineering),
AdvancedEngineering -> List(),
AssaultEngineering -> List(),
FortificationEngineering -> List(),
Hacking -> List(AdvancedHacking),
AdvancedHacking -> List(DataCorruption, ElectronicsExpert, ExpertHacking),
DataCorruption -> List(),
ElectronicsExpert -> List(),
ExpertHacking -> List()
)
}
object Cost {
/**
* For a certification, get its point cost.
* @param certification the certification
* @return the cost
*/
def Of(certification : CertificationType.Value) : Int = points(certification)
/**
* For a list of certifications, find the point cost of all unique certifications.
* @see `Of(Set)`
* @param certifications the certification list
* @return the total cost
*/
def Of(certifications : List[CertificationType.Value]) : Int = Of(certifications.toSet)
/**
* For a set of certifications, find the point cost of all certifications.
* @see `OfAll(List)`
* @param certifications the certification list
* @return the total cost
*/
def Of(certifications : Set[CertificationType.Value]) : Int = OfAll(certifications.toList)
/**
* For a list of certifications, find the point cost of all certifications, counting any duplicates.
* @param certifications the certification list
* @return the total cost
*/
def OfAll(certifications : List[CertificationType.Value]) : Int = {
certifications map points sum
}
import CertificationType._
private val points : Map[CertificationType.Value, Int] = Map(
StandardExoSuit -> 0,
AgileExoSuit -> 0,
ReinforcedExoSuit -> 3,
InfiltrationSuit -> 2,
AAMAX -> 2,
AIMAX -> 3,
AVMAX -> 3,
UniMAX -> 6,
StandardAssault -> 0,
MediumAssault -> 2,
AntiVehicular -> 3,
HeavyAssault -> 4,
Sniping -> 3,
SpecialAssault -> 3,
EliteAssault -> 1,
ATV -> 1,
Switchblade -> 1,
Harasser -> 1,
AssaultBuggy -> 3,
LightScout -> 5,
GroundSupport -> 2,
GroundTransport -> 2,
ArmoredAssault1 -> 2,
ArmoredAssault2 -> 3,
Flail -> 1,
AirCavalryScout -> 3,
AirCavalryAssault -> 2,
AirCavalryInterceptor -> 2,
AirSupport -> 3,
GalaxyGunship -> 2,
Phantasm -> 3,
BattleFrameRobotics -> 4,
BFRAntiInfantry -> 1,
BFRAntiAircraft -> 1,
Medical -> 3,
AdvancedMedical -> 2,
Engineering -> 3,
CombatEngineering -> 2,
AdvancedEngineering -> 5,
AssaultEngineering -> 3,
FortificationEngineering -> 3,
Hacking -> 3,
AdvancedHacking -> 2,
DataCorruption -> 3,
ElectronicsExpert -> 4,
ExpertHacking -> 2
)
}
}

View file

@ -16,47 +16,47 @@ class CertTerminalDefinition extends TerminalDefinition(171) {
* The certifications available.
* All entries are listed on page (tab) number 0.
*/
private val certificationList : Map[String, (CertificationType.Value, Int)] = Map(
"medium_assault" -> (CertificationType.MediumAssault, 2),
"reinforced_armor" -> (CertificationType.ReinforcedExoSuit, 3),
"quad_all" -> (CertificationType.ATV, 1),
"switchblade" -> (CertificationType.Switchblade, 1),
"harasser" -> (CertificationType.Harasser, 1),
"anti_vehicular" -> (CertificationType.AntiVehicular, 3),
"heavy_assault" -> (CertificationType.HeavyAssault, 4),
"sniper" -> (CertificationType.Sniping, 3),
"special_assault" -> (CertificationType.SpecialAssault, 3),
"special_assault_2" -> (CertificationType.EliteAssault, 1),
"infiltration_suit" -> (CertificationType.InfiltrationSuit, 2),
"max_anti_personnel" -> (CertificationType.AIMAX, 3),
"max_anti_vehicular" -> (CertificationType.AVMAX, 3),
"max_anti_aircraft" -> (CertificationType.AAMAX, 2),
"max_all" -> (CertificationType.UniMAX, 6),
"air_cavalry_scout" -> (CertificationType.AirCavalryScout, 3),
"air_cavalry_assault" -> (CertificationType.AirCavalryAssault, 2),
"air_cavalry_interceptor" -> (CertificationType.AirCavalryInterceptor, 2),
"air_support" -> (CertificationType.AirSupport, 3),
"gunship" -> (CertificationType.GalaxyGunship, 2),
"phantasm" -> (CertificationType.Phantasm, 3),
"armored_assault1" -> (CertificationType.ArmoredAssault1, 2),
"armored_assault2" -> (CertificationType.ArmoredAssault2, 1),
"flail" -> (CertificationType.Flail, 1),
"assault_buggy" -> (CertificationType.AssaultBuggy, 3),
"ground_support" -> (CertificationType.GroundSupport, 2),
"ground_transport" -> (CertificationType.GroundTransport, 2),
"light_scout" -> (CertificationType.LightScout, 5),
"Repair" -> (CertificationType.Engineering, 3),
"combat_engineering" -> (CertificationType.CombatEngineering, 2),
"ce_offense" -> (CertificationType.AssaultEngineering, 3),
"ce_defense" -> (CertificationType.FortificationEngineering, 3),
"ce_advanced" -> (CertificationType.AdvancedEngineering, 5),
"Hacking" -> (CertificationType.Hacking, 3),
"advanced_hacking" -> (CertificationType.AdvancedHacking, 2),
"expert_hacking" -> (CertificationType.ExpertHacking, 2),
"virus_hacking" -> (CertificationType.DataCorruption, 3),
"electronics_expert" -> (CertificationType.ElectronicsExpert, 4),
"Medical" -> (CertificationType.Medical, 3),
"advanced_medical" -> (CertificationType.AdvancedMedical, 2)
private val certificationList : Map[String, CertificationType.Value] = Map(
"medium_assault" -> CertificationType.MediumAssault,
"reinforced_armor" -> CertificationType.ReinforcedExoSuit,
"quad_all" -> CertificationType.ATV,
"switchblade" -> CertificationType.Switchblade,
"harasser" -> CertificationType.Harasser,
"anti_vehicular" -> CertificationType.AntiVehicular,
"heavy_assault" -> CertificationType.HeavyAssault,
"sniper" -> CertificationType.Sniping,
"special_assault" -> CertificationType.SpecialAssault,
"special_assault_2" -> CertificationType.EliteAssault,
"infiltration_suit" -> CertificationType.InfiltrationSuit,
"max_anti_personnel" -> CertificationType.AIMAX,
"max_anti_vehicular" -> CertificationType.AVMAX,
"max_anti_aircraft" -> CertificationType.AAMAX,
"max_all" -> CertificationType.UniMAX,
"air_cavalry_scout" -> CertificationType.AirCavalryScout,
"air_cavalry_assault" -> CertificationType.AirCavalryAssault,
"air_cavalry_interceptor" -> CertificationType.AirCavalryInterceptor,
"air_support" -> CertificationType.AirSupport,
"gunship" -> CertificationType.GalaxyGunship,
"phantasm" -> CertificationType.Phantasm,
"armored_assault1" -> CertificationType.ArmoredAssault1,
"armored_assault2" -> CertificationType.ArmoredAssault2,
"flail" -> CertificationType.Flail,
"assault_buggy" -> CertificationType.AssaultBuggy,
"ground_support" -> CertificationType.GroundSupport,
"ground_transport" -> CertificationType.GroundTransport,
"light_scout" -> CertificationType.LightScout,
"Repair" -> CertificationType.Engineering,
"combat_engineering" -> CertificationType.CombatEngineering,
"ce_offense" -> CertificationType.AssaultEngineering,
"ce_defense" -> CertificationType.FortificationEngineering,
"ce_advanced" -> CertificationType.AdvancedEngineering,
"Hacking" -> CertificationType.Hacking,
"advanced_hacking" -> CertificationType.AdvancedHacking,
"expert_hacking" -> CertificationType.ExpertHacking,
"virus_hacking" -> CertificationType.DataCorruption,
"electronics_expert" -> CertificationType.ElectronicsExpert,
"Medical" -> CertificationType.Medical,
"advanced_medical" -> CertificationType.AdvancedMedical
//TODO bfr certification entries
)
@ -68,8 +68,8 @@ class CertTerminalDefinition extends TerminalDefinition(171) {
*/
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { //Learn
certificationList.get(msg.item_name) match {
case Some((cert, cost)) =>
Terminal.LearnCertification(cert, cost)
case Some(cert) =>
Terminal.LearnCertification(cert)
case None =>
Terminal.NoDeal()
}
@ -83,8 +83,8 @@ class CertTerminalDefinition extends TerminalDefinition(171) {
*/
override def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
certificationList.get(msg.item_name) match {
case Some((cert, cost)) =>
Terminal.SellCertification(cert, cost)
case Some(cert) =>
Terminal.SellCertification(cert)
case None =>
Terminal.NoDeal()
}

View file

@ -116,16 +116,14 @@ object Terminal {
/**
* Provide the certification type unlocked by the player.
* @param cert the certification unlocked
* @param cost the certification point cost
*/
final case class LearnCertification(cert : CertificationType.Value, cost : Int) extends Exchange
final case class LearnCertification(cert : CertificationType.Value) extends Exchange
/**
* Provide the certification type freed-up by the player.
* @param cert the certification returned
* @param cost the certification point cost
*/
final case class SellCertification(cert : CertificationType.Value, cost : Int) extends Exchange
final case class SellCertification(cert : CertificationType.Value) extends Exchange
import net.psforever.objects.definition.ImplantDefinition
/**

View file

@ -0,0 +1,140 @@
// Copyright (c) 2017 PSForever
package objects
import net.psforever.objects.avatar.Certification
import net.psforever.types.CertificationType._
import org.specs2.mutable.Specification
class CertificationTest extends Specification {
"Dependencies" should {
//From
"find any certifications immediately dependent on a given certification (nothing)" in {
Certification.Dependencies.From(StandardAssault) mustEqual Set()
}
"find any certifications immediately dependent on a given certification (one)" in {
Certification.Dependencies.From(Engineering) mustEqual Set(CombatEngineering)
}
"find any certifications immediately dependent on a given certification (multiple)" in {
Certification.Dependencies.From(MediumAssault) mustEqual Set(AntiVehicular, HeavyAssault, Sniping, SpecialAssault)
}
"find any certifications immediately dependent on a given certification (intermediate)" in {
Certification.Dependencies.From(ArmoredAssault2) mustEqual Set(BattleFrameRobotics, Flail)
}
//FromAll
"find all certifications dependent on a given certification (nothing)" in {
Certification.Dependencies.FromAll(StandardAssault) mustEqual Set()
}
"find all certifications dependent on a given certification (one)" in {
Certification.Dependencies.FromAll(ATV) mustEqual Set(Switchblade)
}
"find all certifications dependent on a given certification (multiple)" in {
Certification.Dependencies.FromAll(MediumAssault) mustEqual Set(AntiVehicular, HeavyAssault, Sniping, SpecialAssault, EliteAssault)
}
"find all certifications dependent on a given certification (intermediate)" in {
Certification.Dependencies.FromAll(ArmoredAssault2) mustEqual Set(BattleFrameRobotics, Flail, BFRAntiInfantry, BFRAntiAircraft)
}
//For
"find any certifications that are immediate dependencies for a given certification (nothing)" in {
Certification.Dependencies.For(StandardAssault) mustEqual Set()
}
"find any certifications that are immediate dependencies for a given certification (one)" in {
Certification.Dependencies.For(CombatEngineering) mustEqual Set(Engineering)
}
"find any certifications that are immediate dependencies for a given certification (multiple)" in {
Certification.Dependencies.For(AirCavalryAssault) mustEqual Set(AirCavalryScout, LightScout)
}
"find any certifications that are immediate dependencies for a given certification (intermediate)" in {
Certification.Dependencies.For(BattleFrameRobotics) mustEqual Set(ArmoredAssault2)
}
//ForAll
"find all certifications that are dependencies for a given certification (nothing)" in {
Certification.Dependencies.ForAll(StandardAssault) mustEqual Set()
}
"find all certifications that are dependencies for a given certification (one)" in {
Certification.Dependencies.ForAll(CombatEngineering) mustEqual Set(Engineering)
}
"find all certifications that are dependencies for a given certification (multiple)" in {
Certification.Dependencies.ForAll(AirCavalryAssault) mustEqual Set(AirCavalryScout, LightScout)
}
"find all certifications that are dependencies for a given certification (intermediate)" in {
Certification.Dependencies.ForAll(BattleFrameRobotics) mustEqual Set(ArmoredAssault1, ArmoredAssault2)
}
//Like
"find related certifications" in {
Certification.Dependencies.Like(AssaultBuggy) mustEqual Set(Harasser)
Certification.Dependencies.Like(LightScout) mustEqual Set(AirCavalryScout, AssaultBuggy, Harasser)
Certification.Dependencies.Like(UniMAX) mustEqual Set(AIMAX, AVMAX, AAMAX)
Certification.Dependencies.Like(StandardAssault) mustEqual Set()
}
}
"Cost" should {
"calculate the point-value of any certification (no value)" in {
Certification.Cost.Of(StandardAssault) mustEqual 0
}
"calculate the point-value of any certification (value)" in {
Certification.Cost.Of(MediumAssault) mustEqual 2
}
"calculate the sum-point-value of all certifications (no value)" in {
Certification.Cost.Of(Set(StandardAssault)) mustEqual 0
}
"calculate the sum-point-value of all certifications (value)" in {
Certification.Cost.Of(Set(MediumAssault)) mustEqual 2
}
"calculate the sum-point-value of all certifications (add)" in {
Certification.Cost.Of(Set(StandardAssault, MediumAssault)) mustEqual 2
Certification.Cost.Of(Set(HeavyAssault, MediumAssault)) mustEqual 6
}
"calculate the sum-point-value of all certifications (large collection)" in {
Certification.Cost.Of(Set(StandardAssault, MediumAssault, StandardExoSuit, AgileExoSuit, ReinforcedExoSuit, ATV, Harasser)) mustEqual 7
}
"calculate the sum-point-value of all unique certifications (no value)" in {
Certification.Cost.Of(List(StandardAssault, StandardAssault)) mustEqual 0
}
"calculate the sum-point-value of all unique certifications (value)" in {
Certification.Cost.Of(List(MediumAssault, MediumAssault)) mustEqual 2
}
"calculate the sum-point-value of all unique certifications (add)" in {
Certification.Cost.Of(List(StandardAssault, MediumAssault, MediumAssault)) mustEqual 2
Certification.Cost.Of(List(HeavyAssault, MediumAssault, HeavyAssault)) mustEqual 6
}
"calculate the sum-point-value of all unique certifications (large collection)" in {
Certification.Cost.Of(
List(
StandardAssault, MediumAssault, StandardExoSuit, AgileExoSuit, ReinforcedExoSuit, ATV, Harasser,
MediumAssault, StandardExoSuit, ReinforcedExoSuit, ATV
)
) mustEqual 7
}
"calculate the sum-point-value of all certifications (count duplicates)" in {
Certification.Cost.OfAll(
List(
StandardAssault, MediumAssault, StandardExoSuit, AgileExoSuit, ReinforcedExoSuit, ATV, Harasser,
MediumAssault, StandardExoSuit, ReinforcedExoSuit, ATV
)
) mustEqual 13
}
}
}

View file

@ -24,7 +24,7 @@ class CertTerminalTest extends Specification {
"player can learn a certification ('medium_assault')" in {
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Learn, 0, "medium_assault", 0, PlanetSideGUID(0))
terminal.Request(player, msg) mustEqual Terminal.LearnCertification(CertificationType.MediumAssault, 2)
terminal.Request(player, msg) mustEqual Terminal.LearnCertification(CertificationType.MediumAssault)
}
"player can not learn a fake certification ('juggling')" in {
@ -36,7 +36,7 @@ class CertTerminalTest extends Specification {
"player can forget a certification ('medium_assault')" in {
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Sell, 0, "medium_assault", 0, PlanetSideGUID(0))
terminal.Request(player, msg) mustEqual Terminal.SellCertification(CertificationType.MediumAssault, 2)
terminal.Request(player, msg) mustEqual Terminal.SellCertification(CertificationType.MediumAssault)
}
"player can not forget a fake certification ('juggling')" in {

View file

@ -43,7 +43,7 @@ class CertTerminalControl1Test extends ActorTest {
val reply2 = reply.asInstanceOf[Terminal.TerminalMessage]
assert(reply2.player == player)
assert(reply2.msg == msg)
assert(reply2.response == Terminal.LearnCertification(CertificationType.MediumAssault, 2))
assert(reply2.response == Terminal.LearnCertification(CertificationType.MediumAssault))
}
}
@ -73,7 +73,7 @@ class CertTerminalControl3Test extends ActorTest {
val reply2 = reply.asInstanceOf[Terminal.TerminalMessage]
assert(reply2.player == player)
assert(reply2.msg == msg)
assert(reply2.response == Terminal.SellCertification(CertificationType.MediumAssault, 2))
assert(reply2.response == Terminal.SellCertification(CertificationType.MediumAssault))
}
}

View file

@ -13,6 +13,7 @@ import csr.{CSRWarp, CSRZone, Traveler}
import net.psforever.objects.GlobalDefinitions._
import services.ServiceManager.Lookup
import net.psforever.objects._
import net.psforever.objects.avatar.Certification
import net.psforever.objects.ballistics._
import net.psforever.objects.definition.ToolDefinition
import net.psforever.objects.definition.converter.{CorpseConverter, DestroyedVehicleConverter}
@ -1180,27 +1181,43 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Loadout, false))
}
case Terminal.LearnCertification(cert, cost) =>
case Terminal.LearnCertification(cert) =>
val name = tplayer.Name
if(!tplayer.Certifications.contains(cert)) {
log.info(s"$tplayer is learning the $cert certification for $cost points")
val guid = tplayer.GUID
log.info(s"$name is learning the $cert certification for ${Certification.Cost.Of(cert)} points")
avatar.Certifications += cert
sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 24, cert.id))
sendResponse(PlanetsideAttributeMessage(guid, 24, cert.id.toLong))
tplayer.Certifications.intersect(Certification.Dependencies.Like(cert)).foreach(entry => {
log.info(s"$cert replaces the learned certification $entry that cost ${Certification.Cost.Of(entry)} points")
avatar.Certifications -= entry
sendResponse(PlanetsideAttributeMessage(guid, 25, entry.id.toLong))
})
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, true))
}
else {
log.warn(s"$tplayer already knows the $cert certification, so he can't learn it")
log.warn(s"$name already knows the $cert certification, so he can't learn it")
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, false))
}
case Terminal.SellCertification(cert, cost) =>
case Terminal.SellCertification(cert) =>
val name = tplayer.Name
if(tplayer.Certifications.contains(cert)) {
log.info(s"$tplayer is forgetting the $cert certification for $cost points")
val guid = tplayer.GUID
log.info(s"$name is forgetting the $cert certification for ${Certification.Cost.Of(cert)} points")
avatar.Certifications -= cert
sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 25, cert.id))
sendResponse(PlanetsideAttributeMessage(guid, 25, cert.id.toLong))
tplayer.Certifications.intersect(Certification.Dependencies.FromAll(cert)).foreach(entry => {
log.info(s"$name is also forgetting the ${Certification.Cost.Of(entry)}-point $entry certification which depends on $cert")
avatar.Certifications -= entry
sendResponse(PlanetsideAttributeMessage(guid, 25, entry.id.toLong))
})
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Sell, true))
}
else {
log.warn(s"$tplayer doesn't know what a $cert certification is, so he can't forget it")
log.warn(s"$name doesn't know what a $cert certification is, so he can't forget it")
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, false))
}