mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-04-29 16:25:30 +00:00
outfit checkpoint
This commit is contained in:
parent
57b3fd69ab
commit
402259e338
14 changed files with 541 additions and 13 deletions
|
|
@ -12,7 +12,7 @@ import net.psforever.objects.serverobject.mount.Mountable
|
||||||
import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal}
|
import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal}
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
import net.psforever.packet.PlanetSideGamePacket
|
import net.psforever.packet.PlanetSideGamePacket
|
||||||
import net.psforever.packet.game.{AIDamage, ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarGrenadeStateMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BeginZoningMessage, BindPlayerMessage, BugReportMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, ChatMsg, ChildObjectStateMessage, ConnectToWorldRequestMessage, CreateShortcutMessage, DeployObjectMessage, DeployRequestMessage, DismountVehicleCargoMsg, DismountVehicleMsg, DisplayedAwardMessage, DropItemMessage, DroppodLaunchRequestMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FavoritesRequest, FrameVehicleStateMessage, FriendsRequest, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, HitMessage, InvalidTerrainMessage, ItemTransactionMessage, LashMessage, LongRangeProjectileInfoMessage, LootItemMessage, MountVehicleCargoMsg, MountVehicleMsg, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, OutfitRequest, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, ProjectileStateMessage, ProximityTerminalUseMessage, ReleaseAvatarRequestMessage, ReloadMessage, RequestDestroyMessage, SetChatFilterMessage, SpawnRequestMessage, SplashHitMessage, SquadDefinitionActionMessage, SquadMembershipRequest, SquadWaypointRequest, TargetingImplantRequest, TradeMessage, UnuseItemMessage, UplinkRequest, UseItemMessage, VehicleStateMessage, VehicleSubStateMessage, VoiceHostInfo, VoiceHostRequest, WarpgateRequest, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage, ZipLineMessage}
|
import net.psforever.packet.game.{AIDamage, ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarGrenadeStateMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BeginZoningMessage, BindPlayerMessage, BugReportMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, ChatMsg, ChildObjectStateMessage, ConnectToWorldRequestMessage, CreateShortcutMessage, DeployObjectMessage, DeployRequestMessage, DismountVehicleCargoMsg, DismountVehicleMsg, DisplayedAwardMessage, DropItemMessage, DroppodLaunchRequestMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FavoritesRequest, FrameVehicleStateMessage, FriendsRequest, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, HitMessage, InvalidTerrainMessage, ItemTransactionMessage, LashMessage, LongRangeProjectileInfoMessage, LootItemMessage, MountVehicleCargoMsg, MountVehicleMsg, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, OutfitMembershipRequest, OutfitMembershipResponse, OutfitRequest, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, ProjectileStateMessage, ProximityTerminalUseMessage, ReleaseAvatarRequestMessage, ReloadMessage, RequestDestroyMessage, SetChatFilterMessage, SpawnRequestMessage, SplashHitMessage, SquadDefinitionActionMessage, SquadMembershipRequest, SquadWaypointRequest, TargetingImplantRequest, TradeMessage, UnuseItemMessage, UplinkRequest, UseItemMessage, VehicleStateMessage, VehicleSubStateMessage, VoiceHostInfo, VoiceHostRequest, WarpgateRequest, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage, ZipLineMessage}
|
||||||
import net.psforever.services.{InterstellarClusterService => ICS}
|
import net.psforever.services.{InterstellarClusterService => ICS}
|
||||||
import net.psforever.services.CavernRotationService
|
import net.psforever.services.CavernRotationService
|
||||||
import net.psforever.services.CavernRotationService.SendCavernRotationUpdates
|
import net.psforever.services.CavernRotationService.SendCavernRotationUpdates
|
||||||
|
|
@ -610,7 +610,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
case packet: HitHint =>
|
case packet: HitHint =>
|
||||||
logic.general.handleHitHint(packet)
|
logic.general.handleHitHint(packet)
|
||||||
|
|
||||||
case _: OutfitRequest => ()
|
case packet: OutfitRequest =>
|
||||||
|
logic.general.handleOutfitRequest(packet)
|
||||||
|
|
||||||
|
case packet: OutfitMembershipRequest =>
|
||||||
|
logic.general.handleOutfitMembershipRequest(packet)
|
||||||
|
|
||||||
|
case packet: OutfitMembershipResponse =>
|
||||||
|
logic.general.handleOutfitMembershipResponse(packet)
|
||||||
|
|
||||||
case pkt =>
|
case pkt =>
|
||||||
data.log.warn(s"Unhandled GamePacket $pkt")
|
data.log.warn(s"Unhandled GamePacket $pkt")
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,13 @@ import net.psforever.objects.vehicles.Utility
|
||||||
import net.psforever.objects.vital.Vitality
|
import net.psforever.objects.vital.Vitality
|
||||||
import net.psforever.objects.zones.{ZoneProjectile, Zoning}
|
import net.psforever.objects.zones.{ZoneProjectile, Zoning}
|
||||||
import net.psforever.packet.PlanetSideGamePacket
|
import net.psforever.packet.PlanetSideGamePacket
|
||||||
import net.psforever.packet.game.{ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, ChatMsg, CollisionIs, ConnectToWorldRequestMessage, CreateShortcutMessage, DeadState, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, RequestDestroyMessage, TargetingImplantRequest, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
|
import net.psforever.packet.game.OutfitEventAction.{OutfitInfo, OutfitRankNames, Unk0, Unk1}
|
||||||
|
import net.psforever.packet.game.{ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, ChatMsg, CollisionIs, ConnectToWorldRequestMessage, CreateShortcutMessage, DeadState, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, OutfitEvent, OutfitMemberEvent, OutfitMembershipRequest, OutfitMembershipResponse, OutfitRequest, OutfitRequestAction, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, RequestDestroyMessage, TargetingImplantRequest, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
|
||||||
import net.psforever.services.RemoverActor
|
import net.psforever.services.RemoverActor
|
||||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||||
import net.psforever.types.{CapacitorStateType, ChatMessageType, Cosmetic, ExoSuitType, PlanetSideEmpire, PlanetSideGUID, Vector3}
|
import net.psforever.types.{CapacitorStateType, ChatMessageType, Cosmetic, ExoSuitType, PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||||
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
import scala.util.Success
|
import scala.util.Success
|
||||||
|
|
||||||
|
|
@ -665,6 +667,19 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
||||||
val HitHint(_, _) = pkt
|
val HitHint(_, _) = pkt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def handleOutfitMembershipRequest(pkt: OutfitMembershipRequest): Unit = {}
|
||||||
|
|
||||||
|
def handleOutfitMembershipResponse(pkt: OutfitMembershipResponse): Unit = {}
|
||||||
|
|
||||||
|
def handleOutfitRequest(pkt: OutfitRequest): Unit = {
|
||||||
|
pkt match {
|
||||||
|
case OutfitRequest(_, OutfitRequestAction.Unk3(true)) =>
|
||||||
|
|
||||||
|
case OutfitRequest(_, OutfitRequestAction.Unk3(false)) =>
|
||||||
|
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
}
|
||||||
/* messages */
|
/* messages */
|
||||||
|
|
||||||
def handleRenewCharSavedTimer(): Unit = { /* */ }
|
def handleRenewCharSavedTimer(): Unit = { /* */ }
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import net.psforever.actors.session.spectator.SpectatorMode
|
||||||
import net.psforever.actors.session.support.{ChatFunctions, ChatOperations, SessionData}
|
import net.psforever.actors.session.support.{ChatFunctions, ChatOperations, SessionData}
|
||||||
import net.psforever.objects.Session
|
import net.psforever.objects.Session
|
||||||
import net.psforever.packet.game.{ChatMsg, ServerType, SetChatFilterMessage}
|
import net.psforever.packet.game.{ChatMsg, ServerType, SetChatFilterMessage}
|
||||||
import net.psforever.services.chat.{DefaultChannel, SquadChannel}
|
import net.psforever.services.chat.{DefaultChannel, OutfitChannel, SquadChannel}
|
||||||
import net.psforever.types.ChatMessageType
|
import net.psforever.types.ChatMessageType
|
||||||
import net.psforever.types.ChatMessageType.{CMT_TOGGLESPECTATORMODE, CMT_TOGGLE_GM}
|
import net.psforever.types.ChatMessageType.{CMT_TOGGLESPECTATORMODE, CMT_TOGGLE_GM}
|
||||||
import net.psforever.util.Config
|
import net.psforever.util.Config
|
||||||
|
|
@ -79,6 +79,9 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
|
||||||
case (CMT_SQUAD, _, _) =>
|
case (CMT_SQUAD, _, _) =>
|
||||||
ops.commandSquad(session, message, SquadChannel(sessionLogic.squad.squad_guid))
|
ops.commandSquad(session, message, SquadChannel(sessionLogic.squad.squad_guid))
|
||||||
|
|
||||||
|
case (CMT_OUTFIT, _, _) =>
|
||||||
|
ops.commandOutfit(session, message, OutfitChannel(sessionLogic.player.outfit_id))
|
||||||
|
|
||||||
case (CMT_WHO | CMT_WHO_CSR | CMT_WHO_CR | CMT_WHO_PLATOONLEADERS | CMT_WHO_SQUADLEADERS | CMT_WHO_TEAMS, _, _) =>
|
case (CMT_WHO | CMT_WHO_CSR | CMT_WHO_CR | CMT_WHO_PLATOONLEADERS | CMT_WHO_SQUADLEADERS | CMT_WHO_TEAMS, _, _) =>
|
||||||
ops.commandWho(session)
|
ops.commandWho(session)
|
||||||
|
|
||||||
|
|
@ -100,7 +103,7 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
|
||||||
def handleIncomingMessage(message: ChatMsg, fromSession: Session): Unit = {
|
def handleIncomingMessage(message: ChatMsg, fromSession: Session): Unit = {
|
||||||
import ChatMessageType._
|
import ChatMessageType._
|
||||||
message.messageType match {
|
message.messageType match {
|
||||||
case CMT_BROADCAST | CMT_SQUAD | CMT_PLATOON | CMT_COMMAND | CMT_NOTE =>
|
case CMT_BROADCAST | CMT_SQUAD | CMT_PLATOON | CMT_COMMAND | CMT_NOTE | CMT_OUTFIT =>
|
||||||
ops.commandIncomingSendAllIfOnline(session, message)
|
ops.commandIncomingSendAllIfOnline(session, message)
|
||||||
|
|
||||||
case CMT_OPEN =>
|
case CMT_OPEN =>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ package net.psforever.actors.session.normal
|
||||||
import akka.actor.typed.scaladsl.adapter._
|
import akka.actor.typed.scaladsl.adapter._
|
||||||
import akka.actor.{ActorContext, ActorRef, typed}
|
import akka.actor.{ActorContext, ActorRef, typed}
|
||||||
import net.psforever.actors.session.{AvatarActor, SessionActor}
|
import net.psforever.actors.session.{AvatarActor, SessionActor}
|
||||||
import net.psforever.actors.session.support.{GeneralFunctions, GeneralOperations, SessionData}
|
import net.psforever.actors.session.support.{GeneralFunctions, GeneralOperations, SessionData, SessionOutfitHandlers}
|
||||||
import net.psforever.objects.{Account, BoomerDeployable, BoomerTrigger, ConstructionItem, GlobalDefinitions, LivePlayerList, Player, SensorDeployable, ShieldGeneratorDeployable, SpecialEmp, TelepadDeployable, Tool, TrapDeployable, TurretDeployable, Vehicle}
|
import net.psforever.objects.{Account, BoomerDeployable, BoomerTrigger, ConstructionItem, GlobalDefinitions, LivePlayerList, Player, SensorDeployable, ShieldGeneratorDeployable, SpecialEmp, TelepadDeployable, Tool, TrapDeployable, TurretDeployable, Vehicle}
|
||||||
import net.psforever.objects.avatar.{Avatar, PlayerControl, SpecialCarry}
|
import net.psforever.objects.avatar.{Avatar, PlayerControl, SpecialCarry}
|
||||||
import net.psforever.objects.ballistics.Projectile
|
import net.psforever.objects.ballistics.Projectile
|
||||||
|
|
@ -37,13 +37,16 @@ import net.psforever.objects.vital.etc.SuicideReason
|
||||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||||
import net.psforever.objects.zones.{ZoneProjectile, Zoning}
|
import net.psforever.objects.zones.{ZoneProjectile, Zoning}
|
||||||
import net.psforever.packet.PlanetSideGamePacket
|
import net.psforever.packet.PlanetSideGamePacket
|
||||||
import net.psforever.packet.game.{ActionCancelMessage, ActionResultMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestAction, CharacterRequestMessage, ChatMsg, CollisionIs, ConnectToWorldRequestMessage, CreateShortcutMessage, DeadState, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, RequestDestroyMessage, TargetingImplantRequest, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
|
import net.psforever.packet.game.OutfitEventAction.{OutfitInfo, OutfitRankNames, Unk2}
|
||||||
|
import net.psforever.packet.game.{ActionCancelMessage, ActionResultMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestAction, CharacterRequestMessage, ChatMsg, CollisionIs, ConnectToWorldRequestMessage, CreateShortcutMessage, DeadState, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, OutfitEvent, OutfitMembershipRequest, OutfitMembershipRequestAction, OutfitMembershipResponse, OutfitRequest, OutfitRequestAction, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, RequestDestroyMessage, TargetingImplantRequest, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
|
||||||
import net.psforever.services.account.{AccountPersistenceService, RetrieveAccountData}
|
import net.psforever.services.account.{AccountPersistenceService, RetrieveAccountData}
|
||||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
|
import net.psforever.services.chat.OutfitChannel
|
||||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||||
import net.psforever.services.local.support.CaptureFlagManager
|
import net.psforever.services.local.support.CaptureFlagManager
|
||||||
import net.psforever.types.{CapacitorStateType, ChatMessageType, Cosmetic, ExoSuitType, ImplantType, PlanetSideEmpire, PlanetSideGUID, Vector3}
|
import net.psforever.types.{CapacitorStateType, ChatMessageType, Cosmetic, ExoSuitType, ImplantType, PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||||
import net.psforever.util.Config
|
import net.psforever.util.Config
|
||||||
|
import net.psforever.zones.Zones.zones
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
|
@ -796,6 +799,54 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
||||||
val HitHint(_, _) = pkt
|
val HitHint(_, _) = pkt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def handleOutfitMembershipRequest(pkt: OutfitMembershipRequest): Unit = {
|
||||||
|
pkt match {
|
||||||
|
case OutfitMembershipRequest(_, OutfitMembershipRequestAction.Form(_, outfitName)) =>
|
||||||
|
if (player.outfit_id == 0) {
|
||||||
|
SessionOutfitHandlers.HandleOutfitForm(outfitName, player, sessionLogic)
|
||||||
|
}
|
||||||
|
|
||||||
|
case OutfitMembershipRequest(_, OutfitMembershipRequestAction.Invite(_, invitedName)) =>
|
||||||
|
SessionOutfitHandlers.HandleOutfitInvite(zones, invitedName, player)
|
||||||
|
|
||||||
|
case OutfitMembershipRequest(_, OutfitMembershipRequestAction.Kick(memberId, _)) =>
|
||||||
|
SessionOutfitHandlers.HandleOutfitKick(zones, memberId, player)
|
||||||
|
|
||||||
|
case OutfitMembershipRequest(_, OutfitMembershipRequestAction.SetRank(memberId, newRank, _)) =>
|
||||||
|
SessionOutfitHandlers.HandleOutfitPromote(zones, memberId, newRank, player)
|
||||||
|
|
||||||
|
case OutfitMembershipRequest(_, OutfitMembershipRequestAction.AcceptInvite(_)) =>
|
||||||
|
SessionOutfitHandlers.HandleOutfitInviteAccept(player, sessionLogic)
|
||||||
|
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def handleOutfitMembershipResponse(pkt: OutfitMembershipResponse): Unit = {}
|
||||||
|
|
||||||
|
def handleOutfitRequest(pkt: OutfitRequest): Unit = {
|
||||||
|
pkt match {
|
||||||
|
|
||||||
|
case OutfitRequest(_, OutfitRequestAction.Ranks(List(r1, r2, r3, r4, r5, r6, r7, r8))) =>
|
||||||
|
// update db
|
||||||
|
//sendResponse(OutfitEvent(6418, Unk2(OutfitInfo(player.outfit_name, 0, 0, 1, OutfitRankNames(r1.getOrElse(""), r2.getOrElse(""), r3.getOrElse(""), r4.getOrElse(""), r5.getOrElse(""), r6.getOrElse(""), r7.getOrElse(""), r8.getOrElse("")), "Welcome to the first PSForever Outfit!", 0, unk11=true, 0, 8888888, 0, 0, 0))))
|
||||||
|
|
||||||
|
case OutfitRequest(_, OutfitRequestAction.Motd(message)) =>
|
||||||
|
// update db
|
||||||
|
//sendResponse(OutfitEvent(6418, Unk2(OutfitInfo(player.outfit_name, 0, 0, 1, OutfitRankNames("", "", "", "", "", "", "", ""), message, 0, unk11=true, 0, 8888888, 0, 0, 0))))
|
||||||
|
|
||||||
|
case OutfitRequest(_, OutfitRequestAction.Unk3(true)) =>
|
||||||
|
SessionOutfitHandlers.HandleViewOutfitWindow(zones, player, player.outfit_id)
|
||||||
|
|
||||||
|
case OutfitRequest(_, OutfitRequestAction.Unk3(false)) =>
|
||||||
|
|
||||||
|
case OutfitRequest(_, OutfitRequestAction.Unk4(true)) =>
|
||||||
|
SessionOutfitHandlers.HandleGetOutfitList(player)
|
||||||
|
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* messages */
|
/* messages */
|
||||||
|
|
||||||
def handleRenewCharSavedTimer(): Unit = {
|
def handleRenewCharSavedTimer(): Unit = {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import net.psforever.objects.serverobject.doors.Door
|
||||||
import net.psforever.objects.vehicles.Utility
|
import net.psforever.objects.vehicles.Utility
|
||||||
import net.psforever.objects.zones.ZoneProjectile
|
import net.psforever.objects.zones.ZoneProjectile
|
||||||
import net.psforever.packet.PlanetSideGamePacket
|
import net.psforever.packet.PlanetSideGamePacket
|
||||||
import net.psforever.packet.game.{ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, ConnectToWorldRequestMessage, CreateShortcutMessage, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, ImplantAction, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, RequestDestroyMessage, TargetingImplantRequest, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
|
import net.psforever.packet.game.{ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, ConnectToWorldRequestMessage, CreateShortcutMessage, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, ImplantAction, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, OutfitMembershipRequest, OutfitMembershipResponse, OutfitRequest, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, RequestDestroyMessage, TargetingImplantRequest, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
|
||||||
import net.psforever.services.account.AccountPersistenceService
|
import net.psforever.services.account.AccountPersistenceService
|
||||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
import net.psforever.types.{ExoSuitType, Vector3}
|
import net.psforever.types.{ExoSuitType, Vector3}
|
||||||
|
|
@ -375,6 +375,12 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
||||||
|
|
||||||
def handleHitHint(pkt: HitHint): Unit = { /* intentionally blank */ }
|
def handleHitHint(pkt: HitHint): Unit = { /* intentionally blank */ }
|
||||||
|
|
||||||
|
def handleOutfitMembershipRequest(pkt: OutfitMembershipRequest): Unit = {}
|
||||||
|
|
||||||
|
def handleOutfitMembershipResponse(pkt: OutfitMembershipResponse): Unit = {}
|
||||||
|
|
||||||
|
def handleOutfitRequest(pkt: OutfitRequest): Unit = {}
|
||||||
|
|
||||||
/* messages */
|
/* messages */
|
||||||
|
|
||||||
def handleRenewCharSavedTimer(): Unit = { /* intentionally blank */ }
|
def handleRenewCharSavedTimer(): Unit = { /* intentionally blank */ }
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import net.psforever.objects.LivePlayerList
|
||||||
import net.psforever.objects.sourcing.PlayerSource
|
import net.psforever.objects.sourcing.PlayerSource
|
||||||
import net.psforever.objects.zones.ZoneInfo
|
import net.psforever.objects.zones.ZoneInfo
|
||||||
import net.psforever.packet.game.SetChatFilterMessage
|
import net.psforever.packet.game.SetChatFilterMessage
|
||||||
import net.psforever.services.chat.{DefaultChannel, SquadChannel}
|
import net.psforever.services.chat.{DefaultChannel, OutfitChannel, SquadChannel}
|
||||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||||
import net.psforever.services.teamwork.{SquadResponse, SquadService, SquadServiceResponse}
|
import net.psforever.services.teamwork.{SquadResponse, SquadService, SquadServiceResponse}
|
||||||
import net.psforever.types.ChatMessageType.CMT_QUIT
|
import net.psforever.types.ChatMessageType.CMT_QUIT
|
||||||
|
|
@ -446,6 +446,14 @@ class ChatOperations(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def commandOutfit(session: Session, message: ChatMsg, toChannel: ChatChannel): Unit = {
|
||||||
|
channels.foreach {
|
||||||
|
case _/*channel*/: OutfitChannel =>
|
||||||
|
commandSendToRecipient(session, message, toChannel)
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def commandWho(session: Session): Unit = {
|
def commandWho(session: Session): Unit = {
|
||||||
val players = session.zone.Players
|
val players = session.zone.Players
|
||||||
val popTR = players.count(_.faction == PlanetSideEmpire.TR)
|
val popTR = players.count(_.faction == PlanetSideEmpire.TR)
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,12 @@ trait GeneralFunctions extends CommonSessionInterfacingFunctionality {
|
||||||
def handleCanNotPutItemInSlot(msg: Containable.CanNotPutItemInSlot): Unit
|
def handleCanNotPutItemInSlot(msg: Containable.CanNotPutItemInSlot): Unit
|
||||||
|
|
||||||
def handleReceiveDefaultMessage(default: Any, sender: ActorRef): Unit
|
def handleReceiveDefaultMessage(default: Any, sender: ActorRef): Unit
|
||||||
|
|
||||||
|
def handleOutfitMembershipRequest(pkt: OutfitMembershipRequest): Unit
|
||||||
|
|
||||||
|
def handleOutfitMembershipResponse(pkt: OutfitMembershipResponse): Unit
|
||||||
|
|
||||||
|
def handleOutfitRequest(pkt: OutfitRequest): Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
class GeneralOperations(
|
class GeneralOperations(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package net.psforever.actors.session.support
|
||||||
|
|
||||||
|
import net.psforever.objects.Player
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
case class OutfitInvite(
|
||||||
|
sentTo: Player,
|
||||||
|
sentFrom: Player,
|
||||||
|
timestamp: Long = System.currentTimeMillis() / 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
object OutfitInviteManager {
|
||||||
|
private val invites = mutable.Map[Long, OutfitInvite]()
|
||||||
|
private val ExpirationSeconds = 320
|
||||||
|
|
||||||
|
def addOutfitInvite(invite: OutfitInvite): Boolean = {
|
||||||
|
invites.get(invite.sentTo.CharId) match {
|
||||||
|
case Some(existing) if (System.currentTimeMillis() / 1000 - existing.timestamp) < ExpirationSeconds =>
|
||||||
|
false // Reject new invite (previous one is still valid)
|
||||||
|
case _ =>
|
||||||
|
invites(invite.sentTo.CharId) = invite
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def removeOutfitInvite(sentToId: Long): Unit = {
|
||||||
|
invites.remove(sentToId)
|
||||||
|
}
|
||||||
|
|
||||||
|
def getOutfitInvite(sentToId: Long): Option[OutfitInvite] = invites.get(sentToId)
|
||||||
|
|
||||||
|
def getAllOutfitInvites: List[OutfitInvite] = invites.values.toList
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,390 @@
|
||||||
|
// Copyright (c) 2025 PSForever
|
||||||
|
package net.psforever.actors.session.support
|
||||||
|
|
||||||
|
import io.getquill.{ActionReturning, EntityQuery, Insert, PostgresJAsyncContext, Query, Quoted, SnakeCase}
|
||||||
|
import net.psforever.objects.avatar.PlayerControl
|
||||||
|
import net.psforever.objects.zones.Zone
|
||||||
|
import net.psforever.objects.Player
|
||||||
|
import net.psforever.packet.game.OutfitEventAction.{OutfitInfo, OutfitRankNames, Unk0, Unk1, Unk2}
|
||||||
|
import net.psforever.packet.game.OutfitMembershipResponse.PacketType.CreateResponse
|
||||||
|
import net.psforever.packet.game._
|
||||||
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
|
import net.psforever.services.chat.OutfitChannel
|
||||||
|
import net.psforever.types.ChatMessageType
|
||||||
|
import net.psforever.util.Config
|
||||||
|
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import scala.util.{Failure, Success}
|
||||||
|
|
||||||
|
object SessionOutfitHandlers {
|
||||||
|
|
||||||
|
case class Avatar(id: Long, name: String, faction_id: Int, last_login: java.time.LocalDateTime)
|
||||||
|
case class Outfit(id: Long, name: String, faction: Int, owner_id: Long, motd: Option[String], created: java.time.LocalDateTime,
|
||||||
|
rank0: Option[String],
|
||||||
|
rank1: Option[String],
|
||||||
|
rank2: Option[String],
|
||||||
|
rank3: Option[String],
|
||||||
|
rank4: Option[String],
|
||||||
|
rank5: Option[String],
|
||||||
|
rank6: Option[String],
|
||||||
|
rank7: Option[String])
|
||||||
|
case class Outfitmember(id: Long, outfit_id: Long, avatar_id: Long, rank: Int)
|
||||||
|
case class Outfitpoint(id: Long, outfit_id: Long, avatar_id: Long, points: Long)
|
||||||
|
case class OutfitpointMv(outfit_id: Long, points: Long)
|
||||||
|
|
||||||
|
val ctx = new PostgresJAsyncContext(SnakeCase, Config.config.getConfig("database"))
|
||||||
|
import ctx._
|
||||||
|
|
||||||
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
import scala.concurrent.Future
|
||||||
|
|
||||||
|
def HandleOutfitForm(outfitName: String, player: Player, session: SessionData): Unit = {
|
||||||
|
val cleanedName = sanitizeOutfitName(outfitName)
|
||||||
|
|
||||||
|
cleanedName match {
|
||||||
|
case Some(validName) =>
|
||||||
|
ctx.run(findOutfitByName(validName)).flatMap {
|
||||||
|
case existing if existing.nonEmpty =>
|
||||||
|
PlayerControl.sendResponse(player.Zone, player.Name,
|
||||||
|
ChatMsg(ChatMessageType.UNK_227, "@OutfitErrorNameAlreadyTaken"))
|
||||||
|
Future.successful(())
|
||||||
|
|
||||||
|
case _ =>
|
||||||
|
createNewOutfit(validName, player.Faction.id, player.CharId).map { outfit =>
|
||||||
|
val seconds: Long =
|
||||||
|
outfit.created.atZone(java.time.ZoneOffset.UTC).toInstant.toEpochMilli / 1000
|
||||||
|
|
||||||
|
PlayerControl.sendResponse(player.Zone, player.Name,
|
||||||
|
OutfitEvent(outfit.id, Unk2(
|
||||||
|
OutfitInfo(
|
||||||
|
outfit.name, 0, 0, 1,
|
||||||
|
OutfitRankNames("", "", "", "", "", "", "", ""),
|
||||||
|
"",
|
||||||
|
14, unk11 = true, 0, seconds, 0, 0, 0))))
|
||||||
|
|
||||||
|
PlayerControl.sendResponse(player.Zone, player.Name,
|
||||||
|
OutfitMemberUpdate(outfit.id, player.CharId, 7, flag = true))
|
||||||
|
|
||||||
|
PlayerControl.sendResponse(player.Zone, player.Name,
|
||||||
|
ChatMsg(ChatMessageType.UNK_227, "@OutfitCreateSuccess"))
|
||||||
|
|
||||||
|
PlayerControl.sendResponse(player.Zone, player.Name,
|
||||||
|
OutfitMembershipResponse(CreateResponse, 0, 0, player.CharId, 0, "", "", flag = true))
|
||||||
|
|
||||||
|
player.outfit_id = outfit.id
|
||||||
|
player.outfit_name = outfit.name
|
||||||
|
|
||||||
|
session.chat.JoinChannel(OutfitChannel(player.outfit_id))
|
||||||
|
}
|
||||||
|
.recover { case e =>
|
||||||
|
e.printStackTrace()
|
||||||
|
PlayerControl.sendResponse(player.Zone, player.Name,
|
||||||
|
ChatMsg(ChatMessageType.UNK_227, "@OutfitCreateFailure"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case None =>
|
||||||
|
PlayerControl.sendResponse(player.Zone, player.Name,
|
||||||
|
ChatMsg(ChatMessageType.UNK_227, "@OutfitCreateFailure"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def HandleOutfitInvite(zones: Seq[Zone], invitedName: String, sentFrom: Player): Unit = {
|
||||||
|
findPlayerByNameForOutfitAction(zones, invitedName, sentFrom).foreach { invitedPlayer =>
|
||||||
|
|
||||||
|
PlayerControl.sendResponse(invitedPlayer.Zone, invitedPlayer.Name,
|
||||||
|
OutfitMembershipResponse(OutfitMembershipResponse.PacketType.Invite, 0, 0,
|
||||||
|
sentFrom.CharId, sentFrom.CharId, sentFrom.Name, sentFrom.outfit_name, flag = false))
|
||||||
|
|
||||||
|
PlayerControl.sendResponse(sentFrom.Zone, sentFrom.Name,
|
||||||
|
OutfitMembershipResponse(OutfitMembershipResponse.PacketType.Invite, 0, 0,
|
||||||
|
sentFrom.CharId, invitedPlayer.CharId, invitedPlayer.Name, sentFrom.outfit_name, flag = true))
|
||||||
|
|
||||||
|
val outfitInvite = OutfitInvite(invitedPlayer, sentFrom)
|
||||||
|
OutfitInviteManager.addOutfitInvite(outfitInvite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def HandleOutfitInviteAccept(invited: Player, session: SessionData): Unit = {
|
||||||
|
OutfitInviteManager.getOutfitInvite(invited.CharId) match {
|
||||||
|
case Some(outfitInvite) =>
|
||||||
|
val outfitId = outfitInvite.sentFrom.outfit_id
|
||||||
|
|
||||||
|
(for {
|
||||||
|
_ <- addMemberToOutfit(outfitId, invited.CharId)
|
||||||
|
outfitOpt <- ctx.run(getOutfitById(outfitId)).map(_.headOption)
|
||||||
|
memberCount <- ctx.run(getOutfitMemberCount(outfitId))
|
||||||
|
points <- ctx.run(getOutfitPoints(outfitId)).map(_.headOption.map(_.points).getOrElse(0L))
|
||||||
|
} yield (outfitOpt, memberCount, points))
|
||||||
|
.map {
|
||||||
|
case (Some(outfit), memberCount, points) =>
|
||||||
|
|
||||||
|
PlayerControl.sendResponse(outfitInvite.sentFrom.Zone, outfitInvite.sentFrom.Name,
|
||||||
|
OutfitMembershipResponse(
|
||||||
|
OutfitMembershipResponse.PacketType.Unk2, 0, 0,
|
||||||
|
invited.CharId, outfitInvite.sentFrom.CharId, invited.Name, outfit.name, flag = false))
|
||||||
|
|
||||||
|
PlayerControl.sendResponse(invited.Zone, invited.Name,
|
||||||
|
OutfitMembershipResponse(
|
||||||
|
OutfitMembershipResponse.PacketType.Unk2, 0, 0,
|
||||||
|
invited.CharId, outfitInvite.sentFrom.CharId, invited.Name, outfit.name, flag = true))
|
||||||
|
|
||||||
|
PlayerControl.sendResponse(outfitInvite.sentFrom.Zone, outfitInvite.sentFrom.Name,
|
||||||
|
OutfitEvent(outfitId, OutfitEventAction.Unk5(memberCount)))
|
||||||
|
|
||||||
|
PlayerControl.sendResponse(outfitInvite.sentFrom.Zone, outfitInvite.sentFrom.Name,
|
||||||
|
OutfitMemberEvent(outfitId, invited.CharId,
|
||||||
|
OutfitMemberEventAction.Unk0(invited.Name, 0, 0, 0,
|
||||||
|
OutfitMemberEventAction.PacketType.Padding, 0)))
|
||||||
|
|
||||||
|
val seconds: Long = outfit.created.atZone(java.time.ZoneOffset.UTC).toInstant.toEpochMilli / 1000
|
||||||
|
PlayerControl.sendResponse(invited.Zone, invited.Name,
|
||||||
|
OutfitEvent(outfitId, Unk0(OutfitInfo(
|
||||||
|
outfit.name, points, points, memberCount,
|
||||||
|
OutfitRankNames("", "", "", "", "", "", "", ""),
|
||||||
|
outfit.motd.getOrElse(""),
|
||||||
|
14, unk11 = true, 0, seconds, 0, 0, 0))))
|
||||||
|
|
||||||
|
PlayerControl.sendResponse(invited.Zone, invited.Name,
|
||||||
|
OutfitMemberUpdate(outfit.id, invited.CharId, 0, flag=true))
|
||||||
|
|
||||||
|
OutfitInviteManager.removeOutfitInvite(invited.CharId)
|
||||||
|
|
||||||
|
session.chat.JoinChannel(OutfitChannel(outfit.id))
|
||||||
|
invited.outfit_id = outfit.id
|
||||||
|
invited.outfit_name = outfit.name
|
||||||
|
case (None, _, _) =>
|
||||||
|
|
||||||
|
PlayerControl.sendResponse(invited.Zone, invited.Name,
|
||||||
|
ChatMsg(ChatMessageType.UNK_227, "Failed to join outfit"))
|
||||||
|
}
|
||||||
|
.recover { case _ =>
|
||||||
|
PlayerControl.sendResponse(invited.Zone, invited.Name,
|
||||||
|
ChatMsg(ChatMessageType.UNK_227, "Failed to join outfit"))
|
||||||
|
}
|
||||||
|
case None =>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def HandleOutfitKick(zones: Seq[Zone], kickedId: Long, kickedBy: Player): Unit = {
|
||||||
|
// if same id, player has left the outfit by their own choice
|
||||||
|
if (kickedId == kickedBy.CharId) {
|
||||||
|
// db stuff first
|
||||||
|
PlayerControl.sendResponse(kickedBy.Zone, kickedBy.Name, OutfitMemberEvent(kickedBy.outfit_id, kickedId, OutfitMemberEventAction.Unk1()))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// db stuff first
|
||||||
|
// tell player they've been kicked (if online)
|
||||||
|
findPlayerByIdForOutfitAction(zones, kickedId, kickedBy).foreach { kicked =>
|
||||||
|
PlayerControl.sendResponse(kicked.Zone, kicked.Name, OutfitMembershipResponse(OutfitMembershipResponse.PacketType.Kick, 0, 1, kickedBy.CharId, kicked.CharId, kicked.Name, kickedBy.Name, flag = false))
|
||||||
|
kicked.Zone.AvatarEvents ! AvatarServiceMessage(kicked.Zone.id, AvatarAction.PlanetsideAttributeToAll(kicked.GUID, 39, 0))
|
||||||
|
//kicked.Zone.AvatarEvents ! AvatarServiceMessage(kicked.Zone.id, AvatarAction.PlanetsideStringAttributeMessage(kicked.GUID, 0, ""))
|
||||||
|
kicked.outfit_id = 0
|
||||||
|
kicked.outfit_name = ""
|
||||||
|
PlayerControl.sendResponse(kicked.Zone, kicked.Name, OutfitMemberEvent(kickedBy.outfit_id, kickedId, OutfitMemberEventAction.Unk1()))
|
||||||
|
|
||||||
|
// move this out of foreach - db will provide kicked char details
|
||||||
|
PlayerControl.sendResponse(kickedBy.Zone, kickedBy.Name, OutfitMembershipResponse(OutfitMembershipResponse.PacketType.Kick, 0, 1, kickedBy.CharId, kicked.CharId, kicked.Name, "", flag = true))
|
||||||
|
PlayerControl.sendResponse(kickedBy.Zone, kickedBy.Name, OutfitMemberEvent(kickedBy.outfit_id, kickedId, OutfitMemberEventAction.Unk1()))
|
||||||
|
// new number of outfit members?
|
||||||
|
PlayerControl.sendResponse(kickedBy.Zone, kickedBy.Name, OutfitEvent(kickedBy.outfit_id, OutfitEventAction.Unk5(34)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def HandleOutfitPromote(zones: Seq[Zone], promotedId: Long, newRank: Int, promoter: Player): Unit = {
|
||||||
|
// send to all online players in outfit
|
||||||
|
findPlayerByIdForOutfitAction(zones, promotedId, promoter).foreach { promoted =>
|
||||||
|
PlayerControl.sendResponse(promoted.Zone, promoted.Name, OutfitMemberEvent(6418, promotedId, OutfitMemberEventAction.Unk0(promoted.Name, newRank, 1032432, 0, OutfitMemberEventAction.PacketType.Padding, 0)))
|
||||||
|
PlayerControl.sendResponse(promoter.Zone, promoter.Name, OutfitMemberEvent(6418, promotedId, OutfitMemberEventAction.Unk0(promoted.Name, newRank, 1032432, 0, OutfitMemberEventAction.PacketType.Padding, 0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def HandleViewOutfitWindow(zones: Seq[Zone], player: Player, outfitId: Long): Unit = {
|
||||||
|
val outfitDetailsF = for {
|
||||||
|
outfitOpt <- ctx.run(getOutfitById(outfitId)).map(_.headOption)
|
||||||
|
memberCount <- ctx.run(query[Outfitmember].filter(_.outfit_id == lift(outfitId)).size)
|
||||||
|
pointsTotal <- ctx.run(querySchema[OutfitpointMv]("outfitpoint_mv").filter(_.outfit_id == lift(outfitId)))
|
||||||
|
} yield (outfitOpt, memberCount, pointsTotal.headOption.map(_.points).getOrElse(0L))
|
||||||
|
|
||||||
|
val membersF = ctx.run(getOutfitMembersWithDetails(outfitId))
|
||||||
|
|
||||||
|
for {
|
||||||
|
(outfitOpt, memberCount, totalPoints) <- outfitDetailsF
|
||||||
|
members <- membersF
|
||||||
|
} yield {
|
||||||
|
outfitOpt.foreach { outfit =>
|
||||||
|
val seconds: Long = outfit.created.atZone(java.time.ZoneOffset.UTC).toInstant.toEpochMilli / 1000
|
||||||
|
|
||||||
|
PlayerControl.sendResponse(player.Zone, player.Name,
|
||||||
|
OutfitEvent(outfit.id, Unk0(OutfitInfo(
|
||||||
|
outfit.name,
|
||||||
|
totalPoints,
|
||||||
|
totalPoints,
|
||||||
|
memberCount,
|
||||||
|
OutfitRankNames("", "", "", "", "", "", "", ""),
|
||||||
|
outfit.motd.getOrElse(""),
|
||||||
|
14, unk11 = true, 0, seconds, 0, 0, 0))))
|
||||||
|
|
||||||
|
members.foreach { case (avatarId, avatarName, points, rank, login) =>
|
||||||
|
val lastLogin = findPlayerByIdForOutfitAction(zones, avatarId, player) match {
|
||||||
|
case Some(_) => 0L
|
||||||
|
case None if player.Name == avatarName => 0L
|
||||||
|
case None => (System.currentTimeMillis() - login.atZone(java.time.ZoneOffset.UTC).toInstant.toEpochMilli) / 1000
|
||||||
|
}
|
||||||
|
PlayerControl.sendResponse(player.Zone, player.Name,
|
||||||
|
OutfitMemberEvent(outfit.id, avatarId,
|
||||||
|
OutfitMemberEventAction.Unk0(
|
||||||
|
avatarName,
|
||||||
|
rank,
|
||||||
|
points,
|
||||||
|
lastLogin,
|
||||||
|
OutfitMemberEventAction.PacketType.Padding, 0)))
|
||||||
|
}
|
||||||
|
PlayerControl.sendResponse(player.Zone, player.Name,
|
||||||
|
OutfitEvent(outfit.id, Unk1()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def HandleGetOutfitList(player: Player): Unit = {
|
||||||
|
val q = getOutfitsByEmpire(player.Faction.id)
|
||||||
|
val futureResult = ctx.run(q)
|
||||||
|
|
||||||
|
futureResult.onComplete {
|
||||||
|
case Success(rows) =>
|
||||||
|
rows.foreach { case (outfitId, points, name, leaderName, memberCount) =>
|
||||||
|
PlayerControl.sendResponse(player.Zone, player.Name,
|
||||||
|
OutfitListEvent(
|
||||||
|
OutfitListEventAction.ListElementOutfit(
|
||||||
|
outfitId,
|
||||||
|
points,
|
||||||
|
memberCount,
|
||||||
|
name,
|
||||||
|
leaderName)))
|
||||||
|
}
|
||||||
|
|
||||||
|
case Failure(_) =>
|
||||||
|
PlayerControl.sendResponse(player.Zone, player.Name,
|
||||||
|
ChatMsg(ChatMessageType.UNK_227, "Outfit list failed to return")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* supporting functions */
|
||||||
|
|
||||||
|
def sanitizeOutfitName(name: String): Option[String] = {
|
||||||
|
val cleaned = name
|
||||||
|
.replaceAll("""[^A-Za-z0-9\-="\;\[\]\(\)\. ]""", "") // Remove disallowed chars
|
||||||
|
.replaceAll(" +", " ") // Collapse multiple spaces to one
|
||||||
|
.trim // Remove leading/trailing spaces
|
||||||
|
if (cleaned.length >= 2 && cleaned.length <= 32) Some(cleaned) else None
|
||||||
|
}
|
||||||
|
|
||||||
|
def findPlayerByNameForOutfitAction(zones: Iterable[Zone], targetName: String, inviter: Player): Option[Player] = {
|
||||||
|
zones
|
||||||
|
.flatMap(_.LivePlayers)
|
||||||
|
.find(p =>
|
||||||
|
p.Name.equalsIgnoreCase(targetName) && p.Name != inviter.Name &&
|
||||||
|
p.Faction == inviter.Faction && p.outfit_id == 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def findPlayerByIdForOutfitAction(zones: Iterable[Zone], targetId: Long, initiator: Player): Option[Player] = {
|
||||||
|
zones
|
||||||
|
.flatMap(_.LivePlayers)
|
||||||
|
.find(p =>
|
||||||
|
p.CharId == targetId && p.Name != initiator.Name &&
|
||||||
|
p.Faction == initiator.Faction && p.outfit_id == initiator.outfit_id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* db actions */
|
||||||
|
|
||||||
|
def findOutfitByName(name: String): Quoted[EntityQuery[Outfit]] = quote {
|
||||||
|
query[Outfit].filter(outfit => lift(name).toLowerCase == outfit.name.toLowerCase)
|
||||||
|
}
|
||||||
|
|
||||||
|
def insertNewOutfit(name: String, faction: Int, owner_id: Long): Quoted[ActionReturning[Outfit, Outfit]] = quote {
|
||||||
|
query[Outfit]
|
||||||
|
.insert(_.name -> lift(name), _.faction -> lift(faction), _.owner_id -> lift(owner_id))
|
||||||
|
.returning(outfit => outfit)
|
||||||
|
}
|
||||||
|
|
||||||
|
def insertOutfitMember(outfit_id: Long, avatar_id: Long, rank: Int): Quoted[Insert[Outfitmember]] = quote {
|
||||||
|
query[Outfitmember].insert(
|
||||||
|
_.outfit_id -> lift(outfit_id),
|
||||||
|
_.avatar_id -> lift(avatar_id),
|
||||||
|
_.rank -> lift(rank)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def insertOutfitPoint(outfit_id: Long, avatar_id: Long): Quoted[Insert[Outfitpoint]] = quote {
|
||||||
|
query[Outfitpoint].insert(
|
||||||
|
_.outfit_id -> lift(outfit_id),
|
||||||
|
_.avatar_id -> lift(avatar_id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def createNewOutfit(name: String, faction: Int, owner_id: Long): Future[Outfit] = {
|
||||||
|
ctx.transaction { implicit ec =>
|
||||||
|
for {
|
||||||
|
outfit <- ctx.run(insertNewOutfit(name, faction, owner_id))
|
||||||
|
_ <- ctx.run(insertOutfitMember(outfit.id, owner_id, rank=7))
|
||||||
|
_ <- ctx.run(insertOutfitPoint(outfit.id, owner_id))
|
||||||
|
} yield outfit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def addMemberToOutfit(outfit_id: Long, avatar_id: Long): Future[Unit] = {
|
||||||
|
ctx.transaction { implicit ec =>
|
||||||
|
for {
|
||||||
|
_ <- ctx.run(insertOutfitMember(outfit_id, avatar_id, rank=0))
|
||||||
|
_ <- ctx.run(insertOutfitPoint(outfit_id, avatar_id))
|
||||||
|
} yield ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getOutfitById(id: Long): Quoted[EntityQuery[Outfit]] = quote {
|
||||||
|
query[Outfit].filter(_.id == lift(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
def getOutfitMemberCount(id: Long): Quoted[Long] = quote {
|
||||||
|
query[Outfitmember].filter(_.outfit_id == lift(id)).size
|
||||||
|
}
|
||||||
|
|
||||||
|
def getOutfitPoints(id: Long): Quoted[EntityQuery[OutfitpointMv]] = quote {
|
||||||
|
querySchema[OutfitpointMv]("outfitpoint_mv").filter(_.outfit_id == lift(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
def getOutfitMembersWithDetails(outfitId: Long): Quoted[Query[(Long, String, Long, Int, LocalDateTime)]] = quote {
|
||||||
|
query[Outfitmember]
|
||||||
|
.filter(_.outfit_id == lift(outfitId))
|
||||||
|
.join(query[Avatar]).on(_.avatar_id == _.id)
|
||||||
|
.leftJoin(query[Outfitpoint]).on {
|
||||||
|
case ((member, _), points) =>
|
||||||
|
points.outfit_id == member.outfit_id && points.avatar_id == member.avatar_id
|
||||||
|
}
|
||||||
|
.map {
|
||||||
|
case ((member, avatar), pointsOpt) =>
|
||||||
|
(member.avatar_id, avatar.name, pointsOpt.map(_.points).getOrElse(0L), member.rank, avatar.last_login)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getOutfitsByEmpire(playerEmpireId: Int): Quoted[Query[(Long, Long, String, String, Long)]] = quote {
|
||||||
|
query[Outfit]
|
||||||
|
.filter(_.faction == lift(playerEmpireId))
|
||||||
|
.join(query[Avatar]).on((outfit, avatar) => outfit.owner_id == avatar.id)
|
||||||
|
.leftJoin(
|
||||||
|
query[Outfitmember]
|
||||||
|
.groupBy(_.outfit_id)
|
||||||
|
.map { case (oid, members) => (oid, members.size) }
|
||||||
|
).on { case ((outfit, _), (oid, _)) => oid == outfit.id }
|
||||||
|
.leftJoin(querySchema[OutfitpointMv]("outfitpoint_mv")).on {
|
||||||
|
case (((outfit, _), _), points) => points.outfit_id == outfit.id
|
||||||
|
}
|
||||||
|
.map {
|
||||||
|
case (((outfit, leader), memberCounts), points) =>
|
||||||
|
(outfit.id, points.map(_.points).getOrElse(0L), outfit.name, leader.name, memberCounts.map(_._2).getOrElse(0L))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -85,6 +85,8 @@ class Player(var avatar: Avatar)
|
||||||
var silenced: Boolean = false
|
var silenced: Boolean = false
|
||||||
var death_by: Int = 0
|
var death_by: Int = 0
|
||||||
var lastShotSeq_time: Int = -1
|
var lastShotSeq_time: Int = -1
|
||||||
|
var outfit_name: String = ""
|
||||||
|
var outfit_id: Long = 0
|
||||||
|
|
||||||
/** From PlanetsideAttributeMessage */
|
/** From PlanetsideAttributeMessage */
|
||||||
var PlanetsideAttribute: Array[Long] = Array.ofDim(120)
|
var PlanetsideAttribute: Array[Long] = Array.ofDim(120)
|
||||||
|
|
|
||||||
|
|
@ -93,8 +93,8 @@ object AvatarConverter {
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
val ab: (Boolean, Int) => CharacterAppearanceB = CharacterAppearanceB(
|
val ab: (Boolean, Int) => CharacterAppearanceB = CharacterAppearanceB(
|
||||||
0L,
|
obj.outfit_id,
|
||||||
outfit_name = "",
|
obj.outfit_name,
|
||||||
outfit_logo = 0,
|
outfit_logo = 0,
|
||||||
unk1 = false,
|
unk1 = false,
|
||||||
obj.isBackpack,
|
obj.isBackpack,
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,11 @@ object OutfitMembershipResponse extends Marshallable[OutfitMembershipResponse] {
|
||||||
type Type = Value
|
type Type = Value
|
||||||
|
|
||||||
val CreateResponse: PacketType.Value = Value(0)
|
val CreateResponse: PacketType.Value = Value(0)
|
||||||
val Unk1: PacketType.Value = Value(1) // Info: Player has been invited / response to OutfitMembershipRequest Unk2 for that player
|
val Invite: PacketType.Value = Value(1) // Info: Player has been invited / response to OutfitMembershipRequest Unk2 for that player
|
||||||
val Unk2: PacketType.Value = Value(2) // Invited / Accepted / Added
|
val Unk2: PacketType.Value = Value(2) // Invited / Accepted / Added
|
||||||
val Unk3: PacketType.Value = Value(3)
|
val Unk3: PacketType.Value = Value(3)
|
||||||
val Unk4: PacketType.Value = Value(4)
|
val Unk4: PacketType.Value = Value(4)
|
||||||
val Unk5: PacketType.Value = Value(5)
|
val Kick: PacketType.Value = Value(5)
|
||||||
val Unk6: PacketType.Value = Value(6) // 6 and 7 seen as failed decodes, validity unknown
|
val Unk6: PacketType.Value = Value(6) // 6 and 7 seen as failed decodes, validity unknown
|
||||||
val Unk7: PacketType.Value = Value(7)
|
val Unk7: PacketType.Value = Value(7)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,5 @@ final case class SquadChannel(guid: PlanetSideGUID) extends ChatChannel
|
||||||
case object SpectatorChannel extends ChatChannel
|
case object SpectatorChannel extends ChatChannel
|
||||||
|
|
||||||
case object CustomerServiceChannel extends ChatChannel
|
case object CustomerServiceChannel extends ChatChannel
|
||||||
|
|
||||||
|
final case class OutfitChannel(id: Long) extends ChatChannel
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ class ChatService(context: ActorContext[ChatService.Command]) extends AbstractBe
|
||||||
(channel, message.messageType) match {
|
(channel, message.messageType) match {
|
||||||
case (SquadChannel(_), CMT_SQUAD) => ()
|
case (SquadChannel(_), CMT_SQUAD) => ()
|
||||||
case (SquadChannel(_), CMT_VOICE) if message.contents.startsWith("SH") => ()
|
case (SquadChannel(_), CMT_VOICE) if message.contents.startsWith("SH") => ()
|
||||||
|
case (OutfitChannel(_), CMT_OUTFIT) => ()
|
||||||
case (DefaultChannel, messageType) if messageType != CMT_SQUAD => ()
|
case (DefaultChannel, messageType) if messageType != CMT_SQUAD => ()
|
||||||
case (SpectatorChannel, messageType) if messageType != CMT_SQUAD => ()
|
case (SpectatorChannel, messageType) if messageType != CMT_SQUAD => ()
|
||||||
case _ =>
|
case _ =>
|
||||||
|
|
@ -158,6 +159,9 @@ class ChatService(context: ActorContext[ChatService.Command]) extends AbstractBe
|
||||||
case CMT_SQUAD =>
|
case CMT_SQUAD =>
|
||||||
subs.foreach(_.actor ! MessageResponse(session, message, channel))
|
subs.foreach(_.actor ! MessageResponse(session, message, channel))
|
||||||
|
|
||||||
|
case CMT_OUTFIT =>
|
||||||
|
subs.foreach(_.actor ! MessageResponse(session, message, channel))
|
||||||
|
|
||||||
case CMT_NOTE =>
|
case CMT_NOTE =>
|
||||||
subs
|
subs
|
||||||
.filter(_.sessionSource.session.player.Name == message.recipient)
|
.filter(_.sessionSource.session.player.Name == message.recipient)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue