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
This commit is contained in:
Fate-JH 2023-03-16 13:12:54 -04:00 committed by GitHub
parent fdcce870d9
commit 7e899e9ef3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 139 additions and 71 deletions

View file

@ -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")
}

View file

@ -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

View file

@ -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
}
}

View file

@ -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
)
))

View file

@ -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.<br>
* <br>
@ -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"))

View file

@ -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}

View file

@ -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))
)
}

View file

@ -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))
}

View file

@ -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._

View file

@ -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

View file

@ -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