From 7e899e9ef383aa571758f073d9e35cdd27d21cb6 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Thu, 16 Mar 2023 13:12:54 -0400 Subject: [PATCH] No Uniform, No Helmet, No Service (#1040) * added extra checks to eliminate cosmetics from the packet transcoder where having them defined would be considered harmful to the data * new uniform options; moved cosmetics class file * assurance that the cosmetics settings are accurate during transitory points --- .../actors/session/AvatarActor.scala | 51 ++++++++++++------- .../psforever/actors/session/ChatActor.scala | 4 +- .../psforever/objects/avatar/BattleRank.scala | 29 +++++++++-- .../converter/AvatarConverter.scala | 43 +++++++++++----- .../game/objectcreate/CharacterData.scala | 40 +++++---------- .../objectcreate/DetailedCharacterData.scala | 4 +- .../{objects/avatar => types}/Cosmetic.scala | 7 ++- .../net/psforever/types/UniformStyle.scala | 28 ++++++++++ .../game/objectcreate/CharacterDataTest.scala | 1 - .../DetailedCharacterDataTest.scala | 2 +- .../MountedVehiclesTest.scala | 1 - 11 files changed, 139 insertions(+), 71 deletions(-) rename src/main/scala/net/psforever/{objects/avatar => types}/Cosmetic.scala (95%) create mode 100644 src/main/scala/net/psforever/types/UniformStyle.scala diff --git a/src/main/scala/net/psforever/actors/session/AvatarActor.scala b/src/main/scala/net/psforever/actors/session/AvatarActor.scala index 8c974d75d..766c0d639 100644 --- a/src/main/scala/net/psforever/actors/session/AvatarActor.scala +++ b/src/main/scala/net/psforever/actors/session/AvatarActor.scala @@ -24,7 +24,6 @@ import net.psforever.objects.avatar.{ BattleRank, Certification, Cooldowns, - Cosmetic, Friend => AvatarFriend, Ignored => AvatarIgnored, Implant, @@ -45,8 +44,9 @@ import net.psforever.objects.vital.HealFromImplant import net.psforever.packet.game.objectcreate.{ObjectClass, RibbonBars} import net.psforever.packet.game.{Friend => GameFriend, _} import net.psforever.types.{ - CharacterVoice, CharacterSex, + CharacterVoice, + Cosmetic, ExoSuitType, ImplantType, LoadoutType, @@ -806,7 +806,13 @@ object AvatarActor { out.future } - def toAvatar(avatar: persistence.Avatar): Avatar = + def toAvatar(avatar: persistence.Avatar): Avatar = { + val bep = avatar.bep + val convertedCosmetics = if (BattleRank.showCosmetics(bep)) { + avatar.cosmetics.map(c => Cosmetic.valuesFromObjectCreateValue(c)) + } else { + None + } Avatar( avatar.id, BasicCharacterData( @@ -816,10 +822,11 @@ object AvatarActor { avatar.headId, CharacterVoice(avatar.voiceId) ), - avatar.bep, + bep, avatar.cep, - decoration = ProgressDecoration(cosmetics = avatar.cosmetics.map(c => Cosmetic.valuesFromObjectCreateValue(c))) + decoration = ProgressDecoration(cosmetics = convertedCosmetics) ) + } } class AvatarActor( @@ -936,6 +943,7 @@ class AvatarActor( case DeleteAvatar(id) => import ctx._ val performDeletion = for { + _ <- ctx.run(query[persistence.Shortcut].filter(_.avatarId == lift(id)).delete) _ <- ctx.run(query[persistence.Implant].filter(_.avatarId == lift(id)).delete) _ <- ctx.run(query[persistence.Loadout].filter(_.avatarId == lift(id)).delete) _ <- ctx.run(query[persistence.Locker].filter(_.avatarId == lift(id)).delete) @@ -1886,9 +1894,10 @@ class AvatarActor( ) .onComplete { case Success(_) => + val zone = session.get.zone avatarCopy(avatar.copy(decoration = avatar.decoration.copy(cosmetics = Some(cosmetics)))) - session.get.zone.AvatarEvents ! AvatarServiceMessage( - session.get.zone.id, + zone.AvatarEvents ! AvatarServiceMessage( + zone.id, AvatarAction.PlanetsideAttributeToAll( session.get.player.GUID, 106, @@ -2816,23 +2825,31 @@ class AvatarActor( def setBep(bep: Long, modifier: ExperienceType): Unit = { import ctx._ + val current = BattleRank.withExperience(avatar.bep).value + val next = BattleRank.withExperience(bep).value + lazy val br24 = BattleRank.BR24.value val result = for { - _ <- - if (BattleRank.withExperience(bep).value < BattleRank.BR24.value) setCosmetics(Set()) - else Future.successful(()) r <- ctx.run(query[persistence.Avatar].filter(_.id == lift(avatar.id)).update(_.bep -> lift(bep))) } yield r result.onComplete { case Success(_) => val sess = session.get val zone = sess.zone - val pguid = sess.player.GUID + val zoneId = zone.id + val events = zone.AvatarEvents + val player = sess.player + val pguid = player.GUID val localModifier = modifier sessionActor ! SessionActor.SendResponse(BattleExperienceMessage(pguid, bep, localModifier)) - zone.AvatarEvents ! AvatarServiceMessage( - zone.id, - AvatarAction.PlanetsideAttributeToAll(pguid, 17, bep) - ) + events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(pguid, 17, bep)) + if (current < br24 && next >= br24 || current >= br24 && next < br24) { + setCosmetics(Set()).onComplete { _ => + val evts = events + val name = player.Name + val guid = pguid + evts ! AvatarServiceMessage(name, AvatarAction.PlanetsideAttributeToAll(guid, 106, 1)) //set to no helmet + } + } // when the level is reduced, take away any implants over the implant slot limit val implants = avatar.implants.zipWithIndex.map { case (implant, index) => @@ -2845,9 +2862,7 @@ class AvatarActor( ) .onComplete { case Success(_) => - sessionActor ! SessionActor.SendResponse( - AvatarImplantMessage(pguid, ImplantAction.Remove, index, 0) - ) + sessionActor ! SessionActor.SendResponse(AvatarImplantMessage(pguid, ImplantAction.Remove, index, 0)) case Failure(exception) => log.error(exception)("db failure") } diff --git a/src/main/scala/net/psforever/actors/session/ChatActor.scala b/src/main/scala/net/psforever/actors/session/ChatActor.scala index d209d3985..ce9f46f25 100644 --- a/src/main/scala/net/psforever/actors/session/ChatActor.scala +++ b/src/main/scala/net/psforever/actors/session/ChatActor.scala @@ -19,7 +19,7 @@ import scala.concurrent.duration._ import net.psforever.actors.zone.BuildingActor import net.psforever.login.WorldSession import net.psforever.objects.{Default, Player, Session} -import net.psforever.objects.avatar.{BattleRank, Certification, CommandRank, Cosmetic} +import net.psforever.objects.avatar.{BattleRank, Certification, CommandRank} import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} import net.psforever.objects.serverobject.resourcesilo.ResourceSilo import net.psforever.objects.serverobject.structures.{Amenity, Building} @@ -30,7 +30,7 @@ import net.psforever.services.{CavernRotationService, InterstellarClusterService import net.psforever.services.chat.ChatService import net.psforever.services.chat.ChatService.ChatChannel import net.psforever.types.ChatMessageType.UNK_229 -import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID, Vector3} +import net.psforever.types.{ChatMessageType, Cosmetic, PlanetSideEmpire, PlanetSideGUID, Vector3} import net.psforever.util.{Config, PointOfInterest} import net.psforever.zones.Zones diff --git a/src/main/scala/net/psforever/objects/avatar/BattleRank.scala b/src/main/scala/net/psforever/objects/avatar/BattleRank.scala index 6482dc1da..5564a2955 100644 --- a/src/main/scala/net/psforever/objects/avatar/BattleRank.scala +++ b/src/main/scala/net/psforever/objects/avatar/BattleRank.scala @@ -1,7 +1,7 @@ package net.psforever.objects.avatar import enumeratum.values.{IntEnum, IntEnumEntry} -import net.psforever.packet.game.objectcreate.UniformStyle +import net.psforever.types.UniformStyle /** Battle ranks and their starting experience values * Source: http://wiki.psforever.net/wiki/Battle_Rank @@ -19,9 +19,11 @@ sealed abstract class BattleRank(val value: Int, val experience: Long) extends I } } - def uniformStyle: UniformStyle.Value = { + def uniformStyle: UniformStyle = { if (this.value >= BattleRank.BR25.value) { UniformStyle.ThirdUpgrade + } else if (this.value == BattleRank.BR24.value) { + UniformStyle.SecondUpgradeBR24 } else if (this.value >= BattleRank.BR14.value) { UniformStyle.SecondUpgrade } else if (this.value >= BattleRank.BR7.value) { @@ -30,7 +32,6 @@ sealed abstract class BattleRank(val value: Int, val experience: Long) extends I UniformStyle.Normal } } - } case object BattleRank extends IntEnum[BattleRank] { @@ -103,4 +104,26 @@ case object BattleRank extends IntEnum[BattleRank] { } ) } + + /** + * Given a number of battle experience points, + * determine if the resulting battle rank is sufficient to display cosmetic details on a player character. + * @see `BattleRank.withExperience` + * @param bep amount of battle experience + * @return `true`, if cosmetic elements will be visible; + * `false`, otherwise + */ + def showCosmetics(bep: Long): Boolean = { + showCosmetics(BattleRank.withExperience(bep).uniformStyle) + } + /** + * Given a certain level of uniform dress corresponding to battle rank, + * determine if the resulting battle rank is sufficient to display cosmetic details on a player character. + * @param uniform the style of uniform + * @return `true`, if cosmetic elements will be visible; + * `false`, otherwise + */ + def showCosmetics(uniform: UniformStyle): Boolean = { + uniform.value > UniformStyle.SecondUpgrade.value + } } 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 f2da98a8d..62b3eeff3 100644 --- a/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala +++ b/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala @@ -2,6 +2,7 @@ package net.psforever.objects.definition.converter import net.psforever.objects.Player +import net.psforever.objects.avatar.BattleRank import net.psforever.objects.equipment.{Equipment, EquipmentSlot} import net.psforever.packet.game.objectcreate._ import net.psforever.types.{ExoSuitType, GrenadeState, PlanetSideEmpire, PlanetSideGUID} @@ -109,25 +110,41 @@ object AvatarConverter { } def MakeCharacterData(obj: Player): (Boolean, Boolean) => CharacterData = { + val avatar = obj.avatar + val uniformStyle = avatar.br.uniformStyle + val cosmetics = if (BattleRank.showCosmetics(uniformStyle)) { + avatar.decoration.cosmetics + } else { + None + } val MaxArmor = obj.MaxArmor + val armor = if (MaxArmor == 0) { + 0 + } else { + StatConverter.Health(obj.Armor, MaxArmor) + } CharacterData( StatConverter.Health(obj.Health, obj.MaxHealth), - if (MaxArmor == 0) { - 0 - } else { - StatConverter.Health(obj.Armor, MaxArmor) - }, - obj.avatar.br.uniformStyle, + armor, + uniformStyle, 0, - obj.avatar.cr.value, - obj.avatar.implants.flatten.filter(_.active).flatMap(_.definition.implantType.effect).toList, - obj.avatar.decoration.cosmetics + avatar.cr.value, + avatar.implants.flatten.filter(_.active).flatMap(_.definition.implantType.effect).toList, + cosmetics ) } def MakeDetailedCharacterData(obj: Player): Option[Int] => DetailedCharacterData = { - val maxOpt: Option[Long] = if (obj.ExoSuit == ExoSuitType.MAX) { Some(0L) } - else { None } + val maxOpt: Option[Long] = if (obj.ExoSuit == ExoSuitType.MAX) { + Some(0L) + } else { + None + } + val cosmetics = if (BattleRank.BR24.experience >= obj.avatar.bep) { + obj.avatar.decoration.cosmetics + } else { + None + } val ba: DetailedCharacterA = DetailedCharacterA( obj.avatar.bep, obj.avatar.cep, @@ -164,7 +181,7 @@ object AvatarConverter { Nil, Nil, unkC = false, - obj.avatar.decoration.cosmetics + cosmetics ) pad_length: Option[Int] => DetailedCharacterData(ba, bb(obj.avatar.bep, pad_length))(pad_length) } @@ -229,7 +246,7 @@ object AvatarConverter { equip.GUID, 5, DetailedLockerContainerData( - CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0)), + CommonFieldData(PlanetSideEmpire.NEUTRAL, bops=false, alternate=false, v1=true, None, jammered=false, None, None, PlanetSideGUID(0)), None ) )) diff --git a/src/main/scala/net/psforever/packet/game/objectcreate/CharacterData.scala b/src/main/scala/net/psforever/packet/game/objectcreate/CharacterData.scala index 72e0b82ac..014c46bd9 100644 --- a/src/main/scala/net/psforever/packet/game/objectcreate/CharacterData.scala +++ b/src/main/scala/net/psforever/packet/game/objectcreate/CharacterData.scala @@ -1,8 +1,9 @@ // Copyright (c) 2017 PSForever package net.psforever.packet.game.objectcreate -import net.psforever.objects.avatar.Cosmetic +import net.psforever.objects.avatar.BattleRank import net.psforever.packet.{Marshallable, PacketHelpers} +import net.psforever.types.{Cosmetic, UniformStyle} import scodec.codecs._ import scodec.{Attempt, Codec, Err} import shapeless.{::, HNil} @@ -26,24 +27,6 @@ object ImplantEffects extends Enumeration { implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L) } -/** - * Values for the four different color designs that impact a player's uniform. - * Exo-suits get minor graphical updates at the following battle rank levels: seven (1), fourteen (2), and twenty-five (4). - * The values 3 and 5 also exist and are visually descriptive to the third upgrade. - */ -object UniformStyle extends Enumeration { - type Type = Value - - val Normal = Value(0) - val FirstUpgrade = Value(1) - val SecondUpgrade = Value(2) - val SecondUpgradeEx = Value(3) - val ThirdUpgrade = Value(4) - val ThirdUpgradeEx = Value(5) - - implicit val codec = PacketHelpers.createEnumerationCodec(this, uint(3)) -} - /** * A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.
*
@@ -70,9 +53,9 @@ object UniformStyle extends Enumeration { * cosmetic armor associated with the command rank will be applied automatically * @param implant_effects the effects of implants that can be seen on a player's character; * the number of entries equates to the number of effects applied; - * the maximu number of effects is three + * the maximum number of effects is three * @param cosmetics optional decorative features that are added to the player's head model by console/chat commands; - * they become available at battle rank 24, but here they require the third uniform upgrade (rank 25); + * they become available at battle rank 24; * these flags do not exist if they are not applicable * @param is_backpack this player character should be depicted as a corpse; * corpses are either coffins (defunct), backpacks (normal), or a pastry (festive); @@ -83,7 +66,7 @@ object UniformStyle extends Enumeration { final case class CharacterData( health: Int, armor: Int, - uniform_upgrade: UniformStyle.Value, + uniform_upgrade: UniformStyle, unk: Int, command_rank: Int, implant_effects: List[ImplantEffects.Value], @@ -124,7 +107,7 @@ object CharacterData extends Marshallable[CharacterData] { def apply( health: Int, armor: Int, - uniform: UniformStyle.Value, + uniform: UniformStyle, cr: Int, implant_effects: List[ImplantEffects.Value], cosmetics: Option[Set[Cosmetic]] @@ -139,7 +122,7 @@ object CharacterData extends Marshallable[CharacterData] { uint(3) :: //uniform_upgrade is actually interpreted as a 6u field, but the lower 3u seems to be discarded ("command_rank" | uintL(3)) :: listOfN(uint2, "implant_effects" | ImplantEffects.codec) :: - conditional(style.id > UniformStyle.SecondUpgrade.id, "cosmetics" | Cosmetic.codec) + ("cosmetics" | conditional(BattleRank.showCosmetics(style), Cosmetic.codec)) }) ).exmap[CharacterData]( { @@ -178,7 +161,7 @@ object CharacterData extends Marshallable[CharacterData] { uint(3) :: //uniform_upgrade is actually interpreted as a 6u field, but the lower 3u seems to be discarded ("command_rank" | uintL(3)) :: listOfN(uint2, "implant_effects" | ImplantEffects.codec) :: - conditional(style.id > UniformStyle.SecondUpgrade.id, "cosmetics" | Cosmetic.codec) + ("cosmetics" | conditional(BattleRank.showCosmetics(style), Cosmetic.codec)) } ).exmap[CharacterData]( { @@ -200,7 +183,12 @@ object CharacterData extends Marshallable[CharacterData] { }, { case CharacterData(_, _, uniform, unk, cr, implant_effects, cosmetics) => - Attempt.Successful(uniform :: unk :: cr :: implant_effects :: cosmetics :: HNil) + val cos = if (BattleRank.showCosmetics(uniform)) { + cosmetics.orElse(Some(Set[Cosmetic]())) + } else { + None + } + Attempt.Successful(uniform :: unk :: cr :: implant_effects :: cos :: HNil) case _ => Attempt.Failure(Err("invalid character data; can not decode")) diff --git a/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala b/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala index a44a64bc0..a341e8a7c 100644 --- a/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala +++ b/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala @@ -2,9 +2,9 @@ package net.psforever.packet.game.objectcreate import net.psforever.newcodecs.newcodecs -import net.psforever.objects.avatar.{BattleRank, Certification, Cosmetic} +import net.psforever.objects.avatar.{BattleRank, Certification} import net.psforever.packet.{Marshallable, PacketHelpers} -import net.psforever.types.{ExoSuitType, ImplantType} +import net.psforever.types.{Cosmetic, ExoSuitType, ImplantType} import scodec.{Attempt, Codec} import scodec.codecs._ import shapeless.{::, HNil} diff --git a/src/main/scala/net/psforever/objects/avatar/Cosmetic.scala b/src/main/scala/net/psforever/types/Cosmetic.scala similarity index 95% rename from src/main/scala/net/psforever/objects/avatar/Cosmetic.scala rename to src/main/scala/net/psforever/types/Cosmetic.scala index 8d980fe0d..3a93391e4 100644 --- a/src/main/scala/net/psforever/objects/avatar/Cosmetic.scala +++ b/src/main/scala/net/psforever/types/Cosmetic.scala @@ -1,8 +1,8 @@ -package net.psforever.objects.avatar +package net.psforever.types import enumeratum.values.{IntEnum, IntEnumEntry} -import scodec.{Attempt, Codec} import scodec.codecs.uint +import scodec.{Attempt, Codec} /** Avatar cosmetic options */ sealed abstract class Cosmetic(val value: Int) extends IntEnumEntry @@ -58,9 +58,8 @@ case object Cosmetic extends IntEnum[Cosmetic] { } /** Codec for object create messages */ - implicit val codec: Codec[Set[Cosmetic]] = uint(5).exmap( + implicit val codec: Codec[Set[Cosmetic]] = uint(bits = 5).exmap( value => Attempt.Successful(Cosmetic.valuesFromObjectCreateValue(value)), cosmetics => Attempt.Successful(Cosmetic.valuesToObjectCreateValue(cosmetics)) ) - } diff --git a/src/main/scala/net/psforever/types/UniformStyle.scala b/src/main/scala/net/psforever/types/UniformStyle.scala new file mode 100644 index 000000000..29334bc44 --- /dev/null +++ b/src/main/scala/net/psforever/types/UniformStyle.scala @@ -0,0 +1,28 @@ +// Copyright (c) 2017 PSForever +package net.psforever.types + +import enumeratum.values.{IntEnum, IntEnumEntry} +import net.psforever.packet.PacketHelpers +import scodec.codecs._ +import scodec.Codec + +sealed abstract class UniformStyle(val value: Int) extends IntEnumEntry + +/** + * Values for the four different color designs that impact a player's uniform. + * Exo-suits get minor graphical updates at the following battle rank levels: seven (1), fourteen (2), and twenty-five (4). + * At battle rank twenty-four (3), the style does not update visually but switches to one suitable for display of cosmetics. + * The design for value 5 is visually descriptive of the third upgrade. + */ +object UniformStyle extends IntEnum[UniformStyle] { + val values: IndexedSeq[UniformStyle] = findValues + + case object Normal extends UniformStyle(value = 0) + case object FirstUpgrade extends UniformStyle(value = 1) + case object SecondUpgrade extends UniformStyle(value = 2) + case object SecondUpgradeBR24 extends UniformStyle(value = 3) + case object ThirdUpgrade extends UniformStyle(value = 4) + case object ThirdUpgradeEx extends UniformStyle(value = 5) + + implicit val codec: Codec[UniformStyle] = PacketHelpers.createIntEnumCodec(this, uint(bits = 3)) +} diff --git a/src/test/scala/game/objectcreate/CharacterDataTest.scala b/src/test/scala/game/objectcreate/CharacterDataTest.scala index e08986271..6422a9a92 100644 --- a/src/test/scala/game/objectcreate/CharacterDataTest.scala +++ b/src/test/scala/game/objectcreate/CharacterDataTest.scala @@ -1,7 +1,6 @@ // Copyright (c) 2017 PSForever package game.objectcreate -import net.psforever.objects.avatar.Cosmetic import net.psforever.packet.PacketCoding import net.psforever.packet.game.ObjectCreateMessage import net.psforever.packet.game.objectcreate._ diff --git a/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala b/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala index 496f6d596..fd92006cb 100644 --- a/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala +++ b/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package game.objectcreatedetailed -import net.psforever.objects.avatar.{BattleRank, Certification, Cosmetic} +import net.psforever.objects.avatar.{BattleRank, Certification} import org.specs2.mutable._ import net.psforever.packet._ import net.psforever.packet.game.ObjectCreateDetailedMessage diff --git a/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala b/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala index de95c8866..f384ea651 100644 --- a/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala +++ b/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala @@ -1,7 +1,6 @@ // Copyright (c) 2017 PSForever package game.objectcreatevehicle -import net.psforever.objects.avatar.Cosmetic import net.psforever.packet._ import net.psforever.packet.game.objectcreate._ import net.psforever.packet.game.ObjectCreateMessage