Merge branch 'master' into fix-some-commands

This commit is contained in:
Resaec 2025-06-10 22:35:37 +02:00
commit 66362b38d7
28 changed files with 194 additions and 45 deletions

View file

@ -96,3 +96,12 @@ add_property suppressor holstertime 600
add_property trek equiptime 500
add_property trek holstertime 500
add_property vulture requirement_award0 false
add_property aphelion allowed false
add_property aphelion_flight allowed false
add_property aphelion_gunner allowed false
add_property colossus allowed false
add_property colossus_flight allowed false
add_property colossus_gunner allowed false
add_property peregrine allowed false
add_property peregrine_flight allowed false
add_property peregrine_gunner allowed false

View file

@ -88,6 +88,9 @@ game {
# Purchases timers for the battleframe robotics vehicles all update at the same time when either of them would update
shared-bfr-cooldown = yes
# How long the countdown timer is when a facility (main overworld base) is hacked
facility-hack-time = 15.minutes
# HART system, shuttles and facilities
hart {
# How long the shuttle is not boarding passengers (going through the motions)

View file

@ -3039,6 +3039,8 @@ class AvatarActor(
if (exp > 0L) {
setBep(avatar.bep + exp, msg)
zone.actor ! ZoneActor.RewardOurSupporters(playerSource, historyTranscript, killStat, exp)
zone.AvatarEvents ! AvatarServiceMessage(
player.Name, AvatarAction.ShareKillExperienceWithSquad(player, exp))
}
}

View file

@ -28,21 +28,21 @@ class SquadHandlerLogic(val ops: SessionSquadHandlers, implicit val context: Act
/* packet */
def handleSquadDefinitionAction(pkt: SquadDefinitionActionMessage): Unit = {
if (!player.spectator) {
val SquadDefinitionActionMessage(u1, u2, action) = pkt
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Definition(u1, u2, action))
}
/*if (!player.spectator) {
val SquadDefinitionActionMessage(u1, u2, action) = pkt
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Definition(u1, u2, action))
}*/
}
def handleSquadMemberRequest(pkt: SquadMembershipRequest): Unit = {
if (!player.spectator) {
/* if (!player.spectator) {
val SquadMembershipRequest(request_type, char_id, unk3, player_name, unk5) = pkt
squadService ! SquadServiceMessage(
player,
continent,
SquadServiceAction.Membership(request_type, char_id, unk3, player_name, unk5)
)
}
}*/
}
def handleSquadWaypointRequest(pkt: SquadWaypointRequest): Unit = {

View file

@ -473,6 +473,9 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
case AvatarResponse.FacilityCaptureRewards(buildingId, zoneNumber, cep) =>
ops.facilityCaptureRewards(buildingId, zoneNumber, cep)
case AvatarResponse.ShareKillExperienceWithSquad(killer, exp) =>
ops.shareKillExperienceWithSquad(killer, exp)
case AvatarResponse.SendResponse(msg) =>
sendResponse(msg)

View file

@ -18,6 +18,7 @@ import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.containable.Containable
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.generator.Generator
import net.psforever.objects.serverobject.interior.Sidedness.OutsideOf
import net.psforever.objects.serverobject.llu.CaptureFlag
import net.psforever.objects.serverobject.locks.IFFLock
import net.psforever.objects.serverobject.mblocker.Locker
@ -406,7 +407,13 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
}
log.info(s"${player.Name} is constructing a $ammoType deployable")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
ops.handleDeployObject(continent, ammoType, pos, orient, player.WhichSide, player.Faction, player, obj)
if (ammoType == DeployedItem.spitfire_turret || ammoType == DeployedItem.spitfire_cloaked ||
ammoType == DeployedItem.spitfire_aa) {
ops.handleDeployObject(continent, ammoType, pos, orient, OutsideOf, player.Faction, player, obj)
}
else {
ops.handleDeployObject(continent, ammoType, pos, orient, player.WhichSide, player.Faction, player, obj)
}
case Some(obj) =>
log.warn(s"DeployObject: what is $obj, ${player.Name}? It's not a construction tool!")
case None =>

View file

@ -28,17 +28,17 @@ class SquadHandlerLogic(val ops: SessionSquadHandlers, implicit val context: Act
/* packet */
def handleSquadDefinitionAction(pkt: SquadDefinitionActionMessage): Unit = {
val SquadDefinitionActionMessage(u1, u2, action) = pkt
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Definition(u1, u2, action))
/*val SquadDefinitionActionMessage(u1, u2, action) = pkt
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Definition(u1, u2, action))*/
}
def handleSquadMemberRequest(pkt: SquadMembershipRequest): Unit = {
val SquadMembershipRequest(request_type, char_id, unk3, player_name, unk5) = pkt
/*val SquadMembershipRequest(request_type, char_id, unk3, player_name, unk5) = pkt
squadService ! SquadServiceMessage(
player,
continent,
SquadServiceAction.Membership(request_type, char_id, unk3, player_name, unk5)
)
)*/
}
def handleSquadWaypointRequest(pkt: SquadWaypointRequest): Unit = {

View file

@ -8,6 +8,7 @@ import net.psforever.objects.avatar.SpecialCarry
import net.psforever.objects.{GlobalDefinitions, Player, Tool, Vehicle, Vehicles}
import net.psforever.objects.equipment.{Equipment, JammableMountedWeapons, JammableUnit}
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.serverobject.interior.Sidedness.OutsideOf
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
@ -187,6 +188,7 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
s"${player.Sex.possessive} ride"
}
log.info(s"${player.Name} has been kicked from $typeOfRide!")
player.WhichSide = OutsideOf
case VehicleResponse.KickPassenger(_, wasKickedByDriver, _) =>
//seat number (first field) seems to be correct if passenger is kicked manually by driver

View file

@ -15,6 +15,7 @@ import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.types.ChatMessageType.CMT_QUIT
import org.log4s.Logger
import java.util.concurrent.{Executors, TimeUnit}
import scala.annotation.unused
import scala.collection.{Seq, mutable}
import scala.concurrent.duration._
@ -59,6 +60,7 @@ class ChatOperations(
private var channels: List[ChatChannel] = List()
private var silenceTimer: Cancellable = Default.Cancellable
private[session] var transitoryCommandEntered: Option[ChatMessageType] = None
private val scheduler = Executors.newScheduledThreadPool(2)
/**
* when another player is listed as one of our ignored players,
* and that other player sends an emote,
@ -352,8 +354,25 @@ class ChatOperations(
//evaluate results
(resolvedFacilities, resolvedFaction, resolvedTimer) match {
case (Some(buildings), Some(faction), Some(_)) =>
buildings.foreach { building =>
//TODO implement timer
//schedule processing of buildings with a delay
processBuildingsWithDelay(buildings, faction, 1000) //delay of 1000ms between each building operation
true
case _ =>
false
}
}
def processBuildingsWithDelay(
buildings: Seq[Building],
faction: PlanetSideEmpire.Value,
delayMillis: Long
): Unit = {
val buildingIterator = buildings.iterator
scheduler.scheduleAtFixedRate(
() => {
if (buildingIterator.hasNext) {
val building = buildingIterator.next()
val terminal = building.CaptureTerminal.get
val zone = building.Zone
val zoneActor = zone.actor
@ -373,10 +392,11 @@ class ChatOperations(
//push for map updates again
zoneActor ! ZoneActor.ZoneMapUpdate()
}
true
case _ =>
false
}
},
0,
delayMillis,
TimeUnit.MILLISECONDS
)
}
def commandVoice(session: Session, message: ChatMsg, contents: String, toChannel: ChatChannel): Unit = {

View file

@ -3,7 +3,7 @@ package net.psforever.actors.session.support
import akka.actor.{ActorContext, typed}
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.{Default, PlanetSideGameObject}
import net.psforever.objects.{Default, PlanetSideGameObject, Player}
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
import net.psforever.packet.game.objectcreate.ConstructorData
import net.psforever.objects.zones.exp
@ -59,7 +59,7 @@ class SessionAvatarHandlers(
//TODO squad services deactivated, participation trophy rewards for now - 11-20-2023
//must be in a squad to earn experience
val charId = player.CharId
val squadUI = sessionLogic.squad.squadUI
/*val squadUI = sessionLogic.squad.squadUI
val participation = continent
.Building(buildingId)
.map { building =>
@ -117,7 +117,35 @@ class SessionAvatarHandlers(
exp.ToDatabase.reportFacilityCapture(charId, buildingId, zoneNumber, modifiedExp, expType="bep")
avatarActor ! AvatarActor.AwardFacilityCaptureBep(modifiedExp)
Some(modifiedExp)
}*/
//if not in squad (temporary)
exp.ToDatabase.reportFacilityCapture(charId, zoneNumber, buildingId, cep, expType="bep")
avatarActor ! AvatarActor.AwardFacilityCaptureBep(cep)
}
/**
*
* @param killer the player who got the kill
* @param exp the amount of bep they received for the kill
* Squad members of a "killer" will receive a split of the experience if they are both alive
* and in the same zone as the killer. The amount received is
* based on the size of the squad. Each squad member that meets the criteria will receive a fractional split.
*/
def shareKillExperienceWithSquad(killer: Player, exp: Long): Unit = {
//TODO consider squad experience waypoint in exp calculation
val squadUI = sessionLogic.squad.squadUI
val squadSize = squadUI.size
if (squadSize > 1) {
val expSplit = exp / squadSize
val squadMembers = squadUI.filterNot(_._1 == killer.CharId).map { case (_, member) => member }.toList.map(_.name)
val playersInZone = killer.Zone.Players.map { avatar => (avatar.id, avatar.basic.name) }
val squadMembersHere = playersInZone.filter(member => squadMembers.contains(member._2))
squadMembersHere.foreach { member =>
killer.Zone.AvatarEvents ! AvatarServiceMessage(
member._2,
AvatarAction.AwardBep(member._1, expSplit, ExperienceType.Normal))
}
}
}
/**

View file

@ -3,6 +3,7 @@ package net.psforever.actors.session.support
import akka.actor.{ActorContext, typed}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.interior.Sidedness.OutsideOf
import net.psforever.objects.{PlanetSideGameObject, Tool, Vehicle}
import net.psforever.objects.vehicles.{CargoBehavior, MountableWeapons}
import net.psforever.objects.vital.InGameHistory
@ -197,6 +198,7 @@ class SessionMountHandlers(
*/
def DismountVehicleAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
DismountAction(tplayer, obj, seatNum)
tplayer.WhichSide = OutsideOf
//until vehicles maintain synchronized momentum without a driver
obj match {
case v: Vehicle

View file

@ -343,7 +343,7 @@ class WeaponAndProjectileOperations(
case citem: ConstructionItem =>
log.info(s"${player.Name} switched ${player.Sex.possessive} ${obj.Definition.Name} to construct ${citem.AmmoType} (mode #$modeIndex)")
case _ =>
log.info(s"${player.Name} changed ${player.Sex.possessive} her ${obj.Definition.Name}'s fire mode to #$modeIndex")
log.info(s"${player.Name} changed ${player.Sex.possessive} ${obj.Definition.Name}'s fire mode to #$modeIndex")
}
sendResponse(ChangeFireModeMessage(item_guid, modeIndex))
continent.AvatarEvents ! AvatarServiceMessage(

View file

@ -2494,6 +2494,15 @@ class ZoningOperations(
reclaimOurDeployables(continent.DeployableList, player.Name, reassignDeployablesTo(player.GUID))
)
)
//do this to make my deployed telepad appear that way
if (continent.DeployableList.exists(telepad => telepad.Definition == GlobalDefinitions.router_telepad_deployable
&& telepad.OwnerName.contains(player.Name)))
{
continent.Vehicles.filter(router => router.Definition == GlobalDefinitions.router && router.Faction == player.Faction)
.foreach { obj =>
sessionLogic.general.toggleTeleportSystem(obj, TelepadLike.AppraiseTeleportationSystem(obj, continent))
}
}
//begin looking for conditions to set the avatar
context.system.scheduler.scheduleOnce(delay = 250 millisecond, context.self, SessionActor.SetCurrentAvatar(player, 200))
}

View file

@ -182,7 +182,8 @@ class ZoneActor(
case ZoneMapUpdate() =>
zone.Buildings
.filter(_._2.BuildingType == StructureType.Facility)
.filter(building =>
building._2.BuildingType == StructureType.Facility || building._2.BuildingType == StructureType.Tower)
.values
.foreach(_.Actor ! BuildingActor.MapUpdate())
Behaviors.same

View file

@ -42,11 +42,13 @@ object Vehicles {
case Some(tplayer) =>
tplayer.avatar.vehicle = Some(vehicle.GUID)
vehicle.AssignOwnership(playerOpt)
val locked = VehicleLockState.Locked.id
Array(0, 3).foreach(group => vehicle.PermissionGroup(group, locked))
Vehicles.ReloadAccessPermissions(vehicle, tplayer.Faction.toString)
vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
vehicle.Zone.id,
VehicleAction.Ownership(tplayer.GUID, vehicle.GUID)
)
Vehicles.ReloadAccessPermissions(vehicle, tplayer.Faction.toString)
Some(vehicle)
case None =>
None

View file

@ -68,7 +68,12 @@ trait DeployableBehavior {
if DeployableObject.OwnerGuid.nonEmpty =>
val obj = DeployableObject
if (constructed.contains(true)) {
loseOwnership(obj, PlanetSideEmpire.NEUTRAL)
if (obj.Definition.DeployCategory == DeployableCategory.Boomers) {
loseOwnership(obj, PlanetSideEmpire.NEUTRAL)
}
else {
loseOwnership(obj, obj.Faction)
}
} else {
obj.OwnerGuid = None
}
@ -290,11 +295,17 @@ object DeployableBehavior {
originalFaction.toString,
LocalAction.DeployableMapIcon(Service.defaultPlayerGUID, DeploymentAction.Dismiss, info)
)
//remove deployable from original owner's toolbox and UI counter
zone.AllPlayers.filter(p => obj.OriginalOwnerName.contains(p.Name))
.foreach { originalOwner =>
originalOwner.avatar.deployables.Remove(obj)
originalOwner.Zone.LocalEvents ! LocalServiceMessage(originalOwner.Name, LocalAction.DeployableUIFor(obj.Definition.Item))
}
//display to the given faction
localEvents ! LocalServiceMessage(
toFaction.toString,
LocalAction.DeployableMapIcon(Service.defaultPlayerGUID, DeploymentAction.Build, info)
)
}
//display to the given faction
localEvents ! LocalServiceMessage(
toFaction.toString,
LocalAction.DeployableMapIcon(Service.defaultPlayerGUID, DeploymentAction.Build, info)
)
}
}

View file

@ -324,7 +324,7 @@ object EffectTarget {
def FacilityTurretValidateAircraftTarget(target: PlanetSideGameObject): Boolean =
target match {
case v: Vehicle
if GlobalDefinitions.isFlightVehicle(v.Definition) && v.Seats.values.exists(_.isOccupied) =>
if GlobalDefinitions.isFlightVehicle(v.Definition) && v.Seats.values.exists(_.isOccupied) && v.Definition != GlobalDefinitions.mosquito =>
val now = System.currentTimeMillis()
val pos = v.Position
lazy val sector = v.Zone.blockMap.sector(pos, range = 51f)
@ -332,10 +332,10 @@ object EffectTarget {
.collect { case t: Tool => now - t.LastDischarge }
.exists(_ < 2000L)
// from the perspective of a mosquito, at 5th gauge, forward velocity is 59~60
lazy val movingFast = Vector3.MagnitudeSquared(v.Velocity.getOrElse(Vector3.Zero).xy) > 3721f //61
//lazy val movingFast = Vector3.MagnitudeSquared(v.Velocity.getOrElse(Vector3.Zero).xy) > 3721f //61
lazy val isMoving = v.isMoving(test = 1d)
if (v.Cloaked || radarCloakedAms(sector, pos) || radarCloakedAegis(sector, pos)) false
else if (v.Definition == GlobalDefinitions.mosquito) movingFast
//else if (v.Definition == GlobalDefinitions.mosquito) movingFast
else v.isFlying && (isMoving || entityTookDamage(v, now) || usedEquipment)
case _ =>
false

View file

@ -19,6 +19,7 @@ import net.psforever.objects.vital.base.DamageType
import net.psforever.objects.vital.etc.ExplodingRadialDegrade
import net.psforever.objects.vital.prop.DamageWithPosition
import net.psforever.types.{ExoSuitType, Vector3}
import net.psforever.util.Config
import scala.collection.mutable
import scala.concurrent.duration._
@ -485,7 +486,7 @@ object GlobalDefinitionsMiscellaneous {
repair_silo.TargetValidation += EffectTarget.Category.Vehicle -> EffectTarget.Validation.RepairSilo
repair_silo.Damageable = false
repair_silo.Repairable = false
recharge_terminal.Name = "recharge_terminal"
recharge_terminal.Interval = 1000
recharge_terminal.UseRadius = 20
@ -707,7 +708,7 @@ object GlobalDefinitionsMiscellaneous {
capture_terminal.Name = "capture_terminal"
capture_terminal.Damageable = false
capture_terminal.Repairable = false
capture_terminal.FacilityHackTime = 15.minutes
capture_terminal.FacilityHackTime = Config.app.game.facilityHackTime
secondary_capture.Name = "secondary_capture"
secondary_capture.Damageable = false

View file

@ -82,7 +82,7 @@ object EnvironmentAttribute {
/** only interact with living player characters or vehicles */
def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithPlayersAndVehicles(obj)
def testingDepth(obj: _root_.net.psforever.objects.PlanetSideGameObject): Float = 0f
def testingDepth(obj: _root_.net.psforever.objects.PlanetSideGameObject): Float = 4f
}
/**

View file

@ -41,13 +41,13 @@ trait FacilityHackParticipation extends ParticipationLogic {
.filterNot { case (_, (_, _, t)) => curr - t > hackTime }
.partition { case (p, _) => uniqueList2.contains(p) }
}
val newParticipaants = list
val newParticipants = list
.filterNot { p =>
playerContribution.exists { case (u, _) => p.CharId == u }
}
playerContribution =
vanguardParticipants.map { case (u, (p, d, _)) => (u, (p, d + 1, curr)) } ++
newParticipaants.map { p => (p.CharId, (p, 1, curr)) } ++
newParticipants.map { p => (p.CharId, (p, 1, curr)) } ++
missingParticipants
}
}

View file

@ -28,7 +28,9 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
def TryUpdate(): Unit = {
val list = building.PlayersInSOI
updatePlayers(list)
if (list.nonEmpty) {
updatePlayers(list)
}
val now = System.currentTimeMillis()
if (now - lastInfoRequest > 60000L) {
updatePopulationOverTime(list, now, before = 900000L)
@ -123,7 +125,7 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
hackStart,
completionTime,
opposingFaction,
contributionOpposing
contributionVictor
)
)
//1) experience from killing opposingFaction across duration of hack
@ -249,11 +251,14 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
overallTimeMultiplier *
Config.app.game.experience.cep.rate + competitionBonus
).toLong
//8. reward participants
//Classically, only players in the SOI are rewarded, and the llu runner too
//8. reward participants that are still in the zone
val hackerId = hacker.CharId
val contributingPlayers = contributionVictor
.filter { case (player, _, _) => player.Zone.id == building.Zone.id }
.map { case (player, _, _) => player }
.toList
//terminal hacker (always cep)
if (playersInSoi.exists(_.CharId == hackerId) && flagCarrier.map(_.CharId).getOrElse(0L) != hackerId) {
if (contributingPlayers.exists(_.CharId == hackerId) && flagCarrier.map(_.CharId).getOrElse(0L) != hackerId) {
ToDatabase.reportFacilityCapture(
hackerId,
zoneNumber,
@ -264,7 +269,7 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
events ! AvatarServiceMessage(hacker.Name, AvatarAction.AwardCep(hackerId, finalCep))
}
//bystanders (cep if squad leader, bep otherwise)
playersInSoi
contributingPlayers
.filterNot { _.CharId == hackerId }
.foreach { player =>
val charId = player.CharId
@ -336,7 +341,7 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
val towerRadius = math.pow(tower.Definition.SOIRadius.toDouble * 0.7d, 2d).toFloat
list
.map { case (p, f, kills) =>
val filteredKills = kills.filter { kill => Vector3.DistanceSquared(kill.victim.Position.xy, towerPosition) <= towerRadius }
val filteredKills = kills.filter { kill => Vector3.DistanceSquared(kill.victim.Position.xy, towerPosition) >= towerRadius }
(p, f, filteredKills)
}
.filter { case (_, _, kills) => kills.nonEmpty }

View file

@ -11,11 +11,14 @@ import net.psforever.util.Config
final case class TowerHackParticipation(building: Building) extends FacilityHackParticipation {
def TryUpdate(): Unit = {
val list = building.PlayersInSOI
updatePlayers(building.PlayersInSOI)
if (list.nonEmpty) {
updatePlayers(list)
}
val now = System.currentTimeMillis()
if (now - lastInfoRequest > 60000L) {
updatePopulationOverTime(list, now, before = 300000L)
}
lastInfoRequest = now
}
def RewardFacilityCapture(

View file

@ -29,8 +29,13 @@ object Support {
//setup
val historyList = history.toList
val withKills = victim.progress.kills.nonEmpty
//TODO Issue #1259 - Use another method to capture time of death than current time ("kill shots" aren't working)
/*
val fullLifespan = (historyList.headOption, historyList.lastOption) match {
case (Some(spawn), Some(death)) => death.time - spawn.time
*/
val fullLifespan = historyList.headOption match {
case Some(spawn) => System.currentTimeMillis() - spawn.time
case _ => 0L
}
val recordOfWornTimes = countTimeWhileExoSuitOrMounted(historyList)

View file

@ -458,6 +458,15 @@ class AvatarService(zone: Zone) extends Actor {
)
)
case AvatarAction.ShareKillExperienceWithSquad(killer, exp) =>
AvatarEvents.publish(
AvatarServiceResponse(
s"/$forChannel/Avatar",
Service.defaultPlayerGUID,
AvatarResponse.ShareKillExperienceWithSquad(killer, exp)
)
)
case _ => ()
}

View file

@ -160,6 +160,7 @@ object AvatarAction {
final case class AwardBep(charId: Long, bep: Long, expType: ExperienceType) extends Action
final case class AwardCep(charId: Long, bep: Long) extends Action
final case class FacilityCaptureRewards(building_id: Int, zone_number: Int, exp: Long) extends Action
final case class ShareKillExperienceWithSquad(killer: Player, exp: Long) extends Action
final case class TeardownConnection() extends Action
// final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action

View file

@ -132,4 +132,5 @@ object AvatarResponse {
final case class AwardBep(charId: Long, bep: Long, expType: ExperienceType) extends Response
final case class AwardCep(charId: Long, bep: Long) extends Response
final case class FacilityCaptureRewards(building_id: Int, zone_number: Int, exp: Long) extends Response
final case class ShareKillExperienceWithSquad(killer: Player, exp: Long) extends Response
}

View file

@ -106,7 +106,7 @@ class HackCaptureActor extends Actor {
NotifyHackStateChange(target, isResecured = true)
building.Participation.RewardFacilityCapture(
target.Faction,
faction,
HackCaptureActor.GetAttackingFaction(building, faction),
hacker,
facilityHackTime,
hackTime,
@ -347,6 +347,30 @@ object HackCaptureActor {
.get
}
def GetAttackingFaction(
building: Building,
excludeThisFaction: PlanetSideEmpire.Value
): PlanetSideEmpire.Value = {
// Use PlayerContributionRaw to calculate attacking faction
val factionEfforts = building.Participation
.PlayerContributionRaw
.values
.foldLeft(Array.fill(4)(0L)) { case (efforts, (player, duration, _)) =>
val factionId = player.Faction.id
efforts.update(factionId, efforts(factionId) + duration)
efforts
}
// Exclude the specified faction
factionEfforts.update(excludeThisFaction.id, Long.MinValue)
// Find the faction with the highest contribution
factionEfforts.indices
.maxByOption(factionEfforts)
.map(PlanetSideEmpire.apply)
.getOrElse(PlanetSideEmpire.NEUTRAL)
}
def GetHackingFaction(terminal: CaptureTerminal): Option[PlanetSideEmpire.Value] = {
terminal.HackedBy.map { a => a.player.Faction }
}

View file

@ -163,7 +163,8 @@ case class GameConfig(
doorsCanBeOpenedByMedAppFromThisDistance: Float,
experience: Experience,
maxBattleRank: Int,
promotion: PromotionSystem
promotion: PromotionSystem,
facilityHackTime: FiniteDuration
)
case class InstantActionConfig(