resolved issues with spectator implants, at least enough that implants should be stable; created an exclusive permission for spectator mode; database changes to persist permissions for different modes

This commit is contained in:
Fate-JH 2024-05-07 11:45:36 -04:00
parent 8e7be33a15
commit e748f45c2f
21 changed files with 206 additions and 78 deletions

View file

@ -0,0 +1,30 @@
/* Original: V008__Scoring.sql, overrode by V011__ScoringPatch2.sql */
CREATE OR REPLACE FUNCTION fn_assistactivity_updateRelatedStats()
RETURNS TRIGGER
AS
$$
DECLARE killerSessionId Int;
DECLARE killerId Int;
DECLARE weaponId Int;
DECLARE out integer;
BEGIN
killerId := NEW.killer_id;
weaponId := NEW.weapon_id;
killerSessionId := proc_sessionnumber_get(killerId);
out := proc_weaponstatsession_addEntryIfNoneWithSessionId(killerId, weaponId, killerSessionId);
BEGIN
UPDATE weaponstatsession
SET assists = assists + 1
WHERE avatar_id = killerId AND weapon_id = weaponId AND session_id = killerSessionId;
END;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
/* New */
CREATE TABLE IF NOT EXISTS "avatarmodepermission" (
"avatar_id" INT NOT NULL REFERENCES avatar (id),
"can_spectate" BOOLEAN NOT NULL DEFAULT FALSE,
"can_gm" BOOLEAN NOT NULL DEFAULT FALSE,
UNIQUE(avatar_id)
);

View file

@ -7,6 +7,8 @@ import akka.actor.typed.{ActorRef, Behavior, PostStop, SupervisorStrategy}
import java.util.concurrent.atomic.AtomicInteger
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.Session
import net.psforever.objects.avatar.ModePermissions
import net.psforever.objects.avatar.scoring.{Assist, Death, EquipmentStat, KDAStat, Kill, Life, ScoreCard, SupportActivity}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.sourcing.{TurretSource, VehicleSource}
@ -958,6 +960,28 @@ object AvatarActor {
out.future
}
def loadSpectatorModePermissions(avatarId: Long): Future[ModePermissions] = {
import ctx._
import scala.concurrent.ExecutionContext.Implicits.global
val out: Promise[ModePermissions] = Promise()
val result = ctx.run(query[persistence.Avatarmodepermission].filter(_.avatarId == lift(avatarId)))
result.onComplete {
case Success(res) =>
res.headOption
.collect {
case perms: persistence.Avatarmodepermission =>
out.completeWith(Future(ModePermissions(perms.canSpectate, perms.canGm)))
}
.orElse {
out.completeWith(Future(ModePermissions()))
None
}
case _ =>
out.completeWith(Future(ModePermissions()))
}
out.future
}
def toAvatar(avatar: persistence.Avatar): Avatar = {
val bep = avatar.bep
val convertedCosmetics = if (BattleRank.showCosmetics(bep)) {
@ -2046,9 +2070,10 @@ class AvatarActor(
shortcuts <- loadShortcuts(avatarId)
saved <- AvatarActor.loadSavedAvatarData(avatarId)
card <- AvatarActor.loadCampaignKdaData(avatarId)
} yield (loadouts, friends, ignored, shortcuts, saved, card)
perms <- AvatarActor.loadSpectatorModePermissions(avatarId)
} yield (loadouts, friends, ignored, shortcuts, saved, card, perms)
result.onComplete {
case Success((loadoutList, friendsList, ignoredList, shortcutList, saved, card)) =>
case Success((loadoutList, friendsList, ignoredList, shortcutList, saved, card, perms)) =>
avatarCopy(
avatar.copy(
loadouts = avatar.loadouts.copy(suit = loadoutList),
@ -2058,7 +2083,8 @@ class AvatarActor(
purchase = AvatarActor.buildCooldownsFromClob(saved.purchaseCooldowns, Avatar.purchaseCooldowns, log),
use = AvatarActor.buildCooldownsFromClob(saved.useCooldowns, Avatar.useCooldowns, log)
),
scorecard = card
scorecard = card,
permissions = perms
)
)
sessionActor ! SessionActor.AvatarLoadingSync(step = 2)
@ -2239,13 +2265,15 @@ class AvatarActor(
if (implant.active) {
deactivateImplant(implant.definition.implantType)
}
session.get.zone.AvatarEvents ! AvatarServiceMessage(
session.get.zone.id,
AvatarAction.SendResponse(
Service.defaultPlayerGUID,
AvatarImplantMessage(session.get.player.GUID, ImplantAction.Initialization, slot, 0)
if (implant.initialized) {
session.get.zone.AvatarEvents ! AvatarServiceMessage(
session.get.zone.id,
AvatarAction.SendResponse(
Service.defaultPlayerGUID,
AvatarImplantMessage(session.get.player.GUID, ImplantAction.Initialization, slot, 0)
)
)
)
}
Some(implant.copy(initialized = false, active = false))
case (None, _) => None
}))

View file

@ -162,7 +162,7 @@ object ChatActor {
sendTo ! SessionActor.SendResponse(CreateShortcutMessage(
guid,
index + 1,
Some(Shortcut.Medkit())
Some(Shortcut.Medkit)
))
}
}

View file

@ -4,6 +4,7 @@ package net.psforever.actors.session.normal
import akka.actor.ActorContext
import net.psforever.actors.session.support.{ChatFunctions, ChatOperations, SessionData}
import net.psforever.objects.Session
import net.psforever.objects.avatar.ModePermissions
import net.psforever.packet.game.{ChatMsg, SetChatFilterMessage}
import net.psforever.services.chat.DefaultChannel
import net.psforever.types.ChatMessageType
@ -18,10 +19,12 @@ object ChatLogic {
class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) extends ChatFunctions {
def sessionLogic: SessionData = ops.sessionLogic
def handleChatMsg(session: Session, message: ChatMsg): Unit = {
def handleChatMsg(message: ChatMsg): Unit = {
import net.psforever.types.ChatMessageType._
val gmCommandAllowed =
session.account.gm || Config.app.development.unprivilegedGmCommands.contains(message.messageType)
val isAlive = if (player != null) player.isAlive else false
val perms = if (avatar != null) avatar.permissions else ModePermissions()
val gmCommandAllowed = (session.account.gm && perms.canGM) ||
Config.app.development.unprivilegedGmCommands.contains(message.messageType)
(message.messageType, message.recipient.trim, message.contents.trim) match {
/** Messages starting with ! are custom chat commands */
case (_, _, contents) if contents.startsWith("!") &&
@ -42,7 +45,7 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
case (CMT_SPEED, _, contents) if gmCommandAllowed =>
ops.commandSpeed(message, contents)
case (CMT_TOGGLESPECTATORMODE, _, contents) if gmCommandAllowed =>
case (CMT_TOGGLESPECTATORMODE, _, contents) if isAlive && (gmCommandAllowed || perms.canSpectate) =>
ops.commandToggleSpectatorMode(session, contents)
case (CMT_RECALL, _, _) =>
@ -82,19 +85,19 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
case (CMT_GMBROADCASTPOPUP, _, _) if gmCommandAllowed =>
ops.commandSendToRecipient(session, message, DefaultChannel)
case (CMT_OPEN, _, _) if !session.player.silenced =>
case (CMT_OPEN, _, _) if !player.silenced =>
ops.commandSendToRecipient(session, message, DefaultChannel)
case (CMT_VOICE, _, contents) =>
ops.commandVoice(session, message, contents, DefaultChannel)
case (CMT_TELL, _, _) if !session.player.silenced =>
case (CMT_TELL, _, _) if !player.silenced =>
ops.commandTellOrIgnore(session, message, DefaultChannel)
case (CMT_BROADCAST, _, _) if !session.player.silenced =>
case (CMT_BROADCAST, _, _) if !player.silenced =>
ops.commandSendToRecipient(session, message, DefaultChannel)
case (CMT_PLATOON, _, _) if !session.player.silenced =>
case (CMT_PLATOON, _, _) if !player.silenced =>
ops.commandSendToRecipient(session, message, DefaultChannel)
case (CMT_COMMAND, _, _) if gmCommandAllowed =>
@ -151,7 +154,7 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
val SetChatFilterMessage(_, _, _) = pkt
}
def handleIncomingMessage(session: Session, message: ChatMsg, fromSession: Session): Unit = {
def handleIncomingMessage(message: ChatMsg, fromSession: Session): Unit = {
import ChatMessageType._
message.messageType match {
case CMT_BROADCAST | CMT_SQUAD | CMT_PLATOON | CMT_COMMAND | CMT_NOTE =>
@ -186,7 +189,9 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
case a :: b => (a, b)
case _ => ("", Seq(""))
}
val gmBangCommandAllowed = session.account.gm || Config.app.development.unprivilegedGmBangCommands.contains(command)
val perms = if (avatar != null) avatar.permissions else ModePermissions()
val gmBangCommandAllowed = (session.account.gm && perms.canGM) ||
Config.app.development.unprivilegedGmBangCommands.contains(command)
//try gm commands
val tryGmCommandResult = if (gmBangCommandAllowed) {
command match {

View file

@ -71,7 +71,7 @@ class NormalModeLogic(data: SessionData) extends ModeLogic {
vehicleResponse.handle(toChannel, guid, reply)
case ChatService.MessageResponse(fromSession, message, _) =>
chat.handleIncomingMessage(data.session, message, fromSession)
chat.handleIncomingMessage(message, fromSession)
case SessionActor.SendResponse(packet) =>
data.sendResponse(packet)
@ -313,7 +313,7 @@ class NormalModeLogic(data: SessionData) extends ModeLogic {
data.zoning.spawn.handleSpawnRequest(packet)
case packet: ChatMsg =>
chat.handleChatMsg(data.session, packet)
chat.handleChatMsg(packet)
case packet: SetChatFilterMessage =>
chat.handleChatFilter(packet)

View file

@ -3,6 +3,7 @@ package net.psforever.actors.session.spectator
import akka.actor.{ActorContext, typed}
import net.psforever.actors.session.support.AvatarHandlerFunctions
import net.psforever.packet.game.{AvatarImplantMessage, ImplantAction}
import scala.concurrent.duration._
//
@ -404,6 +405,11 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
case AvatarResponse.FacilityCaptureRewards(buildingId, zoneNumber, cep) =>
ops.facilityCaptureRewards(buildingId, zoneNumber, cep)
case AvatarResponse.SendResponse(pkt: AvatarImplantMessage)
if pkt.player_guid == player.GUID && pkt.action == ImplantAction.Initialization =>
//special spectator implants stay initialized and do not deinitialize
()
case AvatarResponse.SendResponse(msg) =>
sendResponse(msg)

View file

@ -21,7 +21,7 @@ object ChatLogic {
class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) extends ChatFunctions {
def sessionLogic: SessionData = ops.sessionLogic
def handleChatMsg(session: Session, message: ChatMsg): Unit = {
def handleChatMsg(message: ChatMsg): Unit = {
import ChatMessageType._
(message.messageType, message.recipient.trim, message.contents.trim) match {
/** Messages starting with ! are custom chat commands */
@ -93,7 +93,7 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
val SetChatFilterMessage(_, _, _) = pkt
}
def handleIncomingMessage(session: Session, message: ChatMsg, fromSession: Session): Unit = {
def handleIncomingMessage(message: ChatMsg, fromSession: Session): Unit = {
import ChatMessageType._
message.messageType match {
case CMT_BROADCAST | CMT_SQUAD | CMT_PLATOON | CMT_COMMAND | CMT_NOTE =>
@ -125,7 +125,6 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
case "list" => ops.customCommandList(session, params, message)
case "nearby" => ops.customCommandNearby(session)
case "loc" => ops.customCommandLoc(session, message)
case "macro" => ops.customCommandMacro(session, params)
case _ => false
}
} else {

View file

@ -6,7 +6,7 @@ import net.psforever.actors.session.AvatarActor
import net.psforever.actors.session.support.{GeneralFunctions, GeneralOperations, SessionData}
import net.psforever.login.WorldSession.RemoveOldEquipmentFromInventory
import net.psforever.objects.{Account, BoomerDeployable, BoomerTrigger, GlobalDefinitions, LivePlayerList, PlanetSideGameObject, Player, TelepadDeployable, Tool, Vehicle}
import net.psforever.objects.avatar.Avatar
import net.psforever.objects.avatar.{Avatar, Implant}
import net.psforever.objects.ballistics.Projectile
import net.psforever.objects.ce.{Deployable, TelepadLike}
import net.psforever.objects.definition.{BasicDefinition, KitDefinition, SpecialExoSuitDefinition}
@ -42,6 +42,8 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
private var customImplants = SpectatorModeLogic.SpectatorImplants.map(_.get)
private var additionalImplants: Seq[CreateShortcutMessage] = Seq()
def handleConnectToWorldRequest(pkt: ConnectToWorldRequestMessage): Unit = { /* intentionally blank */ }
def handleCharacterCreateRequest(pkt: CharacterCreateRequestMessage): Unit = { /* intentionally blank */ }
@ -181,9 +183,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
customImplants.lift(slot)
.collect {
case implant if implant.active =>
customImplants = customImplants.updated(slot, implant.copy(active = false))
sendResponse(AvatarImplantMessage(player.GUID, ImplantAction.Activation, slot, 0))
sendResponse(PlanetsideAttributeMessage(player.GUID, 28, implant.definition.implantType.value * 2))
customImplantOff(slot, implant)
case implant =>
customImplants = customImplants.updated(slot, implant.copy(active = true))
sendResponse(AvatarImplantMessage(player.GUID, ImplantAction.Activation, slot, 1))
@ -349,7 +349,34 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
val BindPlayerMessage(_, _, _, _, _, _, _, _) = pkt
}
def handleCreateShortcut(pkt: CreateShortcutMessage): Unit = { /* intentionally blank */ }
def handleCreateShortcut(pkt: CreateShortcutMessage): Unit = {
val CreateShortcutMessage(_, slot, wouldBeImplant) = pkt
val pguid = player.GUID
if (slot > 1 && slot < 5) {
//protected
customImplants
.zipWithIndex
.find { case (_, index) => index + 2 == slot}
.foreach {
case (implant, _) if wouldBeImplant.contains(implant.definition.implantType.shortcut) => ()
case (implant, _) if implant.active =>
sendResponse(CreateShortcutMessage(pguid, slot, Some(implant.definition.implantType.shortcut)))
customImplantOff(slot, implant)
case (implant, _) =>
sendResponse(CreateShortcutMessage(pguid, slot, Some(implant.definition.implantType.shortcut)))
}
} else {
additionalImplants.indexWhere(_.slot == slot) match {
case -1 => ()
case index =>
additionalImplants = additionalImplants.take(index) ++ additionalImplants.drop(index + 1)
}
wouldBeImplant.collect {
case _ =>
additionalImplants = additionalImplants :+ pkt
}
}
}
def handleChangeShortcutBank(pkt: ChangeShortcutBankMessage): Unit = { /* intentionally blank */ }
@ -629,4 +656,23 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
tplayer.death_by = -1
sessionLogic.accountPersistence ! AccountPersistenceService.Kick(tplayer.Name)
}
private def customImplantOff(slot: Int, implant: Implant): Unit = {
customImplants = customImplants.updated(slot, implant.copy(active = false))
sendResponse(AvatarImplantMessage(player.GUID, ImplantAction.Activation, slot, 0))
sendResponse(PlanetsideAttributeMessage(player.GUID, 28, implant.definition.implantType.value * 2))
}
override protected[session] def stop(): Unit = {
val pguid = player.GUID
//set only originally blank slots blank again; rest will be overwrote later
val originalBlankSlots = ((player.avatar.shortcuts.head, 1) +:
player.avatar.shortcuts.drop(4).zipWithIndex.map { case (scut, slot) => (scut, slot + 4) })
.collect { case (None, slot) => slot }
additionalImplants
.map(_.slot)
.filter(originalBlankSlots.contains)
.map(slot => CreateShortcutMessage(pguid, slot, None))
.foreach(sendResponse)
}
}

View file

@ -61,7 +61,6 @@ class SpectatorModeLogic(data: SessionData) extends ModeLogic {
sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, "on"))
sendResponse(ChatMsg(ChatMessageType.UNK_227, "@SpectatorEnabled"))
continent.actor ! ZoneActor.RemoveFromBlockMap(player)
data.general.avatarActor ! AvatarActor.DeactivateActiveImplants()
continent
.GUID(data.terminals.usingMedicalTerminal)
.foreach { case term: Terminal with ProximityUnit =>
@ -116,11 +115,21 @@ class SpectatorModeLogic(data: SessionData) extends ModeLogic {
val originalEvent = player.History.headOption
player.ClearHistory()
player.LogActivity(originalEvent)
//
player.spectator = true
player.avatar
.shortcuts
.zipWithIndex
.collect { case (Some(_), index) => index + 1 }
.map(CreateShortcutMessage(pguid, _, None))
.foreach(sendResponse)
player.avatar.implants
.collect { case Some(implant) if implant.active =>
data.general.avatarActor ! AvatarActor.DeactivateImplant(implant.definition.implantType)
}
if (player.silenced) {
data.chat.commandIncomingSilence(session, ChatMsg(ChatMessageType.CMT_SILENCE, "player 0"))
}
//
player.spectator = true
data.chat.JoinChannel(SpectatorChannel)
val newPlayer = SpectatorModeLogic.spectatorCharacter(player)
val cud = new SimpleItem(GlobalDefinitions.command_detonater)
@ -147,13 +156,21 @@ class SpectatorModeLogic(data: SessionData) extends ModeLogic {
import scala.concurrent.duration._
val player = data.player
val zoning = data.zoning
val pguid = player.GUID
val sendResponse: PlanetSidePacket => Unit = data.sendResponse
//
data.general.stop()
player.avatar.shortcuts.slice(1, 4)
.zipWithIndex
.collect { case (None, slot) => slot + 1 } //set only actual blank slots blank
.map(CreateShortcutMessage(pguid, _, None))
.foreach(sendResponse)
data.chat.LeaveChannel(SpectatorChannel)
player.spectator = false
sendResponse(ObjectDeleteMessage(player.avatar.locker.GUID, 0)) //free up the slot (from cud)
sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, "off"))
sendResponse(ChatMsg(ChatMessageType.UNK_227, "@SpectatorDisabled"))
zoning.zoneReload = true
zoning.spawn.randomRespawn(0.seconds) //to sanctuary
}
@ -184,7 +201,7 @@ class SpectatorModeLogic(data: SessionData) extends ModeLogic {
vehicleResponse.handle(toChannel, guid, reply)
case ChatService.MessageResponse(fromSession, message, _) =>
chat.handleIncomingMessage(data.session, message, fromSession)
chat.handleIncomingMessage(message, fromSession)
case SessionActor.SendResponse(packet) =>
data.sendResponse(packet)
@ -417,7 +434,7 @@ class SpectatorModeLogic(data: SessionData) extends ModeLogic {
data.zoning.spawn.handleSpawnRequest(packet)
case packet: ChatMsg =>
chat.handleChatMsg(data.session, packet)
chat.handleChatMsg(packet)
case packet: SetChatFilterMessage =>
chat.handleChatFilter(packet)

View file

@ -43,11 +43,11 @@ import net.psforever.zones.Zones
trait ChatFunctions extends CommonSessionInterfacingFunctionality {
def ops: ChatOperations
def handleChatMsg(session: Session, message: ChatMsg): Unit
def handleChatMsg(message: ChatMsg): Unit
def handleChatFilter(pkt: SetChatFilterMessage): Unit
def handleIncomingMessage(session: Session, message: ChatMsg, fromSession: Session): Unit
def handleIncomingMessage(message: ChatMsg, fromSession: Session): Unit
}
class ChatOperations(
@ -811,7 +811,7 @@ class ChatOperations(
sendResponse(CreateShortcutMessage(
guid,
index + 1,
Some(Shortcut.Medkit())
Some(Shortcut.Medkit)
))
}
}

View file

@ -41,7 +41,7 @@ trait CommonSessionInterfacingFunctionality {
protected def sendResponse(pkt: PlanetSideGamePacket): Unit = sessionLogic.sendResponse(pkt)
protected[support] def actionsToCancel(): Unit = { /* to override */ }
protected[session] def actionsToCancel(): Unit = { /* to override */ }
protected[support] def stop(): Unit = { /* to override */ }
protected[session] def stop(): Unit = { /* to override */ }
}

View file

@ -741,7 +741,7 @@ class GeneralOperations(
sendResponse(ChatMsg(ChatMessageType.UNK_227, wideContents=false, "", "@charsaved", None))
}
override protected[support] def actionsToCancel(): Unit = {
override protected[session] def actionsToCancel(): Unit = {
progressBarValue = None
kitToBeUsed = None
collisionHistory.clear()

View file

@ -208,7 +208,7 @@ class SessionTerminalHandlers(
)
}
override protected[support] def actionsToCancel(): Unit = {
override protected[session] def actionsToCancel(): Unit = {
lastTerminalOrderFulfillment = true
usingMedicalTerminal = None
}

View file

@ -323,7 +323,7 @@ class WeaponAndProjectileOperations(
ToDatabase.reportToolDischarge(avatarId, EquipmentStat(weaponId, fired, landed, 0, 0))
}
override protected[support] def actionsToCancel(): Unit = {
override protected[session] def actionsToCancel(): Unit = {
shootingStart.clear()
shootingStop.clear()
(prefire ++ shooting).foreach { guid =>

View file

@ -3142,17 +3142,11 @@ class ZoningOperations(
* Set up and dispatch a list of `CreateShortcutMessage` packets and a single `ChangeShortcutBankMessage` packet.
*/
def initializeShortcutsAndBank(guid: PlanetSideGUID): Unit = {
avatar.shortcuts
.zipWithIndex
.collect { case (Some(shortcut), index) =>
sendResponse(CreateShortcutMessage(
guid,
index + 1,
Some(AvatarShortcut.convert(shortcut))
))
}
sendResponse(ChangeShortcutBankMessage(guid, 0))
initializeShortcutsAndBank(guid, avatar.shortcuts)
}
/**
* Set up and dispatch a list of `CreateShortcutMessage` packets and a single `ChangeShortcutBankMessage` packet.
*/
def initializeShortcutsAndBank(guid: PlanetSideGUID, shortcuts: Array[Option[AvatarShortcut]]): Unit = {
shortcuts
.zipWithIndex

View file

@ -19,7 +19,7 @@ import net.psforever.objects.vital.{HealFromEquipment, InGameActivity, RepairFro
import net.psforever.objects.vital.damage.DamageProfile
import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.vital.resolution.DamageResistanceModel
import net.psforever.objects.zones.blockmap.{BlockMapEntity, SectorPopulation}
import net.psforever.objects.zones.blockmap.BlockMapEntity
import net.psforever.objects.zones.{InteractsWithZone, ZoneAware, Zoning}
import net.psforever.types._
@ -47,7 +47,7 @@ class Player(var avatar: Avatar)
new WithGantry(avatar.name),
new WithMovementTrigger()
)))
interaction(new InteractWithMinesUnlessSpectating(obj = this, range = 10))
interaction(new InteractWithMines(range = 10))
interaction(new InteractWithTurrets())
interaction(new InteractWithRadiationClouds(range = 10f, Some(this)))
@ -653,14 +653,3 @@ object Player {
false
}
}
private class InteractWithMinesUnlessSpectating(
private val obj: Player,
override val range: Float
) extends InteractWithMines(range) {
override def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = {
if (!obj.spectator) {
super.interaction(sector, target)
}
}
}

View file

@ -115,6 +115,11 @@ case class MemberLists(
ignored: List[Ignored] = List[Ignored]()
)
case class ModePermissions(
canSpectate: Boolean = false,
canGM: Boolean = false
)
case class Avatar(
/** unique identifier corresponding to a database table row index */
id: Int,
@ -134,7 +139,8 @@ case class Avatar(
loadouts: Loadouts = Loadouts(),
cooldowns: Cooldowns = Cooldowns(),
people: MemberLists = MemberLists(),
scorecard: ScoreCard = new ScoreCard()
scorecard: ScoreCard = new ScoreCard(),
permissions: ModePermissions = ModePermissions()
) {
assert(bep >= 0)
assert(cep >= 0)

View file

@ -30,7 +30,7 @@ object Shortcut {
*/
def convert(shortcut: Shortcut): GameShortcut = {
shortcut.tile match {
case "medkit" => GameShortcut.Medkit()
case "medkit" => GameShortcut.Medkit
case "shortcut_macro" => GameShortcut.Macro(shortcut.effect1, shortcut.effect2)
case _ => GameShortcut.Implant(shortcut.tile)
}
@ -67,7 +67,7 @@ object Shortcut {
*/
private def typeEquals(a: Shortcut, b: GameShortcut): Boolean = {
b match {
case GameShortcut.Medkit() => true
case GameShortcut.Medkit => true
case GameShortcut.Macro(x, y) => x.equals(a.effect1) && y.equals(a.effect2)
case GameShortcut.Implant(tile) => tile.equals(a.tile)
case _ => true

View file

@ -71,7 +71,7 @@ final case class CreateShortcutMessage(
object Shortcut extends Marshallable[Shortcut] {
/** Preset for the medkit quick-use option. */
final case class Medkit() extends Shortcut(code=0) {
case object Medkit extends Shortcut(code=0) {
def tile = "medkit"
}
@ -98,14 +98,14 @@ object Shortcut extends Marshallable[Shortcut] {
/**
* Main transcoder for medkit shortcuts.
*/
val medkitCodec: Codec[Medkit] = (
val medkitCodec: Codec[Shortcut] = (
("tile" | PacketHelpers.encodedStringAligned(adjustment=5)) ::
("effect1" | PacketHelpers.encodedWideString) ::
("effect2" | PacketHelpers.encodedWideString)
).xmap[Medkit](
_ => Medkit(),
).xmap[Shortcut](
_ => Medkit,
{
case Medkit() => "medkit" :: "" :: "" :: HNil
case Medkit => "medkit" :: "" :: "" :: HNil
}
)

View file

@ -0,0 +1,8 @@
// Copyright (c) 2024 PSForever
package net.psforever.persistence
case class Avatarmodepermission(
avatarId: Int,
canSpectate: Boolean = false,
canGm: Boolean = false
)

View file

@ -19,7 +19,7 @@ class CreateShortcutMessageTest extends Specification {
player_guid mustEqual PlanetSideGUID(4210)
slot mustEqual 1
shortcut match {
case Some(Shortcut.Medkit()) => ok
case Some(Shortcut.Medkit) => ok
case _ => ko
}
case _ =>
@ -53,7 +53,7 @@ class CreateShortcutMessageTest extends Specification {
}
"encode (medkit)" in {
val msg = CreateShortcutMessage(PlanetSideGUID(4210), 1, Some(Shortcut.Medkit()))
val msg = CreateShortcutMessage(PlanetSideGUID(4210), 1, Some(Shortcut.Medkit))
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual stringMedkit
@ -90,8 +90,8 @@ class CreateShortcutMessageTest extends Specification {
ImplantType.DarklightVision.shortcut.tile mustEqual "darklight_vision"
ImplantType.Targeting.shortcut.code mustEqual 2
ImplantType.Targeting.shortcut.tile mustEqual "targeting"
Shortcut.Medkit().code mustEqual 0
Shortcut.Medkit().tile mustEqual "medkit"
Shortcut.Medkit.code mustEqual 0
Shortcut.Medkit.tile mustEqual "medkit"
ImplantType.MeleeBooster.shortcut.code mustEqual 2
ImplantType.MeleeBooster.shortcut.tile mustEqual "melee_booster"
ImplantType.PersonalShield.shortcut.code mustEqual 2