diff --git a/server/src/main/resources/overrides/game_objects0.adb.lst b/server/src/main/resources/overrides/game_objects0.adb.lst index 5e08278e5..d476e61a9 100644 --- a/server/src/main/resources/overrides/game_objects0.adb.lst +++ b/server/src/main/resources/overrides/game_objects0.adb.lst @@ -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 diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 806a23ff1..d912b5911 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -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) diff --git a/src/main/scala/net/psforever/actors/session/AvatarActor.scala b/src/main/scala/net/psforever/actors/session/AvatarActor.scala index 1dd805e3e..b0899301e 100644 --- a/src/main/scala/net/psforever/actors/session/AvatarActor.scala +++ b/src/main/scala/net/psforever/actors/session/AvatarActor.scala @@ -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)) } } diff --git a/src/main/scala/net/psforever/actors/session/csr/SquadHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/csr/SquadHandlerLogic.scala index f6be56232..70e789621 100644 --- a/src/main/scala/net/psforever/actors/session/csr/SquadHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/SquadHandlerLogic.scala @@ -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 = { diff --git a/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala index 96ef7e81c..eb430a45b 100644 --- a/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala @@ -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) diff --git a/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala b/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala index 3a84a4324..fdfa1c9b4 100644 --- a/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala @@ -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 => diff --git a/src/main/scala/net/psforever/actors/session/normal/SquadHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/SquadHandlerLogic.scala index 71bbc20a0..b80eb01cd 100644 --- a/src/main/scala/net/psforever/actors/session/normal/SquadHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/SquadHandlerLogic.scala @@ -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 = { diff --git a/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala index 55d01d2a1..64a7a67a8 100644 --- a/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala @@ -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 diff --git a/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala b/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala index e6cc41527..399fede66 100644 --- a/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala @@ -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 = { diff --git a/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala index 995f7666d..37e2d03da 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala @@ -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)) } + } } /** diff --git a/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala index bc9f30b12..3ee687b12 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala @@ -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 diff --git a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala index 5633e76f7..5ade93f01 100644 --- a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala @@ -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( diff --git a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala index 5e7501c3e..49bb0833c 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -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)) } diff --git a/src/main/scala/net/psforever/actors/zone/ZoneActor.scala b/src/main/scala/net/psforever/actors/zone/ZoneActor.scala index 63bf81700..c5747619e 100644 --- a/src/main/scala/net/psforever/actors/zone/ZoneActor.scala +++ b/src/main/scala/net/psforever/actors/zone/ZoneActor.scala @@ -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 diff --git a/src/main/scala/net/psforever/objects/Vehicles.scala b/src/main/scala/net/psforever/objects/Vehicles.scala index 6a66afe33..259dc2845 100644 --- a/src/main/scala/net/psforever/objects/Vehicles.scala +++ b/src/main/scala/net/psforever/objects/Vehicles.scala @@ -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 diff --git a/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala b/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala index b363b63e3..fbdfa85ca 100644 --- a/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala +++ b/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala @@ -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) - ) } } diff --git a/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala b/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala index 101b1cc5a..689fcf652 100644 --- a/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala +++ b/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala @@ -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 diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala index 3c4650132..d60c9d5ed 100644 --- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala +++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala @@ -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 diff --git a/src/main/scala/net/psforever/objects/serverobject/environment/EnvironmentAttribute.scala b/src/main/scala/net/psforever/objects/serverobject/environment/EnvironmentAttribute.scala index 232923956..74d9e4744 100644 --- a/src/main/scala/net/psforever/objects/serverobject/environment/EnvironmentAttribute.scala +++ b/src/main/scala/net/psforever/objects/serverobject/environment/EnvironmentAttribute.scala @@ -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 } /** diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/participation/FacilityHackParticipation.scala b/src/main/scala/net/psforever/objects/serverobject/structures/participation/FacilityHackParticipation.scala index f85211860..90c750b00 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/participation/FacilityHackParticipation.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/participation/FacilityHackParticipation.scala @@ -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 } } diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/participation/MajorFacilityHackParticipation.scala b/src/main/scala/net/psforever/objects/serverobject/structures/participation/MajorFacilityHackParticipation.scala index 6b7b3f958..230aa2226 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/participation/MajorFacilityHackParticipation.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/participation/MajorFacilityHackParticipation.scala @@ -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 } diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/participation/TowerHackParticipation.scala b/src/main/scala/net/psforever/objects/serverobject/structures/participation/TowerHackParticipation.scala index 824f60898..505c401ae 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/participation/TowerHackParticipation.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/participation/TowerHackParticipation.scala @@ -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( diff --git a/src/main/scala/net/psforever/objects/zones/exp/Support.scala b/src/main/scala/net/psforever/objects/zones/exp/Support.scala index 0233426ca..1ad7ead6e 100644 --- a/src/main/scala/net/psforever/objects/zones/exp/Support.scala +++ b/src/main/scala/net/psforever/objects/zones/exp/Support.scala @@ -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) diff --git a/src/main/scala/net/psforever/services/avatar/AvatarService.scala b/src/main/scala/net/psforever/services/avatar/AvatarService.scala index d7e585f0a..f61e43949 100644 --- a/src/main/scala/net/psforever/services/avatar/AvatarService.scala +++ b/src/main/scala/net/psforever/services/avatar/AvatarService.scala @@ -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 _ => () } diff --git a/src/main/scala/net/psforever/services/avatar/AvatarServiceMessage.scala b/src/main/scala/net/psforever/services/avatar/AvatarServiceMessage.scala index ec2959b11..5cea9482d 100644 --- a/src/main/scala/net/psforever/services/avatar/AvatarServiceMessage.scala +++ b/src/main/scala/net/psforever/services/avatar/AvatarServiceMessage.scala @@ -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 diff --git a/src/main/scala/net/psforever/services/avatar/AvatarServiceResponse.scala b/src/main/scala/net/psforever/services/avatar/AvatarServiceResponse.scala index 897d105b7..5c26d9a02 100644 --- a/src/main/scala/net/psforever/services/avatar/AvatarServiceResponse.scala +++ b/src/main/scala/net/psforever/services/avatar/AvatarServiceResponse.scala @@ -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 } diff --git a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala index 1a88b0e00..d561cd758 100644 --- a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala +++ b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala @@ -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 } } diff --git a/src/main/scala/net/psforever/util/Config.scala b/src/main/scala/net/psforever/util/Config.scala index 419e66cb7..13d7d5c90 100644 --- a/src/main/scala/net/psforever/util/Config.scala +++ b/src/main/scala/net/psforever/util/Config.scala @@ -163,7 +163,8 @@ case class GameConfig( doorsCanBeOpenedByMedAppFromThisDistance: Float, experience: Experience, maxBattleRank: Int, - promotion: PromotionSystem + promotion: PromotionSystem, + facilityHackTime: FiniteDuration ) case class InstantActionConfig(