mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
Fixes
* 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:
parent
63dea5af05
commit
e34f96ce18
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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._
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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._
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 =>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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._
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue