From 70c4393e9bb4dd7a4aba9e7c9b7a8da2894d6c5f Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Mon, 15 May 2023 22:24:35 -0400 Subject: [PATCH] Misc Fixes (#1076) * new paradigm for character creation detection of old characters by name; vehicle channel when seated in vehicle (ant); second wind activates as long as non-fatal damage n>=25; coordinated sequence of deployables whose UI is being updated * the max timer will assert itself through death and respawn * in theory, the tests are fixed; that may change from execution to execution, as is usual * adjusted how the mechanized exo-suit timer asserts itself when in conjunction with prior exo-suit purchase orders * players in seats have their mounted information shortened in a more straightforward, less fault-prone way; stamina recharge command shortened * fixed vehicles not loading when player has no GUID; deactivated squad features (may cause trouble for the Router, but we'll manage); removed lingering, unnecessary radiation tick * even if the player seems to be standing completely still, send an update packet once in a while (1500ms) * removing an active router will always clean up an active router telepad with which it is paired * better timing for refresh of the character select screen; potential to stop moving vehicles from anothers's perspectives * block mounting while vehicle in motion, or in control; if ejected early, end control early * block mounting while vehicle in motion, or in control (2) --- src/main/resources/application.conf | 5 +- .../actors/session/AvatarActor.scala | 309 ++++++++++++------ .../support/SessionAvatarHandlers.scala | 5 +- .../support/SessionMountHandlers.scala | 44 ++- .../support/SessionSquadHandlers.scala | 16 +- .../support/SessionVehicleHandlers.scala | 96 +++--- .../session/support/VehicleOperations.scala | 216 ++++++------ .../net/psforever/objects/Deployables.scala | 20 +- .../psforever/objects/GlobalDefinitions.scala | 9 + .../objects/avatar/PlayerControl.scala | 73 +++-- .../psforever/objects/ce/TelepadLike.scala | 41 ++- .../converter/VehicleConverter.scala | 9 +- .../objects/sourcing/AmenitySource.scala | 4 +- .../objects/sourcing/PlayerSource.scala | 6 +- .../objects/sourcing/TurretSource.scala | 4 +- .../objects/sourcing/VehicleSource.scala | 4 +- .../vehicles/AntTransferBehavior.scala | 2 +- .../objects/vehicles/control/AntControl.scala | 6 +- .../control/DeployingVehicleControl.scala | 6 +- .../vehicles/control/RouterControl.scala | 14 +- .../vehicles/control/VehicleControl.scala | 11 +- .../game/ServerVehicleOverrideMsg.scala | 3 +- .../net/psforever/types/DriveState.scala | 4 +- .../scala/net/psforever/util/Config.scala | 1 + src/test/scala/objects/DamageableTest.scala | 28 +- .../objects/DeployableBehaviorTest.scala | 13 +- src/test/scala/objects/DeployableTest.scala | 36 +- 27 files changed, 593 insertions(+), 392 deletions(-) diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 66d31a914..2b2caa14b 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -90,6 +90,9 @@ game { # Purchases timers for the mechanized assault exo-suits all update at the same time when any of them would update shared-max-cooldown = no + # Purchases timers for the battleframe robotics vehicles all update at the same time when either of them would update + shared-bfr-cooldown = yes + # HART system, shuttles and facilities hart { # How long the shuttle is not boarding passengers (going through the motions) @@ -208,7 +211,7 @@ game { # Use the index of that sample distance from this sequence in the sequence `delays` below. ranges = [150, 300, 400] # The absolute time delay before a successful packet must be dispatched regardless of distance. (s) - delay-max = 1000 + delay-max = 1500 # The time delays for each distance range before a successful packet must be dispatched. [s] # The index for an entry in this sequence is expected to be discovered using the `ranges` sequence above. # Delays between packets may not be as precise as desired diff --git a/src/main/scala/net/psforever/actors/session/AvatarActor.scala b/src/main/scala/net/psforever/actors/session/AvatarActor.scala index c04691e92..719a54c60 100644 --- a/src/main/scala/net/psforever/actors/session/AvatarActor.scala +++ b/src/main/scala/net/psforever/actors/session/AvatarActor.scala @@ -1,24 +1,17 @@ // Copyright (c) 2019 PSForever package net.psforever.actors.session -import java.util.concurrent.atomic.AtomicInteger import akka.actor.Cancellable import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} import akka.actor.typed.{ActorRef, Behavior, PostStop, SupervisorStrategy} -import net.psforever.objects.avatar.{ProgressDecoration, SpecialCarry} -import net.psforever.objects.avatar.scoring.{Death, EquipmentStat, KDAStat, Kill} -import net.psforever.objects.sourcing.SourceWithHealthEntry -import net.psforever.objects.vital.projectile.ProjectileReason -import net.psforever.objects.vital.{DamagingActivity, HealingActivity, SpawningActivity, Vitality} -import net.psforever.packet.game.objectcreate.BasicCharacterData -import net.psforever.types.ExperienceType +import java.util.concurrent.atomic.AtomicInteger import org.joda.time.{LocalDateTime, Seconds} - import scala.collection.mutable import scala.concurrent.{ExecutionContextExecutor, Future, Promise} import scala.util.{Failure, Success} import scala.concurrent.duration._ // +import net.psforever.objects._ import net.psforever.objects.avatar.{ Avatar, BattleRank, @@ -29,25 +22,31 @@ import net.psforever.objects.avatar.{ Implant, MemberLists, PlayerControl, - Shortcut => AvatarShortcut + ProgressDecoration, + Shortcut => AvatarShortcut, + SpecialCarry } +import net.psforever.objects.avatar.scoring.{Death, EquipmentStat, KDAStat, Kill} import net.psforever.objects.definition._ import net.psforever.objects.definition.converter.CharacterSelectConverter -import net.psforever.objects.inventory.Container import net.psforever.objects.equipment.{Equipment, EquipmentSlot} -import net.psforever.objects.inventory.InventoryItem +import net.psforever.objects.inventory.{Container, InventoryItem} import net.psforever.objects.loadouts.{InfantryLoadout, Loadout, VehicleLoadout} -import net.psforever.objects._ import net.psforever.objects.locker.LockerContainer -import net.psforever.objects.sourcing.PlayerSource -import net.psforever.objects.vital.HealFromImplant -import net.psforever.packet.game.objectcreate.{ObjectClass, RibbonBars} +import net.psforever.objects.sourcing.{PlayerSource,SourceWithHealthEntry} +import net.psforever.objects.vital.projectile.ProjectileReason +import net.psforever.objects.vital.{DamagingActivity, HealFromImplant, HealingActivity, SpawningActivity, Vitality} +import net.psforever.packet.game.objectcreate.{BasicCharacterData, ObjectClass, RibbonBars} import net.psforever.packet.game.{Friend => GameFriend, _} +import net.psforever.persistence +import net.psforever.services.Service +import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.types.{ CharacterSex, CharacterVoice, Cosmetic, ExoSuitType, + ExperienceType, ImplantType, LoadoutType, MemberAction, @@ -57,11 +56,7 @@ import net.psforever.types.{ TransactionType } import net.psforever.util.Database._ -import net.psforever.persistence import net.psforever.util.{Config, Database, DefinitionUtil} -import net.psforever.services.Service -//import org.log4s.Logger -import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} object AvatarActor { def apply(sessionActor: ActorRef[SessionActor.Command]): Behavior[Command] = @@ -400,28 +395,33 @@ object AvatarActor { def resolveSharedPurchaseTimeNames(pair: (BasicDefinition, String)): Seq[(BasicDefinition, String)] = { val (definition, name) = pair if (name.matches("(tr|nc|vs)hev_.+") && Config.app.game.sharedMaxCooldown) { + //all mechanized exo-suits val faction = name.take(2) (if (faction.equals("nc")) { - Seq(GlobalDefinitions.nchev_scattercannon, GlobalDefinitions.nchev_falcon, GlobalDefinitions.nchev_sparrow) - } else if (faction.equals("vs")) { - Seq(GlobalDefinitions.vshev_quasar, GlobalDefinitions.vshev_comet, GlobalDefinitions.vshev_starfire) - } else { - Seq(GlobalDefinitions.trhev_dualcycler, GlobalDefinitions.trhev_pounder, GlobalDefinitions.trhev_burster) - }).zip( - Seq(s"${faction}hev_antipersonnel", s"${faction}hev_antivehicular", s"${faction}hev_antiaircraft") - ) - } else { + Seq(GlobalDefinitions.nchev_scattercannon, GlobalDefinitions.nchev_falcon, GlobalDefinitions.nchev_sparrow) + } else if (faction.equals("vs")) { + Seq(GlobalDefinitions.vshev_quasar, GlobalDefinitions.vshev_comet, GlobalDefinitions.vshev_starfire) + } else { + Seq(GlobalDefinitions.trhev_dualcycler, GlobalDefinitions.trhev_pounder, GlobalDefinitions.trhev_burster) + }).map { tdef => (tdef, tdef.Descriptor) } + } else if ((name.matches(".+_gunner") || name.matches(".+_flight")) && Config.app.game.sharedBfrCooldown) { + //all battleframe robotics vehicles definition match { case vdef: VehicleDefinition if GlobalDefinitions.isBattleFrameFlightVehicle(vdef) => val bframe = name.substring(0, name.indexOf('_')) val gunner = bframe + "_gunner" - Seq((DefinitionUtil.fromString(gunner), gunner), (vdef, name)) - + Seq((DefinitionUtil.fromString(gunner), gunner), pair) case vdef: VehicleDefinition if GlobalDefinitions.isBattleFrameGunnerVehicle(vdef) => val bframe = name.substring(0, name.indexOf('_')) val flight = bframe + "_flight" - Seq((vdef, name), (DefinitionUtil.fromString(flight), flight)) - + Seq(pair, (DefinitionUtil.fromString(flight), flight)) + case _ => + Seq.empty + } + } else { + definition match { + case tdef: ToolDefinition if GlobalDefinitions.isMaxArms(tdef) => + Seq((tdef, tdef.Descriptor)) case _ => Seq(pair) } @@ -913,43 +913,8 @@ class AvatarActor( AvatarActor.displayLookingForSquad(session.get, if (lfs) 1 else 0) Behaviors.same - case CreateAvatar(name, head, voice, gender, empire) => - import ctx._ - ctx.run(query[persistence.Avatar].filter(_.name ilike lift(name)).filter(!_.deleted)).onComplete { - case Success(characters) => - characters.headOption match { - case None => - val result = for { - _ <- ctx.run( - query[persistence.Avatar] - .insert( - _.name -> lift(name), - _.accountId -> lift(account.id), - _.factionId -> lift(empire.id), - _.headId -> lift(head), - _.voiceId -> lift(voice.id), - _.genderId -> lift(gender.value), - _.bep -> lift(Config.app.game.newAvatar.br.experience), - _.cep -> lift(Config.app.game.newAvatar.cr.experience) - ) - ) - } yield () - result.onComplete { - case Success(_) => - log.debug(s"AvatarActor: created character $name for account ${account.name}") - sessionActor ! SessionActor.SendResponse(ActionResultMessage.Pass) - sendAvatars(account) - case Failure(e) => log.error(e)("db failure") - } - case Some(_) => - // send "char already exists" - sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(1)) - } - case Failure(e) => - log.error(e)("db failure") - sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(3)) - sendAvatars(account) - } + case CreateAvatar(name, head, voice, sex, empire) => + createAvatar(account, name, sex, empire, head, voice) Behaviors.same case DeleteAvatar(id) => @@ -1482,7 +1447,8 @@ class AvatarActor( Behaviors.same case UpdatePurchaseTime(definition, time) => - var newTimes = avatar.cooldowns.purchase + var theTimes = avatar.cooldowns.purchase + var updateTheTimes: Boolean = false AvatarActor .resolveSharedPurchaseTimeNames(AvatarActor.resolvePurchaseTimeName(avatar.faction, definition)) .foreach { @@ -1490,7 +1456,8 @@ class AvatarActor( Avatar.purchaseCooldowns.get(item) match { case Some(cooldown) => //only send for items with cooldowns - newTimes = newTimes.updated(name, time) + updateTheTimes = true + theTimes = theTimes.updated(name, time) updatePurchaseTimer( name, cooldown.toSeconds, @@ -1502,7 +1469,9 @@ class AvatarActor( case _ => ; } } - avatarCopy(avatar.copy(cooldowns = avatar.cooldowns.copy(purchase = newTimes))) + if (updateTheTimes) { + avatarCopy(avatar.copy(cooldowns = avatar.cooldowns.copy(purchase = theTimes))) + } Behaviors.same case UpdateUseTime(definition, time) => @@ -1632,11 +1601,7 @@ class AvatarActor( Behaviors.same case RestoreStamina(stamina) => - tryRestoreStaminaForSession(stamina) match { - case Some(sess) => - actuallyRestoreStamina(stamina, sess) - case _ => ; - } + tryRestoreStaminaForSession(stamina).collect { actuallyRestoreStamina(stamina, _) } Behaviors.same case RestoreStaminaPeriodically(stamina) => @@ -1897,11 +1862,7 @@ class AvatarActor( ) ) // if we need to start stamina regeneration - tryRestoreStaminaForSession(stamina = 1) match { - case Some(_) => - defaultStaminaRegen(initialDelay = 0.5f seconds) - case _ => ; - } + tryRestoreStaminaForSession(stamina = 1).collect { _ => defaultStaminaRegen(initialDelay = 0.5f seconds) } replyTo ! AvatarLoginResponse(avatar) case Failure(e) => log.error(e)("db failure") @@ -1991,11 +1952,7 @@ class AvatarActor( } def restoreStaminaPeriodically(stamina: Int): Unit = { - tryRestoreStaminaForSession(stamina) match { - case Some(sess) => - actuallyRestoreStaminaIfStationary(stamina, sess) - case _ => ; - } + tryRestoreStaminaForSession(stamina).collect { actuallyRestoreStaminaIfStationary(stamina, _) } startIfStoppedStaminaRegen(initialDelay = 0.5f seconds) } @@ -2633,27 +2590,40 @@ class AvatarActor( def refreshPurchaseTimes(keys: Set[String]): Unit = { var keysToDrop: Seq[String] = Nil + val faction = avatar.faction keys.foreach { key => - avatar.cooldowns.purchase.find { case (name, _) => name.equals(key) } match { - case Some((name, purchaseTime)) => + avatar + .cooldowns + .purchase + .find { case (name, _) => name.equals(key) } + .flatMap { case (name, purchaseTime) => val secondsSincePurchase = Seconds.secondsBetween(purchaseTime, LocalDateTime.now()).getSeconds - Avatar.purchaseCooldowns.find(_._1.Name.equals(name)) match { - case Some((obj, cooldown)) if cooldown.toSeconds - secondsSincePurchase > 0 => - val (_, name) = AvatarActor.resolvePurchaseTimeName(avatar.faction, obj) - updatePurchaseTimer( - name, - cooldown.toSeconds - secondsSincePurchase, - obj match { - case _: KitDefinition => false - case _ => true - } - ) - - case _ => - keysToDrop = keysToDrop :+ key //key has timed-out + Avatar + .purchaseCooldowns + .find(_._1.Descriptor.equals(name)) + .collect { + case (obj, cooldown) => + (obj, cooldown.toSeconds - secondsSincePurchase) + } + .orElse { + keysToDrop = keysToDrop :+ key //key indicates cooldown, but no cooldown delay + None } - case _ => ; - } + } + .collect { + case (obj, remainingTime) if remainingTime > 0 => + val (_, resolvedName) = AvatarActor.resolvePurchaseTimeName(faction, obj) + updatePurchaseTimer( + resolvedName, + remainingTime, + obj match { + case _: KitDefinition => false + case _ => true + } + ) + case (_, _) => + keysToDrop = keysToDrop :+ key //key has timed-out + } } if (keysToDrop.nonEmpty) { val cdown = avatar.cooldowns @@ -2999,4 +2969,131 @@ class AvatarActor( def updateToolDischarge(stats: EquipmentStat): Unit = { avatar.scorecard.rate(stats) } + + def createAvatar( + account: Account, + name: String, + sex: CharacterSex, + empire: PlanetSideEmpire.Value, + head: Int, + voice: CharacterVoice.Value + ): Unit = { + import ctx._ + ctx.run( + query[persistence.Avatar] + .filter(_.name ilike lift(name)) + .map(a => (a.accountId, a.deleted, a.created)) + ) + .onComplete { + case Success(foundCharacters) if foundCharacters.size == 1 => + testFoundCharacters( + account, + name, + foundCharacters + .collect { case (a, b, c) => (a, b, c.toDate.getTime) } + ) + case Success(foundCharacters) if foundCharacters.nonEmpty => + testFoundCharacters( + account, + name, + foundCharacters + .map { case (a, b, created) => (a, b, created.toDate.getTime) } + .sortBy { case (_, _, created) => created } + .toList + ) + case Success(_) => + //create new character + actuallyCreateNewCharacter(account.id, account.name, name, sex, empire, head, voice) + .onComplete(_ => sendAvatars(account)) + case Failure(e) => + log.error(e)("db failure") + sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 3)) + sendAvatars(account) + } + } + + private def testFoundCharacters( + account: Account, + name: String, + foundCharacters: IterableOnce[(Int, Boolean, Long)]): Unit = { + val foundCharactersIterator = foundCharacters.iterator + if (foundCharactersIterator.exists { case (_, deleted, _ ) => !deleted } || + foundCharactersIterator.exists { case (accountId, _, _) => accountId != account.id }) { + //send "char already exists" + sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 1)) + } else { + //reactivate character + reactivateCharacter(account.id, account.name, name) + .onComplete(_ => sendAvatars(account)) + } + } + + private def actuallyCreateNewCharacter( + accountId: Int, + accountName: String, + name: String, + sex: CharacterSex, + empire: PlanetSideEmpire.Value, + head: Int, + voice: CharacterVoice.Value, + bep: Long = Config.app.game.newAvatar.br.experience, + cep: Long = Config.app.game.newAvatar.cr.experience + ): Future[Boolean] = { + val output: Promise[Boolean] = Promise[Boolean]() + import ctx._ + val result = for { + _ <- ctx.run( + query[persistence.Avatar] + .insert( + _.name -> lift(name), + _.accountId -> lift(accountId), + _.factionId -> lift(empire.id), + _.headId -> lift(head), + _.voiceId -> lift(voice.id), + _.genderId -> lift(sex.value), + _.bep -> lift(bep), + _.cep -> lift(cep) + ) + ) + } yield () + result.onComplete { + case Success(_) => + log.debug(s"AvatarActor: created character $name for account $accountName") + sessionActor ! SessionActor.SendResponse(ActionResultMessage.Pass) + output.success(true) + case Failure(e) => + log.error(e)("db failure") + sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 3)) + output.success(false) + } + output.future + } + + private def reactivateCharacter( + accountId: Int, + accountName: String, + characterName: String + ): Future[Boolean] = { + val output: Promise[Boolean] = Promise[Boolean]() + import ctx._ + val result = for { + out <- ctx.run( + query[persistence.Avatar] + .filter(a => a.accountId == lift(accountId)) + .filter(a => a.name ilike lift(characterName)) + .update(_.deleted -> lift(false)) + ) + } yield out + result.onComplete { + case Success(_) => + log.debug(s"AvatarActor: character belonging to $accountName with name $characterName reactivated") + sessionActor ! SessionActor.SendResponse(ActionResultMessage.Pass) + output.success(true) + case Failure(e) => + log.error(e)("db failure") + sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 3)) + output.success(false) + } + output.future + } } diff --git a/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala index 0a9d2de4e..1dc8c6166 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala @@ -104,10 +104,10 @@ class SessionAvatarHandlers( } //ms if (!wasVisible || !previouslyInDrawableRange || + durationSince > drawConfig.delayMax || (!lastMsg.contains(pstateToSave) && (canSeeReallyFar || currentDistance < drawConfig.rangeMin * drawConfig.rangeMin || - durationSince > drawConfig.delayMax || sessionData.canSeeReallyFar || durationSince > targetDelay ) @@ -425,8 +425,7 @@ class SessionAvatarHandlers( sessionData.zoning.zoningStatus = Zoning.Status.None sessionData.zoning.spawn.deadState = DeadState.Dead continent.GUID(mount).collect { case obj: Vehicle => - sessionData.vehicles.ConditionalDriverVehicleControl(obj) - sessionData.vehicles.serverVehicleControlVelocity = None + sessionData.vehicles.DriverVehicleControl(obj) sessionData.unaccessContainer(obj) } sessionData.playerActionsToCancel() diff --git a/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala index 23d18afb9..261b1fed9 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala @@ -16,7 +16,7 @@ import net.psforever.packet.game.{ChatMsg, DelayedPathMountMsg, DismountVehicleM import net.psforever.services.Service import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} -import net.psforever.types.{BailType, ChatMessageType, PlanetSideGUID} +import net.psforever.types.{BailType, ChatMessageType, PlanetSideGUID, Vector3} class SessionMountHandlers( val sessionData: SessionData, @@ -56,6 +56,7 @@ class SessionMountHandlers( sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields)) sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=45, obj.NtuCapacitorScaled)) sendResponse(GenericObjectActionMessage(obj_guid, code=11)) + sessionData.accessContainer(obj) MountingAction(tplayer, obj, seatNumber) case Mountable.CanMount(obj: Vehicle, seatNumber, _) @@ -237,7 +238,7 @@ class SessionMountHandlers( }") sessionData.vehicles.ConditionalDriverVehicleControl(obj) sessionData.unaccessContainer(obj) - DismountAction(tplayer, obj, seatNum) + DismountVehicleAction(tplayer, obj, seatNum) case Mountable.CanDismount(obj: Vehicle, seat_num, _) => continent.VehicleEvents ! VehicleServiceMessage( @@ -288,6 +289,43 @@ class SessionMountHandlers( ) } + /** + * Common activities/procedure when a player dismounts a valid mountable object. + * @param tplayer the player + * @param obj the mountable object + * @param seatNum the mount out of which which the player is disembarking + */ + def DismountVehicleAction(tplayer: Player, obj: PlanetSideGameObject with Mountable, seatNum: Int): Unit = { + DismountAction(tplayer, obj, seatNum) + //until vehicles maintain synchronized momentum without a driver + obj match { + case v: Vehicle + if seatNum == 0 && Vector3.MagnitudeSquared(v.Velocity.getOrElse(Vector3.Zero)) > 0f => + sessionData.vehicles.serverVehicleControlVelocity.collect { _ => + sessionData.vehicles.ServerVehicleOverrideStop(v) + } + v.Velocity = Vector3.Zero + continent.VehicleEvents ! VehicleServiceMessage( + continent.id, + VehicleAction.VehicleState( + tplayer.GUID, + v.GUID, + unk1 = 0, + v.Position, + v.Orientation, + vel = None, + v.Flying, + unk3 = 0, + unk4 = 0, + wheel_direction = 15, + unk5 = false, + unk6 = v.Cloaked + ) + ) + case _ => () + } + } + /** * Common activities/procedure when a player dismounts a valid mountable object. * @param tplayer the player @@ -305,7 +343,7 @@ class SessionMountHandlers( sendResponse(DismountVehicleMsg(playerGuid, bailType, wasKickedByDriver = false)) continent.VehicleEvents ! VehicleServiceMessage( continent.id, - VehicleAction.DismountVehicle(playerGuid, bailType, unk2=false) + VehicleAction.DismountVehicle(playerGuid, bailType, unk2 = false) ) } } diff --git a/src/main/scala/net/psforever/actors/session/support/SessionSquadHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionSquadHandlers.scala index 132924a16..9daeac6fa 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionSquadHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionSquadHandlers.scala @@ -55,17 +55,17 @@ class SessionSquadHandlers( /* packet */ def handleSquadDefinitionAction(pkt: SquadDefinitionActionMessage): Unit = { - val SquadDefinitionActionMessage(u1, u2, action) = pkt - squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Definition(u1, u2, action)) +// val SquadDefinitionActionMessage(u1, u2, action) = pkt +// squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Definition(u1, u2, action)) } def handleSquadMemberRequest(pkt: SquadMembershipRequest): Unit = { - val SquadMembershipRequest(request_type, char_id, unk3, player_name, unk5) = pkt - squadService ! SquadServiceMessage( - player, - continent, - SquadServiceAction.Membership(request_type, char_id, unk3, player_name, unk5) - ) +// val SquadMembershipRequest(request_type, char_id, unk3, player_name, unk5) = pkt +// squadService ! SquadServiceMessage( +// player, +// continent, +// SquadServiceAction.Membership(request_type, char_id, unk3, player_name, unk5) +// ) } def handleSquadWaypointRequest(pkt: SquadWaypointRequest): Unit = { diff --git a/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala index 504399c98..96c5b23fb 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala @@ -34,7 +34,7 @@ class SessionVehicleHandlers( val resolvedPlayerGuid = if (player.HasGUID) { player.GUID } else { - Service.defaultPlayerGUID + PlanetSideGUID(-1) } val isNotSameTarget = resolvedPlayerGuid != guid reply match { @@ -202,17 +202,19 @@ class SessionVehicleHandlers( val strafe = 1 + Vehicles.CargoOrientation(vehicle) val reverseSpeed = if (strafe > 1) { 0 } else { speed } //strafe or reverse, not both - sessionData.vehicles.serverVehicleControlVelocity = Some(reverseSpeed) - sendResponse(ServerVehicleOverrideMsg( - lock_accelerator=true, - lock_wheel=true, - reverse=true, - unk4=false, - lock_vthrust=0, - strafe, - reverseSpeed, - unk8=Some(0) - )) + sessionData.vehicles.ServerVehicleOverrideWithPacket( + vehicle, + ServerVehicleOverrideMsg( + lock_accelerator=true, + lock_wheel=true, + reverse=true, + unk4=false, + lock_vthrust=0, + strafe, + reverseSpeed, + unk8=Some(0) + ) + ) import scala.concurrent.ExecutionContext.Implicits.global context.system.scheduler.scheduleOnce( delay milliseconds, @@ -220,25 +222,27 @@ class SessionVehicleHandlers( VehicleServiceResponse(toChannel, PlanetSideGUID(0), VehicleResponse.KickCargo(vehicle, speed=0, delay)) ) - case VehicleResponse.KickCargo(_, _, _) + case VehicleResponse.KickCargo(cargo, _, _) if player.VehicleSeated.nonEmpty && sessionData.zoning.spawn.deadState == DeadState.Alive => - sessionData.vehicles.serverVehicleControlVelocity = None - sendResponse(ServerVehicleOverrideMsg( - lock_accelerator=false, - lock_wheel=false, - reverse=false, - unk4=false, - lock_vthrust=0, - lock_strafe=0, - movement_speed=0, - unk8=None - )) + sessionData.vehicles.TotalDriverVehicleControl(cargo) case VehicleResponse.StartPlayerSeatedInVehicle(vehicle, _) if player.VisibleSlots.contains(player.DrawnSlot) => val vehicle_guid = vehicle.GUID sessionData.playerActionsToCancel() - sessionData.vehicles.serverVehicleControlVelocity = Some(0) + sessionData.vehicles.ServerVehicleOverrideWithPacket( + vehicle, + ServerVehicleOverrideMsg( + lock_accelerator=false, + lock_wheel=false, + reverse=false, + unk4=false, + lock_vthrust=0, + lock_strafe=0, + movement_speed=0, + unk8=None + ) + ) sessionData.terminals.CancelAllProximityUnits() player.DrawnSlot = Player.HandsDownSlot sendResponse(ObjectHeldMessage(player.GUID, Player.HandsDownSlot, unk1=true)) @@ -253,33 +257,51 @@ class SessionVehicleHandlers( } case VehicleResponse.StartPlayerSeatedInVehicle(vehicle, _) => - val vehicle_guid = vehicle.GUID + val vehicleGuid = vehicle.GUID + val playerGuid = player.GUID sessionData.playerActionsToCancel() - sessionData.vehicles.serverVehicleControlVelocity = Some(0) sessionData.terminals.CancelAllProximityUnits() - sendResponse(PlanetsideAttributeMessage(vehicle_guid, attribute_type=22, attribute_value=1L)) //mount points off - sendResponse(PlanetsideAttributeMessage(player.GUID, attribute_type=21, vehicle_guid)) //ownership + sendResponse(PlanetsideAttributeMessage(vehicleGuid, attribute_type=22, attribute_value=1L)) //mount points off + sendResponse(PlanetsideAttributeMessage(playerGuid, attribute_type=21, vehicleGuid)) //ownership vehicle.MountPoints.find { case (_, mp) => mp.seatIndex == 0 }.collect { case (mountPoint, _) => vehicle.Actor ! Mountable.TryMount(player, mountPoint) } case VehicleResponse.PlayerSeatedInVehicle(vehicle, _) => - val vehicle_guid = vehicle.GUID - sendResponse(PlanetsideAttributeMessage(vehicle_guid, attribute_type=22, attribute_value=0L)) //mount points on Vehicles.ReloadAccessPermissions(vehicle, player.Name) - sessionData.vehicles.ServerVehicleLock(vehicle) + sessionData.vehicles.ServerVehicleOverrideWithPacket( + vehicle, + ServerVehicleOverrideMsg( + lock_accelerator=true, + lock_wheel=true, + reverse=false, + unk4=false, + lock_vthrust=0, + lock_strafe=0, + movement_speed=0, + unk8=Some(0) + ) + ) + sessionData.vehicles.serverVehicleControlVelocity = Some(-1) case VehicleResponse.ServerVehicleOverrideStart(vehicle, _) => val vdef = vehicle.Definition - sessionData.vehicles.ServerVehicleOverride( + sessionData.vehicles.ServerVehicleOverrideWithPacket( vehicle, - vdef.AutoPilotSpeed1, - flight= if (GlobalDefinitions.isFlightVehicle(vdef)) { 1 } else { 0 } + ServerVehicleOverrideMsg( + lock_accelerator=true, + lock_wheel=true, + reverse=false, + unk4=false, + lock_vthrust=if (GlobalDefinitions.isFlightVehicle(vdef)) { 1 } else { 0 }, + lock_strafe=0, + movement_speed=vdef.AutoPilotSpeed1, + unk8=Some(0) + ) ) case VehicleResponse.ServerVehicleOverrideEnd(vehicle, _) => - session = session.copy(avatar = avatar.copy(vehicle = Some(vehicle.GUID))) - sessionData.vehicles.DriverVehicleControl(vehicle, vehicle.Definition.AutoPilotSpeed2) + sessionData.vehicles.ServerVehicleOverrideStop(vehicle) case VehicleResponse.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, data) => sendResponse(ChatMsg( diff --git a/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala b/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala index ecf6dc8bf..4cce70018 100644 --- a/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala @@ -268,22 +268,23 @@ class VehicleOperations( def handleMountVehicle(pkt: MountVehicleMsg): Unit = { val MountVehicleMsg(_, mountable_guid, entry_point) = pkt - sessionData.validObject(mountable_guid, decorator = "MountVehicle") match { - case Some(obj: Mountable) => + sessionData.validObject(mountable_guid, decorator = "MountVehicle").collect { + case obj: Mountable => obj.Actor ! Mountable.TryMount(player, entry_point) - case Some(_) => + case _: Mountable => + log.warn( + s"DismountVehicleMsg: ${player.Name} can not mount while server has asserted control; please wait" + ) + case _ => log.error(s"MountVehicleMsg: object ${mountable_guid.guid} not a mountable thing, ${player.Name}") - case None => ; } } def handleDismountVehicle(pkt: DismountVehicleMsg): Unit = { val DismountVehicleMsg(player_guid, bailType, wasKickedByDriver) = pkt + val dError: (String, Player)=> Unit = dismountError(bailType, wasKickedByDriver) //TODO optimize this later //common warning for this section - def dismountWarning(note: String): Unit = { - log.error(s"$note; some vehicle might not know that ${player.Name} is no longer sitting in it") - } if (player.GUID == player_guid) { //normally disembarking from a mount (sessionData.zoning.interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match { @@ -295,16 +296,9 @@ class VehicleOperations( case out @ Some(_: Mountable) => out case _ => - dismountWarning( - s"DismountVehicleMsg: player ${player.Name}_guid not considered seated in a mountable entity" - ) - sendResponse(DismountVehicleMsg(player_guid, bailType, wasKickedByDriver)) + dError(s"DismountVehicleMsg: player ${player.Name} not considered seated in a mountable entity", player) None }) match { - case Some(_) if serverVehicleControlVelocity.nonEmpty => - log.debug( - s"DismountVehicleMsg: ${player.Name} can not dismount from vehicle while server has asserted control; please wait" - ) case Some(obj: Mountable) => obj.PassengerInSeat(player) match { case Some(seat_num) => @@ -325,15 +319,14 @@ class VehicleOperations( } case None => - dismountWarning( - s"DismountVehicleMsg: can not find where player ${player.Name}_guid is seated in mountable ${player.VehicleSeated}" - ) + dError(s"DismountVehicleMsg: can not find where player ${player.Name}_guid is seated in mountable ${player.VehicleSeated}", player) } case _ => - dismountWarning(s"DismountVehicleMsg: can not find mountable entity ${player.VehicleSeated}") + dError(s"DismountVehicleMsg: can not find mountable entity ${player.VehicleSeated}", player) } } else { //kicking someone else out of a mount; need to own that mount/mountable + val dWarn: (String, Player)=> Unit = dismountWarning(bailType, wasKickedByDriver) player.avatar.vehicle match { case Some(obj_guid) => ( @@ -353,23 +346,47 @@ class VehicleOperations( case Some(seat_num) => obj.Actor ! Mountable.TryDismount(tplayer, seat_num, bailType) case None => - dismountWarning( - s"DismountVehicleMsg: can not find where other player ${player.Name}_guid is seated in mountable $obj_guid" - ) + dError(s"DismountVehicleMsg: can not find where other player ${tplayer.Name} is seated in mountable $obj_guid", tplayer) } - case (None, _) => ; - log.warn(s"DismountVehicleMsg: ${player.Name} can not find his vehicle") - case (_, None) => ; - log.warn(s"DismountVehicleMsg: player $player_guid could not be found to kick, ${player.Name}") + case (None, _) => + dWarn(s"DismountVehicleMsg: ${player.Name} can not find his vehicle", player) + case (_, None) => + dWarn(s"DismountVehicleMsg: player $player_guid could not be found to kick, ${player.Name}", player) case _ => - log.warn(s"DismountVehicleMsg: object is either not a Mountable or not a Player") + dWarn(s"DismountVehicleMsg: object is either not a Mountable or not a Player", player) } case None => - log.warn(s"DismountVehicleMsg: ${player.Name} does not own a vehicle") + dWarn(s"DismountVehicleMsg: ${player.Name} does not own a vehicle", player) } } } + private def dismountWarning( + bailAs: BailType.Value, + kickedByDriver: Boolean + ) + ( + note: String, + player: Player + ): Unit = { + log.warn(note) + player.VehicleSeated = None + sendResponse(DismountVehicleMsg(player.GUID, bailAs, kickedByDriver)) + } + + private def dismountError( + bailAs: BailType.Value, + kickedByDriver: Boolean + ) + ( + note: String, + player: Player + ): Unit = { + log.error(s"$note; some vehicle might not know that ${player.Name} is no longer sitting in it") + player.VehicleSeated = None + sendResponse(DismountVehicleMsg(player.GUID, bailAs, kickedByDriver)) + } + def handleMountVehicleCargo(pkt: MountVehicleCargoMsg): Unit = { val MountVehicleCargoMsg(_, cargo_guid, carrier_guid, _) = pkt (continent.GUID(cargo_guid), continent.GUID(carrier_guid)) match { @@ -514,87 +531,27 @@ class VehicleOperations( } /** - * This function is applied to vehicles that are leaving a cargo vehicle's cargo hold to auto reverse them out - * Lock all applicable controls of the current vehicle - * Set the vehicle to move in reverse + * Place the current vehicle under the control of the driver's commands, + * but leave it in a cancellable auto-drive. + * @param vehicle the vehicle */ - def ServerVehicleLockReverse(): Unit = { - serverVehicleControlVelocity = Some(-1) - sendResponse( - ServerVehicleOverrideMsg( - lock_accelerator = true, - lock_wheel = true, - reverse = true, - unk4 = true, - lock_vthrust = 0, - lock_strafe = 1, - movement_speed = 2, - unk8 = Some(0) - ) - ) - } - - /** - * This function is applied to vehicles that are leaving a cargo vehicle's cargo hold to strafe right out of the cargo hold for vehicles that are mounted sideways e.g. router/BFR - * Lock all applicable controls of the current vehicle - * Set the vehicle to strafe right - */ - def ServerVehicleLockStrafeRight(): Unit = { - serverVehicleControlVelocity = Some(-1) - sendResponse( - ServerVehicleOverrideMsg( - lock_accelerator = true, - lock_wheel = true, - reverse = false, - unk4 = true, - lock_vthrust = 0, - lock_strafe = 3, - movement_speed = 0, - unk8 = Some(0) - ) - ) - } - - /** - * This function is applied to vehicles that are leaving a cargo vehicle's cargo hold to strafe left out of the cargo hold for vehicles that are mounted sideways e.g. router/BFR - * Lock all applicable controls of the current vehicle - * Set the vehicle to strafe left - */ - def ServerVehicleLockStrafeLeft(): Unit = { - serverVehicleControlVelocity = Some(-1) - sendResponse( - ServerVehicleOverrideMsg( - lock_accelerator = true, - lock_wheel = true, - reverse = false, - unk4 = true, - lock_vthrust = 0, - lock_strafe = 2, - movement_speed = 0, - unk8 = Some(0) - ) - ) - } - - /** - * Lock all applicable controls of the current vehicle. - * This includes forward motion, turning, and, if applicable, strafing. - * @param vehicle the vehicle being controlled - */ - def ServerVehicleLock(vehicle: Vehicle): Unit = { - serverVehicleControlVelocity = Some(-1) - sendResponse(ServerVehicleOverrideMsg(lock_accelerator=true, lock_wheel=true, reverse=false, unk4=false, 0, 1, 0, Some(0))) + def ServerVehicleOverrideStop(vehicle: Vehicle): Unit = { + val vehicleGuid = vehicle.GUID + session = session.copy(avatar = avatar.copy(vehicle = Some(vehicleGuid))) + sessionData.vehicles.DriverVehicleControl(vehicle, vehicle.Definition.AutoPilotSpeed2) + sendResponse(PlanetsideAttributeMessage(vehicleGuid, attribute_type=22, attribute_value=0L)) //mount points on } /** * Place the current vehicle under the control of the server's commands. - * @param vehicle the vehicle - * @param speed how fast the vehicle is moving forward - * @param flight whether the vehicle is ascending or not, if the vehicle is an applicable type + * @param vehicle vehicle to be controlled; + * the client's player who is receiving this packet should be mounted as its driver, but this is not explicitly tested + * @param pkt packet to instigate server control */ - def ServerVehicleOverride(vehicle: Vehicle, speed: Int = 0, flight: Int = 0): Unit = { - serverVehicleControlVelocity = Some(speed) - sendResponse(ServerVehicleOverrideMsg(lock_accelerator=true, lock_wheel=true, reverse=false, unk4=false, flight, 0, speed, Some(0))) + def ServerVehicleOverrideWithPacket(vehicle: Vehicle, pkt: ServerVehicleOverrideMsg): Unit = { + serverVehicleControlVelocity = Some(pkt.movement_speed) + vehicle.DeploymentState = DriveState.AutoPilot + sendResponse(pkt) } /** @@ -605,9 +562,20 @@ class VehicleOperations( * @param flight whether the vehicle is ascending or not, if the vehicle is an applicable type */ def DriverVehicleControl(vehicle: Vehicle, speed: Int = 0, flight: Int = 0): Unit = { - if (serverVehicleControlVelocity.nonEmpty) { - serverVehicleControlVelocity = None - sendResponse(ServerVehicleOverrideMsg(lock_accelerator=false, lock_wheel=false, reverse=false, unk4=true, flight, 0, speed, None)) + if (vehicle.DeploymentState == DriveState.AutoPilot) { + TotalDriverVehicleControlWithPacket( + vehicle, + ServerVehicleOverrideMsg( + lock_accelerator=false, + lock_wheel=false, + reverse=false, + unk4=true, + lock_vthrust=flight, + lock_strafe=0, + movement_speed=speed, + unk8=None + ) + ) } } @@ -618,15 +586,45 @@ class VehicleOperations( * @param vehicle the vehicle */ def ConditionalDriverVehicleControl(vehicle: Vehicle): Unit = { - if (serverVehicleControlVelocity.nonEmpty && !serverVehicleControlVelocity.contains(0)) { + if (vehicle.DeploymentState == DriveState.AutoPilot) { TotalDriverVehicleControl(vehicle) } } - //noinspection ScalaUnusedSymbol + /** + * Place the current vehicle under the control of the driver's commands, + * but leave it in a cancellable auto-drive. + * Stop all movement entirely. + * @param vehicle the vehicle + */ def TotalDriverVehicleControl(vehicle: Vehicle): Unit = { + TotalDriverVehicleControlWithPacket( + vehicle, + ServerVehicleOverrideMsg( + lock_accelerator=false, + lock_wheel=false, + reverse=false, + unk4=false, + lock_vthrust=0, + lock_strafe=0, + movement_speed=0, + unk8=None + ) + ) + } + + /** + * Place the current vehicle under the control of the driver's commands, + * but leave it in a cancellable auto-drive. + * Stop all movement entirely. + * @param vehicle the vehicle; + * the client's player who is receiving this packet should be mounted as its driver, but this is not explicitly tested + * @param pkt packet to instigate cancellable control + */ + def TotalDriverVehicleControlWithPacket(vehicle: Vehicle, pkt: ServerVehicleOverrideMsg): Unit = { serverVehicleControlVelocity = None - sendResponse(ServerVehicleOverrideMsg(lock_accelerator=false, lock_wheel=false, reverse=false, unk4=false, 0, 0, 0, None)) + vehicle.DeploymentState = DriveState.Mobile + sendResponse(pkt) } /** diff --git a/src/main/scala/net/psforever/objects/Deployables.scala b/src/main/scala/net/psforever/objects/Deployables.scala index 596a96fb0..085eaef4b 100644 --- a/src/main/scala/net/psforever/objects/Deployables.scala +++ b/src/main/scala/net/psforever/objects/Deployables.scala @@ -82,19 +82,16 @@ object Deployables { val zone = target.Zone val events = zone.LocalEvents val item = target.Definition.Item - target.OwnerName match { - case Some(owner) => - zone.Players.find { p => owner.equals(p.name) } match { - case Some(p) => - if (p.deployables.Remove(target)) { - events ! LocalServiceMessage(owner, LocalAction.DeployableUIFor(item)) - } - case None => ; - } + target.OwnerName + .foreach { owner => + zone.LivePlayers.find { p => owner.equals(p.Name) } + .orElse(zone.Corpses.find { c => owner.equals(c.Name) }) + .foreach { p => + p.Actor ! Player.LoseDeployable(target) + } target.Owner = None target.OwnerName = None - case None => ; - } + } events ! LocalServiceMessage( s"${target.Faction}", LocalAction.DeployableMapIcon( @@ -114,6 +111,7 @@ object Deployables { * @return all previously-owned deployables after they have been processed; * boomers are listed before all other deployable types */ + //noinspection ScalaUnusedSymbol def Disown(zone: Zone, avatar: Avatar, replyTo: ActorRef): List[Deployable] = { avatar.deployables .Clear() diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index e1187a843..b100597ae 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -5411,6 +5411,7 @@ object GlobalDefinitions { dynomite.Tile = InventoryTile.Tile22 trhev_dualcycler.Name = "trhev_dualcycler" + trhev_dualcycler.Descriptor = "trhev_antipersonnel" trhev_dualcycler.Size = EquipmentSize.Max trhev_dualcycler.AmmoTypes += dualcycler_ammo trhev_dualcycler.ProjectileTypes += dualcycler_projectile @@ -5428,6 +5429,7 @@ object GlobalDefinitions { trhev_dualcycler.FireModes(2).Magazine = 200 trhev_pounder.Name = "trhev_pounder" + trhev_pounder.Descriptor = "trhev_antivehicular" trhev_pounder.Size = EquipmentSize.Max trhev_pounder.AmmoTypes += pounder_ammo trhev_pounder.ProjectileTypes += pounder_projectile @@ -5461,6 +5463,7 @@ object GlobalDefinitions { trhev_pounder.FireModes(5).Magazine = 30 trhev_burster.Name = "trhev_burster" + trhev_burster.Descriptor = "trhev_antiaircraft" trhev_burster.Size = EquipmentSize.Max trhev_burster.AmmoTypes += burster_ammo trhev_burster.ProjectileTypes += burster_projectile @@ -5470,6 +5473,7 @@ object GlobalDefinitions { trhev_burster.FireModes.head.Magazine = 40 nchev_scattercannon.Name = "nchev_scattercannon" + nchev_scattercannon.Descriptor = "nchev_antipersonnel" nchev_scattercannon.Size = EquipmentSize.Max nchev_scattercannon.AmmoTypes += scattercannon_ammo nchev_scattercannon.ProjectileTypes += scattercannon_projectile @@ -5490,6 +5494,7 @@ object GlobalDefinitions { nchev_scattercannon.FireModes(2).Chamber = 10 //40 shells * 10 pellets = 400 nchev_falcon.Name = "nchev_falcon" + nchev_falcon.Descriptor = "nchev_antivehicular" nchev_falcon.Size = EquipmentSize.Max nchev_falcon.AmmoTypes += falcon_ammo nchev_falcon.ProjectileTypes += falcon_projectile @@ -5499,6 +5504,7 @@ object GlobalDefinitions { nchev_falcon.FireModes.head.Magazine = 20 nchev_sparrow.Name = "nchev_sparrow" + nchev_sparrow.Descriptor = "nchev_antiaircraft" nchev_sparrow.Size = EquipmentSize.Max nchev_sparrow.AmmoTypes += sparrow_ammo nchev_sparrow.ProjectileTypes += sparrow_projectile @@ -5508,6 +5514,7 @@ object GlobalDefinitions { nchev_sparrow.FireModes.head.Magazine = 12 vshev_quasar.Name = "vshev_quasar" + vshev_quasar.Descriptor = "vshev_antipersonnel" vshev_quasar.Size = EquipmentSize.Max vshev_quasar.AmmoTypes += quasar_ammo vshev_quasar.ProjectileTypes += quasar_projectile @@ -5523,6 +5530,7 @@ object GlobalDefinitions { vshev_quasar.FireModes(1).Magazine = 120 vshev_comet.Name = "vshev_comet" + vshev_comet.Descriptor = "vshev_antivehicular" vshev_comet.Size = EquipmentSize.Max vshev_comet.AmmoTypes += comet_ammo vshev_comet.ProjectileTypes += comet_projectile @@ -5532,6 +5540,7 @@ object GlobalDefinitions { vshev_comet.FireModes.head.Magazine = 10 vshev_starfire.Name = "vshev_starfire" + vshev_starfire.Descriptor = "vshev_antiaircraft" vshev_starfire.Size = EquipmentSize.Max vshev_starfire.AmmoTypes += starfire_ammo vshev_starfire.ProjectileTypes += starfire_projectile diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index c8d32b20a..3bc220335 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -36,7 +36,7 @@ import net.psforever.objects.sourcing.PlayerSource import net.psforever.objects.vital.collision.CollisionReason import net.psforever.objects.vital.environment.EnvironmentReason import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason} -import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} +import net.psforever.objects.vital.interaction.{Adversarial, DamageInteraction, DamageResult} import net.psforever.services.hart.ShuttleState import net.psforever.packet.PlanetSideGamePacket @@ -368,8 +368,6 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) => log.info(s"${player.Name} wants to change equipment loadout to their option #${msg.unk1 + 1}") - val fallbackSubtype = 0 - val fallbackSuit = ExoSuitType.Standard val originalSuit = player.ExoSuit val originalSubtype = Loadout.DetermineSubtype(player) //sanitize exo-suit for change @@ -392,29 +390,39 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm }) ++ dropHolsters ++ dropInventory //a loadout with a prohibited exo-suit type will result in the fallback exo-suit type //imposed 5min delay on mechanized exo-suit switches - val (nextSuit, nextSubtype) = - if ( - Players.CertificationToUseExoSuit(player, exosuit, subtype) && - (if (exosuit == ExoSuitType.MAX) { - player.ResistArmMotion(PlayerControl.maxRestriction) - val weapon = GlobalDefinitions.MAXArms(subtype, player.Faction) - player.avatar.purchaseCooldown(weapon) match { - case Some(_) => false - case None => + val (nextSuit, nextSubtype) = { + lazy val fallbackSuit = if (Players.CertificationToUseExoSuit(player, originalSuit, originalSubtype)) { + //TODO will we ever need to check for the cooldown status of an original non-MAX exo-suit? + (originalSuit, originalSubtype) + } else { + (ExoSuitType.Standard, 0) + } + if (Players.CertificationToUseExoSuit(player, exosuit, subtype)) { + if (exosuit == ExoSuitType.MAX) { + val weapon = GlobalDefinitions.MAXArms(subtype, player.Faction) + val cooldown = player.avatar.purchaseCooldown(weapon) + if (originalSubtype == subtype) { + (exosuit, subtype) //same MAX subtype is free + } else if (cooldown.nonEmpty) { + fallbackSuit //different MAX subtype can not have cooldown + } else { avatarActor ! AvatarActor.UpdatePurchaseTime(weapon) - true + (exosuit, subtype) //switching for first time causes cooldown + } + } else { + (exosuit, subtype) } } else { - player.ResistArmMotion(Player.neverRestrict) - true - }) - ) { - (exosuit, subtype) + log.warn( + s"${player.Name} no longer has permission to wear the exo-suit type $exosuit; will wear ${fallbackSuit._1} instead" + ) + fallbackSuit + } + } + if (nextSuit == ExoSuitType.MAX) { + player.ResistArmMotion(PlayerControl.maxRestriction) } else { - log.warn( - s"${player.Name} no longer has permission to wear the exo-suit type $exosuit; will wear $fallbackSuit instead" - ) - (fallbackSuit, fallbackSubtype) + player.ResistArmMotion(Player.neverRestrict) } //sanitize (incoming) inventory //TODO equipment permissions; these loops may be expanded upon in future @@ -802,7 +810,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm //choose if (target.Health > 0) { //alive - if (target.Health <= 25 && target.Health + damageToHealth > 25 && + if (target.Health <= 25 && (player.avatar.implants.flatten.find { _.definition.implantType == ImplantType.SecondWind } match { case Some(wind) => wind.initialized case _ => false @@ -810,8 +818,8 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm //activate second wind player.Health += 25 player.LogActivity(HealFromImplant(ImplantType.SecondWind, 25)) - avatarActor ! AvatarActor.ResetImplant(ImplantType.SecondWind) avatarActor ! AvatarActor.RestoreStamina(25) + avatarActor ! AvatarActor.ResetImplant(ImplantType.SecondWind) } //take damage/update DamageAwareness(target, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor) @@ -1025,21 +1033,23 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm ) //TODO other methods of death? val pentry = PlayerSource(target) - (cause.adversarial match { - case out @ Some(_) => - out - case _ => + cause + .adversarial + .collect { case out @ Adversarial(attacker, _, _) if attacker != PlayerSource.Nobody => out } + .orElse { target.LastDamage.collect { case attack if System.currentTimeMillis() - attack.interaction.hitTime < (10 seconds).toMillis => - attack.adversarial + attack + .adversarial + .collect { case out @ Adversarial(attacker, _, _) if attacker != PlayerSource.Nobody => out } }.flatten - }) match { + } match { case Some(adversarial) => events ! AvatarServiceMessage( zoneChannel, AvatarAction.DestroyDisplay(adversarial.attacker, pentry, adversarial.implement) ) - case None => + case _ => events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(pentry, pentry, 0)) } } @@ -1288,6 +1298,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm suicide() } + //noinspection ScalaUnusedSymbol def doInteractingWithGantryField( obj: PlanetSideServerObject, body: PieceOfEnvironment, diff --git a/src/main/scala/net/psforever/objects/ce/TelepadLike.scala b/src/main/scala/net/psforever/objects/ce/TelepadLike.scala index 362a2985a..ad018f418 100644 --- a/src/main/scala/net/psforever/objects/ce/TelepadLike.scala +++ b/src/main/scala/net/psforever/objects/ce/TelepadLike.scala @@ -1,7 +1,7 @@ // Copyright (c) 2018 PSForever package net.psforever.objects.ce -import akka.actor.{ActorContext, Cancellable} +import akka.actor.ActorContext import net.psforever.objects.{Default, TelepadDeployable, Vehicle} import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.structures.Amenity @@ -147,6 +147,12 @@ object TelepadLike { LocalAction.SendResponse(GenericObjectActionMessage(telepadGUID, 28)) ) } + + def InitializeTelepadDeployable(zone: Zone, internal: InternalTelepad, pad: TelepadDeployable): Unit = { + internal.Telepad = pad.GUID + TelepadLike.StartRouterInternalTelepad(zone, internal.Owner.GUID, internal) + pad.Actor ! TelepadLike.Activate(internal) + } } /** @@ -157,8 +163,6 @@ object TelepadLike { * @param obj an entity that extends `TelepadLike` */ class TelepadControl(obj: InternalTelepad) extends akka.actor.Actor { - var setup: Cancellable = Default.Cancellable - def receive: akka.actor.Actor.Receive = { case TelepadLike.Activate(o: InternalTelepad) if obj eq o => obj.Active = true @@ -166,11 +170,9 @@ class TelepadControl(obj: InternalTelepad) extends akka.actor.Actor { case TelepadLike.Deactivate(o: InternalTelepad) if obj eq o => obj.Active = false val zone = obj.Zone - zone.GUID(obj.Telepad) match { - case Some(oldTpad: TelepadDeployable) - if !obj.Active && !setup.isCancelled => + zone.GUID(obj.Telepad).collect { + case oldTpad: TelepadDeployable if oldTpad.Active => oldTpad.Actor ! TelepadLike.SeverLink(obj) - case _ => ; } obj.Telepad = None zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SendResponse(ObjectDeleteMessage(obj.GUID, 0))) @@ -178,15 +180,20 @@ class TelepadControl(obj: InternalTelepad) extends akka.actor.Actor { case TelepadLike.RequestLink(tpad: TelepadDeployable) => val zone = obj.Zone if (obj.Active) { - zone.GUID(obj.Telepad) match { - case Some(oldTpad: TelepadDeployable) if !obj.Active && !setup.isCancelled => - oldTpad.Actor ! TelepadLike.SeverLink(obj) - case _ => ; - } - obj.Telepad = tpad.GUID - //zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.StartRouterInternalTelepad(obj.Owner.GUID, obj.GUID, obj)) - TelepadLike.StartRouterInternalTelepad(zone, obj.Owner.GUID, obj) - tpad.Actor ! TelepadLike.Activate(obj) + zone + .GUID(obj.Telepad) + .collect { + case oldTpad: TelepadDeployable if oldTpad eq tpad => + Some(oldTpad) + case oldTpad: TelepadDeployable if oldTpad.Active => + oldTpad.Actor ! TelepadLike.SeverLink(obj) + TelepadLike.InitializeTelepadDeployable(zone, obj, tpad) + Some(oldTpad) + } + .orElse { + TelepadLike.InitializeTelepadDeployable(zone, obj, tpad) + None + } } else { val channel = obj.Owner.asInstanceOf[Vehicle].OwnerName.getOrElse("") zone.LocalEvents ! LocalServiceMessage(channel, LocalAction.RouterTelepadMessage("@Teleport_NotDeployed")) @@ -200,6 +207,6 @@ class TelepadControl(obj: InternalTelepad) extends akka.actor.Actor { zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SendResponse(ObjectDeleteMessage(obj.GUID, 0))) } - case _ => ; + case _ => () } } diff --git a/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala index 3a3665bc8..2e76d7314 100644 --- a/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala +++ b/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala @@ -36,7 +36,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() { health, unk4 = false, no_mount_points = false, - obj.DeploymentState, + SterilizedDeploymentState(obj), unk5 = false, unk6 = false, obj.Cloaked, @@ -105,6 +105,13 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() { .toList } + private def SterilizedDeploymentState(obj: Vehicle): DriveState.Value = { + obj.DeploymentState match { + case state if state.id < 0 => DriveState.Mobile + case state => state + } + } + protected def SpecificFormatModifier: VehicleFormat.Value = VehicleFormat.Normal protected def SpecificFormatData(obj: Vehicle): Option[SpecificVehicleData] = None diff --git a/src/main/scala/net/psforever/objects/sourcing/AmenitySource.scala b/src/main/scala/net/psforever/objects/sourcing/AmenitySource.scala index 32b1b97ea..56e9b600f 100644 --- a/src/main/scala/net/psforever/objects/sourcing/AmenitySource.scala +++ b/src/main/scala/net/psforever/objects/sourcing/AmenitySource.scala @@ -59,7 +59,9 @@ object AmenitySource { ) amenity.copy(occupants = obj match { case o: Mountable => - o.Seats.values.flatMap { _.occupants }.map { p => PlayerSource.inSeat(p, o, amenity) }.toList + o.Seats + .collect { case (num, seat) if seat.isOccupied => (num, seat.occupants.head) } + .map { case (num, p) => PlayerSource.inSeat(p, amenity, num) }.toList case _ => Nil }) diff --git a/src/main/scala/net/psforever/objects/sourcing/PlayerSource.scala b/src/main/scala/net/psforever/objects/sourcing/PlayerSource.scala index 9c6754cbd..1e2874201 100644 --- a/src/main/scala/net/psforever/objects/sourcing/PlayerSource.scala +++ b/src/main/scala/net/psforever/objects/sourcing/PlayerSource.scala @@ -109,17 +109,17 @@ object PlayerSource { * even if this function is entirely for the purpose of establishing that the player is an occupant of the mountable entity.
* Don't think too much about it. * @param player player - * @param mount mountable entity in which the player should be seated * @param source a `SourceEntry` for the aforementioned mountable entity + * @param seatNumber the attributed seating index in which the player is mounted in `source` * @return a `PlayerSource` entity */ - def inSeat(player: Player, mount: Mountable, source: SourceEntry): PlayerSource = { + def inSeat(player: Player, source: SourceEntry, seatNumber: Int): PlayerSource = { val exosuit = player.ExoSuit val faction = player.Faction PlayerSource( player.Definition, exosuit, - Some((source, mount.PassengerInSeat(player).get)), + Some((source, seatNumber)), player.Health, player.Armor, player.Position, diff --git a/src/main/scala/net/psforever/objects/sourcing/TurretSource.scala b/src/main/scala/net/psforever/objects/sourcing/TurretSource.scala index 783e655d8..a1df7df61 100644 --- a/src/main/scala/net/psforever/objects/sourcing/TurretSource.scala +++ b/src/main/scala/net/psforever/objects/sourcing/TurretSource.scala @@ -57,7 +57,9 @@ object TurretSource { ) turret.copy(occupants = obj match { case o: Mountable => - o.Seats.values.flatMap { _.occupants }.map { p => PlayerSource.inSeat(p, o, turret) }.toList + o.Seats + .collect { case (num, seat) if seat.isOccupied => (num, seat.occupants.head) } + .map { case (num, p) => PlayerSource.inSeat(p, turret, num) }.toList case _ => Nil }) diff --git a/src/main/scala/net/psforever/objects/sourcing/VehicleSource.scala b/src/main/scala/net/psforever/objects/sourcing/VehicleSource.scala index c9d84f51e..fc02bdf1c 100644 --- a/src/main/scala/net/psforever/objects/sourcing/VehicleSource.scala +++ b/src/main/scala/net/psforever/objects/sourcing/VehicleSource.scala @@ -53,9 +53,9 @@ object VehicleSource { ) ) vehicle.copy(occupants = { - obj.Seats.values.map { seat => + obj.Seats.map { case (seatNumber, seat) => seat.occupant match { - case Some(p) => PlayerSource.inSeat(p, obj, vehicle) //shallow + case Some(p) => PlayerSource.inSeat(p, vehicle, seatNumber) //shallow case _ => PlayerSource.Nobody } }.toList diff --git a/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala b/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala index 5a948cd93..d1655265c 100644 --- a/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala +++ b/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala @@ -50,7 +50,7 @@ trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior { def UpdateNtuUI(vehicle: Vehicle with NtuContainer): Unit = { if (vehicle.Seats.values.exists(_.isOccupied)) { - val display = scala.math.ceil(vehicle.NtuCapacitorScaled).toLong + val display = vehicle.NtuCapacitorScaled.toLong vehicle.Zone.VehicleEvents ! VehicleServiceMessage( vehicle.Actor.toString, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vehicle.GUID, 45, display) diff --git a/src/main/scala/net/psforever/objects/vehicles/control/AntControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/AntControl.scala index 32cc6f0c1..d1f4dc932 100644 --- a/src/main/scala/net/psforever/objects/vehicles/control/AntControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/control/AntControl.scala @@ -19,7 +19,7 @@ import scala.concurrent.duration._ class AntControl(vehicle: Vehicle) extends DeployingVehicleControl(vehicle) with AntTransferBehavior { - def ChargeTransferObject = vehicle + def ChargeTransferObject: Vehicle = vehicle override def commonEnabledBehavior: Receive = super.commonEnabledBehavior.orElse(antBehavior) @@ -38,7 +38,7 @@ class AntControl(vehicle: Vehicle) vehicle.Actor, TransferBehavior.Charging(Ntu.Nanites) ) - case _ => ; + case _ => () } } @@ -51,7 +51,7 @@ class AntControl(vehicle: Vehicle) state match { case DriveState.Undeploying => TryStopChargingEvent(vehicle) - case _ => ; + case _ => () } } } diff --git a/src/main/scala/net/psforever/objects/vehicles/control/DeployingVehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/DeployingVehicleControl.scala index 2a05d9ec7..445782021 100644 --- a/src/main/scala/net/psforever/objects/vehicles/control/DeployingVehicleControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/control/DeployingVehicleControl.scala @@ -20,7 +20,7 @@ import net.psforever.types._ class DeployingVehicleControl(vehicle: Vehicle) extends VehicleControl(vehicle) with DeploymentBehavior { - def DeploymentObject = vehicle + def DeploymentObject: Vehicle = vehicle override def commonEnabledBehavior : Receive = super.commonEnabledBehavior.orElse(deployBehavior) @@ -45,7 +45,7 @@ class DeployingVehicleControl(vehicle: Vehicle) override def commonDeleteBehavior : Receive = super.commonDeleteBehavior .orElse { - case msg : Deployment.TryUndeploy => + case msg: Deployment.TryUndeploy => deployBehavior.apply(msg) } @@ -53,7 +53,7 @@ class DeployingVehicleControl(vehicle: Vehicle) * Even when disabled, the vehicle can be made to undeploy. */ override def PrepareForDisabled(kickPassengers: Boolean) : Unit = { - vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying) + TryUndeployStateChange(DriveState.Undeploying) super.PrepareForDisabled(kickPassengers) } diff --git a/src/main/scala/net/psforever/objects/vehicles/control/RouterControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/RouterControl.scala index 010a44ee0..7c8cf0238 100644 --- a/src/main/scala/net/psforever/objects/vehicles/control/RouterControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/control/RouterControl.scala @@ -24,11 +24,10 @@ class RouterControl(vehicle: Vehicle) override def specificResponseToDeployment(state: DriveState.Value): Unit = { state match { case DriveState.Deployed => - vehicle.Utility(UtilityType.internal_router_telepad_deployable) match { - case Some(util: Utility.InternalTelepad) => util.Actor ! TelepadLike.Activate(util) - case _ => ; + vehicle.Utility(UtilityType.internal_router_telepad_deployable).collect { + case util: Utility.InternalTelepad => util.Actor ! TelepadLike.Activate(util) } - case _ => ; + case _ => () } } @@ -40,11 +39,10 @@ class RouterControl(vehicle: Vehicle) override def specificResponseToUndeployment(state: DriveState.Value): Unit = { state match { case DriveState.Undeploying => - vehicle.Utility(UtilityType.internal_router_telepad_deployable) match { - case Some(util: Utility.InternalTelepad) => util.Actor ! TelepadLike.Deactivate(util) - case _ => ; + vehicle.Utility(UtilityType.internal_router_telepad_deployable).collect { + case util: Utility.InternalTelepad => util.Actor ! TelepadLike.Deactivate(util) } - case _ => ; + case _ => () } } } diff --git a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala index 81970ff3d..d79b75f8a 100644 --- a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala @@ -13,6 +13,7 @@ import net.psforever.objects.inventory.{GridInventory, InventoryItem} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject, ServerObjectControl} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior} +import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.serverobject.damage.{AggravatedBehavior, DamageableVehicle} import net.psforever.objects.serverobject.environment._ import net.psforever.objects.serverobject.hackable.GenericHackables @@ -120,6 +121,9 @@ class VehicleControl(vehicle: Vehicle) case Vehicle.Ownership(Some(player)) => GainOwnership(player) + case Mountable.TryMount(user, mountPoint) if vehicle.DeploymentState == DriveState.AutoPilot => + sender() ! Mountable.MountMessages(user, Mountable.CanNotMount(vehicle, mountPoint)) + case msg @ Mountable.TryMount(player, mount_point) => mountBehavior.apply(msg) mountCleanup(mount_point, player) @@ -864,10 +868,15 @@ class VehicleControl(vehicle: Vehicle) ) } } + + override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = { + passengerRadiationCloudTimer.cancel() + super.DestructionAwareness(target, cause) + } } object VehicleControl { - import net.psforever.objects.vital.{ShieldCharge} + import net.psforever.objects.vital.ShieldCharge private case class PrepareForDeletion() diff --git a/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala b/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala index f06a83f52..161dece47 100644 --- a/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala +++ b/src/main/scala/net/psforever/packet/game/ServerVehicleOverrideMsg.scala @@ -31,7 +31,8 @@ import scodec.codecs._ * For flight vehicles, for `n`, the forward air speed for the value in this packet will be at least `1.18 * n`. * This approximation is not always going to be accurate but serves as a good rule of thumb. * @param lock_accelerator driver has no control over vehicle acceleration - * @param lock_wheel driver has no control over vehicle turning + * @param lock_wheel driver has no control over vehicle turning; + * generally, the driver never has turning control * @param reverse move in reverse * 0 = forward * 1 = reverse diff --git a/src/main/scala/net/psforever/types/DriveState.scala b/src/main/scala/net/psforever/types/DriveState.scala index 887d93a6e..be192c369 100644 --- a/src/main/scala/net/psforever/types/DriveState.scala +++ b/src/main/scala/net/psforever/types/DriveState.scala @@ -21,5 +21,7 @@ object DriveState extends Enumeration { val State7 = Value(7) //unknown; not encountered on a vehicle that can deploy; functions like Mobile val State127 = Value(127) //unknown - val Kneeling = Value(-1) //flag bfr kneeling state; should not not encode + //the following values should never be encoded + val Kneeling = Value(-1) //flag bfr kneeling state + val AutoPilot = Value(-2) //when emerging from spawn pad, or being kicked from a ferry, during server guidance } diff --git a/src/main/scala/net/psforever/util/Config.scala b/src/main/scala/net/psforever/util/Config.scala index 0e372c70f..b7097cbb4 100644 --- a/src/main/scala/net/psforever/util/Config.scala +++ b/src/main/scala/net/psforever/util/Config.scala @@ -154,6 +154,7 @@ case class GameConfig( newAvatar: NewAvatar, hart: HartConfig, sharedMaxCooldown: Boolean, + sharedBfrCooldown: Boolean, baseCertifications: Seq[Certification], warpGates: WarpGateConfig, cavernRotation: CavernRotationConfig, diff --git a/src/test/scala/objects/DamageableTest.scala b/src/test/scala/objects/DamageableTest.scala index 8a258f2d4..798b4beb2 100644 --- a/src/test/scala/objects/DamageableTest.scala +++ b/src/test/scala/objects/DamageableTest.scala @@ -32,7 +32,7 @@ import org.specs2.mutable.Specification import scala.concurrent.duration._ import net.psforever.objects.avatar.Avatar import net.psforever.objects.serverobject.terminals.implant.{ImplantTerminalMech, ImplantTerminalMechControl} -import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, VehicleSource} +import net.psforever.objects.sourcing.{PlayerSource, SourceEntry} import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.base.DamageResolution import net.psforever.objects.vital.projectile.ProjectileReason @@ -327,21 +327,17 @@ class DamageableEntityDamageTest extends ActorTest { gen.Actor ! Vitality.Damage(applyDamageTo) val msg1 = avatarProbe.receiveOne(500 milliseconds) val msg2 = activityProbe.receiveOne(500 milliseconds) - assert( - msg1 match { - case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true - case _ => false - } - ) - assert( - msg2 match { - case activity: Zone.HotSpot.Activity => - activity.attacker == PlayerSource(player1) && - activity.defender == SourceEntry(gen) && - activity.location == Vector3(1, 0, 0) - case _ => false - } - ) + msg1 match { + case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(ValidPlanetSideGUID(2), 0, 3600)) => () + case _ => assert(false, "DamageableEntity:handle taking damage - player not messaged") + } + msg2 match { + case activity: Zone.HotSpot.Activity + if activity.attacker == PlayerSource(player1) && + activity.defender == SourceEntry(gen) && + activity.location == Vector3(1, 0, 0) => () + case _ => assert(false, "DamageableEntity:handle taking damage - activity not messaged") + } } } } diff --git a/src/test/scala/objects/DeployableBehaviorTest.scala b/src/test/scala/objects/DeployableBehaviorTest.scala index f6ee808b0..7acbf5999 100644 --- a/src/test/scala/objects/DeployableBehaviorTest.scala +++ b/src/test/scala/objects/DeployableBehaviorTest.scala @@ -320,15 +320,12 @@ class DeployableBehaviorDeconstructOwnedTest extends FreedContextActorTest { jmine.Actor ! Deployable.Deconstruct() val eventsMsgs = eventsProbe.receiveN(3, 10.seconds) eventsMsgs.head match { - case LocalServiceMessage("test", LocalAction.EliminateDeployable(`jmine`, PlanetSideGUID(1), Vector3(1,2,3), 2)) => ; + case LocalServiceMessage("test",LocalAction.EliminateDeployable(mine, ValidPlanetSideGUID(1), Vector3(1.0,2.0,3.0),2)) + if mine eq jmine => ; case _ => assert(false, "owned deconstruct test - not eliminating deployable") } eventsMsgs(1) match { - case LocalServiceMessage("TestCharacter1", LocalAction.DeployableUIFor(DeployedItem.jammer_mine)) => ; - case _ => assert(false, "") - } - eventsMsgs(2) match { - case LocalServiceMessage( + case LocalServiceMessage( "TR", LocalAction.DeployableMapIcon( PlanetSideGUID(0), @@ -338,6 +335,10 @@ class DeployableBehaviorDeconstructOwnedTest extends FreedContextActorTest { ) => ; case _ => assert(false, "owned deconstruct test - not removing icon") } + eventsMsgs(2) match { + case LocalServiceMessage("TestCharacter1", LocalAction.DeployableUIFor(DeployedItem.jammer_mine)) => ; + case _ => assert(false, "") + } assert(deployableList.isEmpty, "owned deconstruct test - deployable still in list") assert(!avatar.deployables.Contains(jmine), "owned deconstruct test - avatar still owns deployable") diff --git a/src/test/scala/objects/DeployableTest.scala b/src/test/scala/objects/DeployableTest.scala index 71fd14187..3e918dd08 100644 --- a/src/test/scala/objects/DeployableTest.scala +++ b/src/test/scala/objects/DeployableTest.scala @@ -25,6 +25,7 @@ import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.projectile.ProjectileReason import akka.actor.typed.scaladsl.adapter._ import net.psforever.objects.sourcing.{PlayerSource, SourceEntry} +import net.psforever.services.local.LocalAction.DeployableMapIcon import scala.collection.mutable import scala.concurrent.duration._ @@ -447,9 +448,9 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest { assert(!h_mine.Destroyed) h_mine.Actor ! Vitality.Damage(applyDamageToH) - val eventMsgs = eventsProbe.receiveN(5, 200 milliseconds) + val eventMsgs = eventsProbe.receiveN(4, 200 milliseconds) val p1Msgs = player1Probe.receiveN(1, 200 milliseconds) - player2Probe.expectNoMessage(200 milliseconds) + val p2Msgs = player2Probe.receiveN(1, 200 milliseconds) eventMsgs.head match { case Zone.HotSpot.Conflict(target, attacker, _) if (target.Definition eq h_mine.Definition) && (attacker eq pSource) => ; @@ -461,10 +462,6 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest { case _ => assert(false, "") } eventMsgs(2) match { - case LocalServiceMessage("TestCharacter2", LocalAction.DeployableUIFor(DeployedItem.he_mine)) => ; - case _ => assert(false, "") - } - eventMsgs(3) match { case LocalServiceMessage( "NC", LocalAction.DeployableMapIcon( @@ -475,7 +472,7 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest { ) => ; case _ => assert(false, "") } - eventMsgs(4) match { + eventMsgs(3) match { case AvatarServiceMessage( "test", AvatarAction.Destroy(PlanetSideGUID(2), PlanetSideGUID(3), Service.defaultPlayerGUID, Vector3.Zero) @@ -486,7 +483,10 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest { case Vitality.Damage(_) => ; case _ => assert(false, "") } - assert(!avatar2.deployables.Contains(h_mine)) + p2Msgs.head match { + case Player.LoseDeployable(_) => ; + case _ => assert(false, "") + } assert(h_mine.Destroyed) } } @@ -558,14 +558,10 @@ class ExplosiveDeployableDestructionTest extends ActorTest { assert(!h_mine.Destroyed) h_mine.Actor ! Vitality.Damage(applyDamageTo) - val eventMsgs = eventsProbe.receiveN(4, 200 milliseconds) + val eventMsgs = eventsProbe.receiveN(3, 200 milliseconds) player1Probe.expectNoMessage(200 milliseconds) - player2Probe.expectNoMessage(200 milliseconds) + val p2Msgs = player2Probe.receiveN(1, 200 milliseconds) eventMsgs.head match { - case LocalServiceMessage("TestCharacter2", LocalAction.DeployableUIFor(DeployedItem.he_mine)) => ; - case _ => assert(false, "") - } - eventMsgs(1) match { case LocalServiceMessage( "NC", LocalAction.DeployableMapIcon( @@ -576,17 +572,21 @@ class ExplosiveDeployableDestructionTest extends ActorTest { ) => ; case _ => assert(false, "") } - eventMsgs(2) match { + eventMsgs(1) match { case AvatarServiceMessage( - "test", - AvatarAction.Destroy(PlanetSideGUID(2), PlanetSideGUID(3), Service.defaultPlayerGUID, Vector3.Zero) + "test", + AvatarAction.Destroy(PlanetSideGUID(2), PlanetSideGUID(3), Service.defaultPlayerGUID, Vector3.Zero) ) => ; case _ => assert(false, "") } - eventMsgs(3) match { + eventMsgs(2) match { case LocalServiceMessage("test", LocalAction.TriggerEffect(_, "detonate_damaged_mine", PlanetSideGUID(2))) => ; case _ => assert(false, "") } + p2Msgs.head match { + case Player.LoseDeployable(_) => ; + case _ => assert(false, "") + } assert(h_mine.Health <= h_mine.Definition.DamageDestroysAt) assert(h_mine.Destroyed) }