From 8b5073dcbc4e464e1c6943a9a09e2812195511b1 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Mon, 30 Jul 2018 10:20:23 -0400 Subject: [PATCH] accounted for collapsing cert trees, exclusive certifications, and a method of keeping track of certification costs (#227) --- .../objects/avatar/Certification.scala | 233 ++++++++++++++++++ .../terminals/CertTerminalDefinition.scala | 90 +++---- .../serverobject/terminals/Terminal.scala | 6 +- .../scala/objects/CertificationTest.scala | 140 +++++++++++ .../objects/terminal/CertTerminalTest.scala | 4 +- .../terminal/TerminalControlTest.scala | 4 +- .../src/main/scala/WorldSessionActor.scala | 33 ++- 7 files changed, 449 insertions(+), 61 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/avatar/Certification.scala create mode 100644 common/src/test/scala/objects/CertificationTest.scala diff --git a/common/src/main/scala/net/psforever/objects/avatar/Certification.scala b/common/src/main/scala/net/psforever/objects/avatar/Certification.scala new file mode 100644 index 000000000..f783108a1 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/avatar/Certification.scala @@ -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 + ) + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/CertTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/CertTerminalDefinition.scala index a1583aa0c..1143b2d8e 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/CertTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/CertTerminalDefinition.scala @@ -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() } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala index 61f587e79..eb0c56a55 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala @@ -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 /** diff --git a/common/src/test/scala/objects/CertificationTest.scala b/common/src/test/scala/objects/CertificationTest.scala new file mode 100644 index 000000000..7bdcc25a8 --- /dev/null +++ b/common/src/test/scala/objects/CertificationTest.scala @@ -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 + } + } +} diff --git a/common/src/test/scala/objects/terminal/CertTerminalTest.scala b/common/src/test/scala/objects/terminal/CertTerminalTest.scala index 67682396d..5676d3276 100644 --- a/common/src/test/scala/objects/terminal/CertTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/CertTerminalTest.scala @@ -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 { diff --git a/common/src/test/scala/objects/terminal/TerminalControlTest.scala b/common/src/test/scala/objects/terminal/TerminalControlTest.scala index 39680406a..ed5c0e1f6 100644 --- a/common/src/test/scala/objects/terminal/TerminalControlTest.scala +++ b/common/src/test/scala/objects/terminal/TerminalControlTest.scala @@ -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)) } } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 39d10935a..4d94c6af6 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -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)) }