* RefreshLoadouts no longer queries loadoads from the database
* /gmtell no longer sends to self
* Set AMS spawn timer to 10 seconds
* Fix medkit cooldown display
* Fix wrong max type being purchased
* Fix implants not being locked when fatigued
* Fix implants progress bar
* Make tells case insensitive
This commit is contained in:
Jakob Gillich 2020-08-27 04:09:44 +02:00
parent 63dea5af05
commit e34f96ce18
10 changed files with 106 additions and 88 deletions

View file

@ -2,7 +2,7 @@
<img src="https://psforever.net/index_files/logo_crop.png" align="left" title="PSForever" width="120"> <img src="https://psforever.net/index_files/logo_crop.png" align="left" title="PSForever" width="120">
Welcome to the recreated login and world servers for PlanetSide 1. We are a awesome community of players and developers who took Welcome to the recreated login and world servers for PlanetSide 1. We are a community of players and developers who took
it upon ourselves to preserve PlanetSide 1's unique gameplay and history _forever_. it upon ourselves to preserve PlanetSide 1's unique gameplay and history _forever_.
The login and world servers (this repo runs both by default) are built to work with PlanetSide version 3.15.84.0. The login and world servers (this repo runs both by default) are built to work with PlanetSide version 3.15.84.0.

View file

@ -663,7 +663,13 @@ class AvatarActor(
case LoadoutType.Infantry => case LoadoutType.Infantry =>
storeLoadout(player, name, number).onComplete { storeLoadout(player, name, number).onComplete {
case Success(_) => case Success(_) =>
context.self ! RefreshLoadouts() loadLoadouts().onComplete {
case Success(loadouts) =>
avatar = avatar.copy(loadouts = loadouts)
context.self ! RefreshLoadouts()
case Failure(exception) => log.error(exception)("db failure")
}
case Failure(exception) => log.error(exception)("db failure") case Failure(exception) => log.error(exception)("db failure")
} }
@ -693,23 +699,18 @@ class AvatarActor(
Behaviors.same Behaviors.same
case RefreshLoadouts() => case RefreshLoadouts() =>
loadLoadouts().onComplete { avatar.loadouts.zipWithIndex.foreach {
case Success(loadouts) => case (Some(loadout: InfantryLoadout), index) =>
avatar = avatar.copy(loadouts = loadouts) sessionActor ! SessionActor.SendResponse(
loadouts.zipWithIndex.foreach { FavoritesMessage(
case (Some(loadout: InfantryLoadout), index) => LoadoutType.Infantry,
sessionActor ! SessionActor.SendResponse( session.get.player.GUID,
FavoritesMessage( index,
LoadoutType.Infantry, loadout.label,
session.get.player.GUID, InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype)
index, )
loadout.label, )
InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype) case _ => ;
)
)
case _ => ;
}
case Failure(exception) => log.error(exception)("db failure")
} }
Behaviors.same Behaviors.same
@ -772,7 +773,6 @@ class AvatarActor(
Behaviors.same Behaviors.same
case ActivateImplant(implantType) => case ActivateImplant(implantType) =>
log.info(s"ActivateImplant ${implantType}")
val res = avatar.implants.zipWithIndex.collectFirst { val res = avatar.implants.zipWithIndex.collectFirst {
case (Some(implant), index) if implant.definition.implantType == implantType => (implant, index) case (Some(implant), index) if implant.definition.implantType == implantType => (implant, index)
} }
@ -780,14 +780,14 @@ class AvatarActor(
case Some((implant, slot)) => case Some((implant, slot)) =>
if (!implant.initialized) { if (!implant.initialized) {
log.error(s"requested activation of uninitialized implant $implant") log.error(s"requested activation of uninitialized implant $implant")
} else if (!consumeStamina(implant.definition.ActivationStaminaCost)) { } else if (
sessionActor ! SessionActor.SendResponse( !consumeStamina(implant.definition.ActivationStaminaCost) ||
AvatarImplantMessage(session.get.player.GUID, ImplantAction.OutOfStamina, slot, 1) avatar.stamina < implant.definition.StaminaCost
) ) {
// not enough stamina to activate
} else if (implant.definition.implantType.disabledFor.contains(session.get.player.ExoSuit)) { } else if (implant.definition.implantType.disabledFor.contains(session.get.player.ExoSuit)) {
// TODO can this really happen? can we prevent it? // TODO can this really happen? can we prevent it?
} else { } else {
avatar = avatar.copy( avatar = avatar.copy(
implants = avatar.implants.updated(slot, Some(implant.copy(active = true))) implants = avatar.implants.updated(slot, Some(implant.copy(active = true)))
) )
@ -828,39 +828,15 @@ class AvatarActor(
Behaviors.same Behaviors.same
case DeactivateImplant(implantType) => case DeactivateImplant(implantType) =>
val res = avatar.implants.zipWithIndex.collectFirst { deactivateImplant(implantType)
case (Some(implant), index) if implant.definition.implantType == implantType => (implant, index)
}
res match {
case Some((implant, slot)) =>
implantTimers(slot).cancel()
avatar = avatar.copy(
implants = avatar.implants.updated(slot, Some(implant.copy(active = false)))
)
// Deactivation sound / effect
session.get.zone.AvatarEvents ! AvatarServiceMessage(
session.get.zone.id,
AvatarAction.PlanetsideAttribute(session.get.player.GUID, 28, implant.definition.implantType.value * 2)
)
sessionActor ! SessionActor.SendResponse(
AvatarImplantMessage(
session.get.player.GUID,
ImplantAction.Activation,
slot,
0
)
)
case None => log.error(s"requested deactivation of unknown implant $implantType")
}
Behaviors.same Behaviors.same
case DeactivateActiveImplants() => case DeactivateActiveImplants() =>
avatar.implants.indices.foreach { index => avatar.implants.indices.foreach { index =>
avatar.implants(index).foreach { implant => avatar.implants(index).foreach { implant =>
if (implant.active && implant.definition.GetCostIntervalByExoSuit(session.get.player.ExoSuit) > 0) if (implant.active && implant.definition.GetCostIntervalByExoSuit(session.get.player.ExoSuit) > 0) {
context.self ! DeactivateImplant(implant.definition.implantType) deactivateImplant(implant.definition.implantType)
}
} }
} }
Behaviors.same Behaviors.same
@ -870,13 +846,18 @@ class AvatarActor(
if (session.get.player.HasGUID) { if (session.get.player.HasGUID) {
val totalStamina = math.min(avatar.maxStamina, avatar.stamina + stamina) val totalStamina = math.min(avatar.maxStamina, avatar.stamina + stamina)
val fatigued = if (avatar.fatigued && totalStamina >= 20) { val fatigued = if (avatar.fatigued && totalStamina >= 20) {
context.self ! InitializeImplants(instant = true) avatar.implants.zipWithIndex.foreach {
case (Some(implant), slot) =>
sessionActor ! SessionActor.SendResponse(
AvatarImplantMessage(session.get.player.GUID, ImplantAction.OutOfStamina, slot, 0)
)
case _ => ()
}
false false
} else { } else {
avatar.fatigued avatar.fatigued
} }
avatar = avatar.copy(stamina = totalStamina, fatigued = fatigued) avatar = avatar.copy(stamina = totalStamina, fatigued = fatigued)
sessionActor ! SessionActor.SendResponse( sessionActor ! SessionActor.SendResponse(
PlanetsideAttributeMessage(session.get.player.GUID, 2, avatar.stamina) PlanetsideAttributeMessage(session.get.player.GUID, 2, avatar.stamina)
) )
@ -1027,6 +1008,19 @@ class AvatarActor(
} else { } else {
totalStamina == 0 totalStamina == 0
} }
if (!avatar.fatigued && fatigued) {
avatar.implants.zipWithIndex.foreach {
case (Some(implant), slot) =>
if (implant.active) {
deactivateImplant(implant.definition.implantType)
}
sessionActor ! SessionActor.SendResponse(
AvatarImplantMessage(session.get.player.GUID, ImplantAction.OutOfStamina, slot, 1)
)
case _ => ()
}
}
avatar = avatar.copy(stamina = totalStamina, fatigued = fatigued) avatar = avatar.copy(stamina = totalStamina, fatigued = fatigued)
sessionActor ! SessionActor.SendResponse(PlanetsideAttributeMessage(session.get.player.GUID, 2, avatar.stamina)) sessionActor ! SessionActor.SendResponse(PlanetsideAttributeMessage(session.get.player.GUID, 2, avatar.stamina))
consumed consumed
@ -1047,6 +1041,14 @@ class AvatarActor(
) )
) )
// Start client side initialization timer, visible on the character screen
// Progress accumulates according to the client's knowledge of the implant initialization time
// What is normally a 60s timer that is set to 120s on the server will still visually update as if 60s
session.get.zone.AvatarEvents ! AvatarServiceMessage(
avatar.name,
AvatarAction.SendResponse(Service.defaultPlayerGUID, ActionProgressMessage(slot + 6, 0))
)
implantTimers.get(slot).foreach(_.cancel()) implantTimers.get(slot).foreach(_.cancel())
implantTimers(slot) = context.system.scheduler.scheduleOnce( implantTimers(slot) = context.system.scheduler.scheduleOnce(
if (instant) 0.seconds else implant.definition.InitializationDuration.seconds, if (instant) 0.seconds else implant.definition.InitializationDuration.seconds,
@ -1084,6 +1086,35 @@ class AvatarActor(
}) })
} }
def deactivateImplant(implantType: ImplantType): Unit = {
val res = avatar.implants.zipWithIndex.collectFirst {
case (Some(implant), index) if implant.definition.implantType == implantType => (implant, index)
}
res match {
case Some((implant, slot)) =>
implantTimers(slot).cancel()
avatar = avatar.copy(
implants = avatar.implants.updated(slot, Some(implant.copy(active = false)))
)
// Deactivation sound / effect
session.get.zone.AvatarEvents ! AvatarServiceMessage(
session.get.zone.id,
AvatarAction.PlanetsideAttribute(session.get.player.GUID, 28, implant.definition.implantType.value * 2)
)
sessionActor ! SessionActor.SendResponse(
AvatarImplantMessage(
session.get.player.GUID,
ImplantAction.Activation,
slot,
0
)
)
case None => log.error(s"requested deactivation of unknown implant $implantType")
}
}
/** Send list of avatars to client (show character selection screen) */ /** Send list of avatars to client (show character selection screen) */
def sendAvatars(account: Account): Unit = { def sendAvatars(account: Account): Unit = {
import ctx._ import ctx._

View file

@ -488,7 +488,7 @@ class ChatActor(
case (CMT_GMTELL, _, _) if gmCommandAllowed => case (CMT_GMTELL, _, _) if gmCommandAllowed =>
chatService ! ChatService.Message( chatService ! ChatService.Message(
session, session,
message.copy(recipient = session.player.Name), message,
ChatChannel.Default() ChatChannel.Default()
) )

View file

@ -90,18 +90,6 @@ import akka.util.Timeout
import scala.collection.mutable import scala.collection.mutable
object SessionActor { object SessionActor {
/** Object use cooldowns.<br>
* key - object id<br>
* value - time last used (ms)
*/
val delayedGratificationEntries: Map[Int, Long] = Map(
GlobalDefinitions.medkit.ObjectId -> 5000, //5s
GlobalDefinitions.super_armorkit.ObjectId -> 1200000, //20min
GlobalDefinitions.super_medkit.ObjectId -> 1200000, //20min
GlobalDefinitions.super_staminakit.ObjectId -> 1200000 //20min
)
sealed trait Command sealed trait Command
final case class ResponseToSelf(pkt: PlanetSideGamePacket) final case class ResponseToSelf(pkt: PlanetSideGamePacket)

View file

@ -10,7 +10,6 @@ import org.json4s._
import org.json4s.native.Serialization.write import org.json4s.native.Serialization.write
import scodec.bits._ import scodec.bits._
import scodec.interop.akka._ import scodec.interop.akka._
import net.psforever.services.ServiceManager.Lookup
import net.psforever.services._ import net.psforever.services._
import scala.collection.mutable.Map import scala.collection.mutable.Map
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._

View file

@ -6528,7 +6528,7 @@ object GlobalDefinitions {
*/ */
private def initMiscellaneous(): Unit = { private def initMiscellaneous(): Unit = {
ams_respawn_tube.Name = "ams_respawn_tube" ams_respawn_tube.Name = "ams_respawn_tube"
ams_respawn_tube.Delay = 5 ams_respawn_tube.Delay = 10
ams_respawn_tube.SpecificPointFunc = SpawnPoint.AMS ams_respawn_tube.SpecificPointFunc = SpawnPoint.AMS
ams_respawn_tube.Damageable = false ams_respawn_tube.Damageable = false
ams_respawn_tube.Repairable = false ams_respawn_tube.Repairable = false

View file

@ -110,10 +110,9 @@ case class Avatar(
times.get(definition.Name) match { times.get(definition.Name) match {
case Some(purchaseTime) => case Some(purchaseTime) =>
val secondsSincePurchase = Seconds.secondsBetween(purchaseTime, LocalDateTime.now()) val secondsSincePurchase = Seconds.secondsBetween(purchaseTime, LocalDateTime.now())
val duration = secondsSincePurchase.toStandardDuration
cooldowns.get(definition) match { cooldowns.get(definition) match {
case Some(cooldown) if (cooldown.toSeconds - secondsSincePurchase.getSeconds) > 0 => case Some(cooldown) if (cooldown.toSeconds - secondsSincePurchase.getSeconds) > 0 =>
Some(duration) Some(Seconds.seconds((cooldown.toSeconds - secondsSincePurchase.getSeconds).toInt).toStandardDuration)
case _ => None case _ => None
} }
case None => case None =>

View file

@ -358,12 +358,13 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
val originalArmor = player.Armor val originalArmor = player.Armor
player.ExoSuit = nextSuit player.ExoSuit = nextSuit
val toMaxArmor = player.MaxArmor val toMaxArmor = player.MaxArmor
val toArmor = if (originalSuit != nextSuit || originalSubtype != nextSubtype || originalArmor > toMaxArmor) { val toArmor =
player.History(HealFromExoSuitChange(PlayerSource(player), nextSuit)) if (originalSuit != nextSuit || originalSubtype != nextSubtype || originalArmor > toMaxArmor) {
player.Armor = toMaxArmor player.History(HealFromExoSuitChange(PlayerSource(player), nextSuit))
} else { player.Armor = toMaxArmor
player.Armor = originalArmor } else {
} player.Armor = originalArmor
}
//ensure arm is down, even if it needs to go back up //ensure arm is down, even if it needs to go back up
if (player.DrawnSlot != Player.HandsDownSlot) { if (player.DrawnSlot != Player.HandsDownSlot) {
player.DrawnSlot = Player.HandsDownSlot player.DrawnSlot = Player.HandsDownSlot

View file

@ -31,15 +31,15 @@ object EquipmentTerminalDefinition {
* value - a `Tuple` containing exo-suit specifications * value - a `Tuple` containing exo-suit specifications
*/ */
val maxSuits: Map[String, (ExoSuitType.Value, Int)] = Map( val maxSuits: Map[String, (ExoSuitType.Value, Int)] = Map(
"trhev_antiaircraft" -> (ExoSuitType.MAX, 3), "trhev_antiaircraft" -> (ExoSuitType.MAX, 1),
"trhev_antipersonnel" -> (ExoSuitType.MAX, 1), "trhev_antipersonnel" -> (ExoSuitType.MAX, 2),
"trhev_antivehicular" -> (ExoSuitType.MAX, 2), "trhev_antivehicular" -> (ExoSuitType.MAX, 3),
"nchev_antiaircraft" -> (ExoSuitType.MAX, 3), "nchev_antiaircraft" -> (ExoSuitType.MAX, 1),
"nchev_antipersonnel" -> (ExoSuitType.MAX, 1), "nchev_antipersonnel" -> (ExoSuitType.MAX, 2),
"nchev_antivehicular" -> (ExoSuitType.MAX, 2), "nchev_antivehicular" -> (ExoSuitType.MAX, 3),
"vshev_antiaircraft" -> (ExoSuitType.MAX, 3), "vshev_antiaircraft" -> (ExoSuitType.MAX, 1),
"vshev_antipersonnel" -> (ExoSuitType.MAX, 1), "vshev_antipersonnel" -> (ExoSuitType.MAX, 2),
"vshev_antivehicular" -> (ExoSuitType.MAX, 2) "vshev_antivehicular" -> (ExoSuitType.MAX, 3)
) )
import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.GlobalDefinitions._

View file

@ -33,7 +33,7 @@ object ImplantAction extends Enumeration {
* `Initialization` - 0 to revoke slot; 1 to allocate implant slot<br> * `Initialization` - 0 to revoke slot; 1 to allocate implant slot<br>
* `Activation` - 0 to deactivate implant; 1 to activate implant<br> * `Activation` - 0 to deactivate implant; 1 to activate implant<br>
* `UnlockMessage` - 0-3 as an unlocked implant slot; display a message<br> * `UnlockMessage` - 0-3 as an unlocked implant slot; display a message<br>
* `OutOfStamina` - lock implant; 0 to lock; 1 to unlock; display a message * `OutOfStamina` - lock implant; 1 to lock; 0 to unlock; display a message
*/ */
final case class AvatarImplantMessage( final case class AvatarImplantMessage(
player_guid: PlanetSideGUID, player_guid: PlanetSideGUID,