diff --git a/src/main/scala/net/psforever/actors/session/AvatarActor.scala b/src/main/scala/net/psforever/actors/session/AvatarActor.scala index 0adefdf1c..504239463 100644 --- a/src/main/scala/net/psforever/actors/session/AvatarActor.scala +++ b/src/main/scala/net/psforever/actors/session/AvatarActor.scala @@ -1,3 +1,4 @@ +// Copyright (c) 2019 PSForever package net.psforever.actors.session import java.util.concurrent.atomic.AtomicInteger @@ -16,7 +17,7 @@ import net.psforever.objects._ import net.psforever.objects.ballistics.PlayerSource import net.psforever.objects.locker.LockerContainer import net.psforever.objects.vital.HealFromImplant -import net.psforever.packet.game.objectcreate.ObjectClass +import net.psforever.packet.game.objectcreate.{ObjectClass, RibbonBars} import net.psforever.packet.game._ import net.psforever.types._ import net.psforever.util.Database._ @@ -178,6 +179,8 @@ object AvatarActor { /** Set cosmetics. Only allowed for BR24 or higher. */ final case class SetCosmetics(personalStyles: Set[Cosmetic]) extends Command + final case class SetRibbon(ribbon: MeritCommendation.Value, bar: RibbonBarSlot.Value) extends Command + private case class ServiceManagerLookupResult(result: ServiceManager.LookupResult) extends Command final case class SetStamina(stamina: Int) extends Command @@ -188,6 +191,14 @@ object AvatarActor { final case class AvatarLoginResponse(avatar: Avatar) + def changeRibbons(ribbons: RibbonBars, ribbon: MeritCommendation.Value, bar: RibbonBarSlot.Value): RibbonBars = { + bar match { + case RibbonBarSlot.Top => ribbons.copy(upper = ribbon) + case RibbonBarSlot.Middle => ribbons.copy(middle = ribbon) + case RibbonBarSlot.Bottom => ribbons.copy(lower = ribbon) + case RibbonBarSlot.TermOfService => ribbons.copy(tos = ribbon) + } + } } class AvatarActor( @@ -379,7 +390,7 @@ class AvatarActor( )) // if we need to start stamina regeneration tryRestoreStaminaForSession(stamina = 1) match { - case Some(sess) => + case Some(_) => defaultStaminaRegen(initialDelay = 0.5f seconds) case _ => ; } @@ -1003,6 +1014,22 @@ class AvatarActor( case SetCosmetics(cosmetics) => setCosmetics(cosmetics) Behaviors.same + + case SetRibbon(ribbon, bar) => + val previousRibbonBars = avatar.ribbonBars + val useRibbonBars = Seq(previousRibbonBars.upper, previousRibbonBars.middle, previousRibbonBars.lower) + .indexWhere { _ == ribbon } match { + case -1 => previousRibbonBars + case n => AvatarActor.changeRibbons(previousRibbonBars, MeritCommendation.None, RibbonBarSlot(n)) + } + replaceAvatar(avatar.copy(ribbonBars = AvatarActor.changeRibbons(useRibbonBars, ribbon, bar))) + val player = session.get.player + val zone = player.Zone + zone.AvatarEvents ! AvatarServiceMessage( + zone.id, + AvatarAction.SendResponse(Service.defaultPlayerGUID, DisplayedAwardMessage(player.GUID, ribbon, bar)) + ) + Behaviors.same } .receiveSignal { case (_, PostStop) => diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index 8616a6a48..9063e81e6 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -159,6 +159,8 @@ object SessionActor { tool: ConstructionItem, index: Int ) + + private final case class AvatarAwardMessageBundle(bundle: Iterable[Iterable[PlanetSidePacket]], delay: Long) } class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], connectionId: String, sessionId: Long) @@ -279,6 +281,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con var heightTrend: Boolean = false //up = true, down = false var heightHistory: Float = 0f val collisionHistory: mutable.HashMap[ActorRef, Long] = mutable.HashMap() + var populateAvatarAwardRibbonsFunc: (Int, Long) => Unit = setupAvatarAwardMessageDelivery var clientKeepAlive: Cancellable = Default.Cancellable var progressBarUpdate: Cancellable = Default.Cancellable @@ -1347,6 +1350,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con } } + case SessionActor.AvatarAwardMessageBundle(pkts, delay) => + performAvatarAwardMessageDelivery(pkts, delay) + case ResponseToSelf(pkt) => sendResponse(pkt) @@ -3157,8 +3163,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con if (tplayer.ExoSuit == ExoSuitType.MAX) { sendResponse(PlanetsideAttributeMessage(guid, 7, tplayer.Capacitor.toLong)) } - //AvatarAwardMessage - //DisplayAwardMessage + // AvatarAwardMessage + populateAvatarAwardRibbonsFunc(1, 20L) + sendResponse(PlanetsideStringAttributeMessage(guid, 0, "Outfit Name")) //squad stuff (loadouts, assignment) squadSetup() @@ -3251,6 +3258,83 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con HandleSetCurrentAvatar(tplayer) } + /** + * Don't extract the award advancement information from a player character upon respawning or zoning. + * You only need to perform that population once at login. + * @param bundleSize it doesn't matter + * @param delay it doesn't matter + */ + def skipAvatarAwardMessageDelivery(bundleSize: Int, delay: Long): Unit = { } + + /** + * Extract the award advancement information from a player character, and + * coordinate timed dispatches of groups of packets. + * @param bundleSize divide packets into groups of this size + * @param delay dispatch packet divisions in intervals + */ + def setupAvatarAwardMessageDelivery(bundleSize: Int, delay: Long): Unit = { + setupAvatarAwardMessageDelivery(player, bundleSize, delay) + populateAvatarAwardRibbonsFunc = skipAvatarAwardMessageDelivery + } + + /** + * Extract the merit commendation advancement information from a player character, + * filter unnecessary or not applicable statistics, + * translate the information into packet data, and + * coordinate timed dispatches of groups of packets. + * @param tplayer the player character + * @param bundleSize divide packets into groups of this size + * @param delay dispatch packet divisions in intervals + */ + def setupAvatarAwardMessageDelivery(tplayer: Player, bundleSize: Int, delay: Long): Unit = { + val date: Int = (System.currentTimeMillis() / 1000L).toInt - 604800 //last week, in seconds + performAvatarAwardMessageDelivery( + Award + .values + .filter { merit => + val label = merit.value + val alignment = merit.alignment + if (merit.category == AwardCategory.Exclusive) false + else if (alignment != PlanetSideEmpire.NEUTRAL && alignment != player.Faction) false + else if (label.contains("Male") && player.Sex != CharacterSex.Male) false + else if (label.contains("Female") && player.Sex != CharacterSex.Female) false + else true + } + .flatMap { merit => + merit.progression.map { level => + AvatarAwardMessage(level.commendation, AwardCompletion(date)) + } + } + .grouped(bundleSize) + .iterator + .to(Iterable), + delay + ) + } + + /** + * Coordinate timed dispatches of groups of packets. + * @param messageBundles groups of packets to be dispatched + * @param delay dispatch packet divisions in intervals + */ + def performAvatarAwardMessageDelivery( + messageBundles: Iterable[Iterable[PlanetSidePacket]], + delay: Long + ): Unit = { + messageBundles match { + case Nil => ; + case x :: Nil => + x.foreach { sendResponse } + case x :: xs => + x.foreach { sendResponse } + context.system.scheduler.scheduleOnce( + delay.milliseconds, + self, + SessionActor.AvatarAwardMessageBundle(xs, delay) + ) + } + } + /** * These messages are dispatched when first starting up the client and connecting to the server for the first time. * While many of these messages will be reused for other situations, they appear in this order only during startup. @@ -5966,12 +6050,16 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con log.error(s"InvalidTerrain: ${player.Name} is complaining about a thing@$vehicle_guid that can not be found") } - case msg @ ActionCancelMessage(u1, u2, u3) => + case ActionCancelMessage(_, _, _) => progressBarUpdate.cancel() progressBarValue = None case TradeMessage(trade) => - log.info(s"${player.Name} wants to trade, for some reason - $trade") + log.trace(s"${player.Name} wants to trade for some reason - $trade") + + case DisplayedAwardMessage(_, ribbon, bar) => + log.trace(s"${player.Name} changed the $bar displayed award ribbon to $ribbon") + avatarActor ! AvatarActor.SetRibbon(ribbon, bar) case _ => log.warn(s"Unhandled GamePacket $pkt") diff --git a/src/main/scala/net/psforever/objects/avatar/Avatar.scala b/src/main/scala/net/psforever/objects/avatar/Avatar.scala index 036273e17..d488dd68d 100644 --- a/src/main/scala/net/psforever/objects/avatar/Avatar.scala +++ b/src/main/scala/net/psforever/objects/avatar/Avatar.scala @@ -1,3 +1,4 @@ +// Copyright (c) 2017 PSForever package net.psforever.objects.avatar import net.psforever.objects.definition.{AvatarDefinition, BasicDefinition} @@ -6,6 +7,7 @@ import net.psforever.objects.inventory.LocallyRegisteredInventory import net.psforever.objects.loadouts.{Loadout, SquadLoadout} import net.psforever.objects.locker.{LockerContainer, LockerEquipment} import net.psforever.objects.{GlobalDefinitions, OffhandEquipmentSlot} +import net.psforever.packet.game.objectcreate.RibbonBars import net.psforever.types._ import org.joda.time.{Duration, LocalDateTime, Seconds} @@ -94,6 +96,7 @@ case class Avatar( stamina: Int = 100, fatigued: Boolean = false, cosmetics: Option[Set[Cosmetic]] = None, + ribbonBars: RibbonBars = RibbonBars(), certifications: Set[Certification] = Set(), loadouts: Seq[Option[Loadout]] = Seq.fill(20)(None), squadLoadouts: Seq[Option[SquadLoadout]] = Seq.fill(10)(None), diff --git a/src/main/scala/net/psforever/objects/avatar/Award.scala b/src/main/scala/net/psforever/objects/avatar/Award.scala new file mode 100644 index 000000000..29239d1c5 --- /dev/null +++ b/src/main/scala/net/psforever/objects/avatar/Award.scala @@ -0,0 +1,1201 @@ +// Copyright (c) 2022 PSForever +package net.psforever.objects.avatar + +import enumeratum.values.{StringEnum, StringEnumEntry} +import net.psforever.types.{MeritCommendation, PlanetSideEmpire} + +/** + * Awards organize merit commendations into an iterative series that have their own accomplishment statistics. + * @see `MeritCommendation.Value` + * @param commendation the merit commendation attached to this rank + */ +final case class CommendationRank(commendation: MeritCommendation.Value) + +/** + * A unique accomplishment that statuses positive player behavior and + * assigns classification and advancement metrics to the accomplishment. + * @param value the specific label that indicates this award + * @param category a generalization of the award's accreditation + * @param progression the iterative merit commendations associated with increasing ranks of this award + */ +sealed abstract class Award( + val value: String, + val category: AwardCategory.Value, + val progression: List[CommendationRank] + ) extends StringEnumEntry { + assert( + Merit.values.exists(_.toString().equals(value)), + s"$value is not a merit found in the appropriate Enumeration" + ) + assert( + progression.nonEmpty, + s"$value must define a base progression rank" + ) + + def alignment: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL +} + +sealed abstract class NewConglomerateAward( + override val value: String, + override val category: AwardCategory.Value, + override val progression: List[CommendationRank] + ) extends Award(value, category, progression) { + override def alignment: PlanetSideEmpire.Value = PlanetSideEmpire.NC +} + +sealed abstract class TerranRepublicAward( + override val value: String, + override val category: AwardCategory.Value, + override val progression: List[CommendationRank] + ) extends Award(value, category, progression) { + override def alignment: PlanetSideEmpire.Value = PlanetSideEmpire.TR +} + +sealed abstract class VanuSovereigntyAward( + override val value: String, + override val category: AwardCategory.Value, + override val progression: List[CommendationRank] + ) extends Award(value, category, progression) { + override def alignment: PlanetSideEmpire.Value = PlanetSideEmpire.VS +} + +/** + * A collection of all award labels. + */ +object Merit extends Enumeration { + val AdvancedMedic = Value("AdvancedMedic") + val AdvancedMedicAssists = Value("AdvancedMedicAssists") + val AirDefender = Value("AirDefender") + val AMSSupport = Value("AMSSupport") + val AntiVehicular = Value("AntiVehicular") + val Avenger = Value("Avenger") + val BendingMovieActor = Value("BendingMovieActor") + val BFRAdvanced = Value("BFRAdvanced") + val BFRBuster = Value("BFRBuster") + val BlackOpsHunter = Value("BlackOpsHunter") + val BlackOpsParticipant = Value("BlackOpsParticipant") + val BlackOpsVictory = Value("BlackOpsVictory") + val Bombadier = Value("Bombadier") + val BomberAce = Value("BomberAce") + val Boomer = Value("Boomer") + val CalvaryDriver = Value("CalvaryDriver") + val CalvaryPilot = Value("CalvaryPilot") + val CMTopOutfit = Value("CMTopOutfit") + val CombatMedic = Value("CombatMedic") + val CombatRepair = Value("CombatRepair") + val ContestFirstBR40 = Value("ContestFirstBR40") + val ContestMovieMaker = Value("ContestMovieMaker") + val ContestMovieMakerOutfit = Value("ContestMovieMakerOutfit") + val ContestPlayerOfTheMonth = Value("ContestPlayerOfTheMonth") + val ContestPlayerOfTheYear = Value("ContestPlayerOfTheYear") + val CSAppreciation = Value("CSAppreciation") + val DefenseNC = Value("DefenseNC") + val DefenseTR = Value("DefenseTR") + val DefenseVS = Value("DefenseVS") + val DevilDogsMovie = Value("DevilDogsMovie") + val DogFighter = Value("DogFighter") + val DriverGunner = Value("DriverGunner") + val EliteAssault = Value("EliteAssault") + val EmeraldVeteran = Value("EmeraldVeteran") + val Engineer = Value("Engineer") + val EquipmentSupport = Value("EquipmentSupport") + val EventNC = Value("EventNC") + val EventNCCommander = Value("EventNCCommander") + val EventNCElite = Value("EventNCElite") + val EventNCSoldier = Value("EventNCSoldier") + val EventTR = Value("EventTR") + val EventTRCommander = Value("EventTRCommander") + val EventTRElite = Value("EventTRElite") + val EventTRSoldier = Value("EventTRSoldier") + val EventVS = Value("EventVS") + val EventVSCommander = Value("EventVSCommander") + val EventVSElite = Value("EventVSElite") + val EventVSSoldier = Value("EventVSSoldier") + val Explorer = Value("Explorer") + val FanFaire2005Commander = Value("FanFaire2005Commander") + val FanFaire2005Soldier = Value("FanFaire2005Soldier") + val FanFaire2006Atlanta = Value("FanFaire2006Atlanta") + val FanFaire2007 = Value("FanFaire2007") + val FanFaire2008 = Value("FanFaire2008") + val FanFaire2009 = Value("FanFaire2009") + val GalaxySupport = Value("GalaxySupport") + val Grenade = Value("Grenade") + val GroundGunner = Value("GroundGunner") + val HackingSupport = Value("HackingSupport") + val HalloweenMassacre2006NC = Value("HalloweenMassacre2006NC") + val HalloweenMassacre2006TR = Value("HalloweenMassacre2006TR") + val HalloweenMassacre2006VS = Value("HalloweenMassacre2006VS") + val HeavyAssault = Value("HeavyAssault") + val HeavyInfantry = Value("HeavyInfantry") + val InfantryExpert = Value("InfantryExpert") + val Jacking = Value("Jacking") + val KnifeCombat = Value("KnifeCombat") + val LightInfantry = Value("LightInfantry") + val LockerCracker = Value("LockerCracker") + val LodestarSupport = Value("LodestarSupport") + val Loser = Value("Loser") + val MarkovVeteran = Value("MarkovVeteran") + val Max = Value("Max") + val MaxBuster = Value("MaxBuster") + val MediumAssault = Value("MediumAssault") + val None = Value("None") //the no merit commendation award label + val Orion = Value("Orion") + val Osprey = Value("Osprey") + val Phalanx = Value("Phalanx") + val PSUMaAttendee = Value("PSUMaAttendee") + val PSUMbAttendee = Value("PSUMbAttendee") + val QAAppreciation = Value("QAAppreciation") + val ReinforcementHackSpecialist = Value("ReinforcementHackSpecialist") + val ReinforcementInfantrySpecialist = Value("ReinforcementInfantrySpecialist") + val ReinforcementSpecialist = Value("ReinforcementSpecialist") + val ReinforcementVehicleSpecialist = Value("ReinforcementVehicleSpecialist") + val RouterSupport = Value("RouterSupport") + val RouterTelepadDeploy = Value("RouterTelepadDeploy") + val ScavengerNC = Value("ScavengerNC") + val ScavengerTR = Value("ScavengerTR") + val ScavengerVS = Value("ScavengerVS") + val Sniper = Value("Sniper") + val SpecialAssault = Value("SpecialAssault") + val StandardAssault = Value("StandardAssault") + val StracticsHistorian = Value("StracticsHistorian") + val Supply = Value("Supply") + val TankBuster = Value("TankBuster") + val TermOfServiceNC = Value("TermOfServiceNC") + val TermOfServiceTR = Value("TermOfServiceTR") + val TermOfServiceVS = Value("TermOfServiceVS") + val TinyRoboticSupport = Value("TinyRoboticSupport") + val Transport = Value("Transport") + val TransportationCitation = Value("TransportationCitation") + val ValentineFemale = Value("ValentineFemale") + val ValentineMale = Value("ValentineMale") + val WernerVeteran = Value("WernerVeteran") + val XmasGingerman = Value("XmasGingerman") + val XmasSnowman = Value("XmasSnowman") + val XmasSpirit = Value("XmasSpirit") +} + +/** + * A collection of all award categories. + */ +object AwardCategory extends Enumeration { + val + Activity, + Defense, + Exclusive, + Support, + Vehicular, + Weaponry + = Value +} + +object Award extends StringEnum[Award] { + import net.psforever.types.MeritCommendation._ + + val values = findValues + + case object AdvancedMedic extends Award( + value = "AdvancedMedic", + AwardCategory.Support, + List( + CommendationRank(AdvancedMedic1), + CommendationRank(AdvancedMedic2), + CommendationRank(AdvancedMedic3), + CommendationRank(AdvancedMedic4), + CommendationRank(AdvancedMedic5), + CommendationRank(AdvancedMedic6), + CommendationRank(AdvancedMedic7) + ) + ) + case object AdvancedMedicAssists extends Award( + value = "AdvancedMedicAssists", + AwardCategory.Support, + List( + CommendationRank(AdvancedMedicAssists1), + CommendationRank(AdvancedMedicAssists2), + CommendationRank(AdvancedMedicAssists3), + CommendationRank(AdvancedMedicAssists4), + CommendationRank(AdvancedMedicAssists5), + CommendationRank(AdvancedMedicAssists6), + CommendationRank(AdvancedMedicAssists7) + ) + ) + case object AirDefender extends Award( + value = "AirDefender", + AwardCategory.Vehicular, + List( + CommendationRank(AirDefender1), + CommendationRank(AirDefender2), + CommendationRank(AirDefender3), + CommendationRank(AirDefender4), + CommendationRank(AirDefender5), + CommendationRank(AirDefender6), + CommendationRank(AirDefender7) + ) + ) + case object AMSSupport extends Award( + value = "AMSSupport", + AwardCategory.Support, + List( + CommendationRank(AMSSupport1), + CommendationRank(AMSSupport2), + CommendationRank(AMSSupport3), + CommendationRank(AMSSupport4), + CommendationRank(AMSSupport5), + CommendationRank(AMSSupport6), + CommendationRank(AMSSupport7) + ) + ) + case object AntiVehicular extends Award( + value = "AntiVehicular", + AwardCategory.Weaponry, + List( + CommendationRank(AntiVehicular1), + CommendationRank(AntiVehicular2), + CommendationRank(AntiVehicular3), + CommendationRank(AntiVehicular4), + CommendationRank(AntiVehicular5), + CommendationRank(AntiVehicular6), + CommendationRank(AntiVehicular7) + ) + ) + case object Avenger extends TerranRepublicAward( + value = "Avenger", + AwardCategory.Weaponry, + List( + CommendationRank(Avenger1), + CommendationRank(Avenger2), + CommendationRank(Avenger3), + CommendationRank(Avenger4), + CommendationRank(Avenger5), + CommendationRank(Avenger6), + CommendationRank(Avenger7) + ) + ) + case object BendingMovieActor extends Award( + value = "BendingMovieActor", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.BendingMovieActor)) + ) + case object BFRAdvanced extends Award( + value = "BFRAdvanced", + AwardCategory.Vehicular, + List( + CommendationRank(MeritCommendation.BFRAdvanced), + CommendationRank(MeritCommendation.BFRAdvanced2), + CommendationRank(MeritCommendation.BFRAdvanced3), + CommendationRank(MeritCommendation.BFRAdvanced4), + CommendationRank(MeritCommendation.BFRAdvanced5) + ) + ) + case object BFRBuster extends Award( + value = "BFRBuster", + AwardCategory.Activity, + List( + CommendationRank(BFRBuster1), + CommendationRank(BFRBuster2), + CommendationRank(BFRBuster3), + CommendationRank(BFRBuster4), + CommendationRank(BFRBuster5), + CommendationRank(BFRBuster6), + CommendationRank(BFRBuster7) + ) + ) + case object BlackOpsHunter extends Award( + value = "BlackOpsHunter", + AwardCategory.Activity, + List( + CommendationRank(MeritCommendation.BlackOpsHunter1), + CommendationRank(MeritCommendation.BlackOpsHunter2), + CommendationRank(MeritCommendation.BlackOpsHunter3), + CommendationRank(MeritCommendation.BlackOpsHunter4), + CommendationRank(MeritCommendation.BlackOpsHunter5) + ) + ) + case object BlackOpsParticipant extends Award( + value = "BlackOpsParticipant", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.BlackOpsParticipant)) + ) + case object BlackOpsVictory extends Award( + value = "BlackOpsVictory", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.BlackOpsVictory)) + ) + case object Bombadier extends Award( + value = "Bombadier", + AwardCategory.Vehicular, + List( + CommendationRank(Bombadier1), + CommendationRank(Bombadier2), + CommendationRank(Bombadier3), + CommendationRank(Bombadier4), + CommendationRank(Bombadier5), + CommendationRank(Bombadier6), + CommendationRank(Bombadier7) + ) + ) + case object BomberAce extends Award( + value = "BomberAce", + AwardCategory.Vehicular, + List( + CommendationRank(BomberAce1), + CommendationRank(BomberAce2), + CommendationRank(BomberAce3), + CommendationRank(BomberAce4), + CommendationRank(BomberAce5), + CommendationRank(BomberAce6), + CommendationRank(BomberAce7) + ) + ) + case object Boomer extends Award( + value = "Boomer", + AwardCategory.Weaponry, + List( + CommendationRank(Boomer1), + CommendationRank(Boomer2), + CommendationRank(Boomer3), + CommendationRank(Boomer4), + CommendationRank(Boomer5), + CommendationRank(Boomer6), + CommendationRank(Boomer7) + ) + ) + case object CalvaryDriver extends Award( + value = "CalvaryDriver", + AwardCategory.Vehicular, + List( + CommendationRank(CalvaryDriver1), + CommendationRank(CalvaryDriver2), + CommendationRank(CalvaryDriver3), + CommendationRank(CalvaryDriver4), + CommendationRank(CalvaryDriver5), + CommendationRank(CalvaryDriver6), + CommendationRank(CalvaryDriver7) + ) + ) + case object CalvaryPilot extends Award( + value = "CalvaryPilot", + AwardCategory.Vehicular, + List( + CommendationRank(MeritCommendation.CalvaryPilot), + CommendationRank(CalvaryPilot2), + CommendationRank(CalvaryPilot3), + CommendationRank(CalvaryPilot4), + CommendationRank(CalvaryPilot5), + CommendationRank(CalvaryPilot6), + CommendationRank(CalvaryPilot7) + ) + ) + case object CMTopOutfit extends Award( + value = "CMTopOutfit", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.CMTopOutfit)) + ) + case object CombatMedic extends Award( + value = "CombatMedic", + AwardCategory.Support, + List( + CommendationRank(MeritCommendation.CombatMedic), + CommendationRank(CombatMedic2), + CommendationRank(CombatMedic3), + CommendationRank(CombatMedic4), + CommendationRank(CombatMedic5), + CommendationRank(CombatMedic6), + CommendationRank(CombatMedic7) + ) + ) + case object CombatRepair extends Award( + value = "CombatRepair", + AwardCategory.Support, + List( + CommendationRank(CombatRepair1), + CommendationRank(CombatRepair2), + CommendationRank(CombatRepair3), + CommendationRank(CombatRepair4), + CommendationRank(CombatRepair5), + CommendationRank(CombatRepair6), + CommendationRank(CombatRepair7) + ) + ) + case object ContestFirstBR40 extends Award( + value = "ContestFirstBR40", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.ContestFirstBR40)) + ) + case object ContestMovieMaker extends Award( + value = "ContestMovieMaker", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.ContestMovieMaker)) + ) + case object ContestMovieMakerOutfit extends Award( + value = "ContestMovieMakerOutfit", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.ContestMovieMakerOutfit)) + ) + case object ContestPlayerOfTheMonth extends Award( + value = "ContestPlayerOfTheMonth", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.ContestPlayerOfTheMonth)) + ) + case object ContestPlayerOfTheYear extends Award( + value = "ContestPlayerOfTheYear", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.ContestPlayerOfTheYear)) + ) + case object CSAppreciation extends Award( + value = "CSAppreciation", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.CSAppreciation)) + ) + case object DefenseNC extends NewConglomerateAward( + value = "DefenseNC", + AwardCategory.Defense, + List( + CommendationRank(DefenseNC1), + CommendationRank(DefenseNC2), + CommendationRank(DefenseNC3), + CommendationRank(DefenseNC4), + CommendationRank(DefenseNC5), + CommendationRank(DefenseNC6), + CommendationRank(DefenseNC7) + ) + ) + case object DefenseTR extends TerranRepublicAward( + value = "DefenseTR", + AwardCategory.Defense, + List( + CommendationRank(DefenseTR1), + CommendationRank(DefenseTR2), + CommendationRank(DefenseTR3), + CommendationRank(DefenseTR4), + CommendationRank(DefenseTR5), + CommendationRank(DefenseTR6), + CommendationRank(DefenseTR7) + ) + ) + case object DefenseVS extends VanuSovereigntyAward( + value = "DefenseVS", + AwardCategory.Defense, + List( + CommendationRank(DefenseVS1), + CommendationRank(DefenseVS2), + CommendationRank(DefenseVS3), + CommendationRank(DefenseVS4), + CommendationRank(DefenseVS5), + CommendationRank(DefenseVS6), + CommendationRank(DefenseVS7) + ) + ) + case object DevilDogsMovie extends Award( + value = "DevilDogsMovie", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.DevilDogsMovie)) + ) + case object DogFighter extends Award( + value = "DogFighter", + AwardCategory.Vehicular, + List( + CommendationRank(DogFighter1), + CommendationRank(DogFighter2), + CommendationRank(DogFighter3), + CommendationRank(DogFighter4), + CommendationRank(DogFighter5), + CommendationRank(DogFighter6), + CommendationRank(DogFighter7) + ) + ) + case object DriverGunner extends Award( + value = "DriverGunner", + AwardCategory.Vehicular, + List( + CommendationRank(DriverGunner1), + CommendationRank(DriverGunner2), + CommendationRank(DriverGunner3), + CommendationRank(DriverGunner4), + CommendationRank(DriverGunner5), + CommendationRank(DriverGunner6), + CommendationRank(DriverGunner7) + ) + ) + case object EliteAssault extends Award( + value = "EliteAssault", + AwardCategory.Weaponry, + List( + CommendationRank(EliteAssault0), + CommendationRank(EliteAssault1), + CommendationRank(EliteAssault2), + CommendationRank(EliteAssault3), + CommendationRank(EliteAssault4), + CommendationRank(EliteAssault5), + CommendationRank(EliteAssault6), + CommendationRank(EliteAssault7) + ) + ) + case object EmeraldVeteran extends Award( + value = "EmeraldVeteran", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.EmeraldVeteran)) + ) + case object Engineer extends Award( + value = "Engineer", + AwardCategory.Support, + List( + CommendationRank(Engineer1), + CommendationRank(Engineer2), + CommendationRank(Engineer3), + CommendationRank(Engineer4), + CommendationRank(Engineer5), + CommendationRank(Engineer6) + ) + ) + case object EquipmentSupport extends Award( + value = "EquipmentSupport", + AwardCategory.Support, + List( + CommendationRank(EquipmentSupport1), + CommendationRank(EquipmentSupport2), + CommendationRank(EquipmentSupport3), + CommendationRank(EquipmentSupport4), + CommendationRank(EquipmentSupport5), + CommendationRank(EquipmentSupport6), + CommendationRank(EquipmentSupport7) + ) + ) + case object EventNCCommander extends NewConglomerateAward( + value = "EventNCCommander", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.EventNCCommander)) + ) + case object EventNCElite extends NewConglomerateAward( + value = "EventNCElite", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.EventNCElite)) + ) + case object EventNCSoldier extends NewConglomerateAward( + value = "EventNCSoldier", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.EventNCSoldier)) + ) + case object EventTRCommander extends TerranRepublicAward( + value = "EventTRCommander", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.EventTRCommander)) + ) + case object EventTRElite extends TerranRepublicAward( + value = "EventTRElite", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.EventTRElite)) + ) + case object EventTRSoldier extends TerranRepublicAward( + value = "EventTRSoldier", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.EventTRSoldier)) + ) + case object EventVSCommander extends VanuSovereigntyAward( + value = "EventVSCommander", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.EventVSCommander)) + ) + case object EventVSElite extends VanuSovereigntyAward( + value = "EventVSElite", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.EventVSElite)) + ) + case object EventVSSoldier extends VanuSovereigntyAward( + value = "EventVSSoldier", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.EventVSSoldier)) + ) +// case object EventNC extends NewConglomerateAward( +// value = "EventNC", +// AwardCategory.Exclusive, +// List( +// CommendationRank(MeritCommendation.EventNCSoldier), +// CommendationRank(MeritCommendation.EventNCElite), +// CommendationRank(MeritCommendation.EventNCCommander) +// ) +// ) +// case object EventTR extends TerranRepublicAward( +// value = "EventTR", +// AwardCategory.Exclusive, +// List( +// CommendationRank(MeritCommendation.EventTRSoldier), +// CommendationRank(MeritCommendation.EventTRElite), +// CommendationRank(MeritCommendation.EventTRCommander) +// ) +// ) +// case object EventVS extends VanuSovereigntyAward( +// value = "EventVS", +// AwardCategory.Exclusive, +// List( +// CommendationRank(MeritCommendation.EventVSSoldier), +// CommendationRank(MeritCommendation.EventVSElite), +// CommendationRank(MeritCommendation.EventVSCommander) +// ) +// ) + case object Explorer extends Award( + value = "Explorer", + AwardCategory.Activity, + List(CommendationRank(MeritCommendation.Explorer1)) + ) + case object FanFaire2005Commander extends Award( + value = "FanFaire2005Commander", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.FanFaire2005Commander)) + ) + case object FanFaire2005Soldier extends Award( + value = "FanFaire2005Soldier", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.FanFaire2005Soldier)) + ) + case object FanFaire2006Atlanta extends Award( + value = "FanFaire2006Atlanta", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.FanFaire2006Atlanta)) + ) + case object FanFaire2007 extends Award( + value = "FanFaire2007", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.FanFaire2007)) + ) + case object FanFaire2008 extends Award( + value = "FanFaire2008", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.FanFaire2008)) + ) + case object FanFaire2009 extends Award( + value = "FanFaire2009", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.FanFaire2009)) + ) + case object GalaxySupport extends Award( + value = "GalaxySupport", + AwardCategory.Support, + List( + CommendationRank(GalaxySupport1), + CommendationRank(GalaxySupport2), + CommendationRank(GalaxySupport3), + CommendationRank(GalaxySupport4), + CommendationRank(GalaxySupport5), + CommendationRank(GalaxySupport6), + CommendationRank(GalaxySupport7) + ) + ) + case object Grenade extends Award( + value = "Grenade", + AwardCategory.Weaponry, + List( + CommendationRank(Grenade1), + CommendationRank(Grenade2), + CommendationRank(Grenade3), + CommendationRank(Grenade4), + CommendationRank(Grenade5), + CommendationRank(Grenade6), + CommendationRank(Grenade7) + ) + ) + case object GroundGunner extends Award( + value = "GroundGunner", + AwardCategory.Vehicular, + List( + CommendationRank(GroundGunner1), + CommendationRank(GroundGunner2), + CommendationRank(GroundGunner3), + CommendationRank(GroundGunner4), + CommendationRank(GroundGunner5), + CommendationRank(GroundGunner6), + CommendationRank(GroundGunner7) + ) + ) + case object HackingSupport extends Award( + value = "HackingSupport", + AwardCategory.Support, + List( + CommendationRank(HackingSupport1), + CommendationRank(HackingSupport2), + CommendationRank(HackingSupport3), + CommendationRank(HackingSupport4), + CommendationRank(HackingSupport5), + CommendationRank(HackingSupport6), + CommendationRank(HackingSupport7) + ) + ) + case object HalloweenMassacre2006NC extends NewConglomerateAward( + value = "HalloweenMassacre2006NC", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.HalloweenMassacre2006NC)) + ) + case object HalloweenMassacre2006TR extends TerranRepublicAward( + value = "HalloweenMassacre2006TR", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.HalloweenMassacre2006TR)) + ) + case object HalloweenMassacre2006VS extends VanuSovereigntyAward( + value = "HalloweenMassacre2006VS", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.HalloweenMassacre2006VS)) + ) + case object HeavyAssault extends Award( + value = "HeavyAssault", + AwardCategory.Weaponry, + List( + CommendationRank(HeavyAssault1), + CommendationRank(HeavyAssault2), + CommendationRank(HeavyAssault3), + CommendationRank(HeavyAssault4), + CommendationRank(HeavyAssault5), + CommendationRank(HeavyAssault6), + CommendationRank(HeavyAssault7) + ) + ) + case object HeavyInfantry extends Award( + value = "HeavyInfantry", + AwardCategory.Weaponry, + List( + CommendationRank(MeritCommendation.HeavyInfantry), + CommendationRank(HeavyInfantry2), + CommendationRank(HeavyInfantry3), + CommendationRank(HeavyInfantry4) + ) + ) + case object InfantryExpert extends Award( + value = "InfantryExpert", + AwardCategory.Weaponry, + List( + CommendationRank(InfantryExpert1), + CommendationRank(InfantryExpert2), + CommendationRank(InfantryExpert3) + ) + ) + case object Jacking extends Award( + value = "Jacking", + AwardCategory.Activity, + List( + CommendationRank(MeritCommendation.Jacking), + CommendationRank(Jacking2), + CommendationRank(Jacking3), + CommendationRank(Jacking4), + CommendationRank(Jacking5), + CommendationRank(Jacking6), + CommendationRank(Jacking7) + ) + ) + case object KnifeCombat extends Award( + value = "KnifeCombat", + AwardCategory.Weaponry, + List( + CommendationRank(KnifeCombat1), + CommendationRank(KnifeCombat2), + CommendationRank(KnifeCombat3), + CommendationRank(KnifeCombat4), + CommendationRank(KnifeCombat5), + CommendationRank(KnifeCombat6), + CommendationRank(KnifeCombat7) + ) + ) + case object LightInfantry extends Award( + value = "LightInfantry", + AwardCategory.Weaponry, + List(CommendationRank(MeritCommendation.LightInfantry)) + ) + case object LockerCracker extends Award( + value = "LockerCracker", + AwardCategory.Support, + List( + CommendationRank(LockerCracker1), + CommendationRank(LockerCracker2), + CommendationRank(LockerCracker3), + CommendationRank(LockerCracker4), + CommendationRank(LockerCracker5), + CommendationRank(LockerCracker6), + CommendationRank(LockerCracker7) + ) + ) + case object LodestarSupport extends Award( + value = "LodestarSupport", + AwardCategory.Support, + List( + CommendationRank(LodestarSupport1), + CommendationRank(LodestarSupport2), + CommendationRank(LodestarSupport3), + CommendationRank(LodestarSupport4), + CommendationRank(LodestarSupport5), + CommendationRank(LodestarSupport6), + CommendationRank(LodestarSupport7) + ) + ) + case object Loser extends Award( + value = "Loser", + AwardCategory.Exclusive, + List( + CommendationRank(MeritCommendation.Loser), + CommendationRank(MeritCommendation.Loser2), + CommendationRank(MeritCommendation.Loser3), + CommendationRank(MeritCommendation.Loser4) + ) + ) + case object MarkovVeteran extends Award( + value = "MarkovVeteran", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.MarkovVeteran)) + ) + case object Max extends Award( + value = "Max", + AwardCategory.Vehicular, + List( + CommendationRank(Max1), + CommendationRank(Max2), + CommendationRank(Max3), + CommendationRank(Max4), + CommendationRank(Max5), + CommendationRank(Max6) + ) + ) + case object MaxBuster extends Award( + value = "MaxBuster", + AwardCategory.Activity, + List( + CommendationRank(MaxBuster1), + CommendationRank(MaxBuster2), + CommendationRank(MaxBuster3), + CommendationRank(MaxBuster4), + CommendationRank(MaxBuster5), + CommendationRank(MaxBuster6) + ) + ) + case object MediumAssault extends Award( + value = "MediumAssault", + AwardCategory.Weaponry, + List( + CommendationRank(MediumAssault1), + CommendationRank(MediumAssault2), + CommendationRank(MediumAssault3), + CommendationRank(MediumAssault4), + CommendationRank(MediumAssault5), + CommendationRank(MediumAssault6), + CommendationRank(MediumAssault7) + ) + ) + case object None extends Award( + value = "None", + AwardCategory.Exclusive, + List( + CommendationRank(MeritCommendation.None) + ) + ) + case object Orion extends VanuSovereigntyAward( + value = "Orion", + AwardCategory.Vehicular, + List( + CommendationRank(Orion1), + CommendationRank(Orion2), + CommendationRank(Orion3), + CommendationRank(Orion4), + CommendationRank(Orion5), + CommendationRank(Orion6), + CommendationRank(Orion7) + ) + ) + case object Osprey extends NewConglomerateAward( + value = "Osprey", + AwardCategory.Vehicular, + List( + CommendationRank(Osprey1), + CommendationRank(Osprey2), + CommendationRank(Osprey3), + CommendationRank(Osprey4), + CommendationRank(Osprey5), + CommendationRank(Osprey6), + CommendationRank(Osprey7) + ) + ) + case object Phalanx extends Award( + value = "Phalanx", + AwardCategory.Weaponry, + List( + CommendationRank(Phalanx1), + CommendationRank(Phalanx2), + CommendationRank(Phalanx3), + CommendationRank(Phalanx4), + CommendationRank(Phalanx5), + CommendationRank(Phalanx6), + CommendationRank(Phalanx7) + ) + ) + case object PSUMaAttendee extends Award( + value = "PSUMaAttendee", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.PSUMaAttendee)) + ) + case object PSUMbAttendee extends Award( + value = "PSUMbAttendee", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.PSUMbAttendee)) + ) + case object QAAppreciation extends Award( + value = "QAAppreciation", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.QAAppreciation)) + ) + case object ReinforcementHackSpecialist extends Award( + value = "ReinforcementHackSpecialist", + AwardCategory.Support, + List(CommendationRank(MeritCommendation.ReinforcementHackSpecialist)) + ) + case object ReinforcementInfantrySpecialist extends Award( + value = "ReinforcementInfantrySpecialist", + AwardCategory.Support, + List(CommendationRank(MeritCommendation.ReinforcementInfantrySpecialist)) + ) + case object ReinforcementSpecialist extends Award( + value = "ReinforcementSpecialist", + AwardCategory.Support, + List(CommendationRank(MeritCommendation.ReinforcementSpecialist)) + ) + case object ReinforcementVehicleSpecialist extends Award( + value = "ReinforcementVehicleSpecialist", + AwardCategory.Support, + List(CommendationRank(MeritCommendation.ReinforcementVehicleSpecialist)) + ) + case object RouterSupport extends Award( + value = "RouterSupport", + AwardCategory.Support, + List( + CommendationRank(RouterSupport1), + CommendationRank(RouterSupport2), + CommendationRank(RouterSupport3), + CommendationRank(RouterSupport4), + CommendationRank(RouterSupport5), + CommendationRank(RouterSupport6), + CommendationRank(RouterSupport7) + ) + ) + case object RouterTelepadDeploy extends Award( + value = "RouterTelepadDeploy", + AwardCategory.Support, + List( + CommendationRank(RouterTelepadDeploy1), + CommendationRank(RouterTelepadDeploy2), + CommendationRank(RouterTelepadDeploy3), + CommendationRank(RouterTelepadDeploy4), + CommendationRank(RouterTelepadDeploy5), + CommendationRank(RouterTelepadDeploy6), + CommendationRank(RouterTelepadDeploy7) + ) + ) + case object ScavengerNC extends NewConglomerateAward( + value = "ScavengerNC", + AwardCategory.Weaponry, + List( + CommendationRank(ScavengerNC1), + CommendationRank(ScavengerNC2), + CommendationRank(ScavengerNC3), + CommendationRank(ScavengerNC4), + CommendationRank(ScavengerNC5), + CommendationRank(ScavengerNC6) + ) + ) + case object ScavengerTR extends TerranRepublicAward( + value = "ScavengerTR", + AwardCategory.Weaponry, + List( + CommendationRank(ScavengerTR1), + CommendationRank(ScavengerTR2), + CommendationRank(ScavengerTR3), + CommendationRank(ScavengerTR4), + CommendationRank(ScavengerTR5), + CommendationRank(ScavengerTR6) + ) + ) + case object ScavengerVS extends VanuSovereigntyAward( + value = "ScavengerVS", + AwardCategory.Weaponry, + List( + CommendationRank(ScavengerVS1), + CommendationRank(ScavengerVS2), + CommendationRank(ScavengerVS3), + CommendationRank(ScavengerVS4), + CommendationRank(ScavengerVS5), + CommendationRank(ScavengerVS6) + ) + ) + case object Sniper extends Award( + value = "Sniper", + AwardCategory.Weaponry, + List( + CommendationRank(Sniper1), + CommendationRank(Sniper2), + CommendationRank(Sniper3), + CommendationRank(Sniper4), + CommendationRank(Sniper5), + CommendationRank(Sniper6), + CommendationRank(Sniper7) + ) + ) + case object SpecialAssault extends Award( + value = "SpecialAssault", + AwardCategory.Weaponry, + List( + CommendationRank(SpecialAssault1), + CommendationRank(SpecialAssault2), + CommendationRank(SpecialAssault3), + CommendationRank(SpecialAssault4), + CommendationRank(SpecialAssault5), + CommendationRank(SpecialAssault6), + CommendationRank(SpecialAssault7) + ) + ) + case object StandardAssault extends Award( + value = "StandardAssault", + AwardCategory.Weaponry, + List( + CommendationRank(StandardAssault1), + CommendationRank(StandardAssault2), + CommendationRank(StandardAssault3), + CommendationRank(StandardAssault4), + CommendationRank(StandardAssault5), + CommendationRank(StandardAssault6), + CommendationRank(StandardAssault7) + ) + ) + case object StracticsHistorian extends Award( + value = "StracticsHistorian", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.StracticsHistorian)) + ) + case object Supply extends Award( + value = "Supply", + AwardCategory.Activity, + List( + CommendationRank(Supply1), + CommendationRank(Supply2), + CommendationRank(Supply3), + CommendationRank(Supply4), + CommendationRank(Supply5), + CommendationRank(Supply6), + CommendationRank(Supply7) + ) + ) + case object TankBuster extends Award( + value = "TankBuster", + AwardCategory.Activity, + List( + CommendationRank(TankBuster1), + CommendationRank(TankBuster2), + CommendationRank(TankBuster3), + CommendationRank(TankBuster4), + CommendationRank(TankBuster5), + CommendationRank(TankBuster6), + CommendationRank(TankBuster7) + ) + ) + case object TermOfServiceNC extends NewConglomerateAward( + value = "TermOfServiceNC", + AwardCategory.Exclusive, //Activity, + List( + CommendationRank(OneYearNC), + CommendationRank(TwoYearNC), + CommendationRank(ThreeYearNC), + CommendationRank(FourYearNC), + CommendationRank(FiveYearNC), + CommendationRank(SixYearNC) + ) + ) + case object TermOfServiceTR extends TerranRepublicAward( + value = "TermOfServiceTR", + AwardCategory.Exclusive, //Activity, + List( + CommendationRank(OneYearTR), + CommendationRank(TwoYearTR), + CommendationRank(ThreeYearTR), + CommendationRank(FourYearTR), + CommendationRank(FiveYearTR), + CommendationRank(SixYearTR) + ) + ) + case object TermOfServiceVS extends VanuSovereigntyAward( + value = "TermOfServiceVS", + AwardCategory.Exclusive, //Activity, + List( + CommendationRank(OneYearVS), + CommendationRank(TwoYearVS), + CommendationRank(ThreeYearVS), + CommendationRank(FourYearVS), + CommendationRank(FiveYearVS), + CommendationRank(SixYearVS) + ) + ) + case object TinyRoboticSupport extends Award( + value = "TinyRoboticSupport", + AwardCategory.Support, + List( + CommendationRank(TinyRoboticSupport1), + CommendationRank(TinyRoboticSupport2), + CommendationRank(TinyRoboticSupport3), + CommendationRank(TinyRoboticSupport4), + CommendationRank(TinyRoboticSupport5), + CommendationRank(TinyRoboticSupport6), + CommendationRank(TinyRoboticSupport7) + ) + ) + case object Transport extends Award( + value = "Transport", + AwardCategory.Activity, + List( + CommendationRank(Transport1), + CommendationRank(Transport2), + CommendationRank(Transport3), + CommendationRank(Transport4), + CommendationRank(Transport5), + CommendationRank(Transport6), + CommendationRank(Transport7) + ) + ) + case object TransportationCitation extends Award( + value = "TransportationCitation", + AwardCategory.Activity, + List( + CommendationRank(TransportationCitation1), + CommendationRank(TransportationCitation2), + CommendationRank(TransportationCitation3), + CommendationRank(TransportationCitation4), + CommendationRank(TransportationCitation5) + ) + ) + case object ValentineFemale extends Award( + value = "ValentineFemale", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.ValentineFemale)) + ) + case object ValentineMale extends Award( + value = "ValentineMale", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.ValentineMale)) + ) + case object WernerVeteran extends Award( + value = "WernerVeteran", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.WernerVeteran)) + ) + case object XmasGingerman extends Award( + value = "XmasGingerman", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.XmasGingerman)) + ) + case object XmasSnowman extends Award( + value = "XmasSnowman", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.XmasSnowman)) + ) + case object XmasSpirit extends Award( + value = "XmasSpirit", + AwardCategory.Exclusive, + List(CommendationRank(MeritCommendation.XmasSpirit)) + ) + + final val common: List[Award] = List( + AdvancedMedic, AdvancedMedicAssists, AirDefender, AMSSupport, AntiVehicular, + BFRAdvanced, BFRBuster, Bombadier, BomberAce, Boomer, + TankBuster, TinyRoboticSupport, Transport, TransportationCitation + ) +} diff --git a/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala index 97c77b543..2a95d4256 100644 --- a/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala +++ b/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala @@ -105,7 +105,7 @@ object AvatarConverter { unk7 = false, on_zipline = None ) - CharacterAppearanceData(aa, ab, RibbonBars()) + CharacterAppearanceData(aa, ab, obj.avatar.ribbonBars) } def MakeCharacterData(obj: Player): (Boolean, Boolean) => CharacterData = { diff --git a/src/main/scala/net/psforever/objects/definition/converter/CharacterSelectConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/CharacterSelectConverter.scala index 12a5d77d8..9133065ea 100644 --- a/src/main/scala/net/psforever/objects/definition/converter/CharacterSelectConverter.scala +++ b/src/main/scala/net/psforever/objects/definition/converter/CharacterSelectConverter.scala @@ -79,7 +79,7 @@ class CharacterSelectConverter extends AvatarConverter { unk7 = false, on_zipline = None ) - CharacterAppearanceData(aa, ab, RibbonBars()) + CharacterAppearanceData(aa, ab, obj.avatar.ribbonBars) } private def MakeDetailedCharacterData(obj: Player): Option[Int] => DetailedCharacterData = { diff --git a/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala index fd9ba6344..3c985ac66 100644 --- a/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala +++ b/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala @@ -70,7 +70,7 @@ class CorpseConverter extends AvatarConverter { unk7 = false, on_zipline = None ) - CharacterAppearanceData(aa, ab, RibbonBars()) + CharacterAppearanceData(aa, ab, obj.avatar.ribbonBars) } private def MakeDetailedCharacterData(obj: Player): Option[Int] => DetailedCharacterData = { diff --git a/src/main/scala/net/psforever/packet/game/AvatarAwardMessage.scala b/src/main/scala/net/psforever/packet/game/AvatarAwardMessage.scala index bda9214a0..60660dba5 100644 --- a/src/main/scala/net/psforever/packet/game/AvatarAwardMessage.scala +++ b/src/main/scala/net/psforever/packet/game/AvatarAwardMessage.scala @@ -2,37 +2,73 @@ package net.psforever.packet.game import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} +import net.psforever.types.MeritCommendation import scodec.Codec import scodec.codecs._ import shapeless.{::, HNil} -import scala.annotation.switch - -abstract class AwardOption(val code: Int) { - def unk1: Long - def unk2: Long +/** + * Base class for all merit commendation advancement stages. + */ +sealed trait AwardOption { + def value: Long + def completion: Long } - -final case class AwardOptionZero(unk1: Long, unk2: Long) extends AwardOption(code = 0) - -final case class AwardOptionOne(unk1: Long) extends AwardOption(code = 1) { - def unk2: Long = 0L +/** + * Display this award's development progress. + * @param value the current count towards this award + * @param completion the target (maximum) count + */ +final case class AwardProgress(value: Long, completion: Long) extends AwardOption +/** + * Display this award's qualification progress. + * The process is the penultimate conditions necessary for award completion, + * separate from the aforementioned progress. + * This is almost always a kill streak, + * where the user must terminate a certain numbers of enemies without dying. + * @param value the current count towards this award + */ +final case class AwardQualificationProgress(value: Long) extends AwardOption { + /** zero'd as the value is not reported here */ + def completion: Long = 0L } - -final case class AwardOptionTwo(unk1: Long) extends AwardOption(code = 3) { - def unk2: Long = 0L +/** + * Display this award as completed. + * @param value the date (mm/dd/yyyy) that the award was achieved in POSIX seconds; + * that's `System.currentTimeMillis() / 1000` + */ +final case class AwardCompletion(value: Long) extends AwardOption { + /** same as the parameter value */ + def completion: Long = value } /** - * na - * @param unk1 na - * @param unk2 na - * @param unk3 na + * Dispatched from the server to load information about a character's merit commendation awards progress.
+ *
+ * The three stages of a merit commendation award are: progress, qualification, and completion. + * The progress stage and the qualification stage have their own development conditions. + * Ocassionally, the development is nonexistent and the award is merely an on/off switch. + * Occasionally, there is no qualification requirement and the award merely advances in the progress stage + * then transitions directly from progress to completion. + * Completion information is available from the character info / achievements tab + * and takes the form of ribbons associated with the merit commendation at a given rank + * and the date that rank was attained. + * Progress and qualification information are visible from the character info / achievements / award progress window + * and take the form of the name and rank of the merit commendation + * and two numbers that indicate the current and the goal towards the next stage. + * The completion stage is also visible from this window + * and will take the form of the same name and rank of the merit commendation indicated as "Completed" as of a date. + * @see `MeritCommendation.Value` + * @param merit_commendation the award and rank + * @param state the current state of the award advancement + * @param unk na; + * 0 and 1 are the possible values; + * 0 is the common value */ final case class AvatarAwardMessage( - unk1: Long, - unk2: AwardOption, - unk3: Int + merit_commendation: MeritCommendation.Value, + state: AwardOption, + unk: Int ) extends PlanetSideGamePacket { type Packet = AvatarAwardMessage @@ -41,61 +77,67 @@ final case class AvatarAwardMessage( } object AvatarAwardMessage extends Marshallable[AvatarAwardMessage] { - private val codec_one: Codec[AwardOptionOne] = { + def apply(meritCommendation: MeritCommendation.Value, state: AwardOption):AvatarAwardMessage = + AvatarAwardMessage(meritCommendation, state, unk = 0) + + private val qualification_codec: Codec[AwardOption] = { uint32L.hlist - }.xmap[AwardOptionOne]( + }.xmap[AwardOption]( { - case a :: HNil => AwardOptionOne(a) + case a :: HNil => AwardQualificationProgress(a) }, { - case AwardOptionOne(a) => a :: HNil + case AwardQualificationProgress(a) => a :: HNil } ) - private val codec_two: Codec[AwardOptionTwo] = { + private val completion_codec: Codec[AwardOption] = { uint32L.hlist - }.xmap[AwardOptionTwo]( + }.xmap[AwardOption]( { - case a :: HNil => AwardOptionTwo(a) + case a :: HNil => AwardCompletion(a) }, { - case AwardOptionTwo(a) => a :: HNil + case AwardCompletion(a) => a :: HNil } ) - private val codec_zero: Codec[AwardOptionZero] = { + private val progress_codec: Codec[AwardOption] = { uint32L :: uint32L - }.xmap[AwardOptionZero]( + }.xmap[AwardOption]( { - case a :: b :: HNil => AwardOptionZero(a, b) + case a :: b :: HNil => AwardProgress(a, b) }, { - case AwardOptionZero(a, b) => a :: b :: HNil + case AwardProgress(a, b) => a :: b :: HNil } ) - private def selectAwardOption(code: Int): Codec[AwardOption] = { - ((code: @switch) match { - case 2 | 3 => codec_two - case 1 => codec_one - case 0 => codec_zero - }).asInstanceOf[Codec[AwardOption]] - } - implicit val codec: Codec[AvatarAwardMessage] = ( - ("unk1" | uint32L) :: - (uint2 >>:~ { code => - ("unk2" | selectAwardOption(code)) :: - ("unk3" | uint8L) - }) - ).xmap[AvatarAwardMessage]( - { - case unk1 :: _ :: unk2 :: unk3 :: HNil => - AvatarAwardMessage(unk1, unk2, unk3) - }, - { - case AvatarAwardMessage(unk1, unk2, unk3) => - unk1 :: unk2.code :: unk2 :: unk3 :: HNil - } - ) + ("merit_commendation" | MeritCommendation.codec) :: + ("state" | either(bool, + either(bool, progress_codec, qualification_codec).xmap[AwardOption]( + { + case Left(d) => d + case Right(d) => d + }, + { + case d: AwardProgress => Left(d) + case d: AwardQualificationProgress => Right(d) + } + ), + completion_codec + ).xmap[AwardOption]( + { + case Left(d) => d + case Right(d) => d + }, + { + case d: AwardProgress => Left(d) + case d: AwardQualificationProgress => Left(d) + case d: AwardCompletion => Right(d) + } + )) :: + ("unk" | uint8L) + ).as[AvatarAwardMessage] } diff --git a/src/main/scala/net/psforever/packet/game/DisplayedAwardMessage.scala b/src/main/scala/net/psforever/packet/game/DisplayedAwardMessage.scala index f31441999..2bc7fd169 100644 --- a/src/main/scala/net/psforever/packet/game/DisplayedAwardMessage.scala +++ b/src/main/scala/net/psforever/packet/game/DisplayedAwardMessage.scala @@ -9,7 +9,7 @@ import scodec.codecs._ /** * An `Enumeration` of the slots for award ribbons on a player's `RibbonBars`. */ -object RibbonBarsSlot extends Enumeration { +object RibbonBarSlot extends Enumeration { type Type = Value val Top, Middle, Bottom, TermOfService //technically,the slot above "Top" @@ -21,29 +21,28 @@ object RibbonBarsSlot extends Enumeration { /** * Dispatched to configure a player's merit commendation ribbons.
*
- * Normally, this packet is dispatched by the client when managing merit commendations through the "Character Info/Achievements" tab. + * Normally, this packet is dispatched by the client when managing merit commendations + * through the "Character Info/Achievements" tab. * On Gemini Live, this packet was also always dispatched once by the server during character login. * It set the term of service ribbon explicitly. * Generally, this was unnecessary, as the encoded character data maintains information about displayed ribbons. - * This behavior was probably a routine that ensured that correct yearly progression was tracked if the player earned it while offline. + * This behavior was probably a routine that ensured that correct yearly progression was tracked + * if the player earned it while offline. * It never set any of the other ribbon slot positions during login.
*
* A specific ribbon may only be set once to one slot. * The last set slot is considered the valid position to which that ribbon will be placed/moved. * @param player_guid the player - * @param ribbon the award to be displayed; - * defaults to `MeritCommendation.None`; - * use `MeritCommendation.None` when indicating "no ribbon" - * @param bar any of the four positions where the award ribbon is to be displayed; - * defaults to `TermOfService` + * @param ribbon the award to be displayed + * @param bar any of the four positions where the award ribbon is to be displayed * @see `RibbonBars` * @see `MeritCommendation` */ final case class DisplayedAwardMessage( - player_guid: PlanetSideGUID, - ribbon: MeritCommendation.Value = MeritCommendation.None, - bar: RibbonBarsSlot.Value = RibbonBarsSlot.TermOfService -) extends PlanetSideGamePacket { + player_guid: PlanetSideGUID, + ribbon: MeritCommendation.Value, + bar: RibbonBarSlot.Value + ) extends PlanetSideGamePacket { type Packet = DisplayedAwardMessage def opcode = GamePacketOpcode.DisplayedAwardMessage def encode = DisplayedAwardMessage.encode(this) @@ -52,7 +51,7 @@ final case class DisplayedAwardMessage( object DisplayedAwardMessage extends Marshallable[DisplayedAwardMessage] { implicit val codec: Codec[DisplayedAwardMessage] = ( ("player_guid" | PlanetSideGUID.codec) :: - ("ribbon" | MeritCommendation.codec) :: - ("bar" | RibbonBarsSlot.codec) + ("ribbon" | MeritCommendation.codec) :: + ("bar" | RibbonBarSlot.codec) ).as[DisplayedAwardMessage] } diff --git a/src/test/scala/game/AvatarAwardMessageTest.scala b/src/test/scala/game/AvatarAwardMessageTest.scala index 5568b26c3..1d2e55556 100644 --- a/src/test/scala/game/AvatarAwardMessageTest.scala +++ b/src/test/scala/game/AvatarAwardMessageTest.scala @@ -4,18 +4,20 @@ package game import org.specs2.mutable._ import net.psforever.packet._ import net.psforever.packet.game._ +import net.psforever.types.MeritCommendation import scodec.bits._ class AvatarAwardMessageTest extends Specification { val string0 = hex"cf 15010000014000003d0040000000" val string1 = hex"cf 2a010000c717b12a0000" val string2 = hex"cf a6010000e9058cab0080" + val string3 = hex"cf 7a010000400000000000" "decode (0)" in { PacketCoding.decodePacket(string0).require match { case AvatarAwardMessage(unk1, unk2, unk3) => - unk1 mustEqual 277 - unk2 mustEqual AwardOptionZero(5, 500) + unk1 mustEqual MeritCommendation.Max1 + unk2 mustEqual AwardProgress(5, 500) unk3 mustEqual 0 case _ => ko @@ -25,8 +27,8 @@ class AvatarAwardMessageTest extends Specification { "decode (1)" in { PacketCoding.decodePacket(string1).require match { case AvatarAwardMessage(unk1, unk2, unk3) => - unk1 mustEqual 298 - unk2 mustEqual AwardOptionTwo(2831441436L) + unk1 mustEqual MeritCommendation.OneYearVS + unk2 mustEqual AwardCompletion(1415720846L) unk3 mustEqual 0 case _ => ko @@ -36,32 +38,50 @@ class AvatarAwardMessageTest extends Specification { "decode (2)" in { PacketCoding.decodePacket(string2).require match { case AvatarAwardMessage(unk1, unk2, unk3) => - unk1 mustEqual 422 - unk2 mustEqual AwardOptionTwo(2888963748L) - unk3 mustEqual 2 + unk1 mustEqual MeritCommendation.TwoYearVS + unk2 mustEqual AwardCompletion(1444482002L) + unk3 mustEqual 1 + case _ => + ko + } + } + + "decode (3)" in { + PacketCoding.decodePacket(string3).require match { + case AvatarAwardMessage(unk1, unk2, unk3) => + unk1 mustEqual MeritCommendation.StandardAssault3 + unk2 mustEqual AwardQualificationProgress(0) + unk3 mustEqual 0 case _ => ko } } "encode (0)" in { - val msg = AvatarAwardMessage(277, AwardOptionZero(5, 500), 0) + val msg = AvatarAwardMessage(MeritCommendation.Max1, AwardProgress(5, 500)) val pkt = PacketCoding.encodePacket(msg).require.toByteVector pkt mustEqual string0 } "encode (1)" in { - val msg = AvatarAwardMessage(298, AwardOptionTwo(2831441436L), 0) + val msg = AvatarAwardMessage(MeritCommendation.OneYearVS, AwardCompletion(1415720846L)) val pkt = PacketCoding.encodePacket(msg).require.toByteVector pkt mustEqual string1 } "encode (2)" in { - val msg = AvatarAwardMessage(422, AwardOptionTwo(2888963748L), 2) + val msg = AvatarAwardMessage(MeritCommendation.TwoYearVS, AwardCompletion(1444482002L), 1) val pkt = PacketCoding.encodePacket(msg).require.toByteVector pkt mustEqual string2 } + + "encode (3)" in { + val msg = AvatarAwardMessage(MeritCommendation.StandardAssault3, AwardQualificationProgress(0)) + val pkt = PacketCoding.encodePacket(msg).require.toByteVector + + pkt mustEqual string3 + } } diff --git a/src/test/scala/game/DisplayedAwardMessageTest.scala b/src/test/scala/game/DisplayedAwardMessageTest.scala index 2bb05d768..a85348981 100644 --- a/src/test/scala/game/DisplayedAwardMessageTest.scala +++ b/src/test/scala/game/DisplayedAwardMessageTest.scala @@ -15,14 +15,14 @@ class DisplayedAwardMessageTest extends Specification { case DisplayedAwardMessage(player_guid, ribbon, bar) => player_guid mustEqual PlanetSideGUID(1695) ribbon mustEqual MeritCommendation.TwoYearVS - bar mustEqual RibbonBarsSlot.TermOfService + bar mustEqual RibbonBarSlot.TermOfService case _ => ko } } "encode" in { - val msg = DisplayedAwardMessage(PlanetSideGUID(1695), MeritCommendation.TwoYearVS, RibbonBarsSlot.TermOfService) + val msg = DisplayedAwardMessage(PlanetSideGUID(1695), MeritCommendation.TwoYearVS, RibbonBarSlot.TermOfService) val pkt = PacketCoding.encodePacket(msg).require.toByteVector pkt mustEqual string