From e5d0690c42f45592b03122c590d0c15578a7f56a Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 10 Jul 2020 00:26:30 -0400 Subject: [PATCH 01/19] introduction of aggravated damage properties and modification of damage pathways such that 'aggravated' is treated as a unique damage type --- .../objects/ballistics/AggravatedDamage.scala | 37 ++ .../objects/vital/StandardDamages.scala | 176 ++++++ .../vital/damage/DamageSelection.scala | 35 ++ .../actors/session/SessionActor.scala | 512 ++++++++++-------- .../psforever/objects/GlobalDefinitions.scala | 73 ++- .../objects/ballistics/Projectile.scala | 10 +- .../ballistics/ResolvedProjectile.scala | 3 +- .../definition/ProjectileDefinition.scala | 14 +- .../objects/vital/DamageResistanceModel.scala | 4 +- .../psforever/objects/vital/DamageType.scala | 2 +- .../objects/vital/StandardResistances.scala | 90 +-- .../resistance/ResistanceSelection.scala | 30 +- src/test/scala/objects/DamageModelTests.scala | 32 +- src/test/scala/objects/DamageableTest.scala | 30 +- src/test/scala/objects/DeployableTest.scala | 3 - src/test/scala/objects/GeneratorTest.scala | 7 - .../scala/objects/PlayerControlTest.scala | 3 - src/test/scala/objects/ProjectileTest.scala | 2 - src/test/scala/objects/VehicleTest.scala | 2 +- src/test/scala/objects/VitalityTest.scala | 2 - 20 files changed, 696 insertions(+), 371 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala create mode 100644 common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala create mode 100644 common/src/main/scala/net/psforever/objects/vital/damage/DamageSelection.scala diff --git a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala new file mode 100644 index 00000000..67a217cc --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala @@ -0,0 +1,37 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.ballistics + +import net.psforever.objects.vital.DamageType + +object AggravatedEffect extends Enumeration { + type Type = Value + val Comet, Fire, Plasma, Napalm, None = Value +} + +final case class AggravatedInfo(damage_type : DamageType.Value, + degradation_percentage : Float, + infliction_rate : Long) { + assert(damage_type == DamageType.Direct || damage_type == DamageType.Splash, s"aggravated damage is an unsupported type - $damage_type") +} + +final case class AggravatedDamage(info : List[AggravatedInfo], + effect_type : AggravatedEffect.Value, + duration : Long, + max_factor : Float, + cumulative_damage_degrade : Boolean, + vanu_aggravated : Boolean) + +object AggravatedDamage { + def apply(info : AggravatedInfo, + effect_type : AggravatedEffect.Value, + duration : Long, + max_factor : Float) : AggravatedDamage = + AggravatedDamage(List(info), effect_type, duration, max_factor, cumulative_damage_degrade = true, vanu_aggravated = false) + + def apply(info : AggravatedInfo, + effect_type : AggravatedEffect.Value, + duration : Long, + max_factor : Float, + vanu_aggravated : Boolean) : AggravatedDamage = + AggravatedDamage(List(info), effect_type, duration, max_factor, cumulative_damage_degrade = true, vanu_aggravated) +} diff --git a/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala b/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala new file mode 100644 index 00000000..7e460c58 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala @@ -0,0 +1,176 @@ +// Copyright (c) 2017 PSForever +//package net.psforever.objects.vital +// +//import net.psforever.objects.vital.StandardAmenityDamage.None +//import net.psforever.objects.vital.StandardDeployableDamage.None +//import net.psforever.objects.vital.damage._ +//import net.psforever.objects.vital.damage.DamageCalculations._ +//import net.psforever.objects.vital.projectile.ProjectileCalculations +// +///** +// * A protected super class for calculating "no damage." +// * Used for `NoDamage` but also for the base of `*LashDamage` calculation objects +// * to maintain the polymorphic identity of `DamageCalculations`. +// */ +//protected class NoDamageBase +// extends DamageCalculations( +// DamageCalculations.NoDamage, +// DamageWithModifiers(NoDamageAgainst), +// TooFar +// ) +// +//object NoDamage extends NoDamageBase +// +//object InfantryHitDamage +// extends DamageCalculations( +// DirectHitDamageWithDegrade, +// DamageWithModifiers(DamageAgainstExoSuit), +// DistanceBetweenTargetandSource +// ) +// +//object MaxHitDamage +// extends DamageCalculations( +// DirectHitDamageWithDegrade, +// DamageWithModifiers(DamageAgainstMaxSuit), +// DistanceBetweenTargetandSource +// ) +// +//object VehicleHitDamage +// extends DamageCalculations( +// DirectHitDamageWithDegrade, +// DamageWithModifiers(DamageAgainstVehicle), +// DistanceBetweenTargetandSource +// ) +// +//object AircraftHitDamage +// extends DamageCalculations( +// DirectHitDamageWithDegrade, +// DamageWithModifiers(DamageAgainstAircraft), +// DistanceBetweenTargetandSource +// ) +// +//object InfantrySplashDamage +// extends DamageCalculations( +// SplashDamageWithRadialDegrade, +// DamageWithModifiers(DamageAgainstExoSuit), +// DistanceFromExplosionToTarget +// ) +// +//object MaxSplashDamage +// extends DamageCalculations( +// SplashDamageWithRadialDegrade, +// DamageWithModifiers(DamageAgainstMaxSuit), +// DistanceFromExplosionToTarget +// ) +// +//object VehicleSplashDamage +// extends DamageCalculations( +// SplashDamageWithRadialDegrade, +// DamageWithModifiers(DamageAgainstVehicle), +// DistanceFromExplosionToTarget +// ) +// +//object AircraftSplashDamage +// extends DamageCalculations( +// SplashDamageWithRadialDegrade, +// DamageWithModifiers(DamageAgainstAircraft), +// DistanceFromExplosionToTarget +// ) +// +//object InfantrySplashDamageDirect +// extends DamageCalculations( +// SplashDamageWithRadialDegrade, +// DamageWithModifiers(DamageAgainstAircraft), +// NoDistance +// ) +// +//object InfantryLashDamage +// extends DamageCalculations( +// LashDamage, +// DamageWithModifiers(DamageAgainstExoSuit), +// DistanceBetweenTargetandSource +// ) +// +//object MaxLashDamage +// extends DamageCalculations( +// LashDamage, +// DamageWithModifiers(DamageAgainstMaxSuit), +// DistanceBetweenTargetandSource +// ) +// +//object VehicleLashDamage +// extends DamageCalculations( +// LashDamage, +// DamageWithModifiers(DamageAgainstVehicle), +// DistanceBetweenTargetandSource +// ) +// +//object AircraftLashDamage +// extends DamageCalculations( +// LashDamage, +// DamageWithModifiers(DamageAgainstAircraft), +// DistanceBetweenTargetandSource +// ) +// +//object AmenityHitDamage +// extends DamageCalculations( +// DirectHitDamageWithDegrade, +// DamageWithModifiers(DamageAgainstVehicle), +// DistanceBetweenTargetandSource +// ) +// +//object AmenitySplashDamage +// extends DamageCalculations( +// SplashDamageWithRadialDegrade, +// DamageWithModifiers(DamageAgainstVehicle), +// DistanceFromExplosionToTarget +// ) +// +//object NoDamageSelection extends DamageSelection { +// def Direct = None +// def Splash = None +// def Lash = None +// def Aggravated : ProjectileCalculations.Form = None +//} +// +//object StandardInfantryDamage extends DamageSelection { +// def Direct : ProjectileCalculations.Form = InfantryHitDamage.Calculate +// def Splash : ProjectileCalculations.Form = InfantrySplashDamage.Calculate +// def Lash : ProjectileCalculations.Form = InfantryLashDamage.Calculate +// def Aggravated : ProjectileCalculations.Form = InfantrySplashDamage.Calculate +//} +// +//object StandardMaxDamage extends DamageSelection { +// def Direct : ProjectileCalculations.Form = MaxHitDamage.Calculate +// def Splash : ProjectileCalculations.Form = MaxSplashDamage.Calculate +// def Lash : ProjectileCalculations.Form = MaxLashDamage.Calculate +// def Aggravated : ProjectileCalculations.Form = None +//} +// +//object StandardVehicleDamage extends DamageSelection { +// def Direct : ProjectileCalculations.Form = VehicleHitDamage.Calculate +// def Splash : ProjectileCalculations.Form = VehicleSplashDamage.Calculate +// def Lash : ProjectileCalculations.Form = VehicleLashDamage.Calculate +// def Aggravated : ProjectileCalculations.Form = None +//} +// +//object StandardAircraftDamage extends DamageSelection { +// def Direct : ProjectileCalculations.Form = AircraftHitDamage.Calculate +// def Splash : ProjectileCalculations.Form = AircraftSplashDamage.Calculate +// def Lash : ProjectileCalculations.Form = AircraftLashDamage.Calculate +// def Aggravated : ProjectileCalculations.Form = None +//} +// +//object StandardDeployableDamage extends DamageSelection { +// def Direct : ProjectileCalculations.Form = VehicleHitDamage.Calculate +// def Splash : ProjectileCalculations.Form = VehicleSplashDamage.Calculate +// def Lash : ProjectileCalculations.Form = NoDamage.Calculate +// def Aggravated : ProjectileCalculations.Form = None +//} +// +//object StandardAmenityDamage extends DamageSelection { +// def Direct : ProjectileCalculations.Form = AmenityHitDamage.Calculate +// def Splash : ProjectileCalculations.Form = AmenitySplashDamage.Calculate +// def Lash : ProjectileCalculations.Form = NoDamage.Calculate +// def Aggravated : ProjectileCalculations.Form = None +//} diff --git a/common/src/main/scala/net/psforever/objects/vital/damage/DamageSelection.scala b/common/src/main/scala/net/psforever/objects/vital/damage/DamageSelection.scala new file mode 100644 index 00000000..8b15c500 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vital/damage/DamageSelection.scala @@ -0,0 +1,35 @@ +// Copyright (c) 2017 PSForever +//package net.psforever.objects.vital.damage +// +//import net.psforever.objects.ballistics.ResolvedProjectile +//import net.psforever.objects.vital.{DamageType, NoDamage} +//import net.psforever.objects.vital.projectile.ProjectileCalculations +// +///** +// * Maintain information about three primary forms of damage calculation +// * and a means to test which calculation is valid in a given situation. +// */ +//trait DamageSelection { +// final def None: ProjectileCalculations.Form = NoDamage +// +// def Direct : ProjectileCalculations.Form +// def Splash : ProjectileCalculations.Form +// def Lash : ProjectileCalculations.Form +// def Aggravated : ProjectileCalculations.Form +// +// def apply(data : ResolvedProjectile) : ProjectileCalculations.Form = data.projectile.profile.ProjectileDamageType match { +// case DamageType.Direct => Direct +// case DamageType.Splash => Splash +// case DamageType.Lash => Lash +// case DamageType.Aggravated => Aggravated +// case _ => None +// } +// +// def apply(res : DamageType.Value) : ProjectileCalculations.Form = res match { +// case DamageType.Direct => Direct +// case DamageType.Splash => Splash +// case DamageType.Lash => Lash +// case DamageType.Aggravated => Aggravated +// case _ => None +// } +//} diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index 692c4d4d..0c1c76b4 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -744,7 +744,7 @@ class SessionActor extends Actor with MDCContextAware { unk7 = 0 ) ) //repeat of our entry - val playerGuid = player.GUID + val playerGuid = player.GUID //turn lfs off val factionChannel = s"${player.Faction}" if (avatar.lookingForSquad) { @@ -897,7 +897,7 @@ class SessionActor extends Actor with MDCContextAware { SquadUIElement(element.name, element.index, entry.zone_number, entry.health, entry.armor, entry.pos) entry case (entry, element) - if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position => + if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position => //other elements that need to be updated squadUI(entry.char_id) = SquadUIElement(element.name, element.index, entry.zone_number, entry.health, entry.armor, entry.pos) @@ -1291,6 +1291,49 @@ class SessionActor extends Actor with MDCContextAware { taskResolver ! RegisterNewAvatar(player) } + case msg @ Zoning.InstantAction.Located(zone, _, spawn_point) => + //in between subsequent reply messages, it does not matter if the destination changes + //so long as there is at least one destination at all (including the fallback) + if (ContemplateZoningResponse(Zoning.InstantAction.Request(player.Faction), cluster)) { + val (pos, ori) = spawn_point.SpecificPoint(player) + SpawnThroughZoningProcess(zone, pos, ori) + } else if (zoningStatus != Zoning.Status.None) { + instantActionFallbackDestination = Some(msg) + } + + case Zoning.InstantAction.NotLocated() => + instantActionFallbackDestination match { + case Some(Zoning.InstantAction.Located(zone, _, spawn_point)) + if spawn_point.Owner.Faction == player.Faction && !spawn_point.Offline => + if (ContemplateZoningResponse(Zoning.InstantAction.Request(player.Faction), cluster)) { + val (pos, ori) = spawn_point.SpecificPoint(player) + SpawnThroughZoningProcess(zone, pos, ori) + } else if (zoningCounter == 0) { + CancelZoningProcessWithReason("@InstantActionNoHotspotsAvailable") + } + case _ => + //no instant action available + CancelZoningProcessWithReason("@InstantActionNoHotspotsAvailable") + } + + case Zoning.Recall.Located(zone, spawn_point) => + if (ContemplateZoningResponse(Zoning.Recall.Request(player.Faction, zone.Id), cluster)) { + val (pos, ori) = spawn_point.SpecificPoint(player) + SpawnThroughZoningProcess(zone, pos, ori) + } + + case Zoning.Recall.Denied(reason) => + CancelZoningProcessWithReason(s"@norecall_sanctuary_$reason", Some(ChatMessageType.CMT_QUIT)) + + case Zoning.Quit() => + if (ContemplateZoningResponse(Zoning.Quit(), self)) { + log.info("Good-bye") + ImmediateDisconnect() + } + + case ZoningReset() => + CancelZoningProcess() + case NewPlayerLoaded(tplayer) => //new zone log.info(s"Player ${tplayer.Name} has been loaded") @@ -1402,7 +1445,7 @@ class SessionActor extends Actor with MDCContextAware { } else if (tplayer.isAlive) { if ( zoneLoaded.contains(true) && - tplayer.HasGUID && tplayer.Actor != Default.Actor && (continent.GUID(tplayer.VehicleSeated) match { + tplayer.HasGUID && tplayer.Actor != Default.Actor && (continent.GUID(tplayer.VehicleSeated) match { case Some(o: Vehicle) => o.HasGUID && o.Actor != Default.Actor && !o.Destroyed case _ => true }) @@ -1566,11 +1609,11 @@ class SessionActor extends Actor with MDCContextAware { } case msg @ Containable.ItemPutInSlot( - _: PlanetSideServerObject with Container, - _: Equipment, - _: Int, - _: Option[Equipment] - ) => + _: PlanetSideServerObject with Container, + _: Equipment, + _: Int, + _: Option[Equipment] + ) => log.info(s"$msg") case msg @ Containable.CanNotPutItemInSlot(_: PlanetSideServerObject with Container, _: Equipment, _: Int) => @@ -1972,19 +2015,19 @@ class SessionActor extends Actor with MDCContextAware { } case AvatarResponse.PlayerState( - pos, - vel, - yaw, - pitch, - yaw_upper, - seq_time, - is_crouching, - is_jumping, - jump_thrust, - is_cloaking, - spectating, - weaponInHand - ) => + pos, + vel, + yaw, + pitch, + yaw_upper, + seq_time, + is_crouching, + is_jumping, + jump_thrust, + is_cloaking, + spectating, + weaponInHand + ) => if (tplayer_guid != guid) { val now = System.currentTimeMillis() val (location, time, distanceSq): (Vector3, Long, Float) = if (spectating) { @@ -2815,18 +2858,18 @@ class SessionActor extends Actor with MDCContextAware { } case VehicleResponse.VehicleState( - vehicle_guid, - unk1, - pos, - ang, - vel, - unk2, - unk3, - unk4, - wheel_direction, - unk5, - unk6 - ) => + vehicle_guid, + unk1, + pos, + ang, + vel, + unk2, + unk3, + unk4, + wheel_direction, + unk5, + unk6 + ) => if (tplayer_guid != guid) { sendResponse( VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6) @@ -2960,10 +3003,10 @@ class SessionActor extends Actor with MDCContextAware { * @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet */ def CargoMountBehaviorForUs( - carrier: Vehicle, - cargo: Vehicle, - mountPoint: Int - ): (ObjectAttachMessage, CargoMountPointStatusMessage) = { + carrier: Vehicle, + cargo: Vehicle, + mountPoint: Int + ): (ObjectAttachMessage, CargoMountPointStatusMessage) = { val msgs @ (attachMessage, mountPointStatusMessage) = CargoBehavior.CargoMountMessages(carrier, cargo, mountPoint) CargoMountMessagesForUs(attachMessage, mountPointStatusMessage) msgs @@ -2977,9 +3020,9 @@ class SessionActor extends Actor with MDCContextAware { * @param mountPointStatusMessage a `CargoMountPointStatusMessage` packet suitable for initializing cargo operations */ def CargoMountMessagesForUs( - attachMessage: ObjectAttachMessage, - mountPointStatusMessage: CargoMountPointStatusMessage - ): Unit = { + attachMessage: ObjectAttachMessage, + mountPointStatusMessage: CargoMountPointStatusMessage + ): Unit = { sendResponse(attachMessage) sendResponse(mountPointStatusMessage) } @@ -3743,21 +3786,21 @@ class SessionActor extends Actor with MDCContextAware { zoneLoaded = Some(true) case msg @ PlayerStateMessageUpstream( - avatar_guid, - pos, - vel, - yaw, - pitch, - yaw_upper, - seq_time, - unk3, - is_crouching, - is_jumping, - jump_thrust, - is_cloaking, - unk5, - unk6 - ) => + avatar_guid, + pos, + vel, + yaw, + pitch, + yaw_upper, + seq_time, + unk3, + is_crouching, + is_jumping, + jump_thrust, + is_cloaking, + unk5, + unk6 + ) => //log.info(s"$msg") persist() turnCounterFunc(avatar_guid) @@ -3872,18 +3915,18 @@ class SessionActor extends Actor with MDCContextAware { } case msg @ VehicleStateMessage( - vehicle_guid, - unk1, - pos, - ang, - vel, - flying, - unk6, - unk7, - wheels, - is_decelerating, - is_cloaked - ) => + vehicle_guid, + unk1, + pos, + ang, + vel, + flying, + unk6, + unk7, + wheels, + is_decelerating, + is_cloaked + ) => //log.info(s"$msg") GetVehicleAndSeat() match { case (Some(obj), Some(0)) => @@ -4107,7 +4150,7 @@ class SessionActor extends Actor with MDCContextAware { //the decimator does not send a ChangeFireState_Start on the last shot if ( tool.Definition == GlobalDefinitions.phoenix && - tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile + tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile ) { //suppress the decimator's alternate fire mode, however continent.AvatarEvents ! AvatarServiceMessage( @@ -4232,12 +4275,12 @@ class SessionActor extends Actor with MDCContextAware { } val sumReloadValue: Int = box.Capacity + tailReloadValue val actualReloadValue = (if (sumReloadValue <= reloadValue) { - deleteFunc(box) - sumReloadValue - } else { - modifyFunc(box, reloadValue - tailReloadValue) - reloadValue - }) + currentMagazine + deleteFunc(box) + sumReloadValue + } else { + modifyFunc(box, reloadValue - tailReloadValue) + reloadValue + }) + currentMagazine log.info(s"ReloadMessage: success, $tool <- $actualReloadValue ${tool.AmmoType}") tool.Magazine = actualReloadValue sendResponse(ReloadMessage(item_guid, actualReloadValue, unk1)) @@ -4450,10 +4493,10 @@ class SessionActor extends Actor with MDCContextAware { log.info(s"MoveItem: $msg") (continent.GUID(source_guid), continent.GUID(destination_guid), ValidObject(item_guid)) match { case ( - Some(source: PlanetSideServerObject with Container), - Some(destination: PlanetSideServerObject with Container), - Some(item: Equipment) - ) => + Some(source: PlanetSideServerObject with Container), + Some(destination: PlanetSideServerObject with Container), + Some(item: Equipment) + ) => source.Actor ! Containable.MoveItem(destination, item, dest) case (None, _, _) => log.error(s"MoveItem: wanted to move $item_guid from $source_guid, but could not find source object") @@ -4524,18 +4567,18 @@ class SessionActor extends Actor with MDCContextAware { } case msg @ UseItemMessage( - avatar_guid, - item_used_guid, - object_guid, - unk2, - unk3, - unk4, - unk5, - unk6, - unk7, - unk8, - itemType - ) => + avatar_guid, + item_used_guid, + object_guid, + unk2, + unk3, + unk4, + unk5, + unk6, + unk7, + unk8, + itemType + ) => //log.info("UseItem: " + msg) // TODO: Not all fields in the response are identical to source in real packet logs (but seems to be ok) // TODO: Not all incoming UseItemMessage's respond with another UseItemMessage (i.e. doors only send out GenericObjectStateMsg) @@ -4812,8 +4855,8 @@ class SessionActor extends Actor with MDCContextAware { //access to trunk if ( obj.AccessingTrunk.isEmpty && - (!obj.PermissionGroup(AccessPermissionGroup.Trunk.id).contains(VehicleLockState.Locked) || obj.Owner - .contains(player.GUID)) + (!obj.PermissionGroup(AccessPermissionGroup.Trunk.id).contains(VehicleLockState.Locked) || obj.Owner + .contains(player.GUID)) ) { CancelZoningProcessWithDescriptiveReason("cancel_use") obj.AccessingTrunk = player.GUID @@ -4846,7 +4889,7 @@ class SessionActor extends Actor with MDCContextAware { terminal.Actor ! CommonMessages.Use(player, Some(item)) case None - if terminal.Owner == Building.NoBuilding || terminal.Faction == player.Faction || terminal.HackedBy.nonEmpty => + if terminal.Owner == Building.NoBuilding || terminal.Faction == player.Faction || terminal.HackedBy.nonEmpty => val tdef = terminal.Definition if (tdef.isInstanceOf[MatrixTerminalDefinition]) { //TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks) @@ -4856,7 +4899,7 @@ class SessionActor extends Actor with MDCContextAware { ) } else if ( tdef == GlobalDefinitions.multivehicle_rearm_terminal || tdef == GlobalDefinitions.bfr_rearm_terminal || - tdef == GlobalDefinitions.air_rearm_terminal || tdef == GlobalDefinitions.ground_rearm_terminal + tdef == GlobalDefinitions.air_rearm_terminal || tdef == GlobalDefinitions.ground_rearm_terminal ) { FindLocalVehicle match { case Some(vehicle) => @@ -5225,7 +5268,6 @@ class SessionActor extends Actor with MDCContextAware { case msg @ FavoritesRequest(player_guid, loadoutType, action, line, label) => CancelZoningProcessWithDescriptiveReason("cancel_use") log.info(s"FavoritesRequest: $msg") - action match { case FavoritesAction.Save => avatarActor ! AvatarActor.SaveLoadout(player, loadoutType, label, line) case FavoritesAction.Delete => avatarActor ! AvatarActor.DeleteLoadout(player, loadoutType, line) @@ -5247,18 +5289,18 @@ class SessionActor extends Actor with MDCContextAware { } case msg @ WeaponFireMessage( - seq_time, - weapon_guid, - projectile_guid, - shot_origin, - unk1, - unk2, - unk3, - unk4, - unk5, - unk6, - unk7 - ) => + seq_time, + weapon_guid, + projectile_guid, + shot_origin, + unk1, + unk2, + unk3, + unk4, + unk5, + unk6, + unk7 + ) => //log.info(s"WeaponFire: $msg") HandleWeaponFire(weapon_guid, projectile_guid, shot_origin) @@ -5347,15 +5389,15 @@ class SessionActor extends Actor with MDCContextAware { } case msg @ SplashHitMessage( - seq_time, - projectile_guid, - explosion_pos, - direct_victim_uid, - unk3, - projectile_vel, - unk4, - targets - ) => + seq_time, + projectile_guid, + explosion_pos, + direct_victim_uid, + unk3, + projectile_vel, + unk4, + targets + ) => log.info(s"Splash: $msg") FindProjectileEntry(projectile_guid) match { case Some(projectile) => @@ -5426,11 +5468,11 @@ class SessionActor extends Actor with MDCContextAware { if (deadState != DeadState.RespawnTime) { continent.Buildings.values.find(building => building.GUID == building_guid) match { case Some(wg: WarpGate) if (wg.Active && (GetKnownVehicleAndSeat() match { - case (Some(vehicle), _) => - wg.Definition.VehicleAllowance && !wg.Definition.NoWarp.contains(vehicle.Definition) - case _ => - true - })) => + case (Some(vehicle), _) => + wg.Definition.VehicleAllowance && !wg.Definition.NoWarp.contains(vehicle.Definition) + case _ => + true + })) => deadState = DeadState.RespawnTime cluster ! InterstellarClusterService.GetSpawnPoint( destinationZoneGuid.guid, @@ -5588,17 +5630,17 @@ class SessionActor extends Actor with MDCContextAware { log.debug("Ouch! " + msg) case msg @ BugReportMessage( - version_major, - version_minor, - version_date, - bug_type, - repeatable, - location, - zone, - pos, - summary, - desc - ) => + version_major, + version_minor, + version_date, + bug_type, + repeatable, + location, + zone, + pos, + summary, + desc + ) => log.info("BugReportMessage: " + msg) case msg @ BindPlayerMessage(action, bindDesc, unk1, logging, unk2, unk3, unk4, pos) => @@ -6305,7 +6347,7 @@ class SessionActor extends Actor with MDCContextAware { sendResponse(ObjectDetachMessage(tool.GUID, previousBox.GUID, Vector3.Zero, 0f)) sendResponse(ObjectDetachMessage(player.GUID, box.GUID, Vector3.Zero, 0f)) obj.Inventory -= x.start //remove replacement ammo from inventory - val ammoSlotIndex = tool.FireMode.AmmoSlotIndex + val ammoSlotIndex = tool.FireMode.AmmoSlotIndex tool.AmmoSlots(ammoSlotIndex).Box = box //put replacement ammo in tool sendResponse(ObjectAttachMessage(tool.GUID, box.GUID, ammoSlotIndex)) @@ -6330,16 +6372,16 @@ class SessionActor extends Actor with MDCContextAware { //handle inventory contents box.Capacity = (if (sumReloadValue <= fullMagazine) { - sumReloadValue - } else { - val splitReloadAmmo: Int = sumReloadValue - fullMagazine - log.info( - s"ChangeAmmo: taking ${originalBoxCapacity - splitReloadAmmo} from a box of ${originalBoxCapacity} $requestedAmmoType" - ) - val boxForInventory = AmmoBox(box.Definition, splitReloadAmmo) - taskResolver ! stowNewFunc(boxForInventory) - fullMagazine - }) + sumReloadValue + } else { + val splitReloadAmmo: Int = sumReloadValue - fullMagazine + log.info( + s"ChangeAmmo: taking ${originalBoxCapacity - splitReloadAmmo} from a box of ${originalBoxCapacity} $requestedAmmoType" + ) + val boxForInventory = AmmoBox(box.Definition, splitReloadAmmo) + taskResolver ! stowNewFunc(boxForInventory) + fullMagazine + }) sendResponse( InventoryStateMessage(box.GUID, tool.GUID, box.Capacity) ) //should work for both players and vehicles @@ -6453,12 +6495,12 @@ class SessionActor extends Actor with MDCContextAware { else { xs.map(_.obj.asInstanceOf[Tool].Magazine).reduce(_ + _) } val sumReloadValue: Int = box.Magazine + tailReloadValue val actualReloadValue = (if (sumReloadValue <= 3) { - RemoveOldEquipmentFromInventory(player, taskResolver)(x.obj) - sumReloadValue - } else { - ModifyAmmunition(player)(box.AmmoSlot.Box, 3 - tailReloadValue) - 3 - }) + RemoveOldEquipmentFromInventory(player, taskResolver)(x.obj) + sumReloadValue + } else { + ModifyAmmunition(player)(box.AmmoSlot.Box, 3 - tailReloadValue) + 3 + }) log.info(s"found $actualReloadValue more $ammoType grenades to throw") ModifyAmmunition(player)( tool.AmmoSlot.Box, @@ -6480,8 +6522,8 @@ class SessionActor extends Actor with MDCContextAware { * the second value is the slot position of the object */ def FindInLocalContainer( - object_guid: PlanetSideGUID - )(parent: PlanetSideServerObject with Container): Option[(PlanetSideServerObject with Container, Option[Int])] = { + object_guid: PlanetSideGUID + )(parent: PlanetSideServerObject with Container): Option[(PlanetSideServerObject with Container, Option[Int])] = { val slot: Option[Int] = parent.Find(object_guid) slot match { case place @ Some(_) => @@ -6498,10 +6540,10 @@ class SessionActor extends Actor with MDCContextAware { * @param reason a string explaining why the state can not or will not change */ def CanNotChangeDeployment( - obj: PlanetSideServerObject with Deployment, - state: DriveState.Value, - reason: String - ): Unit = { + obj: PlanetSideServerObject with Deployment, + state: DriveState.Value, + reason: String + ): Unit = { val mobileShift: String = if (obj.DeploymentState != DriveState.Mobile) { obj.DeploymentState = DriveState.Mobile sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Mobile, 0, false, Vector3.Zero)) @@ -6943,10 +6985,10 @@ class SessionActor extends Actor with MDCContextAware { * `(None, None)`, otherwise (even if the vehicle can be determined) */ def GetMountableAndSeat( - direct: Option[PlanetSideGameObject with Mountable], - occupant: Player, - zone: Zone - ): (Option[PlanetSideGameObject with Mountable], Option[Int]) = + direct: Option[PlanetSideGameObject with Mountable], + occupant: Player, + zone: Zone + ): (Option[PlanetSideGameObject with Mountable], Option[Int]) = direct.orElse(zone.GUID(occupant.VehicleSeated)) match { case Some(obj: PlanetSideGameObject with Mountable) => obj.PassengerInSeat(occupant) match { @@ -7623,11 +7665,11 @@ class SessionActor extends Actor with MDCContextAware { * @return the projectile */ def ResolveProjectileEntry( - projectile_guid: PlanetSideGUID, - resolution: ProjectileResolution.Value, - target: PlanetSideGameObject with FactionAffinity with Vitality, - pos: Vector3 - ): Option[ResolvedProjectile] = { + projectile_guid: PlanetSideGUID, + resolution: ProjectileResolution.Value, + target: PlanetSideGameObject with FactionAffinity with Vitality, + pos: Vector3 + ): Option[ResolvedProjectile] = { FindProjectileEntry(projectile_guid) match { case Some(projectile) => ResolveProjectileEntry(projectile, resolution, target, pos) @@ -7645,12 +7687,12 @@ class SessionActor extends Actor with MDCContextAware { * @return a copy of the projectile */ def ResolveProjectileEntry( - projectile: Projectile, - index: Int, - resolution: ProjectileResolution.Value, - target: PlanetSideGameObject with FactionAffinity with Vitality, - pos: Vector3 - ): Option[ResolvedProjectile] = { + projectile: Projectile, + index: Int, + resolution: ProjectileResolution.Value, + target: PlanetSideGameObject with FactionAffinity with Vitality, + pos: Vector3 + ): Option[ResolvedProjectile] = { if (!projectiles(index).contains(projectile)) { log.error(s"expected projectile could not be found at $index; can not resolve") None @@ -7666,11 +7708,11 @@ class SessionActor extends Actor with MDCContextAware { * @return a copy of the projectile */ def ResolveProjectileEntry( - projectile: Projectile, - resolution: ProjectileResolution.Value, - target: PlanetSideGameObject with FactionAffinity with Vitality, - pos: Vector3 - ): Option[ResolvedProjectile] = { + projectile: Projectile, + resolution: ProjectileResolution.Value, + target: PlanetSideGameObject with FactionAffinity with Vitality, + pos: Vector3 + ): Option[ResolvedProjectile] = { if (projectile.isMiss) { log.error("expected projectile was already counted as a missed shot; can not resolve any further") None @@ -7762,11 +7804,11 @@ class SessionActor extends Actor with MDCContextAware { * @return a `DestroyDisplayMessage` packet that is properly formatted */ def DestroyDisplayMessage( - killer: SourceEntry, - victim: SourceEntry, - method: Int, - unk: Int = 121 - ): DestroyDisplayMessage = { + killer: SourceEntry, + victim: SourceEntry, + method: Int, + unk: Int = 121 + ): DestroyDisplayMessage = { val killer_seated = killer match { case obj: PlayerSource => obj.Seated case _ => false @@ -7926,12 +7968,12 @@ class SessionActor extends Actor with MDCContextAware { * @return `true`, if the desired certification requirements are met; `false`, otherwise */ def ConstructionItemPermissionComparison( - sample: Set[Certification], - test: Set[Certification] - ): Boolean = { - import Certification._ - val engineeringCerts: Set[Certification] = Set(AssaultEngineering, FortificationEngineering) - val testDiff: Set[Certification] = test diff (engineeringCerts ++ Set(AdvancedEngineering)) + sample: Set[CertificationType.Value], + test: Set[CertificationType.Value] + ): Boolean = { + import CertificationType._ + val engineeringCerts: Set[CertificationType.Value] = Set(AssaultEngineering, FortificationEngineering) + val testDiff: Set[CertificationType.Value] = test diff (engineeringCerts ++ Set(AdvancedEngineering)) //substitute `AssaultEngineering` and `FortificationEngineering` for `AdvancedEngineering` val sampleIntersect = if (sample contains AdvancedEngineering) { engineeringCerts @@ -8073,10 +8115,10 @@ class SessionActor extends Actor with MDCContextAware { * `false`, otherwise */ def SafelyRemoveConstructionItemFromSlot( - tool: ConstructionItem, - index: Int, - logDecorator: String = "SafelyRemoveConstructionItemFromSlot" - ): Boolean = { + tool: ConstructionItem, + index: Int, + logDecorator: String = "SafelyRemoveConstructionItemFromSlot" + ): Boolean = { if ({ val holster = player.Slot(index) if (holster.Equipment.contains(tool)) { @@ -8175,7 +8217,7 @@ class SessionActor extends Actor with MDCContextAware { */ def FindEquipmentToDelete(object_guid: PlanetSideGUID, obj: Equipment): Boolean = { val findFunc - : PlanetSideServerObject with Container => Option[(PlanetSideServerObject with Container, Option[Int])] = + : PlanetSideServerObject with Container => Option[(PlanetSideServerObject with Container, Option[Int])] = FindInLocalContainer(object_guid) findFunc(player.avatar.locker) @@ -8242,12 +8284,12 @@ class SessionActor extends Actor with MDCContextAware { * @param deletionType the value passed to `ObjectDeleteMessage` concerning the deconstruction animation */ def DeconstructDeployable( - obj: PlanetSideGameObject with Deployable, - guid: PlanetSideGUID, - pos: Vector3, - orient: Vector3, - deletionType: Int - ): Unit = { + obj: PlanetSideGameObject with Deployable, + guid: PlanetSideGUID, + pos: Vector3, + orient: Vector3, + deletionType: Int + ): Unit = { StartBundlingPackets() sendResponse(SetEmpireMessage(guid, PlanetSideEmpire.NEUTRAL)) //for some, removes the green marker circle sendResponse(TriggerEffectMessage("spawn_object_failed_effect", pos, orient)) @@ -8511,17 +8553,17 @@ class SessionActor extends Actor with MDCContextAware { player.Continent = zoneId //forward-set the continent id to perform a test interstellarFerryTopLevelGUID = (if ( - manifest.passengers.isEmpty && manifest.cargo.count { case (name, _) => !name.equals("MISSING_DRIVER") } == 0 - ) { - //do not delete if vehicle has passengers or cargo - continent.VehicleEvents ! VehicleServiceMessage( - continent.id, - VehicleAction.UnloadVehicle(pguid, continent, vehicle, topLevel) - ) - None - } else { - Some(topLevel) - }) + manifest.passengers.isEmpty && manifest.cargo.count { case (name, _) => !name.equals("MISSING_DRIVER") } == 0 + ) { + //do not delete if vehicle has passengers or cargo + continent.VehicleEvents ! VehicleServiceMessage( + continent.Id, + VehicleAction.UnloadVehicle(pguid, continent, vehicle, topLevel) + ) + None + } else { + Some(topLevel) + }) //unregister vehicle and driver whole + GiveWorld continent.Transport ! Zone.Vehicle.Despawn(vehicle) taskThenZoneChange( @@ -8685,10 +8727,10 @@ class SessionActor extends Actor with MDCContextAware { * @param remoteTelepad the endpoint of the teleportation system that exists in the environment */ def LinkRouterToRemoteTelepad( - router: Vehicle, - internalTelepad: Utility.InternalTelepad, - remoteTelepad: TelepadDeployable - ): Unit = { + router: Vehicle, + internalTelepad: Utility.InternalTelepad, + remoteTelepad: TelepadDeployable + ): Unit = { internalTelepad.Telepad = remoteTelepad.GUID //necessary; backwards link to the (new) telepad CreateRouterInternalTelepad(router, internalTelepad) LinkRemoteTelepad(remoteTelepad.GUID) @@ -8749,12 +8791,12 @@ class SessionActor extends Actor with MDCContextAware { * @param dest the destination of the teleportation (where the player is going) */ def UseRouterTelepadSystem( - router: Vehicle, - internalTelepad: InternalTelepad, - remoteTelepad: TelepadDeployable, - src: PlanetSideGameObject with TelepadLike, - dest: PlanetSideGameObject with TelepadLike - ) = { + router: Vehicle, + internalTelepad: InternalTelepad, + remoteTelepad: TelepadDeployable, + src: PlanetSideGameObject with TelepadLike, + dest: PlanetSideGameObject with TelepadLike + ) = { val time = System.nanoTime if ( time - recentTeleportAttempt > (2 seconds).toNanos && router.DeploymentState == DriveState.Deployed && internalTelepad.Active && remoteTelepad.Active @@ -9003,7 +9045,7 @@ class SessionActor extends Actor with MDCContextAware { // Charge else if ( player.Capacitor < player.ExoSuitDef.MaxCapacitor - && (player.CapacitorState == CapacitorStateType.Idle || player.CapacitorState == CapacitorStateType.Charging || (player.CapacitorState == CapacitorStateType.ChargeDelay && System + && (player.CapacitorState == CapacitorStateType.Idle || player.CapacitorState == CapacitorStateType.Charging || (player.CapacitorState == CapacitorStateType.ChargeDelay && System .currentTimeMillis() - player.CapacitorLastUsedMillis > player.ExoSuitDef.CapacitorRechargeDelayMillis)) ) { if (player.CapacitorState == CapacitorStateType.Charging) { @@ -9137,10 +9179,10 @@ class SessionActor extends Actor with MDCContextAware { } def CheckForHitPositionDiscrepancy( - projectile_guid: PlanetSideGUID, - hitPos: Vector3, - target: PlanetSideGameObject with FactionAffinity with Vitality - ): Unit = { + projectile_guid: PlanetSideGUID, + hitPos: Vector3, + target: PlanetSideGameObject with FactionAffinity with Vitality + ): Unit = { val hitPositionDiscrepancy = Vector3.DistanceSquared(hitPos, target.Position) if (hitPositionDiscrepancy > Config.app.antiCheat.hitPositionDiscrepancyThreshold) { // If the target position on the server does not match the position where the projectile landed within reason there may be foul play @@ -9350,18 +9392,18 @@ class SessionActor extends Actor with MDCContextAware { s"WeaponFireMessage: ${projectile_info.Name} is a remote projectile" ) taskResolver ! (if (projectile.HasGUID) { - continent.AvatarEvents ! AvatarServiceMessage( - continent.id, - AvatarAction.ProjectileExplodes( - player.GUID, - projectile.GUID, - projectile - ) - ) - ReregisterProjectile(projectile) - } else { - RegisterProjectile(projectile) - }) + continent.AvatarEvents ! AvatarServiceMessage( + continent.Id, + AvatarAction.ProjectileExplodes( + player.GUID, + projectile.GUID, + projectile + ) + ) + ReregisterProjectile(projectile) + } else { + RegisterProjectile(projectile) + }) } projectilesToCleanUp(projectileIndex) = false diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 1f8415d2..561484b6 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2,7 +2,7 @@ package net.psforever.objects import net.psforever.objects.avatar.Certification -import net.psforever.objects.ballistics.Projectiles +import net.psforever.objects.ballistics.{AggravatedDamage, AggravatedEffect, AggravatedInfo, Projectiles} import net.psforever.objects.ce.{DeployableCategory, DeployedItem} import net.psforever.objects.definition._ import net.psforever.objects.definition.converter._ @@ -207,6 +207,8 @@ object GlobalDefinitions { val flail_projectile = ProjectileDefinition(Projectiles.flail_projectile) + val flamethrower_fire_cloud = ProjectileDefinition(Projectiles.flamethrower_projectile) //flamethrower_fire_cloud + val flamethrower_fireball = ProjectileDefinition(Projectiles.flamethrower_fireball) val flamethrower_projectile = ProjectileDefinition(Projectiles.flamethrower_projectile) @@ -2269,6 +2271,7 @@ object GlobalDefinitions { ProjectileDefinition.CalculateDerivedFields(aphelion_laser_projectile) aphelion_plasma_rocket_projectile.Name = "aphelion_plasma_rocket_projectile" + //has property aggravated_damage_max_factor, but it's the aphelion_plasma_cloud that performs aggravated damage aphelion_plasma_rocket_projectile.Damage0 = 38 aphelion_plasma_rocket_projectile.Damage1 = 70 aphelion_plasma_rocket_projectile.Damage2 = 95 @@ -2313,6 +2316,13 @@ object GlobalDefinitions { aphelion_starfire_projectile.InitialVelocity = 45 aphelion_starfire_projectile.Lifespan = 7f aphelion_starfire_projectile.ProjectileDamageType = DamageType.Aggravated + aphelion_starfire_projectile.Aggravated = AggravatedDamage( + AggravatedInfo(DamageType.Direct, 0.25f, 250), + AggravatedEffect.None, + 0, + 0f, + true + ) aphelion_starfire_projectile.ExistsOnRemoteClients = true aphelion_starfire_projectile.RemoteClientData = (39577, 249) //starfire_projectile data aphelion_starfire_projectile.AutoLock = true @@ -2437,6 +2447,12 @@ object GlobalDefinitions { comet_projectile.DamageAtEdge = 0.45f comet_projectile.DamageRadius = 1.0f comet_projectile.ProjectileDamageType = DamageType.Aggravated + comet_projectile.Aggravated = AggravatedDamage( + AggravatedInfo(DamageType.Direct, 0.2f, 500), + AggravatedEffect.Comet, + 0, + 10f + ) comet_projectile.InitialVelocity = 80 comet_projectile.Lifespan = 3.1f ProjectileDefinition.CalculateDerivedFields(comet_projectile) @@ -2582,6 +2598,14 @@ object GlobalDefinitions { flamethrower_fireball.DamageAtEdge = 0.15f flamethrower_fireball.DamageRadius = 5f flamethrower_fireball.ProjectileDamageType = DamageType.Aggravated + flamethrower_fireball.Aggravated = AggravatedDamage( + List(AggravatedInfo(DamageType.Direct, 0.9f, 500), AggravatedInfo(DamageType.Splash, 0.9f, 500)), + AggravatedEffect.Fire, + 5000, + 0.1f, + false, + false + ) flamethrower_fireball.InitialVelocity = 15 flamethrower_fireball.Lifespan = 1.2f ProjectileDefinition.CalculateDerivedFields(flamethrower_fireball) @@ -2595,6 +2619,14 @@ object GlobalDefinitions { flamethrower_projectile.Acceleration = -5 flamethrower_projectile.AccelerationUntil = 2f flamethrower_projectile.ProjectileDamageType = DamageType.Aggravated + flamethrower_projectile.Aggravated = AggravatedDamage( + List(AggravatedInfo(DamageType.Direct, 0.5f, 500)), + AggravatedEffect.Fire, + 5000, + 0.5f, + false, + false + ) flamethrower_projectile.DegradeDelay = 1.0f flamethrower_projectile.DegradeMultiplier = 0.5f flamethrower_projectile.InitialVelocity = 10 @@ -3423,6 +3455,14 @@ object GlobalDefinitions { plasma_cartridge_projectile.DamageAtEdge = 0.2f plasma_cartridge_projectile.DamageRadius = 7f plasma_cartridge_projectile.ProjectileDamageType = DamageType.Aggravated + plasma_cartridge_projectile.Aggravated = AggravatedDamage( + List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), + AggravatedEffect.Plasma, + 0, + 1.5f, + true, + false + ) plasma_cartridge_projectile.InitialVelocity = 30 plasma_cartridge_projectile.Lifespan = 15f ProjectileDefinition.CalculateDerivedFields(plasma_cartridge_projectile) @@ -3434,6 +3474,14 @@ object GlobalDefinitions { plasma_cartridge_projectile_b.DamageAtEdge = 0.2f plasma_cartridge_projectile_b.DamageRadius = 7f plasma_cartridge_projectile_b.ProjectileDamageType = DamageType.Aggravated + plasma_cartridge_projectile_b.Aggravated = AggravatedDamage( + List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), + AggravatedEffect.Plasma, + 0, + 1.5f, + true, + false + ) plasma_cartridge_projectile_b.InitialVelocity = 30 plasma_cartridge_projectile_b.Lifespan = 2f ProjectileDefinition.CalculateDerivedFields(plasma_cartridge_projectile_b) @@ -3444,6 +3492,14 @@ object GlobalDefinitions { plasma_grenade_projectile.DamageAtEdge = 0.1f plasma_grenade_projectile.DamageRadius = 7f plasma_grenade_projectile.ProjectileDamageType = DamageType.Aggravated + plasma_grenade_projectile.Aggravated = AggravatedDamage( + List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), + AggravatedEffect.Plasma, + 0, + 1.5f, + true, + false + ) plasma_grenade_projectile.InitialVelocity = 30 plasma_grenade_projectile.Lifespan = 15f ProjectileDefinition.CalculateDerivedFields(plasma_grenade_projectile) @@ -3455,6 +3511,14 @@ object GlobalDefinitions { plasma_grenade_projectile_B.DamageAtEdge = 0.1f plasma_grenade_projectile_B.DamageRadius = 7f plasma_grenade_projectile_B.ProjectileDamageType = DamageType.Aggravated + plasma_grenade_projectile_B.Aggravated = AggravatedDamage( + List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), + AggravatedEffect.Plasma, + 0, + 1.5f, + true, + false + ) plasma_grenade_projectile_B.InitialVelocity = 30 plasma_grenade_projectile_B.Lifespan = 3f ProjectileDefinition.CalculateDerivedFields(plasma_grenade_projectile_B) @@ -3756,6 +3820,13 @@ object GlobalDefinitions { starfire_projectile.Acceleration = 12 starfire_projectile.AccelerationUntil = 5f starfire_projectile.ProjectileDamageType = DamageType.Aggravated + starfire_projectile.Aggravated = AggravatedDamage( + AggravatedInfo(DamageType.Direct, 0.25f, 250), + AggravatedEffect.Comet, + 0, + 0f, + true + ) starfire_projectile.InitialVelocity = 45 starfire_projectile.Lifespan = 7.8f starfire_projectile.ExistsOnRemoteClients = true diff --git a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala index 05705de3..57b6de22 100644 --- a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala +++ b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala @@ -10,11 +10,11 @@ import net.psforever.types.Vector3 /** * A summation of weapon (`Tool`) discharge. - * @see `ProjectileDefinition`
- * `ToolDefinition`
- * `FireModeDefinition`
- * `SourceEntry`
- * `PlayerSource` + * @see `ProjectileDefinition` + * @see `ToolDefinition` + * @see `FireModeDefinition` + * @see `SourceEntry` + * @see `PlayerSource` * @param profile an explanation of the damage that can be performed by this discharge * @param tool_def the weapon that caused this discharge * @param fire_mode the current fire mode of the tool used diff --git a/src/main/scala/net/psforever/objects/ballistics/ResolvedProjectile.scala b/src/main/scala/net/psforever/objects/ballistics/ResolvedProjectile.scala index a51fb03f..c06e3e16 100644 --- a/src/main/scala/net/psforever/objects/ballistics/ResolvedProjectile.scala +++ b/src/main/scala/net/psforever/objects/ballistics/ResolvedProjectile.scala @@ -9,14 +9,13 @@ import net.psforever.types.Vector3 * about the interaction of weapons discharge and a target * to the point that the original event might be reconstructed. * Reenacting the calculations of this entry should always produce the same values. - * @param resolution how the projectile hit was executed * @param projectile the original projectile * @param target what the projectile hit * @param damage_model the kind of damage model to which the `target` is/was subject * @param hit_pos where the projectile hit */ final case class ResolvedProjectile( - resolution: ProjectileResolution.Value, + resolution : ProjectileResolution.Value, projectile: Projectile, target: SourceEntry, damage_model: DamageResistanceModel, diff --git a/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala b/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala index 69c6ed51..5b83ab1c 100644 --- a/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.definition -import net.psforever.objects.ballistics.Projectiles +import net.psforever.objects.ballistics.{AggravatedDamage, Projectiles} import net.psforever.objects.equipment.JammingUnit import net.psforever.objects.vital.damage.DamageModifiers import net.psforever.objects.vital.{DamageType, StandardDamageProfile} @@ -36,6 +36,7 @@ class ProjectileDefinition(objectId: Int) private var autoLock: Boolean = false private var additionalEffect: Boolean = false private var jammerProjectile: Boolean = false + private var aggravated_damage : Option[AggravatedDamage] = None //derived calculations private var distanceMax: Float = 0f private var distanceFromAcceleration: Float = 0f @@ -174,7 +175,16 @@ class ProjectileDefinition(objectId: Int) JammerProjectile } - def DistanceMax: Float = distanceMax //accessor only + def Aggravated : Option[AggravatedDamage] = aggravated_damage + + def Aggravated_=(damage : AggravatedDamage) : Option[AggravatedDamage] = Aggravated_=(Some(damage)) + + def Aggravated_=(damage : Option[AggravatedDamage]) : Option[AggravatedDamage] = { + aggravated_damage = damage + Aggravated + } + + def DistanceMax : Float = distanceMax //accessor only def DistanceFromAcceleration: Float = distanceFromAcceleration //accessor only diff --git a/src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala b/src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala index 1fd9efc7..56140883 100644 --- a/src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala +++ b/src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala @@ -1,8 +1,8 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.vital -import net.psforever.objects.ballistics.{ProjectileResolution, ResolvedProjectile} import net.psforever.objects.vital.damage.DamageCalculations +import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.vital.projectile.ProjectileCalculations import net.psforever.objects.vital.resistance.ResistanceSelection import net.psforever.objects.vital.resolution.ResolutionCalculations @@ -72,7 +72,7 @@ trait DamageResistanceModel { * @param resolution an explicit damage resolution overriding the one in the `ResolvedProjectile` object * @return a function literal that encapsulates delayed modification instructions for certain objects */ - def Calculate(data: ResolvedProjectile, resolution: ProjectileResolution.Value): ResolutionCalculations.Output = { + def Calculate(data: ResolvedProjectile, resolution: DamageType.Value): ResolutionCalculations.Output = { val res: ProjectileCalculations.Form = ResistUsing(resolution) Model(DamageUsing, res, data) } diff --git a/src/main/scala/net/psforever/objects/vital/DamageType.scala b/src/main/scala/net/psforever/objects/vital/DamageType.scala index c34a6ea2..5f5edb65 100644 --- a/src/main/scala/net/psforever/objects/vital/DamageType.scala +++ b/src/main/scala/net/psforever/objects/vital/DamageType.scala @@ -9,5 +9,5 @@ package net.psforever.objects.vital object DamageType extends Enumeration(1) { type Type = Value - final val Direct, Splash, Radiation, Aggravated, Plasma, Comet, None = Value + final val Direct, Splash, Lash, Radiation, Aggravated, Plasma, Comet, None = Value } diff --git a/src/main/scala/net/psforever/objects/vital/StandardResistances.scala b/src/main/scala/net/psforever/objects/vital/StandardResistances.scala index 34b0e454..c8a5a74f 100644 --- a/src/main/scala/net/psforever/objects/vital/StandardResistances.scala +++ b/src/main/scala/net/psforever/objects/vital/StandardResistances.scala @@ -6,70 +6,70 @@ import net.psforever.objects.vital.projectile.ProjectileCalculations import net.psforever.objects.vital.resistance.{ResistanceCalculations, ResistanceSelection} object NoResistance - extends ResistanceCalculations[SourceEntry]( - ResistanceCalculations.ValidInfantryTarget, - ResistanceCalculations.NoResistExtractor - ) + extends ResistanceCalculations[SourceEntry]( + ResistanceCalculations.ValidInfantryTarget, + ResistanceCalculations.NoResistExtractor + ) object InfantryHitResistance - extends ResistanceCalculations[PlayerSource]( - ResistanceCalculations.ValidInfantryTarget, - ResistanceCalculations.ExoSuitDirectExtractor - ) + extends ResistanceCalculations[PlayerSource]( + ResistanceCalculations.ValidInfantryTarget, + ResistanceCalculations.ExoSuitDirectExtractor + ) object InfantrySplashResistance - extends ResistanceCalculations[PlayerSource]( - ResistanceCalculations.ValidInfantryTarget, - ResistanceCalculations.ExoSuitSplashExtractor - ) + extends ResistanceCalculations[PlayerSource]( + ResistanceCalculations.ValidInfantryTarget, + ResistanceCalculations.ExoSuitSplashExtractor + ) object InfantryLashResistance - extends ResistanceCalculations[PlayerSource]( - ResistanceCalculations.ValidInfantryTarget, - ResistanceCalculations.MaximumResistance - ) + extends ResistanceCalculations[PlayerSource]( + ResistanceCalculations.ValidInfantryTarget, + ResistanceCalculations.MaximumResistance + ) object InfantryAggravatedResistance - extends ResistanceCalculations[PlayerSource]( - ResistanceCalculations.ValidInfantryTarget, - ResistanceCalculations.ExoSuitAggravatedExtractor - ) + extends ResistanceCalculations[PlayerSource]( + ResistanceCalculations.ValidInfantryTarget, + ResistanceCalculations.ExoSuitAggravatedExtractor + ) object VehicleHitResistance - extends ResistanceCalculations[VehicleSource]( - ResistanceCalculations.ValidVehicleTarget, - ResistanceCalculations.VehicleDirectExtractor - ) + extends ResistanceCalculations[VehicleSource]( + ResistanceCalculations.ValidVehicleTarget, + ResistanceCalculations.VehicleDirectExtractor + ) object VehicleSplashResistance - extends ResistanceCalculations[VehicleSource]( - ResistanceCalculations.ValidVehicleTarget, - ResistanceCalculations.VehicleSplashExtractor - ) + extends ResistanceCalculations[VehicleSource]( + ResistanceCalculations.ValidVehicleTarget, + ResistanceCalculations.VehicleSplashExtractor + ) object VehicleLashResistance - extends ResistanceCalculations[VehicleSource]( - ResistanceCalculations.ValidVehicleTarget, - ResistanceCalculations.NoResistExtractor - ) + extends ResistanceCalculations[VehicleSource]( + ResistanceCalculations.ValidVehicleTarget, + ResistanceCalculations.NoResistExtractor + ) object VehicleAggravatedResistance - extends ResistanceCalculations[VehicleSource]( - ResistanceCalculations.ValidVehicleTarget, - ResistanceCalculations.VehicleAggravatedExtractor - ) + extends ResistanceCalculations[VehicleSource]( + ResistanceCalculations.ValidVehicleTarget, + ResistanceCalculations.VehicleAggravatedExtractor + ) object AmenityHitResistance - extends ResistanceCalculations[ObjectSource]( - ResistanceCalculations.ValidAmenityTarget, - ResistanceCalculations.OtherDirectExtractor - ) + extends ResistanceCalculations[ObjectSource]( + ResistanceCalculations.ValidAmenityTarget, + ResistanceCalculations.OtherDirectExtractor + ) -object AMenitySplashResistance - extends ResistanceCalculations[ObjectSource]( - ResistanceCalculations.ValidAmenityTarget, - ResistanceCalculations.OtherSplashExtractor - ) +object AmenitySplashResistance + extends ResistanceCalculations[ObjectSource]( + ResistanceCalculations.ValidAmenityTarget, + ResistanceCalculations.OtherSplashExtractor + ) object NoResistanceSelection extends ResistanceSelection { def Direct: ProjectileCalculations.Form = None diff --git a/src/main/scala/net/psforever/objects/vital/resistance/ResistanceSelection.scala b/src/main/scala/net/psforever/objects/vital/resistance/ResistanceSelection.scala index ea889e1b..0e7c466d 100644 --- a/src/main/scala/net/psforever/objects/vital/resistance/ResistanceSelection.scala +++ b/src/main/scala/net/psforever/objects/vital/resistance/ResistanceSelection.scala @@ -2,7 +2,7 @@ package net.psforever.objects.vital.resistance import net.psforever.objects.ballistics._ -import net.psforever.objects.vital.NoResistance +import net.psforever.objects.vital.{DamageType, NoResistance} import net.psforever.objects.vital.projectile.ProjectileCalculations /** @@ -17,19 +17,19 @@ trait ResistanceSelection { def Lash: ProjectileCalculations.Form def Aggravated: ProjectileCalculations.Form - def apply(data: ResolvedProjectile): ProjectileCalculations.Form = - data.resolution match { - case ProjectileResolution.Hit => Direct - case ProjectileResolution.Splash => Splash - case ProjectileResolution.Lash => Lash - case _ => None - } + def apply(data : ResolvedProjectile) : ProjectileCalculations.Form = data.projectile.profile.ProjectileDamageType match { + case DamageType.Direct => Direct + case DamageType.Splash => Splash + case DamageType.Lash => Lash + case DamageType.Aggravated => Aggravated + case _ => None + } - def apply(res: ProjectileResolution.Value): ProjectileCalculations.Form = - res match { - case ProjectileResolution.Hit => Direct - case ProjectileResolution.Splash => Splash - case ProjectileResolution.Lash => Lash - case _ => None - } + def apply(res : DamageType.Value) : ProjectileCalculations.Form = res match { + case DamageType.Direct => Direct + case DamageType.Splash => Splash + case DamageType.Lash => Lash + case DamageType.Aggravated => Aggravated + case _ => None + } } diff --git a/src/test/scala/objects/DamageModelTests.scala b/src/test/scala/objects/DamageModelTests.scala index 06f461a3..c9de3554 100644 --- a/src/test/scala/objects/DamageModelTests.scala +++ b/src/test/scala/objects/DamageModelTests.scala @@ -28,7 +28,7 @@ class DamageCalculationsTests extends Specification { val target = Vehicle(GlobalDefinitions.fury) target.Position = Vector3(10, 0, 0) val resprojectile = ResolvedProjectile( - ProjectileResolution.Splash, + ProjectileResolution.Hit, projectile, SourceEntry(target), target.DamageModel, @@ -180,7 +180,7 @@ class ResistanceCalculationsTests extends Specification { "ignore all targets" in { val target = Vehicle(GlobalDefinitions.fury) val resprojectile = ResolvedProjectile( - ProjectileResolution.Splash, + ProjectileResolution.Hit, projectile, SourceEntry(target), target.DamageModel, @@ -192,7 +192,7 @@ class ResistanceCalculationsTests extends Specification { "discern standard infantry targets" in { val target = player val resprojectile = ResolvedProjectile( - ProjectileResolution.Splash, + ProjectileResolution.Hit, projectile, SourceEntry(target), target.DamageModel, @@ -208,7 +208,7 @@ class ResistanceCalculationsTests extends Specification { val target = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) target.ExoSuit = ExoSuitType.MAX val resprojectile = ResolvedProjectile( - ProjectileResolution.Splash, + ProjectileResolution.Hit, projectile, SourceEntry(target), target.DamageModel, @@ -223,7 +223,7 @@ class ResistanceCalculationsTests extends Specification { "discern ground vehicle targets" in { val target = Vehicle(GlobalDefinitions.fury) val resprojectile = ResolvedProjectile( - ProjectileResolution.Splash, + ProjectileResolution.Hit, projectile, SourceEntry(target), target.DamageModel, @@ -238,7 +238,7 @@ class ResistanceCalculationsTests extends Specification { "discern flying vehicle targets" in { val target = Vehicle(GlobalDefinitions.mosquito) val resprojectile = ResolvedProjectile( - ProjectileResolution.Splash, + ProjectileResolution.Hit, projectile, SourceEntry(target), target.DamageModel, @@ -284,7 +284,7 @@ class ResolutionCalculationsTests extends Specification { "calculate no damage" in { val target = player val resprojectile = ResolvedProjectile( - ProjectileResolution.Splash, + ProjectileResolution.Hit, projectile, SourceEntry(target), target.DamageModel, @@ -296,7 +296,7 @@ class ResolutionCalculationsTests extends Specification { "calculate no infantry damage for vehicles" in { val target1 = Vehicle(GlobalDefinitions.fury) //! val resprojectile1 = ResolvedProjectile( - ProjectileResolution.Splash, + ProjectileResolution.Hit, projectile, SourceEntry(target1), target1.DamageModel, @@ -306,7 +306,7 @@ class ResolutionCalculationsTests extends Specification { val target2 = player val resprojectile2 = ResolvedProjectile( - ProjectileResolution.Splash, + ProjectileResolution.Hit, projectile, SourceEntry(target2), target2.DamageModel, @@ -340,7 +340,7 @@ class ResolutionCalculationsTests extends Specification { "calculate no max damage for vehicles" in { val target1 = Vehicle(GlobalDefinitions.fury) //! val resprojectile1 = ResolvedProjectile( - ProjectileResolution.Splash, + ProjectileResolution.Hit, projectile, SourceEntry(target1), target1.DamageModel, @@ -350,7 +350,7 @@ class ResolutionCalculationsTests extends Specification { val target2 = player2 val resprojectile2 = ResolvedProjectile( - ProjectileResolution.Splash, + ProjectileResolution.Hit, projectile, SourceEntry(target2), target2.DamageModel, @@ -376,7 +376,7 @@ class ResolutionCalculationsTests extends Specification { "do not care if target is infantry for vehicle calculations" in { val target1 = player val resprojectile1 = ResolvedProjectile( - ProjectileResolution.Splash, + ProjectileResolution.Hit, projectile, SourceEntry(target1), target1.DamageModel, @@ -386,7 +386,7 @@ class ResolutionCalculationsTests extends Specification { val target2 = Vehicle(GlobalDefinitions.fury) //! val resprojectile2 = ResolvedProjectile( - ProjectileResolution.Splash, + ProjectileResolution.Hit, projectile, SourceEntry(target2), target2.DamageModel, @@ -450,7 +450,6 @@ class DamageModelTests extends Specification { Vector3.Zero ) val func: Any => ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile) - func(tplayer) tplayer.Health mustEqual 54 tplayer.Armor mustEqual 46 @@ -471,8 +470,7 @@ class DamageModelTests extends Specification { Vector3.Zero ) val func: Any => ResolvedProjectile = - resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash) - + resprojectile.damage_model.Calculate(resprojectile, DamageType.Splash) func(tplayer) tplayer.Health mustEqual 65 tplayer.Armor mustEqual 35 @@ -572,7 +570,7 @@ class DamageModelTests extends Specification { Vector3.Zero ) val func: Any => ResolvedProjectile = - resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash) + resprojectile.damage_model.Calculate(resprojectile, DamageType.Splash) func(vehicle) vehicle.Health mustEqual 518 diff --git a/src/test/scala/objects/DamageableTest.scala b/src/test/scala/objects/DamageableTest.scala index 6cce2e8b..554e6887 100644 --- a/src/test/scala/objects/DamageableTest.scala +++ b/src/test/scala/objects/DamageableTest.scala @@ -40,7 +40,6 @@ class DamageableTest extends Specification { "permit damage" in { val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -53,7 +52,6 @@ class DamageableTest extends Specification { "ignore attempts at non-zero damage" in { val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectileA, weaponA.Definition, @@ -78,7 +76,6 @@ class DamageableTest extends Specification { Faction = player1.Faction } val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -101,7 +98,6 @@ class DamageableTest extends Specification { Faction = PlanetSideEmpire.NC } val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -128,7 +124,6 @@ class DamageableTest extends Specification { Faction = player1.Faction } val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -152,7 +147,6 @@ class DamageableTest extends Specification { "permit jamming" in { val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -166,7 +160,6 @@ class DamageableTest extends Specification { "ignore attempts at jamming if the projectile is does not cause the effect" in { val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -181,7 +174,6 @@ class DamageableTest extends Specification { val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) target.Faction = player1.Faction val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -196,7 +188,6 @@ class DamageableTest extends Specification { "ignore attempts at jamming targets that are not jammable" in { val target = new TrapDeployable(GlobalDefinitions.tank_traps) val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -216,7 +207,6 @@ class DamageableTest extends Specification { val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) target.Faction = player1.Faction val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -271,7 +261,6 @@ class DamageableEntityDamageTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectile, weapon.Definition, @@ -342,7 +331,6 @@ class DamageableEntityDestroyedTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectile, weapon.Definition, @@ -416,7 +404,6 @@ class DamageableEntityNotDestroyTwice extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectile, weapon.Definition, @@ -487,7 +474,6 @@ class DamageableAmenityTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectile, weapon.Definition, @@ -580,7 +566,6 @@ class DamageableMountableDamageTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectile, weapon.Definition, @@ -675,7 +660,6 @@ class DamageableMountableDestroyTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectile, weapon.Definition, @@ -764,7 +748,6 @@ class DamageableWeaponTurretDamageTest extends ActorTest { val projectile = weapon.Projectile val turretSource = SourceEntry(turret) val resolved = ResolvedProjectile( - ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -862,7 +845,6 @@ class DamageableWeaponTurretJammerTest extends ActorTest { val projectile = weapon.Projectile val turretSource = SourceEntry(turret) val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectile, weapon.Definition, @@ -964,7 +946,6 @@ class DamageableWeaponTurretDestructionTest extends ActorTest { val weaponA = Tool(GlobalDefinitions.jammer_grenade) val projectileA = weaponA.Projectile val resolvedA = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectileA, weaponA.Definition, @@ -983,7 +964,6 @@ class DamageableWeaponTurretDestructionTest extends ActorTest { val weaponB = Tool(GlobalDefinitions.phoenix) //decimator val projectileB = weaponB.Projectile val resolvedB = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectileB, weaponB.Definition, @@ -1046,8 +1026,8 @@ class DamageableWeaponTurretDestructionTest extends ActorTest { ) assert( msg56.head match { - case VehicleServiceMessage.TurretUpgrade(SupportActor.ClearSpecific(List(t), _)) if t eq turret => true - case _ => false + case VehicleServiceMessage.TurretUpgrade(SupportActor.ClearSpecific(List(t), _)) if turret eq t => true + case _ => false } ) assert( @@ -1105,7 +1085,6 @@ class DamageableVehicleDamageTest extends ActorTest { val projectile = weapon.Projectile val vehicleSource = SourceEntry(atv) val resolved = ResolvedProjectile( - ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -1237,7 +1216,6 @@ class DamageableVehicleDamageMountedTest extends ActorTest { val projectile = weapon.Projectile val vehicleSource = SourceEntry(lodestar) val resolved = ResolvedProjectile( - ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -1385,7 +1363,6 @@ class DamageableVehicleJammeringMountedTest extends ActorTest { val weapon = Tool(GlobalDefinitions.jammer_grenade) val projectile = weapon.Projectile val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectile, weapon.Definition, @@ -1475,7 +1452,6 @@ class DamageableVehicleDestroyTest extends ActorTest { val projectile = weapon.Projectile val vehicleSource = SourceEntry(atv) val resolved = ResolvedProjectile( - ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -1601,7 +1577,6 @@ class DamageableVehicleDestroyMountedTest extends ActorTest { val weaponA = Tool(GlobalDefinitions.jammer_grenade) val projectileA = weaponA.Projectile val resolvedA = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectileA, weaponA.Definition, @@ -1620,7 +1595,6 @@ class DamageableVehicleDestroyMountedTest extends ActorTest { val weaponB = Tool(GlobalDefinitions.phoenix) //decimator val projectileB = weaponB.Projectile val resolvedB = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectileB, weaponB.Definition, diff --git a/src/test/scala/objects/DeployableTest.scala b/src/test/scala/objects/DeployableTest.scala index e96e4cfe..63e99a62 100644 --- a/src/test/scala/objects/DeployableTest.scala +++ b/src/test/scala/objects/DeployableTest.scala @@ -334,7 +334,6 @@ class ExplosiveDeployableJammerTest extends ActorTest { val pSource = PlayerSource(player1) val projectile = weapon.Projectile val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), jMineSource, j_mine.DamageModel, @@ -432,7 +431,6 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest { val pSource = PlayerSource(player1) val projectile = weapon.Projectile val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), hMineSource, h_mine.DamageModel, @@ -542,7 +540,6 @@ class ExplosiveDeployableDestructionTest extends ActorTest { val pSource = PlayerSource(player1) val projectile = weapon.Projectile val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), hMineSource, h_mine.DamageModel, diff --git a/src/test/scala/objects/GeneratorTest.scala b/src/test/scala/objects/GeneratorTest.scala index d334f156..48c98756 100644 --- a/src/test/scala/objects/GeneratorTest.scala +++ b/src/test/scala/objects/GeneratorTest.scala @@ -80,7 +80,6 @@ class GeneratorControlDamageTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectile, weapon.Definition, @@ -162,7 +161,6 @@ class GeneratorControlCriticalTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectile, weapon.Definition, @@ -253,7 +251,6 @@ class GeneratorControlDestroyedTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectile, weapon.Definition, @@ -384,7 +381,6 @@ class GeneratorControlKillsTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectile, weapon.Definition, @@ -507,7 +503,6 @@ class GeneratorControlNotDestroyTwice extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectile, weapon.Definition, @@ -597,7 +592,6 @@ class GeneratorControlNotDamageIfExplodingTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectile, weapon.Definition, @@ -691,7 +685,6 @@ class GeneratorControlNotRepairIfExplodingTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( - ProjectileResolution.Splash, Projectile( projectile, weapon.Definition, diff --git a/src/test/scala/objects/PlayerControlTest.scala b/src/test/scala/objects/PlayerControlTest.scala index 56b34460..06a528f2 100644 --- a/src/test/scala/objects/PlayerControlTest.scala +++ b/src/test/scala/objects/PlayerControlTest.scala @@ -378,7 +378,6 @@ class PlayerControlDamageTest extends ActorTest { val projectile = tool.Projectile val playerSource = SourceEntry(player2) val resolved = ResolvedProjectile( - ProjectileResolution.Hit, Projectile( projectile, tool.Definition, @@ -478,7 +477,6 @@ class PlayerControlDeathStandingTest extends ActorTest { val projectile = tool.Projectile val player1Source = SourceEntry(player1) val resolved = ResolvedProjectile( - ProjectileResolution.Hit, Projectile(projectile, tool.Definition, tool.FireMode, player1Source, 0, Vector3(2, 0, 0), Vector3(-1, 0, 0)), SourceEntry(player2), player2.DamageModel, @@ -607,7 +605,6 @@ class PlayerControlDeathSeatedTest extends ActorTest { val projectile = tool.Projectile val player1Source = SourceEntry(player1) val resolved = ResolvedProjectile( - ProjectileResolution.Hit, Projectile(projectile, tool.Definition, tool.FireMode, player1Source, 0, Vector3(2, 0, 0), Vector3(-1, 0, 0)), SourceEntry(player2), player2.DamageModel, diff --git a/src/test/scala/objects/ProjectileTest.scala b/src/test/scala/objects/ProjectileTest.scala index 414e7d85..44638010 100644 --- a/src/test/scala/objects/ProjectileTest.scala +++ b/src/test/scala/objects/ProjectileTest.scala @@ -352,13 +352,11 @@ class ProjectileTest extends Specification { "construct" in { val obj = ResolvedProjectile( - ProjectileResolution.Hit, projectile, PlayerSource(player2), fury_dm, Vector3(1.2f, 3.4f, 5.6f) ) - obj.resolution mustEqual ProjectileResolution.Hit obj.projectile mustEqual projectile obj.target mustEqual p2_source obj.damage_model mustEqual fury.DamageModel diff --git a/src/test/scala/objects/VehicleTest.scala b/src/test/scala/objects/VehicleTest.scala index 9992e8fb..7bd0fbf7 100644 --- a/src/test/scala/objects/VehicleTest.scala +++ b/src/test/scala/objects/VehicleTest.scala @@ -997,7 +997,7 @@ class VehicleControlShieldsNotChargingTooEarlyTest extends ActorTest { // val p_source = PlayerSource( Player(Avatar(0, "TestTarget", PlanetSideEmpire.NC, CharacterGender.Female, 1, CharacterVoice.Mute)) ) // val projectile = Projectile(beamer_wep.Projectile, GlobalDefinitions.beamer, beamer_wep.FireMode, p_source, GlobalDefinitions.beamer.ObjectId, Vector3.Zero, Vector3.Zero) // val fury_dm = Vehicle(GlobalDefinitions.fury).DamageModel -// val obj = ResolvedProjectile(ProjectileResolution.Hit, projectile, p_source, fury_dm, Vector3(1.2f, 3.4f, 5.6f)) +// val obj = ResolvedProjectile(projectile, p_source, fury_dm, Vector3(1.2f, 3.4f, 5.6f)) // // "not charge vehicle shields if recently damaged" in { // assert(vehicle.Shields == 0) diff --git a/src/test/scala/objects/VitalityTest.scala b/src/test/scala/objects/VitalityTest.scala index f5b1a74e..750ca986 100644 --- a/src/test/scala/objects/VitalityTest.scala +++ b/src/test/scala/objects/VitalityTest.scala @@ -21,7 +21,6 @@ class VitalityTest extends Specification { val pSource = PlayerSource(player) val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero) val resprojectile = ResolvedProjectile( - ProjectileResolution.Splash, projectile, SourceEntry(player), player.DamageModel, @@ -69,7 +68,6 @@ class VitalityTest extends Specification { val pSource = PlayerSource(player) val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero) val resprojectile = ResolvedProjectile( - ProjectileResolution.Splash, projectile, SourceEntry(player), player.DamageModel, From 5c8331ed9be8e97518a6ee40d3f6f96dbf566dea Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 10 Jul 2020 19:20:04 -0400 Subject: [PATCH 02/19] initial work and tests for AggravatedDamageMessage --- .../packet/game/AggravatedDamageMessage.scala | 27 +++++++++++++++++ .../game/AggravatedDamageMessageTest.scala | 29 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 common/src/main/scala/net/psforever/packet/game/AggravatedDamageMessage.scala create mode 100644 common/src/test/scala/game/AggravatedDamageMessageTest.scala diff --git a/common/src/main/scala/net/psforever/packet/game/AggravatedDamageMessage.scala b/common/src/main/scala/net/psforever/packet/game/AggravatedDamageMessage.scala new file mode 100644 index 00000000..faa48528 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/AggravatedDamageMessage.scala @@ -0,0 +1,27 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} +import net.psforever.types.PlanetSideGUID +import scodec.Codec +import scodec.codecs._ + +/** + * na + * @param guid na + * @param unk na + */ +final case class AggravatedDamageMessage(guid : PlanetSideGUID, + unk : Long) + extends PlanetSideGamePacket { + type Packet = AggravatedDamageMessage + def opcode = GamePacketOpcode.AggravatedDamageMessage + def encode = AggravatedDamageMessage.encode(this) +} + +object AggravatedDamageMessage extends Marshallable[AggravatedDamageMessage] { + implicit val codec : Codec[AggravatedDamageMessage] = ( + ("guid" | PlanetSideGUID.codec) :: + ("unk" | uint32L) + ).as[AggravatedDamageMessage] +} diff --git a/common/src/test/scala/game/AggravatedDamageMessageTest.scala b/common/src/test/scala/game/AggravatedDamageMessageTest.scala new file mode 100644 index 00000000..61529e56 --- /dev/null +++ b/common/src/test/scala/game/AggravatedDamageMessageTest.scala @@ -0,0 +1,29 @@ +// Copyright (c) 2020 PSForever +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import net.psforever.types.PlanetSideGUID +import scodec.bits._ + +class AggravatedDamageMessageTest extends Specification { + val string = hex"6a350a0e000000" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case AggravatedDamageMessage(guid,unk) => + guid mustEqual PlanetSideGUID(2613) + unk mustEqual 14 + case _ => + ko + } + } + + "encode" in { + val msg = AggravatedDamageMessage(PlanetSideGUID(2613), 14) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } +} From 811c7b09b5668d9f1a74f7a20493d3588b3842e5 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 14 Jul 2020 00:14:43 -0400 Subject: [PATCH 03/19] wrote aggravated damage properties into the definitions of projectiles that convey them; basic workflow for aggravation from projectile damage is in place, but not tested --- .../net/psforever/objects/avatar/Aura.scala | 10 ++ .../objects/avatar/AuraEffectBehavior.scala | 130 ++++++++++++++++++ .../objects/ballistics/AggravatedDamage.scala | 12 +- .../actors/session/SessionActor.scala | 99 +++++++------ .../psforever/objects/GlobalDefinitions.scala | 21 +-- .../scala/net/psforever/objects/Player.scala | 26 +++- 6 files changed, 233 insertions(+), 65 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/avatar/Aura.scala create mode 100644 common/src/main/scala/net/psforever/objects/avatar/AuraEffectBehavior.scala diff --git a/common/src/main/scala/net/psforever/objects/avatar/Aura.scala b/common/src/main/scala/net/psforever/objects/avatar/Aura.scala new file mode 100644 index 00000000..935dce50 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/avatar/Aura.scala @@ -0,0 +1,10 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.avatar + +object Aura extends Enumeration { + final val None = Value(0) + final val Plasma = Value(1) + final val Comet = Value(2) + final val Napalm = Value(4) + final val Fire = Value(8) +} diff --git a/common/src/main/scala/net/psforever/objects/avatar/AuraEffectBehavior.scala b/common/src/main/scala/net/psforever/objects/avatar/AuraEffectBehavior.scala new file mode 100644 index 00000000..3a467a94 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/avatar/AuraEffectBehavior.scala @@ -0,0 +1,130 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.avatar + +import akka.actor.{Actor, Cancellable} +import net.psforever.objects.Player +import net.psforever.objects.ballistics.{AggravatedDamage, AggravatedInfo, ResolvedProjectile} +import net.psforever.objects.vital.DamageType + +import scala.collection.mutable +import scala.concurrent.duration._ +import scala.concurrent.ExecutionContext.Implicits.global + +trait AuraEffectBehavior { + _ : Actor => + private var activeEffectIndex : Long = 0 + private var effectsToIds : mutable.HashMap[Aura.Value, List[Long]] = mutable.HashMap.empty[Aura.Value, List[Long]] + private var idsToTimers : mutable.LongMap[Cancellable] = mutable.LongMap.empty[Cancellable] + private var idsToEntries : mutable.LongMap[AuraEffectBehavior.Entry] = mutable.LongMap.empty[AuraEffectBehavior.Entry] + + def AuraTargetObject : Player + + val auraBehavior : Receive = { + case AuraEffectBehavior.Aggravate(id, 0) => + CleanupEffect(id) match { + case Aura.None => ; + case _ => UpdateAggravatedEffect(AuraTargetObject) + } + + case AuraEffectBehavior.Aggravate(id, iteration) => ; + idsToTimers.remove(id) match { + case Some(timer) => timer.cancel + case _ => ; + } + idsToEntries.get(id) match { + case Some(entry) => + //TODO stuff ... + idsToTimers += id -> context.system.scheduler.scheduleOnce( + entry.effect.infliction_rate milliseconds, self, AuraEffectBehavior.Aggravate(id, iteration - 1)) + case _ => ; + } + } + + def AggravationEffect(data : ResolvedProjectile) : Unit = { + data.projectile.profile.Aggravated match { + case Some(damage) + if data.projectile.profile.ProjectileDamageType == DamageType.Aggravated && damage.effect_type != Aura.None => + AggravationEffect(damage, data) + case _ => ; + } + } + + private def AggravationEffect(aggravation : AggravatedDamage, data : ResolvedProjectile) : Unit = { + val effect = aggravation.effect_type + val obj = AuraTargetObject + if(obj.Aura.contains(effect)) { //TODO cumulative? + SetupAggravationEntry(aggravation) + } + else if(obj.Aura.diff(obj.AddEffectToAura(effect)).contains(effect)) { + SetupAggravationEntry(aggravation) + UpdateAggravatedEffect(obj) + } + } + + private def SetupAggravationEntry(aggravation : AggravatedDamage) : Unit = { + val effect = aggravation.effect_type + aggravation.info.foreach { infos => + //get unused id + val id = activeEffectIndex + activeEffectIndex += 1 + //pair aura effect with id + effectsToIds.get(effect) match { + case None | Some(Nil) => effectsToIds += effect -> List(id) + case Some(list) => effectsToIds -> (list :+ id) + } + //pair id with entry + idsToEntries += id -> AuraEffectBehavior.Entry(id, infos, aggravation, 0) + //pair id with timer + val iterations = (aggravation.duration / infos.infliction_rate).toInt + idsToTimers += id -> context.system.scheduler.scheduleOnce(infos.infliction_rate milliseconds, self, AuraEffectBehavior.Aggravate(id, iterations)) + } + } + + def CleanupEffect(id : Long) : Aura.Value = { + //remove and cancel timer + idsToTimers.remove(id) match { + case Some(timer) => timer.cancel + case _ => ; + } + //remove entry and cache effect + val out = idsToEntries.remove(id) match { + case Some(entry) => entry.aggravation.effect_type + case _ => Aura.None + } + //remove id and, if now unsupported, effect + (effectsToIds.get(out) match { + case Some(list) => (out, list.filterNot(_ == id)) + case _ => (Aura.None, Nil) + }) match { + case (Aura.None, _) => + Aura.None + case (effect, Nil) => + effectsToIds.remove(effect) + effect + case (effect, list) => + effectsToIds += effect -> list + Aura.None + } + } + + def EndAllEffects() : Unit = { + idsToEntries.clear + idsToTimers.values.foreach { _.cancel } + idsToTimers.clear + effectsToIds.clear + UpdateAggravatedEffect(AuraTargetObject) + } + + def UpdateAggravatedEffect(target : Player) : Unit = { + import services.avatar.{AvatarAction, AvatarServiceMessage} + val zone = target.Zone + val value = target.Aura.foldLeft(0)(_ + _.id) + zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(target.GUID, 54, value)) + } +} + +object AuraEffectBehavior { + private case class Entry(id : Long, effect : AggravatedInfo, aggravation : AggravatedDamage, damage : Any) + + private case class Aggravate(id : Long, iteration : Int) +} diff --git a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala index 67a217cc..e430bcda 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala +++ b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala @@ -1,13 +1,9 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.ballistics +import net.psforever.objects.avatar.Aura import net.psforever.objects.vital.DamageType -object AggravatedEffect extends Enumeration { - type Type = Value - val Comet, Fire, Plasma, Napalm, None = Value -} - final case class AggravatedInfo(damage_type : DamageType.Value, degradation_percentage : Float, infliction_rate : Long) { @@ -15,7 +11,7 @@ final case class AggravatedInfo(damage_type : DamageType.Value, } final case class AggravatedDamage(info : List[AggravatedInfo], - effect_type : AggravatedEffect.Value, + effect_type : Aura.Value, duration : Long, max_factor : Float, cumulative_damage_degrade : Boolean, @@ -23,13 +19,13 @@ final case class AggravatedDamage(info : List[AggravatedInfo], object AggravatedDamage { def apply(info : AggravatedInfo, - effect_type : AggravatedEffect.Value, + effect_type : Aura.Value, duration : Long, max_factor : Float) : AggravatedDamage = AggravatedDamage(List(info), effect_type, duration, max_factor, cumulative_damage_degrade = true, vanu_aggravated = false) def apply(info : AggravatedInfo, - effect_type : AggravatedEffect.Value, + effect_type : Aura.Value, duration : Long, max_factor : Float, vanu_aggravated : Boolean) : AggravatedDamage = diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index 0c1c76b4..8770d1e4 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -3383,10 +3383,10 @@ class SessionActor extends Actor with MDCContextAware { context.system.scheduler.scheduleWithFixedDelay(0 seconds, 500 milliseconds, self, PokeClient()) accountIntermediary ! RetrieveAccountData(token) - case msg @ MountVehicleCargoMsg(player_guid, cargo_guid, carrier_guid, unk4) => + case msg@MountVehicleCargoMsg(player_guid, cargo_guid, carrier_guid, unk4) => log.info(msg.toString) (continent.GUID(cargo_guid), continent.GUID(carrier_guid)) match { - case (Some(cargo: Vehicle), Some(carrier: Vehicle)) => + case (Some(cargo : Vehicle), Some(carrier : Vehicle)) => carrier.CargoHolds.find({ case (_, hold) => !hold.isOccupied }) match { case Some((mountPoint, _)) => //try begin the mount process cargo.Actor ! CargoBehavior.CheckCargoMounting(carrier_guid, mountPoint, 0) @@ -3402,24 +3402,24 @@ class SessionActor extends Actor with MDCContextAware { case _ => ; } - case msg @ DismountVehicleCargoMsg(player_guid, cargo_guid, bailed, requestedByPassenger, kicked) => + case msg@DismountVehicleCargoMsg(player_guid, cargo_guid, bailed, requestedByPassenger, kicked) => log.info(msg.toString) //when kicked by carrier driver, player_guid will be PlanetSideGUID(0) //when exiting of the cargo vehicle driver's own accord, player_guid will be the cargo vehicle driver continent.GUID(cargo_guid) match { - case Some(cargo: Vehicle) if !requestedByPassenger => + case Some(cargo : Vehicle) if !requestedByPassenger => continent.GUID(cargo.MountedIn) match { - case Some(carrier: Vehicle) => + case Some(carrier : Vehicle) => CargoBehavior.HandleVehicleCargoDismount(continent, cargo_guid, bailed, requestedByPassenger, kicked) case _ => ; } case _ => ; } - case msg @ CharacterCreateRequestMessage(name, head, voice, gender, empire) => + case msg@CharacterCreateRequestMessage(name, head, voice, gender, empire) => avatarActor ! AvatarActor.CreateAvatar(name, head, voice, gender, empire) - case msg @ CharacterRequestMessage(charId, action) => + case msg@CharacterRequestMessage(charId, action) => action match { case CharacterRequestAction.Delete => avatarActor ! AvatarActor.DeleteAvatar(charId.toInt) @@ -3430,11 +3430,11 @@ class SessionActor extends Actor with MDCContextAware { case KeepAliveMessage(_) => keepAliveFunc() - case msg @ BeginZoningMessage() => + case msg@BeginZoningMessage() => log.info("Reticulating splines ...") zoneLoaded = None - val continentId = continent.id - val faction = player.Faction + val continentId = continent.id + val faction = player.Faction val factionChannel = s"$faction" continent.AvatarEvents ! Service.Join(continentId) continent.AvatarEvents ! Service.Join(factionChannel) @@ -3447,7 +3447,7 @@ class SessionActor extends Actor with MDCContextAware { if (connectionState != 100) configZone(continent) sendResponse(TimeOfDayMessage(1191182336)) //custom - sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list + sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 0)) // disable festive backpacks //find and reclaim own deployables, if any @@ -3476,7 +3476,7 @@ class SessionActor extends Actor with MDCContextAware { ) }) turrets.foreach(obj => { - val objGUID = obj.GUID + val objGUID = obj.GUID val definition = obj.Definition sendResponse( ObjectCreateMessage( @@ -3510,11 +3510,11 @@ class SessionActor extends Actor with MDCContextAware { normal .filter(obj => obj.Definition.DeployCategory == DeployableCategory.Sensors && - !obj.Destroyed && - (obj match { - case jObj: JammableUnit => !jObj.Jammed; - case _ => true - }) + !obj.Destroyed && + (obj match { + case jObj : JammableUnit => !jObj.Jammed; + case _ => true + }) ) .foreach(obj => { sendResponse(TriggerEffectMessage(obj.GUID, "on", true, 1000)) @@ -3581,7 +3581,7 @@ class SessionActor extends Actor with MDCContextAware { ( a, (continent.GUID(player.VehicleSeated) match { - case Some(vehicle: Vehicle) if vehicle.PassengerInSeat(player).isDefined => + case Some(vehicle : Vehicle) if vehicle.PassengerInSeat(player).isDefined => b.partition { _.GUID != vehicle.GUID } @@ -3599,7 +3599,7 @@ class SessionActor extends Actor with MDCContextAware { val allActiveVehicles = vehicles ++ usedVehicle //active vehicles (and some wreckage) vehicles.foreach(vehicle => { - val vguid = vehicle.GUID + val vguid = vehicle.GUID val vdefinition = vehicle.Definition sendResponse( ObjectCreateMessage(vdefinition.ObjectId, vguid, vdefinition.Packet.ConstructorData(vehicle).get) @@ -3609,7 +3609,7 @@ class SessionActor extends Actor with MDCContextAware { .filter({ case (index, seat) => seat.isOccupied && live.contains(seat.Occupant.get) && index > 0 }) .foreach({ case (index, seat) => - val targetPlayer = seat.Occupant.get + val targetPlayer = seat.Occupant.get val targetDefiniton = targetPlayer.avatar.definition sendResponse( ObjectCreateMessage( @@ -3637,7 +3637,7 @@ class SessionActor extends Actor with MDCContextAware { }) .foreach({ case (index, seat) => - val targetPlayer = seat.Occupant.get + val targetPlayer = seat.Occupant.get val targetDefinition = targetPlayer.avatar.definition sendResponse( ObjectCreateMessage( @@ -3691,9 +3691,9 @@ class SessionActor extends Actor with MDCContextAware { //special effects sendResponse(PlanetsideAttributeMessage(obj.GUID, 52, 1)) // ant panel glow Vehicles.FindANTChargingSource(obj, None).orElse(Vehicles.FindANTDischargingTarget(obj, None)) match { - case Some(silo: ResourceSilo) => + case Some(silo : ResourceSilo) => sendResponse(PlanetsideAttributeMessage(silo.GUID, 49, 1)) // silo orb particle effect - case Some(_: WarpGate) => + case Some(_ : WarpGate) => sendResponse(PlanetsideAttributeMessage(obj.GUID, 49, 1)) // ant orb particle effect case _ => ; } @@ -3710,7 +3710,7 @@ class SessionActor extends Actor with MDCContextAware { case ((terminal_guid, interface_guid)) => val parent_guid = PlanetSideGUID(terminal_guid) continent.GUID(interface_guid) match { - case Some(obj: Terminal) => + case Some(obj : Terminal) => val objDef = obj.Definition sendResponse( ObjectCreateMessage( @@ -3724,7 +3724,7 @@ class SessionActor extends Actor with MDCContextAware { } //seat terminal occupants continent.GUID(terminal_guid) match { - case Some(obj: Mountable) => + case Some(obj : Mountable) => obj.Seats(0).Occupant match { case Some(targetPlayer) => val targetDefinition = targetPlayer.avatar.definition @@ -3746,12 +3746,12 @@ class SessionActor extends Actor with MDCContextAware { continent.map.turretToWeapon .map { case ((turret_guid, _)) => continent.GUID(turret_guid) } .collect { - case Some(turret: FacilityTurret) => + case Some(turret : FacilityTurret) => val pguid = turret.GUID //attached weapon if (!turret.isUpgrading) { turret.ControlledWeapon(wepNumber = 1) match { - case Some(obj: Tool) => + case Some(obj : Tool) => val objDef = obj.Definition sendResponse( ObjectCreateMessage( @@ -3781,30 +3781,36 @@ class SessionActor extends Actor with MDCContextAware { case None => ; } } - continent.VehicleEvents ! VehicleServiceMessage(continent.id, VehicleAction.UpdateAmsSpawnPoint(continent)) + continent.VehicleEvents ! VehicleServiceMessage( + continent.Id, + VehicleAction.UpdateAmsSpawnPoint(continent) + ) upstreamMessageCount = 0 zoneLoaded = Some(true) case msg @ PlayerStateMessageUpstream( - avatar_guid, - pos, - vel, - yaw, - pitch, - yaw_upper, - seq_time, - unk3, - is_crouching, - is_jumping, - jump_thrust, - is_cloaking, - unk5, - unk6 - ) => + avatar_guid, + pos, + vel, + yaw, + pitch, + yaw_upper, + seq_time, + unk3, + is_crouching, + is_jumping, + jump_thrust, + is_cloaking, + unk5, + unk6 + ) => //log.info(s"$msg") persist() turnCounterFunc(avatar_guid) - val isMoving = WorldEntity.isMoving(vel) + if(is_crouching && !player.Crouching) { + //sendResponse(PlanetsideAttributeMessage(player.GUID, 54, 2)) + } + val isMoving = WorldEntity.isMoving(vel) val isMovingPlus = isMoving || is_jumping || jump_thrust if (isMovingPlus) { CancelZoningProcessWithDescriptiveReason("cancel_motion") @@ -3829,7 +3835,10 @@ class SessionActor extends Actor with MDCContextAware { } accessedContainer match { case Some(veh: Vehicle) => - if (isMoving || veh.isMoving(1) || Vector3.DistanceSquared(player.Position, veh.TrunkLocation) > 9) { + if (isMoving || veh.isMoving(1) || Vector3.DistanceSquared( + player.Position, + veh.TrunkLocation + ) > 9) { val guid = player.GUID sendResponse(UnuseItemMessage(guid, veh.GUID)) sendResponse(UnuseItemMessage(guid, guid)) diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 561484b6..c581a8a5 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2,7 +2,8 @@ package net.psforever.objects import net.psforever.objects.avatar.Certification -import net.psforever.objects.ballistics.{AggravatedDamage, AggravatedEffect, AggravatedInfo, Projectiles} +import net.psforever.objects.avatar.Aura +import net.psforever.objects.ballistics.{AggravatedDamage, AggravatedInfo, Projectiles} import net.psforever.objects.ce.{DeployableCategory, DeployedItem} import net.psforever.objects.definition._ import net.psforever.objects.definition.converter._ @@ -2318,7 +2319,7 @@ object GlobalDefinitions { aphelion_starfire_projectile.ProjectileDamageType = DamageType.Aggravated aphelion_starfire_projectile.Aggravated = AggravatedDamage( AggravatedInfo(DamageType.Direct, 0.25f, 250), - AggravatedEffect.None, + Aura.None, 0, 0f, true @@ -2449,7 +2450,7 @@ object GlobalDefinitions { comet_projectile.ProjectileDamageType = DamageType.Aggravated comet_projectile.Aggravated = AggravatedDamage( AggravatedInfo(DamageType.Direct, 0.2f, 500), - AggravatedEffect.Comet, + Aura.Comet, 0, 10f ) @@ -2600,7 +2601,7 @@ object GlobalDefinitions { flamethrower_fireball.ProjectileDamageType = DamageType.Aggravated flamethrower_fireball.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.9f, 500), AggravatedInfo(DamageType.Splash, 0.9f, 500)), - AggravatedEffect.Fire, + Aura.Fire, 5000, 0.1f, false, @@ -2621,7 +2622,7 @@ object GlobalDefinitions { flamethrower_projectile.ProjectileDamageType = DamageType.Aggravated flamethrower_projectile.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.5f, 500)), - AggravatedEffect.Fire, + Aura.Fire, 5000, 0.5f, false, @@ -3457,7 +3458,7 @@ object GlobalDefinitions { plasma_cartridge_projectile.ProjectileDamageType = DamageType.Aggravated plasma_cartridge_projectile.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), - AggravatedEffect.Plasma, + Aura.Plasma, 0, 1.5f, true, @@ -3476,7 +3477,7 @@ object GlobalDefinitions { plasma_cartridge_projectile_b.ProjectileDamageType = DamageType.Aggravated plasma_cartridge_projectile_b.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), - AggravatedEffect.Plasma, + Aura.Plasma, 0, 1.5f, true, @@ -3494,7 +3495,7 @@ object GlobalDefinitions { plasma_grenade_projectile.ProjectileDamageType = DamageType.Aggravated plasma_grenade_projectile.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), - AggravatedEffect.Plasma, + Aura.Plasma, 0, 1.5f, true, @@ -3513,7 +3514,7 @@ object GlobalDefinitions { plasma_grenade_projectile_B.ProjectileDamageType = DamageType.Aggravated plasma_grenade_projectile_B.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), - AggravatedEffect.Plasma, + Aura.Plasma, 0, 1.5f, true, @@ -3822,7 +3823,7 @@ object GlobalDefinitions { starfire_projectile.ProjectileDamageType = DamageType.Aggravated starfire_projectile.Aggravated = AggravatedDamage( AggravatedInfo(DamageType.Direct, 0.25f, 250), - AggravatedEffect.Comet, + Aura.Comet, 0, 0f, true diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala index bd79347f..19ff2ad7 100644 --- a/src/main/scala/net/psforever/objects/Player.scala +++ b/src/main/scala/net/psforever/objects/Player.scala @@ -1,8 +1,16 @@ // Copyright (c) 2017 PSForever package net.psforever.objects -import net.psforever.objects.avatar.{Avatar, LoadoutManager} -import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition} +import net.psforever.objects.avatar.{ + Avatar, + Aura => AuraEffect, + LoadoutManager +} +import net.psforever.objects.definition.{ + AvatarDefinition, + ExoSuitDefinition, + SpecialExoSuitDefinition +} import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit} import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} import net.psforever.objects.serverobject.PlanetSideServerObject @@ -49,6 +57,8 @@ class Player(var avatar: Avatar) private var vehicleSeated: Option[PlanetSideGUID] = None + private var aura : Set[AuraEffect.Value] = Set.empty[AuraEffect.Value] + Continent = "home2" //the zone id var spectator: Boolean = false @@ -345,6 +355,18 @@ class Player(var avatar: Avatar) afk = away AwayFromKeyboard } + + def Aura : Set[AuraEffect.Value] = aura + + def AddEffectToAura(effect : AuraEffect.Value) : Set[AuraEffect.Value] = { + aura = aura + effect + Aura + } + + def RemoveEffectFromAura(effect : AuraEffect.Value) : Set[AuraEffect.Value] = { + aura = aura - effect + Aura + } private var usingSpecial: SpecialExoSuitDefinition.Mode.Value => SpecialExoSuitDefinition.Mode.Value = DefaultUsingSpecial From 2054586a0dfdb9f839e3c486ba59f00f9f7e5d7b Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 14 Jul 2020 17:52:19 -0400 Subject: [PATCH 04/19] merge rebase resolutions --- src/main/scala/net/psforever/actors/session/SessionActor.scala | 3 --- src/main/scala/net/psforever/objects/Player.scala | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index 8770d1e4..a76870c1 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -3807,9 +3807,6 @@ class SessionActor extends Actor with MDCContextAware { //log.info(s"$msg") persist() turnCounterFunc(avatar_guid) - if(is_crouching && !player.Crouching) { - //sendResponse(PlanetsideAttributeMessage(player.GUID, 54, 2)) - } val isMoving = WorldEntity.isMoving(vel) val isMovingPlus = isMoving || is_jumping || jump_thrust if (isMovingPlus) { diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala index 19ff2ad7..111fa739 100644 --- a/src/main/scala/net/psforever/objects/Player.scala +++ b/src/main/scala/net/psforever/objects/Player.scala @@ -355,7 +355,7 @@ class Player(var avatar: Avatar) afk = away AwayFromKeyboard } - + def Aura : Set[AuraEffect.Value] = aura def AddEffectToAura(effect : AuraEffect.Value) : Set[AuraEffect.Value] = { From d149e07e89c1136094815e48971c9302104711a9 Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 17 Jul 2020 21:36:01 -0400 Subject: [PATCH 05/19] grenade projectile flag on projectile definition; better timing control on aura behavior and integration with player control --- .../objects/avatar/AuraEffectBehavior.scala | 79 ++++++++++++------- .../actors/session/SessionActor.scala | 12 ++- .../psforever/objects/GlobalDefinitions.scala | 30 ++++++- .../objects/avatar/PlayerControl.scala | 13 ++- .../definition/ProjectileDefinition.scala | 8 ++ 5 files changed, 106 insertions(+), 36 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/avatar/AuraEffectBehavior.scala b/common/src/main/scala/net/psforever/objects/avatar/AuraEffectBehavior.scala index 3a467a94..0d4cb0ab 100644 --- a/common/src/main/scala/net/psforever/objects/avatar/AuraEffectBehavior.scala +++ b/common/src/main/scala/net/psforever/objects/avatar/AuraEffectBehavior.scala @@ -13,43 +13,63 @@ import scala.concurrent.ExecutionContext.Implicits.global trait AuraEffectBehavior { _ : Actor => private var activeEffectIndex : Long = 0 - private var effectsToIds : mutable.HashMap[Aura.Value, List[Long]] = mutable.HashMap.empty[Aura.Value, List[Long]] - private var idsToTimers : mutable.LongMap[Cancellable] = mutable.LongMap.empty[Cancellable] + private var effectsToIds : mutable.HashMap[Aura.Value, List[Long]] = mutable.HashMap.empty[Aura.Value, List[Long]] + private var idsToTimers : mutable.LongMap[Cancellable] = mutable.LongMap.empty[Cancellable] private var idsToEntries : mutable.LongMap[AuraEffectBehavior.Entry] = mutable.LongMap.empty[AuraEffectBehavior.Entry] def AuraTargetObject : Player val auraBehavior : Receive = { - case AuraEffectBehavior.Aggravate(id, 0) => - CleanupEffect(id) match { - case Aura.None => ; - case _ => UpdateAggravatedEffect(AuraTargetObject) - } + case AuraEffectBehavior.Aggravate(id, 0, 0) => + CancelEffectTimer(id) + PerformCleanupEffect(id) - case AuraEffectBehavior.Aggravate(id, iteration) => ; - idsToTimers.remove(id) match { - case Some(timer) => timer.cancel - case _ => ; - } - idsToEntries.get(id) match { - case Some(entry) => - //TODO stuff ... - idsToTimers += id -> context.system.scheduler.scheduleOnce( - entry.effect.infliction_rate milliseconds, self, AuraEffectBehavior.Aggravate(id, iteration - 1)) - case _ => ; - } + case AuraEffectBehavior.Aggravate(id, 0, leftoverTime) => + PerformAggravationAndRetimeEvent(id, iteration = 0, Some(leftoverTime), leftoverTime = 0) + + case AuraEffectBehavior.Aggravate(id, iteration, leftover) => ; + PerformAggravationAndRetimeEvent(id, iteration - 1, None, leftover) } - def AggravationEffect(data : ResolvedProjectile) : Unit = { - data.projectile.profile.Aggravated match { - case Some(damage) - if data.projectile.profile.ProjectileDamageType == DamageType.Aggravated && damage.effect_type != Aura.None => - AggravationEffect(damage, data) + def PerformAggravationAndRetimeEvent(id : Long, iteration : Int, time : Option[Long], leftoverTime : Long) : Unit = { + CancelEffectTimer(id) + idsToEntries.get(id) match { + case Some(entry) => + //TODO stuff ... + idsToTimers += id -> context.system.scheduler.scheduleOnce( + time.getOrElse(entry.effect.infliction_rate) milliseconds, + self, + AuraEffectBehavior.Aggravate(id, iteration, leftoverTime) + ) + case _ => + PerformCleanupEffect(id) + } + } + + def CancelEffectTimer(id : Long) : Unit = { + idsToTimers.remove(id) match { + case Some(timer) => timer.cancel case _ => ; } } - private def AggravationEffect(aggravation : AggravatedDamage, data : ResolvedProjectile) : Unit = { + def PerformCleanupEffect(id : Long) : Unit = { + CleanupEffect(id) match { + case Aura.None => ; + case _ => UpdateAggravatedEffect(AuraTargetObject) + } + } + + def TryAggravationEffect(data : ResolvedProjectile) : Unit = { + data.projectile.profile.Aggravated match { + case Some(damage) + if data.projectile.profile.ProjectileDamageType == DamageType.Aggravated && damage.effect_type != Aura.None => + TryAggravationEffect(damage, data) + case _ => ; + } + } + + private def TryAggravationEffect(aggravation : AggravatedDamage, data : ResolvedProjectile) : Unit = { val effect = aggravation.effect_type val obj = AuraTargetObject if(obj.Aura.contains(effect)) { //TODO cumulative? @@ -76,7 +96,8 @@ trait AuraEffectBehavior { idsToEntries += id -> AuraEffectBehavior.Entry(id, infos, aggravation, 0) //pair id with timer val iterations = (aggravation.duration / infos.infliction_rate).toInt - idsToTimers += id -> context.system.scheduler.scheduleOnce(infos.infliction_rate milliseconds, self, AuraEffectBehavior.Aggravate(id, iterations)) + val leftoverTime = aggravation.duration % infos.infliction_rate + idsToTimers += id -> context.system.scheduler.scheduleOnce(infos.infliction_rate milliseconds, self, AuraEffectBehavior.Aggravate(id, iterations, leftoverTime)) } } @@ -112,6 +133,10 @@ trait AuraEffectBehavior { idsToTimers.values.foreach { _.cancel } idsToTimers.clear effectsToIds.clear + } + + def EndAllEffectsAndUpdate() : Unit = { + EndAllEffects() UpdateAggravatedEffect(AuraTargetObject) } @@ -126,5 +151,5 @@ trait AuraEffectBehavior { object AuraEffectBehavior { private case class Entry(id : Long, effect : AggravatedInfo, aggravation : AggravatedDamage, damage : Any) - private case class Aggravate(id : Long, iteration : Int) + private case class Aggravate(id : Long, iterations : Int, leftover : Long) } diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index a76870c1..06cf7d55 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -5411,9 +5411,15 @@ class SessionActor extends Actor with MDCContextAware { projectile.Velocity = projectile_vel //direct_victim_uid ValidObject(direct_victim_uid) match { - case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) => - CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target) - ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, target.Position) match { + case Some( + target: PlanetSideGameObject with FactionAffinity with Vitality + ) => + CheckForHitPositionDiscrepancy( + projectile_guid, + explosion_pos, + target + ) + ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, explosion_pos) match { case Some(projectile) => HandleDealingDamage(target, projectile) case None => ; diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index c581a8a5..dbdf1411 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2474,6 +2474,7 @@ object GlobalDefinitions { dynomite_projectile.Damage1 = 175 dynomite_projectile.DamageAtEdge = 0.1f dynomite_projectile.DamageRadius = 10f + dynomite_projectile.GrenadeProjectile = true dynomite_projectile.ProjectileDamageType = DamageType.Splash dynomite_projectile.InitialVelocity = 30 dynomite_projectile.Lifespan = 3f @@ -2691,6 +2692,7 @@ object GlobalDefinitions { frag_cartridge_projectile.Damage1 = 100 frag_cartridge_projectile.DamageAtEdge = 0.1f frag_cartridge_projectile.DamageRadius = 7f + frag_cartridge_projectile.GrenadeProjectile = true frag_cartridge_projectile.ProjectileDamageType = DamageType.Splash frag_cartridge_projectile.InitialVelocity = 30 frag_cartridge_projectile.Lifespan = 15f @@ -2703,6 +2705,7 @@ object GlobalDefinitions { frag_cartridge_projectile_b.Damage1 = 100 frag_cartridge_projectile_b.DamageAtEdge = 0.1f frag_cartridge_projectile_b.DamageRadius = 5f + frag_cartridge_projectile_b.GrenadeProjectile = true frag_cartridge_projectile_b.ProjectileDamageType = DamageType.Splash frag_cartridge_projectile_b.InitialVelocity = 30 frag_cartridge_projectile_b.Lifespan = 2f @@ -2714,6 +2717,7 @@ object GlobalDefinitions { frag_grenade_projectile.Damage1 = 100 frag_grenade_projectile.DamageAtEdge = 0.1f frag_grenade_projectile.DamageRadius = 7f + frag_grenade_projectile.GrenadeProjectile = true frag_grenade_projectile.ProjectileDamageType = DamageType.Splash frag_grenade_projectile.InitialVelocity = 30 frag_grenade_projectile.Lifespan = 15f @@ -2726,6 +2730,7 @@ object GlobalDefinitions { frag_grenade_projectile_enh.Damage1 = 100 frag_grenade_projectile_enh.DamageAtEdge = 0.1f frag_grenade_projectile_enh.DamageRadius = 7f + frag_grenade_projectile_enh.GrenadeProjectile = true frag_grenade_projectile_enh.ProjectileDamageType = DamageType.Splash frag_grenade_projectile_enh.InitialVelocity = 30 frag_grenade_projectile_enh.Lifespan = 2f @@ -2776,6 +2781,7 @@ object GlobalDefinitions { heavy_grenade_projectile.Damage4 = 66 heavy_grenade_projectile.DamageAtEdge = 0.1f heavy_grenade_projectile.DamageRadius = 5f + heavy_grenade_projectile.GrenadeProjectile = true heavy_grenade_projectile.ProjectileDamageType = DamageType.Splash heavy_grenade_projectile.InitialVelocity = 75 heavy_grenade_projectile.Lifespan = 5f @@ -2861,6 +2867,7 @@ object GlobalDefinitions { jammer_cartridge_projectile.Damage1 = 0 jammer_cartridge_projectile.DamageAtEdge = 1.0f jammer_cartridge_projectile.DamageRadius = 10f + jammer_cartridge_projectile.GrenadeProjectile = true jammer_cartridge_projectile.ProjectileDamageType = DamageType.Splash jammer_cartridge_projectile.InitialVelocity = 30 jammer_cartridge_projectile.Lifespan = 15f @@ -2899,6 +2906,7 @@ object GlobalDefinitions { jammer_cartridge_projectile_b.Damage1 = 0 jammer_cartridge_projectile_b.DamageAtEdge = 1.0f jammer_cartridge_projectile_b.DamageRadius = 10f + jammer_cartridge_projectile_b.GrenadeProjectile = true jammer_cartridge_projectile_b.ProjectileDamageType = DamageType.Splash jammer_cartridge_projectile_b.InitialVelocity = 30 jammer_cartridge_projectile_b.Lifespan = 2f @@ -2936,6 +2944,7 @@ object GlobalDefinitions { jammer_grenade_projectile.Damage1 = 0 jammer_grenade_projectile.DamageAtEdge = 1.0f jammer_grenade_projectile.DamageRadius = 10f + jammer_grenade_projectile.GrenadeProjectile = true jammer_grenade_projectile.ProjectileDamageType = DamageType.Splash jammer_grenade_projectile.InitialVelocity = 30 jammer_grenade_projectile.Lifespan = 15f @@ -2974,6 +2983,7 @@ object GlobalDefinitions { jammer_grenade_projectile_enh.Damage1 = 0 jammer_grenade_projectile_enh.DamageAtEdge = 1.0f jammer_grenade_projectile_enh.DamageRadius = 10f + jammer_grenade_projectile_enh.GrenadeProjectile = true jammer_grenade_projectile_enh.ProjectileDamageType = DamageType.Splash jammer_grenade_projectile_enh.InitialVelocity = 30 jammer_grenade_projectile_enh.Lifespan = 3f @@ -3104,6 +3114,7 @@ object GlobalDefinitions { maelstrom_grenade_projectile.Damage1 = 60 maelstrom_grenade_projectile.DamageRadius = 20f maelstrom_grenade_projectile.LashRadius = 5f + maelstrom_grenade_projectile.GrenadeProjectile = true maelstrom_grenade_projectile.ProjectileDamageType = DamageType.Direct maelstrom_grenade_projectile.InitialVelocity = 30 maelstrom_grenade_projectile.Lifespan = 2f @@ -3117,6 +3128,7 @@ object GlobalDefinitions { maelstrom_grenade_projectile_contact.Damage1 = 60 maelstrom_grenade_projectile_contact.DamageRadius = 20f maelstrom_grenade_projectile_contact.LashRadius = 5f + maelstrom_grenade_projectile_contact.GrenadeProjectile = true maelstrom_grenade_projectile_contact.ProjectileDamageType = DamageType.Direct maelstrom_grenade_projectile_contact.InitialVelocity = 30 maelstrom_grenade_projectile_contact.Lifespan = 15f @@ -3241,6 +3253,7 @@ object GlobalDefinitions { mine_sweeper_projectile.Damage1 = 0 mine_sweeper_projectile.DamageAtEdge = .33f mine_sweeper_projectile.DamageRadius = 25f + mine_sweeper_projectile.GrenadeProjectile = true mine_sweeper_projectile.ProjectileDamageType = DamageType.Splash mine_sweeper_projectile.InitialVelocity = 30 mine_sweeper_projectile.Lifespan = 15f @@ -3252,6 +3265,7 @@ object GlobalDefinitions { mine_sweeper_projectile_enh.Damage1 = 0 mine_sweeper_projectile_enh.DamageAtEdge = 0.33f mine_sweeper_projectile_enh.DamageRadius = 25f + mine_sweeper_projectile_enh.GrenadeProjectile = true mine_sweeper_projectile_enh.ProjectileDamageType = DamageType.Splash mine_sweeper_projectile_enh.InitialVelocity = 30 mine_sweeper_projectile_enh.Lifespan = 3f @@ -3455,11 +3469,12 @@ object GlobalDefinitions { plasma_cartridge_projectile.Damage1 = 15 plasma_cartridge_projectile.DamageAtEdge = 0.2f plasma_cartridge_projectile.DamageRadius = 7f + plasma_cartridge_projectile.GrenadeProjectile = true plasma_cartridge_projectile.ProjectileDamageType = DamageType.Aggravated plasma_cartridge_projectile.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), Aura.Plasma, - 0, + 4000, //hard-coded default? 1.5f, true, false @@ -3474,11 +3489,12 @@ object GlobalDefinitions { plasma_cartridge_projectile_b.Damage1 = 15 plasma_cartridge_projectile_b.DamageAtEdge = 0.2f plasma_cartridge_projectile_b.DamageRadius = 7f + plasma_cartridge_projectile_b.GrenadeProjectile = true plasma_cartridge_projectile_b.ProjectileDamageType = DamageType.Aggravated plasma_cartridge_projectile_b.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), Aura.Plasma, - 0, + 4000, //hard-coded default? 1.5f, true, false @@ -3492,11 +3508,12 @@ object GlobalDefinitions { plasma_grenade_projectile.Damage1 = 30 plasma_grenade_projectile.DamageAtEdge = 0.1f plasma_grenade_projectile.DamageRadius = 7f + plasma_grenade_projectile.GrenadeProjectile = true plasma_grenade_projectile.ProjectileDamageType = DamageType.Aggravated plasma_grenade_projectile.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), Aura.Plasma, - 0, + 4000, //hard-coded default? 1.5f, true, false @@ -3511,11 +3528,12 @@ object GlobalDefinitions { plasma_grenade_projectile_B.Damage1 = 30 plasma_grenade_projectile_B.DamageAtEdge = 0.1f plasma_grenade_projectile_B.DamageRadius = 7f + plasma_grenade_projectile_B.GrenadeProjectile = true plasma_grenade_projectile_B.ProjectileDamageType = DamageType.Aggravated plasma_grenade_projectile_B.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), Aura.Plasma, - 0, + 4000, //hard-coded default? 1.5f, true, false @@ -3532,6 +3550,7 @@ object GlobalDefinitions { pounder_projectile.Damage4 = 132 pounder_projectile.DamageAtEdge = 0.1f pounder_projectile.DamageRadius = 1f + pounder_projectile.GrenadeProjectile = true pounder_projectile.ProjectileDamageType = DamageType.Splash pounder_projectile.InitialVelocity = 120 pounder_projectile.Lifespan = 2.5f @@ -3547,6 +3566,7 @@ object GlobalDefinitions { pounder_projectile_enh.Damage4 = 132 pounder_projectile_enh.DamageAtEdge = 0.1f pounder_projectile_enh.DamageRadius = 1f + pounder_projectile_enh.GrenadeProjectile = true pounder_projectile_enh.ProjectileDamageType = DamageType.Splash pounder_projectile_enh.InitialVelocity = 120 pounder_projectile_enh.Lifespan = 3.2f @@ -3598,6 +3618,7 @@ object GlobalDefinitions { ProjectileDefinition.CalculateDerivedFields(quasar_projectile) radiator_grenade_projectile.Name = "radiator_grenade_projectile" // Todo : Radiator damages ? + radiator_grenade_projectile.GrenadeProjectile = true //not really, but technically yes radiator_grenade_projectile.ProjectileDamageType = DamageType.Direct radiator_grenade_projectile.InitialVelocity = 30 radiator_grenade_projectile.Lifespan = 3f @@ -3605,6 +3626,7 @@ object GlobalDefinitions { radiator_sticky_projectile.Name = "radiator_sticky_projectile" // TODO for later, maybe : set_resource_parent radiator_sticky_projectile game_objects radiator_grenade_projectile + radiator_sticky_projectile.GrenadeProjectile = true //not really, but technically yes radiator_sticky_projectile.ProjectileDamageType = DamageType.Direct radiator_sticky_projectile.InitialVelocity = 30 radiator_sticky_projectile.Lifespan = 4f diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index 7f9db7df..09d0550c 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -30,13 +30,16 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm extends Actor with JammableBehavior with Damageable - with ContainableBehavior { + with ContainableBehavior + with AuraEffectBehavior { def JammableObject = player def DamageableObject = player def ContainerObject = player + def AuraTargetObject = player + private[this] val log = org.log4s.getLogger(player.Name) private[this] val damageLog = org.log4s.getLogger(Damageable.LogChannel) @@ -53,11 +56,13 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm override def postStop(): Unit = { lockerControlAgent ! akka.actor.PoisonPill player.avatar.locker.Actor = Default.Actor + EndAllEffects() } def receive: Receive = jammableBehavior .orElse(takesDamage) + .orElse(auraBehavior) .orElse(containerBehavior) .orElse { case Player.Die() => @@ -544,9 +549,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm //alert damage source DamageAwareness(target, cause) } + //special effects if (Damageable.CanJammer(target, cause)) { - target.Actor ! JammableUnit.Jammered(cause) + TryJammerEffectActivate(target, cause) } + TryAggravationEffect(cause) } else { DestructionAwareness(target, Some(cause)) } @@ -599,6 +606,8 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm val nameChannel = target.Name val zoneChannel = zone.id target.Die + //aura effects cancel + EndAllEffects() //unjam CancelJammeredSound(target) CancelJammeredStatus(target) diff --git a/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala b/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala index 5b83ab1c..0bfab8f2 100644 --- a/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala @@ -36,6 +36,7 @@ class ProjectileDefinition(objectId: Int) private var autoLock: Boolean = false private var additionalEffect: Boolean = false private var jammerProjectile: Boolean = false + private var grenade_projectile : Boolean = false private var aggravated_damage : Option[AggravatedDamage] = None //derived calculations private var distanceMax: Float = 0f @@ -175,6 +176,13 @@ class ProjectileDefinition(objectId: Int) JammerProjectile } + def GrenadeProjectile : Boolean = grenade_projectile + + def GrenadeProjectile_=(isGrenade : Boolean) : Boolean = { + grenade_projectile = isGrenade + GrenadeProjectile + } + def Aggravated : Option[AggravatedDamage] = aggravated_damage def Aggravated_=(damage : AggravatedDamage) : Option[AggravatedDamage] = Aggravated_=(Some(damage)) From 97e64d5edc61b09e88f285670a49fc41d72620d2 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 4 Aug 2020 23:08:57 -0400 Subject: [PATCH 06/19] timing and damage tuning around: standard, reinforced, max; plasma grenades, dragon --- .../objects/avatar/AuraEffectBehavior.scala | 219 +++++++++++++----- .../actors/session/SessionActor.scala | 104 +++++---- .../psforever/objects/GlobalDefinitions.scala | 72 +++++- .../scala/net/psforever/objects/Player.scala | 4 +- .../ballistics/ProjectileResolution.scala | 30 ++- .../definition/ProjectileDefinition.scala | 12 + .../objects/vital/StandardResolutions.scala | 4 +- .../vital/damage/DamageModifiers.scala | 107 ++++++++- .../resolution/ResolutionCalculations.scala | 28 ++- src/test/scala/objects/DamageModelTests.scala | 8 +- src/test/scala/objects/DamageableTest.scala | 26 +++ src/test/scala/objects/DeployableTest.scala | 9 +- src/test/scala/objects/GeneratorTest.scala | 7 + .../scala/objects/PlayerControlTest.scala | 3 + src/test/scala/objects/ProjectileTest.scala | 1 + src/test/scala/objects/VitalityTest.scala | 2 + 16 files changed, 500 insertions(+), 136 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/avatar/AuraEffectBehavior.scala b/common/src/main/scala/net/psforever/objects/avatar/AuraEffectBehavior.scala index 0d4cb0ab..90da7f59 100644 --- a/common/src/main/scala/net/psforever/objects/avatar/AuraEffectBehavior.scala +++ b/common/src/main/scala/net/psforever/objects/avatar/AuraEffectBehavior.scala @@ -3,19 +3,24 @@ package net.psforever.objects.avatar import akka.actor.{Actor, Cancellable} import net.psforever.objects.Player -import net.psforever.objects.ballistics.{AggravatedDamage, AggravatedInfo, ResolvedProjectile} -import net.psforever.objects.vital.DamageType +import net.psforever.objects.ballistics._ +import net.psforever.objects.serverobject.damage.Damageable +import net.psforever.objects.vital.{DamageType, Vitality} +import net.psforever.types.Vector3 import scala.collection.mutable import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global trait AuraEffectBehavior { - _ : Actor => - private var activeEffectIndex : Long = 0 - private var effectsToIds : mutable.HashMap[Aura.Value, List[Long]] = mutable.HashMap.empty[Aura.Value, List[Long]] - private var idsToTimers : mutable.LongMap[Cancellable] = mutable.LongMap.empty[Cancellable] - private var idsToEntries : mutable.LongMap[AuraEffectBehavior.Entry] = mutable.LongMap.empty[AuraEffectBehavior.Entry] + _ : Actor with Damageable => + private var activeEffectIndex: Long = 0 + private val effectToEntryId: mutable.HashMap[Aura.Value, List[Long]] = + mutable.HashMap.empty[Aura.Value, List[Long]] + private val entryIdToTimer: mutable.LongMap[Cancellable] = + mutable.LongMap.empty[Cancellable] + private val entryIdToEntry: mutable.LongMap[AuraEffectBehavior.Entry] = + mutable.LongMap.empty[AuraEffectBehavior.Entry] def AuraTargetObject : Player @@ -25,114 +30,181 @@ trait AuraEffectBehavior { PerformCleanupEffect(id) case AuraEffectBehavior.Aggravate(id, 0, leftoverTime) => - PerformAggravationAndRetimeEvent(id, iteration = 0, Some(leftoverTime), leftoverTime = 0) + RemoveEffectEntry(id) + RetimeEvent(id, iteration = 0, Some(leftoverTime), leftoverTime = 0) case AuraEffectBehavior.Aggravate(id, iteration, leftover) => ; - PerformAggravationAndRetimeEvent(id, iteration - 1, None, leftover) + RetimeEventAndPerformAggravation(id, iteration - 1, None, leftover) } - def PerformAggravationAndRetimeEvent(id : Long, iteration : Int, time : Option[Long], leftoverTime : Long) : Unit = { + private def RetimeEvent( + id: Long, + iteration: Int, + time: Option[Long], + leftoverTime: Long + ): Option[AuraEffectBehavior.Entry] = { CancelEffectTimer(id) - idsToEntries.get(id) match { - case Some(entry) => - //TODO stuff ... - idsToTimers += id -> context.system.scheduler.scheduleOnce( - time.getOrElse(entry.effect.infliction_rate) milliseconds, + entryIdToEntry.get(id) match { + case Some(oldEntry) => + val target = SourceEntry(AuraTargetObject) + val entry = PairIdWithAggravationEntry( + id, + oldEntry.effect, + oldEntry.retime, + oldEntry.data, + target, + target.Position - oldEntry.data.target.Position + ) + entryIdToTimer += id -> context.system.scheduler.scheduleOnce( + time.getOrElse(entry.retime) milliseconds, self, AuraEffectBehavior.Aggravate(id, iteration, leftoverTime) ) + Some(entry) case _ => PerformCleanupEffect(id) + None } } - def CancelEffectTimer(id : Long) : Unit = { - idsToTimers.remove(id) match { + private def RetimeEventAndPerformAggravation(id: Long, iteration: Int, time: Option[Long], leftoverTime: Long) : Unit = { + RetimeEvent(id, iteration, time, leftoverTime) match { + case Some(entry) => + PerformAggravation(entry) + case _ => ; + } + } + + def CancelEffectTimer(id: Long) : Unit = { + entryIdToTimer.remove(id) match { case Some(timer) => timer.cancel case _ => ; } } - def PerformCleanupEffect(id : Long) : Unit = { + def PerformCleanupEffect(id: Long) : Unit = { CleanupEffect(id) match { case Aura.None => ; case _ => UpdateAggravatedEffect(AuraTargetObject) } } - def TryAggravationEffect(data : ResolvedProjectile) : Unit = { + def TryAggravationEffect(data: ResolvedProjectile) : Unit = { data.projectile.profile.Aggravated match { case Some(damage) - if data.projectile.profile.ProjectileDamageType == DamageType.Aggravated && damage.effect_type != Aura.None => + if data.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) && + damage.effect_type != Aura.None => TryAggravationEffect(damage, data) case _ => ; } } - private def TryAggravationEffect(aggravation : AggravatedDamage, data : ResolvedProjectile) : Unit = { + private def TryAggravationEffect(aggravation: AggravatedDamage, data: ResolvedProjectile) : Unit = { val effect = aggravation.effect_type val obj = AuraTargetObject - if(obj.Aura.contains(effect)) { //TODO cumulative? - SetupAggravationEntry(aggravation) - } - else if(obj.Aura.diff(obj.AddEffectToAura(effect)).contains(effect)) { - SetupAggravationEntry(aggravation) - UpdateAggravatedEffect(obj) - } - } - - private def SetupAggravationEntry(aggravation : AggravatedDamage) : Unit = { - val effect = aggravation.effect_type - aggravation.info.foreach { infos => - //get unused id - val id = activeEffectIndex - activeEffectIndex += 1 - //pair aura effect with id - effectsToIds.get(effect) match { - case None | Some(Nil) => effectsToIds += effect -> List(id) - case Some(list) => effectsToIds -> (list :+ id) + if(CheckForUniqueUnqueuedProjectile(data.projectile)) { + val auraEffects = obj.Aura + if(auraEffects.contains(effect) && aggravation.cumulative_damage_degrade) { //TODO cumulative? + SetupAggravationEntry(aggravation, data) + } + else if(obj.AddEffectToAura(effect).diff(auraEffects).contains(effect)) { + SetupAggravationEntry(aggravation, data) + UpdateAggravatedEffect(obj) } - //pair id with entry - idsToEntries += id -> AuraEffectBehavior.Entry(id, infos, aggravation, 0) - //pair id with timer - val iterations = (aggravation.duration / infos.infliction_rate).toInt - val leftoverTime = aggravation.duration % infos.infliction_rate - idsToTimers += id -> context.system.scheduler.scheduleOnce(infos.infliction_rate milliseconds, self, AuraEffectBehavior.Aggravate(id, iterations, leftoverTime)) } } - def CleanupEffect(id : Long) : Aura.Value = { + private def CheckForUniqueUnqueuedProjectile(projectile : Projectile) : Boolean = { + !entryIdToEntry.values.exists { entry => entry.data.projectile eq projectile } + } + + private def SetupAggravationEntry(aggravation: AggravatedDamage, data: ResolvedProjectile) : Unit = { + val effect = aggravation.effect_type + aggravation.info.find(_.damage_type == AuraEffectBehavior.basicDamageType(data.resolution)) match { + case Some(info) => + //get unused id + val id = activeEffectIndex + activeEffectIndex += 1 + //pair aura effect with id + effectToEntryId.get(effect) match { + case None | Some(Nil) => effectToEntryId += effect -> List(id) + case Some(list) => effectToEntryId -> (list :+ id) + } + //pair id with timer + val inflictionRate = info.infliction_rate + val iterations = (aggravation.duration / inflictionRate).toInt + val leftoverTime = aggravation.duration % inflictionRate + entryIdToTimer += id -> context.system.scheduler.scheduleOnce(inflictionRate milliseconds, self, AuraEffectBehavior.Aggravate(id, iterations, leftoverTime)) + //pair id with entry + PairIdWithAggravationEntry(id, effect, inflictionRate, data, data.target, Vector3.Zero) + case _ => ; + } + } + + private def PairIdWithAggravationEntry( + id: Long, + effect: Aura.Value, + retime:Long, + data: ResolvedProjectile, + target: SourceEntry, + offset: Vector3 + ): AuraEffectBehavior.Entry = { + val aggravatedDamageInfo = ResolvedProjectile( + AuraEffectBehavior.burning(data.resolution), + data.projectile, + target, + data.damage_model, + data.hit_pos + offset + ) + val entry = AuraEffectBehavior.Entry(id, effect, retime, aggravatedDamageInfo) + entryIdToEntry += id -> entry + entry + } + + def RemoveEffectEntry(id: Long) : Aura.Value = { + entryIdToEntry.remove(id) match { + case Some(entry) => + entry.data.projectile.profile.Aggravated.get.effect_type + case None => + effectToEntryId.find { case (_, values) => values.contains(id) } match { + case Some((effect, _)) => effect + case _ => Aura.None + } + } + } + + def CleanupEffect(id: Long) : Aura.Value = { //remove and cancel timer - idsToTimers.remove(id) match { + entryIdToTimer.remove(id) match { case Some(timer) => timer.cancel case _ => ; } //remove entry and cache effect - val out = idsToEntries.remove(id) match { - case Some(entry) => entry.aggravation.effect_type - case _ => Aura.None - } + val out = RemoveEffectEntry(id) //remove id and, if now unsupported, effect - (effectsToIds.get(out) match { + (effectToEntryId.get(out) match { case Some(list) => (out, list.filterNot(_ == id)) case _ => (Aura.None, Nil) }) match { case (Aura.None, _) => Aura.None case (effect, Nil) => - effectsToIds.remove(effect) + AuraTargetObject.RemoveEffectFromAura(effect) + effectToEntryId.remove(effect) effect case (effect, list) => - effectsToIds += effect -> list + effectToEntryId += effect -> list Aura.None } } def EndAllEffects() : Unit = { - idsToEntries.clear - idsToTimers.values.foreach { _.cancel } - idsToTimers.clear - effectsToIds.clear + entryIdToEntry.clear + entryIdToTimer.values.foreach { _.cancel } + entryIdToTimer.clear + effectToEntryId.clear + val obj = AuraTargetObject + obj.Aura.foreach { obj.RemoveEffectFromAura } } def EndAllEffectsAndUpdate() : Unit = { @@ -140,16 +212,39 @@ trait AuraEffectBehavior { UpdateAggravatedEffect(AuraTargetObject) } - def UpdateAggravatedEffect(target : Player) : Unit = { + def UpdateAggravatedEffect(target: Player) : Unit = { import services.avatar.{AvatarAction, AvatarServiceMessage} val zone = target.Zone val value = target.Aura.foldLeft(0)(_ + _.id) zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(target.GUID, 54, value)) } + + private def PerformAggravation(entry: AuraEffectBehavior.Entry) : Unit = { + TakesDamage.apply(Vitality.Damage(entry.data.damage_model.Calculate(entry.data))) + } } object AuraEffectBehavior { - private case class Entry(id : Long, effect : AggravatedInfo, aggravation : AggravatedDamage, damage : Any) + private case class Entry(id: Long, effect: Aura.Value, retime: Long, data: ResolvedProjectile) - private case class Aggravate(id : Long, iterations : Int, leftover : Long) + private case class Aggravate(id: Long, iterations: Int, leftover: Long) + + private def burning(resolution: ProjectileResolution.Value): ProjectileResolution.Value = { + resolution match { + case ProjectileResolution.AggravatedDirect => ProjectileResolution.AggravatedDirectBurn + case ProjectileResolution.AggravatedSplash => ProjectileResolution.AggravatedSplashBurn + case _ => resolution + } + } + + private def basicDamageType(resolution: ProjectileResolution.Value): DamageType.Value = { + resolution match { + case ProjectileResolution.AggravatedDirect | ProjectileResolution.AggravatedDirectBurn => + DamageType.Direct + case ProjectileResolution.AggravatedSplash | ProjectileResolution.AggravatedSplashBurn => + DamageType.Splash + case _ => + DamageType.None + } + } } diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index 06cf7d55..23e5d283 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -1,13 +1,26 @@ package net.psforever.actors.session import java.util.concurrent.TimeUnit - import akka.actor.MDCContextAware.Implicits._ import akka.actor.typed import akka.actor.typed.receptionist.Receptionist import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware} +import java.util.concurrent.atomic.AtomicInteger +import org.log4s.MDC +import scala.collection.mutable.LongMap +import scala.concurrent.{Await, Future} +import scala.concurrent.duration._ +import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.{Failure, Success} +import scodec.bits.ByteVector +import services.properties.PropertyOverrideManager +import org.joda.time.{LocalDateTime, Period} +import csr.{CSRWarp, CSRZone, Traveler} +import MDCContextAware.Implicits._ import net.psforever.objects.{GlobalDefinitions, _} import net.psforever.objects.avatar.{Avatar, Certification, DeployableToolbox} +import net.psforever.objects._ +import net.psforever.objects.avatar.{Certification, DeployableToolbox, FirstTimeEvents} import net.psforever.objects.ballistics._ import net.psforever.objects.ce._ import net.psforever.objects.definition._ @@ -37,15 +50,7 @@ import net.psforever.objects.serverobject.turret.{FacilityTurret, WeaponTurret} import net.psforever.objects.serverobject.zipline.ZipLinePath import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.teamwork.Squad -import net.psforever.objects.vehicles.{ - AccessPermissionGroup, - CargoBehavior, - MountedWeapons, - Utility, - UtilityType, - VehicleControl, - VehicleLockState -} +import net.psforever.objects.vehicles._ import net.psforever.objects.vehicles.Utility.InternalTelepad import net.psforever.objects.vital._ import net.psforever.objects.zones.{Zone, ZoneHotSpotProjector, Zoning} @@ -53,6 +58,13 @@ import net.psforever.packet._ import net.psforever.packet.control._ import net.psforever.packet.game.objectcreate._ import net.psforever.packet.game.{HotSpotInfo => PacketHotSpotInfo, _} +import net.psforever.objects.zones.{InterstellarCluster, Zone, ZoneHotSpotProjector, Zoning} +import net.psforever.packet._ +import net.psforever.packet.control._ +import net.psforever.packet.game._ +import net.psforever.packet.game.objectcreate._ +import net.psforever.packet.game.{HotSpotInfo => PacketHotSpotInfo} +import net.psforever.persistence import net.psforever.types._ import org.log4s.MDC import scodec.bits.ByteVector @@ -78,7 +90,6 @@ import net.psforever.login.WorldSession._ import net.psforever.zones.Zones import net.psforever.services.chat.ChatService import net.psforever.objects.avatar.Cosmetic - import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ import scala.concurrent.{Await, Future} @@ -86,7 +97,6 @@ import scala.util.Success import akka.actor.typed.scaladsl.adapter._ import akka.pattern.ask import akka.util.Timeout - import scala.collection.mutable object SessionActor { @@ -5328,7 +5338,15 @@ class SessionActor extends Actor with MDCContextAware { case _ => ; } - case msg @ HitMessage(seq_time, projectile_guid, unk1, hit_info, unk2, unk3, unk4) => + case msg @ HitMessage( + seq_time, + projectile_guid, + unk1, + hit_info, + unk2, + unk3, + unk4 + ) => log.info(s"Hit: $msg") //find defined projectile FindProjectileEntry(projectile_guid) match { @@ -5395,31 +5413,33 @@ class SessionActor extends Actor with MDCContextAware { } case msg @ SplashHitMessage( - seq_time, - projectile_guid, - explosion_pos, - direct_victim_uid, - unk3, - projectile_vel, - unk4, - targets + seq_time, + projectile_guid, + explosion_pos, + direct_victim_uid, + unk3, + projectile_vel, + unk4, + targets ) => log.info(s"Splash: $msg") FindProjectileEntry(projectile_guid) match { case Some(projectile) => + val profile = projectile.profile projectile.Position = explosion_pos projectile.Velocity = projectile_vel + val (resolution1, resolution2) = profile.Aggravated match { + case Some(_) + if profile.ProjectileDamageTypes.contains(DamageType.Aggravated) => + (ProjectileResolution.AggravatedDirect, ProjectileResolution.AggravatedSplash) + case _ => + (ProjectileResolution.Splash, ProjectileResolution.Splash) + } //direct_victim_uid ValidObject(direct_victim_uid) match { - case Some( - target: PlanetSideGameObject with FactionAffinity with Vitality - ) => - CheckForHitPositionDiscrepancy( - projectile_guid, - explosion_pos, - target - ) - ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, explosion_pos) match { + case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) => + CheckForHitPositionDiscrepancy(projectile_guid, target.Position, target) + ResolveProjectileEntry(projectile, resolution1, target, target.Position) match { case Some(projectile) => HandleDealingDamage(target, projectile) case None => ; @@ -5431,7 +5451,7 @@ class SessionActor extends Actor with MDCContextAware { ValidObject(elem.uid) match { case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) => CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target) - ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, explosion_pos) match { + ResolveProjectileEntry(projectile, resolution2, target, explosion_pos) match { case Some(projectile) => HandleDealingDamage(target, projectile) case None => ; @@ -5439,7 +5459,7 @@ class SessionActor extends Actor with MDCContextAware { case _ => ; } }) - if (projectile.profile.ExistsOnRemoteClients && projectile.HasGUID) { + if (profile.ExistsOnRemoteClients && projectile.HasGUID) { //cleanup val localIndex = projectile_guid.guid - Projectile.baseUID if (projectile.HasGUID) { @@ -5642,16 +5662,16 @@ class SessionActor extends Actor with MDCContextAware { log.debug("Ouch! " + msg) case msg @ BugReportMessage( - version_major, - version_minor, - version_date, - bug_type, - repeatable, - location, - zone, - pos, - summary, - desc + version_major, + version_minor, + version_date, + bug_type, + repeatable, + location, + zone, + pos, + summary, + desc ) => log.info("BugReportMessage: " + msg) diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index dbdf1411..e33c4dc3 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2040,6 +2040,7 @@ object GlobalDefinitions { no_projectile.Name = "none" ProjectileDefinition.CalculateDerivedFields(no_projectile) + no_projectile.Modifiers = Nil bullet_105mm_projectile.Name = "105mmbullet_projectile" bullet_105mm_projectile.Damage0 = 150 @@ -2363,6 +2364,7 @@ object GlobalDefinitions { chainblade_projectile.InitialVelocity = 100 chainblade_projectile.Lifespan = .02f ProjectileDefinition.CalculateDerivedFields(chainblade_projectile) + chainblade_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff colossus_100mm_projectile.Name = "colossus_100mm_projectile" colossus_100mm_projectile.Damage0 = 58 @@ -2597,6 +2599,7 @@ object GlobalDefinitions { flamethrower_fireball.Damage2 = 0 flamethrower_fireball.Damage3 = 20 flamethrower_fireball.Damage4 = 0 + flamethrower_fireball.DamageToHealthOnly = true flamethrower_fireball.DamageAtEdge = 0.15f flamethrower_fireball.DamageRadius = 5f flamethrower_fireball.ProjectileDamageType = DamageType.Aggravated @@ -2611,6 +2614,13 @@ object GlobalDefinitions { flamethrower_fireball.InitialVelocity = 15 flamethrower_fireball.Lifespan = 1.2f ProjectileDefinition.CalculateDerivedFields(flamethrower_fireball) + flamethrower_fireball.Modifiers = List( + DamageModifiers.AggravatedDirect, + DamageModifiers.AggravatedDirectBurn, + DamageModifiers.AggravatedSplash, + DamageModifiers.AggravatedSplashBurn, + DamageModifiers.RadialDegrade + ) flamethrower_projectile.Name = "flamethrower_projectile" flamethrower_projectile.Damage0 = 10 @@ -2618,6 +2628,7 @@ object GlobalDefinitions { flamethrower_projectile.Damage2 = 0 flamethrower_projectile.Damage3 = 4 flamethrower_projectile.Damage4 = 0 + flamethrower_projectile.DamageToHealthOnly = true flamethrower_projectile.Acceleration = -5 flamethrower_projectile.AccelerationUntil = 2f flamethrower_projectile.ProjectileDamageType = DamageType.Aggravated @@ -2634,6 +2645,11 @@ object GlobalDefinitions { flamethrower_projectile.InitialVelocity = 10 flamethrower_projectile.Lifespan = 2.0f ProjectileDefinition.CalculateDerivedFields(flamethrower_projectile) + flamethrower_projectile.Modifiers = List( + DamageModifiers.AggravatedDirect, + DamageModifiers.AggravatedDirectBurn, + DamageModifiers.MaxDistanceCutoff + ) flux_cannon_apc_projectile.Name = "flux_cannon_apc_projectile" // TODO for later, maybe : set_resource_parent flux_cannon_apc_projectile game_objects flux_cannon_thresher_projectile @@ -2685,6 +2701,7 @@ object GlobalDefinitions { forceblade_projectile.InitialVelocity = 100 forceblade_projectile.Lifespan = .02f ProjectileDefinition.CalculateDerivedFields(forceblade_projectile) + forceblade_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff frag_cartridge_projectile.Name = "frag_cartridge_projectile" // TODO for later, maybe : set_resource_parent frag_cartridge_projectile game_objects frag_grenade_projectile @@ -2898,7 +2915,7 @@ object GlobalDefinitions { EffectTarget.Validation.VehicleNotAMS ) -> 10000 ProjectileDefinition.CalculateDerivedFields(jammer_cartridge_projectile) - jammer_cartridge_projectile.Modifiers = Nil + jammer_cartridge_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff jammer_cartridge_projectile_b.Name = "jammer_cartridge_projectile_b" // TODO for later, maybe : set_resource_parent jammer_cartridge_projectile_b game_objects jammer_grenade_projectile_enh @@ -2937,7 +2954,7 @@ object GlobalDefinitions { EffectTarget.Validation.VehicleNotAMS ) -> 10000 ProjectileDefinition.CalculateDerivedFields(jammer_cartridge_projectile_b) - jammer_cartridge_projectile_b.Modifiers = Nil + jammer_cartridge_projectile_b.Modifiers = DamageModifiers.MaxDistanceCutoff jammer_grenade_projectile.Name = "jammer_grenade_projectile" jammer_grenade_projectile.Damage0 = 0 @@ -2975,7 +2992,7 @@ object GlobalDefinitions { EffectTarget.Validation.VehicleNotAMS ) -> 10000 ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile) - jammer_grenade_projectile.Modifiers = Nil + jammer_grenade_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff jammer_grenade_projectile_enh.Name = "jammer_grenade_projectile_enh" // TODO for later, maybe : set_resource_parent jammer_grenade_projectile_enh game_objects jammer_grenade_projectile @@ -3014,7 +3031,7 @@ object GlobalDefinitions { EffectTarget.Validation.VehicleNotAMS ) -> 10000 ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile_enh) - jammer_grenade_projectile_enh.Modifiers = Nil + jammer_grenade_projectile_enh.Modifiers = DamageModifiers.MaxDistanceCutoff katana_projectile.Name = "katana_projectile" katana_projectile.Damage0 = 25 @@ -3057,7 +3074,10 @@ object GlobalDefinitions { lasher_projectile.LashRadius = 2.5f lasher_projectile.Lifespan = 0.75f ProjectileDefinition.CalculateDerivedFields(lasher_projectile) - lasher_projectile.Modifiers = List(DamageModifiers.DistanceDegrade, DamageModifiers.Lash) + lasher_projectile.Modifiers = List( + DamageModifiers.DistanceDegrade, + DamageModifiers.Lash + ) lasher_projectile_ap.Name = "lasher_projectile_ap" lasher_projectile_ap.Damage0 = 12 @@ -3072,7 +3092,10 @@ object GlobalDefinitions { lasher_projectile_ap.LashRadius = 2.5f lasher_projectile_ap.Lifespan = 0.75f ProjectileDefinition.CalculateDerivedFields(lasher_projectile_ap) - lasher_projectile_ap.Modifiers = List(DamageModifiers.DistanceDegrade, DamageModifiers.Lash) + lasher_projectile_ap.Modifiers = List( + DamageModifiers.DistanceDegrade, + DamageModifiers.Lash + ) liberator_bomb_cluster_bomblet_projectile.Name = "liberator_bomb_cluster_bomblet_projectile" liberator_bomb_cluster_bomblet_projectile.Damage0 = 75 @@ -3145,6 +3168,7 @@ object GlobalDefinitions { maelstrom_stream_projectile.InitialVelocity = 200 maelstrom_stream_projectile.Lifespan = 0.2f ProjectileDefinition.CalculateDerivedFields(maelstrom_stream_projectile) + maelstrom_stream_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff magcutter_projectile.Name = "magcutter_projectile" // TODO for later, maybe : set_resource_parent magcutter_projectile game_objects melee_ammo_projectile @@ -3154,6 +3178,7 @@ object GlobalDefinitions { magcutter_projectile.InitialVelocity = 100 magcutter_projectile.Lifespan = .02f ProjectileDefinition.CalculateDerivedFields(magcutter_projectile) + magcutter_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff melee_ammo_projectile.Name = "melee_ammo_projectile" melee_ammo_projectile.Damage0 = 25 @@ -3162,6 +3187,7 @@ object GlobalDefinitions { melee_ammo_projectile.InitialVelocity = 100 melee_ammo_projectile.Lifespan = .02f ProjectileDefinition.CalculateDerivedFields(melee_ammo_projectile) + melee_ammo_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff meteor_common.Name = "meteor_common" meteor_common.DamageAtEdge = .1f @@ -3482,6 +3508,13 @@ object GlobalDefinitions { plasma_cartridge_projectile.InitialVelocity = 30 plasma_cartridge_projectile.Lifespan = 15f ProjectileDefinition.CalculateDerivedFields(plasma_cartridge_projectile) + plasma_cartridge_projectile.Modifiers = List( + DamageModifiers.AggravatedDirect, + DamageModifiers.AggravatedDirectBurn, + DamageModifiers.AggravatedSplash, + DamageModifiers.AggravatedSplashBurn, + DamageModifiers.RadialDegrade + ) plasma_cartridge_projectile_b.Name = "plasma_cartridge_projectile_b" // TODO for later, maybe : set_resource_parent plasma_cartridge_projectile_b game_objects plasma_grenade_projectile_B @@ -3502,6 +3535,13 @@ object GlobalDefinitions { plasma_cartridge_projectile_b.InitialVelocity = 30 plasma_cartridge_projectile_b.Lifespan = 2f ProjectileDefinition.CalculateDerivedFields(plasma_cartridge_projectile_b) + plasma_cartridge_projectile_b.Modifiers = List( + DamageModifiers.AggravatedDirect, + DamageModifiers.AggravatedDirectBurn, + DamageModifiers.AggravatedSplash, + DamageModifiers.AggravatedSplashBurn, + DamageModifiers.RadialDegrade + ) plasma_grenade_projectile.Name = "plasma_grenade_projectile" plasma_grenade_projectile.Damage0 = 40 @@ -3521,6 +3561,13 @@ object GlobalDefinitions { plasma_grenade_projectile.InitialVelocity = 30 plasma_grenade_projectile.Lifespan = 15f ProjectileDefinition.CalculateDerivedFields(plasma_grenade_projectile) + plasma_grenade_projectile.Modifiers = List( + DamageModifiers.AggravatedDirect, + DamageModifiers.AggravatedDirectBurn, + DamageModifiers.AggravatedSplash, + DamageModifiers.AggravatedSplashBurn, + DamageModifiers.RadialDegrade + ) plasma_grenade_projectile_B.Name = "plasma_grenade_projectile_B" // TODO for later, maybe : set_resource_parent plasma_grenade_projectile_B game_objects plasma_grenade_projectile @@ -3541,6 +3588,13 @@ object GlobalDefinitions { plasma_grenade_projectile_B.InitialVelocity = 30 plasma_grenade_projectile_B.Lifespan = 3f ProjectileDefinition.CalculateDerivedFields(plasma_grenade_projectile_B) + plasma_grenade_projectile_B.Modifiers = List( + DamageModifiers.AggravatedDirect, + DamageModifiers.AggravatedDirectBurn, + DamageModifiers.AggravatedSplash, + DamageModifiers.AggravatedSplashBurn, + DamageModifiers.RadialDegrade + ) pounder_projectile.Name = "pounder_projectile" pounder_projectile.Damage0 = 31 @@ -3689,7 +3743,7 @@ object GlobalDefinitions { rocklet_jammer_projectile.InitialVelocity = 50 rocklet_jammer_projectile.Lifespan = 8f ProjectileDefinition.CalculateDerivedFields(rocklet_jammer_projectile) - rocklet_jammer_projectile.Modifiers = Nil + //TODO rocklet_jammer_projectile.Modifiers = DamageModifiers.RadialDegrade? scattercannon_projectile.Name = "scattercannon_projectile" scattercannon_projectile.Damage0 = 11 @@ -3809,7 +3863,6 @@ object GlobalDefinitions { spiker_projectile.InitialVelocity = 40 spiker_projectile.Lifespan = 5f ProjectileDefinition.CalculateDerivedFields(spiker_projectile) - spiker_projectile.Modifiers = DamageModifiers.RadialDegrade spitfire_aa_ammo_projectile.Name = "spitfire_aa_ammo_projectile" spitfire_aa_ammo_projectile.Damage0 = 5 @@ -3907,6 +3960,7 @@ object GlobalDefinitions { trek_projectile.InitialVelocity = 40 trek_projectile.Lifespan = 7f ProjectileDefinition.CalculateDerivedFields(trek_projectile) + trek_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff vanu_sentry_turret_projectile.Name = "vanu_sentry_turret_projectile" vanu_sentry_turret_projectile.Damage0 = 25 @@ -4585,7 +4639,7 @@ object GlobalDefinitions { flamethrower.FireModes(1).AmmoSlotIndex = 0 flamethrower.FireModes(1).Magazine = 100 flamethrower.FireModes(1).RoundsPerShot = 50 - flamethrower.Tile = InventoryTile.Tile63 + flamethrower.Tile = InventoryTile.Tile93 winchester.Name = "winchester" winchester.Size = EquipmentSize.Rifle diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala index 111fa739..aec00096 100644 --- a/src/main/scala/net/psforever/objects/Player.scala +++ b/src/main/scala/net/psforever/objects/Player.scala @@ -359,7 +359,9 @@ class Player(var avatar: Avatar) def Aura : Set[AuraEffect.Value] = aura def AddEffectToAura(effect : AuraEffect.Value) : Set[AuraEffect.Value] = { - aura = aura + effect + if(effect != AuraEffect.None) { + aura = aura + effect + } Aura } diff --git a/src/main/scala/net/psforever/objects/ballistics/ProjectileResolution.scala b/src/main/scala/net/psforever/objects/ballistics/ProjectileResolution.scala index 87b56805..09b2e3b3 100644 --- a/src/main/scala/net/psforever/objects/ballistics/ProjectileResolution.scala +++ b/src/main/scala/net/psforever/objects/ballistics/ProjectileResolution.scala @@ -2,16 +2,32 @@ package net.psforever.objects.ballistics /** - * An `Enumeration` of outcomes regarding what actually happened to the projectile. + * An `Enumeration` of outcomes regarding what actually happened to the projectile, + * complementing normal damage type distinction in directing damage calculations.
+ *
+ * Although the latter states reflect what sort of damage the projectile might perform - `Hit`, `Splash`, etc. - + * the state is more a communication about how that damage is interpreted by the server. + * For example, some projectiles: + * perform `Direct` damage, are reported by `HitMessage` packets, and resolve as `Hit`; + * or, perform `Direct` damage, are reported by `LashDamage` packets, and resolve as `Lash`. + * Furthermore, some projectiles: + * perform `Splash` damage, are reported by `SplashHitMessage` packets, and resolve as `Splash`; + * or, perform `Aggravated` damage, are reported by `SplashHitMessage` packets + * and resolve either as `AggravatedDirect` or as `AggravatedSplash`. */ object ProjectileResolution extends Enumeration { type Type = Value - val Unresolved, //original basic non-resolution - MissedShot, //projectile did not encounter any collision object and was despawned - Resolved, //a general "projectile encountered something" status with a more specific resolution - Hit, //direct hit, one target - Splash, //area of effect damage, potentially multiple targets - Lash //lashing damage, potentially multiple targets + val + Unresolved, //original basic non-resolution + MissedShot, //projectile did not encounter any collision object and was despawned + Resolved, //a general "projectile encountered something" status with a more specific resolution + Hit, //direct hit, one target + Splash, //area of effect damage, potentially multiple targets + Lash, //lashing damage, potentially multiple targets + AggravatedDirect, //direct hit aggravated damage + AggravatedDirectBurn, //continuous direct hit aggravated damage + AggravatedSplash, //splashed aggravated damage + AggravatedSplashBurn //continuous splashed aggravated damage = Value } diff --git a/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala b/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala index 0bfab8f2..3f68364c 100644 --- a/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala @@ -21,6 +21,7 @@ class ProjectileDefinition(objectId: Int) private var accelerationUntil: Float = 0f private var damageType: DamageType.Value = DamageType.None private var damageTypeSecondary: DamageType.Value = DamageType.None + private var damageToHealthOnly: Boolean = false private var degradeDelay: Float = 1f private var degradeMultiplier: Float = 1f private var initialVelocity: Int = 1 @@ -83,6 +84,17 @@ class ProjectileDefinition(objectId: Int) ProjectileDamageTypeSecondary } + def ProjectileDamageTypes : Set[DamageType.Value] = { + Set(damageType, damageTypeSecondary).filterNot(_ == DamageType.None) + } + + def DamageToHealthOnly : Boolean = damageToHealthOnly + + def DamageToHealthOnly_=(healthOnly: Boolean) : Boolean = { + damageToHealthOnly = healthOnly + DamageToHealthOnly + } + def DegradeDelay: Float = degradeDelay def DegradeDelay_=(degradeDelay: Float): Float = { diff --git a/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala b/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala index fb5eb164..673c7aea 100644 --- a/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala +++ b/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala @@ -11,13 +11,13 @@ object NoResolutions object InfantryResolutions extends DamageResistCalculations( - ResolutionCalculations.InfantryDamageAfterResist, + ResolutionCalculations.InfantryDamage, ResolutionCalculations.InfantryApplication ) object MaxResolutions extends DamageResistCalculations( - ResolutionCalculations.MaxDamageAfterResist, + ResolutionCalculations.MaxDamage, ResolutionCalculations.InfantryApplication ) diff --git a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala index 478f5bfb..bb7aedc9 100644 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala @@ -1,8 +1,9 @@ // Copyright (c) 2020 PSForever package net.psforever.objects.vital.damage -import net.psforever.objects.ballistics.{ProjectileResolution, ResolvedProjectile} -import net.psforever.types.Vector3 +import net.psforever.objects.ballistics.{PlayerSource, ProjectileResolution, ResolvedProjectile} +import net.psforever.objects.vital.DamageType +import net.psforever.types.{ExoSuitType, Vector3} /** * Adjustments performed on the subsequent manipulations of the "base damage" value of an attack vector @@ -45,6 +46,21 @@ object DamageModifiers { private def function(damage: Int, data: ResolvedProjectile): Int = damage } + case object MaxDistanceCutoff extends Mod { + def Calculate: DamageModifiers.Format = function + + private def function(damage: Int, data: ResolvedProjectile): Int = { + val projectile = data.projectile + val profile = projectile.profile + val distance = Vector3.Distance(data.hit_pos, projectile.shot_origin) + if (distance <= profile.DistanceMax) { + damage + } else { + 0 + } + } + } + /** * The input value degrades (lessens) * the further the distance between the point of origin (`shot_origin`) @@ -117,4 +133,91 @@ object DamageModifiers { } } } + + case object AggravatedDirect extends Mod { + def Calculate: DamageModifiers.Format = + BaseAggravatedFormula(ProjectileResolution.AggravatedDirect, DamageType.Direct) + } + + case object AggravatedSplash extends Mod { + def Calculate: DamageModifiers.Format = + BaseAggravatedFormula(ProjectileResolution.AggravatedSplash, DamageType.Splash) + } + + case object AggravatedDirectBurn extends Mod { + def Calculate: DamageModifiers.Format = + BaseAggravatedBurnFormula(ProjectileResolution.AggravatedDirectBurn, DamageType.Direct) + } + + case object AggravatedSplashBurn extends Mod { + def Calculate: DamageModifiers.Format = + BaseAggravatedBurnFormula(ProjectileResolution.AggravatedSplashBurn, DamageType.Splash) + } + + private def BaseAggravatedFormula( + resolution: ProjectileResolution.Value, + damageType : DamageType.Value + ) + ( + damage: Int, + data: ResolvedProjectile + ): Int = { + if (data.resolution == resolution) { + (data.projectile.profile.Aggravated, data.target) match { + case (Some(aggravation), p: PlayerSource) if p.ExoSuit == ExoSuitType.MAX => + val aggravatedDirectDamage = (aggravation.info.find(_.damage_type == damageType) match { + case Some(infos) => + damage * infos.degradation_percentage + case _ => + damage toFloat + }) * aggravation.max_factor + damage + aggravatedDirectDamage toInt + case _ => + damage + } + } else { + damage + } + } + + private def BaseAggravatedBurnFormula( + resolution: ProjectileResolution.Value, + damageType : DamageType.Value + ) + ( + damage: Int, + data: ResolvedProjectile + ): Int = { + if (data.resolution == resolution) { + (data.projectile.profile.Aggravated, data.target) match { + case (Some(aggravation), p: PlayerSource) => + val degradation = aggravation.info.find(_.damage_type == damageType) match { + case Some(info) => + info.degradation_percentage + case _ => + 1f + } + if (p.exosuit == ExoSuitType.MAX) { + (damage * degradation * aggravation.max_factor) toInt + } else { + val resist = data.damage_model.ResistUsing(data)(data) + if (damage > resist) { + ((damage - resist) * degradation).toInt + resist + } else { + val degradedDamage = damage * degradation + if (degradedDamage > resist) { + degradedDamage toInt + } + else { + damage + } + } + } + case _ => + damage + } + } else { + damage + } + } } diff --git a/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala b/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala index 5067a74e..9166f7d0 100644 --- a/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala +++ b/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala @@ -38,15 +38,31 @@ object ResolutionCalculations { def NoDamage(data: ResolvedProjectile)(a: Int, b: Int): Int = 0 - def InfantryDamageAfterResist(data: ResolvedProjectile): (Int, Int) => (Int, Int) = { + def InfantryDamage(data: ResolvedProjectile): (Int, Int) => (Int, Int) = { data.target match { case target: PlayerSource => - InfantryDamageAfterResist(target.health, target.armor) + if(data.projectile.profile.DamageToHealthOnly) { + DamageToHealthOnly(target.health) + } else { + InfantryDamageAfterResist(target.health, target.armor) + } case _ => InfantryDamageAfterResist(0, 0) } } + def DamageToHealthOnly(currentHP: Int)(damages: Int, resistance: Int): (Int, Int) = { + if (damages > 0 && currentHP > 0) { + if(damages > resistance) { + (damages - resistance, 0) + } else { + (damages, 0) + } + } else { + (0, 0) + } + } + def InfantryDamageAfterResist(currentHP: Int, currentArmor: Int)(damages: Int, resistance: Int): (Int, Int) = { if (damages > 0 && currentHP > 0) { if (currentArmor <= 0) { @@ -67,10 +83,14 @@ object ResolutionCalculations { } } - def MaxDamageAfterResist(data: ResolvedProjectile): (Int, Int) => (Int, Int) = { + def MaxDamage(data: ResolvedProjectile): (Int, Int) => (Int, Int) = { data.target match { case target: PlayerSource => - MaxDamageAfterResist(target.health, target.armor) + if(data.projectile.profile.DamageToHealthOnly) { + DamageToHealthOnly(target.health) + } else { + MaxDamageAfterResist(target.health, target.armor) + } case _ => MaxDamageAfterResist(0, 0) } diff --git a/src/test/scala/objects/DamageModelTests.scala b/src/test/scala/objects/DamageModelTests.scala index c9de3554..7e6935cd 100644 --- a/src/test/scala/objects/DamageModelTests.scala +++ b/src/test/scala/objects/DamageModelTests.scala @@ -302,7 +302,7 @@ class ResolutionCalculationsTests extends Specification { target1.DamageModel, Vector3.Zero ) - InfantryDamageAfterResist(resprojectile1)(50, 10) mustEqual (0, 0) + InfantryDamage(resprojectile1)(50, 10) mustEqual (0, 0) val target2 = player val resprojectile2 = ResolvedProjectile( @@ -312,7 +312,7 @@ class ResolutionCalculationsTests extends Specification { target2.DamageModel, Vector3.Zero ) - InfantryDamageAfterResist(resprojectile2)(50, 10) mustEqual (40, 10) + InfantryDamage(resprojectile2)(50, 10) mustEqual (40, 10) } "calculate health and armor damage for infantry target" in { @@ -346,7 +346,7 @@ class ResolutionCalculationsTests extends Specification { target1.DamageModel, Vector3.Zero ) - MaxDamageAfterResist(resprojectile1)(50, 10) mustEqual (0, 0) + MaxDamage(resprojectile1)(50, 10) mustEqual (0, 0) val target2 = player2 val resprojectile2 = ResolvedProjectile( @@ -356,7 +356,7 @@ class ResolutionCalculationsTests extends Specification { target2.DamageModel, Vector3.Zero ) - MaxDamageAfterResist(resprojectile2)(50, 10) mustEqual (0, 40) + MaxDamage(resprojectile2)(50, 10) mustEqual (0, 40) } "calculate health and armor damage for max target" in { diff --git a/src/test/scala/objects/DamageableTest.scala b/src/test/scala/objects/DamageableTest.scala index 554e6887..63c59290 100644 --- a/src/test/scala/objects/DamageableTest.scala +++ b/src/test/scala/objects/DamageableTest.scala @@ -40,6 +40,7 @@ class DamageableTest extends Specification { "permit damage" in { val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -52,6 +53,7 @@ class DamageableTest extends Specification { "ignore attempts at non-zero damage" in { val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectileA, weaponA.Definition, @@ -76,6 +78,7 @@ class DamageableTest extends Specification { Faction = player1.Faction } val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -98,6 +101,7 @@ class DamageableTest extends Specification { Faction = PlanetSideEmpire.NC } val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -124,6 +128,7 @@ class DamageableTest extends Specification { Faction = player1.Faction } val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -147,6 +152,7 @@ class DamageableTest extends Specification { "permit jamming" in { val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -160,6 +166,7 @@ class DamageableTest extends Specification { "ignore attempts at jamming if the projectile is does not cause the effect" in { val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -174,6 +181,7 @@ class DamageableTest extends Specification { val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) target.Faction = player1.Faction val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -188,6 +196,7 @@ class DamageableTest extends Specification { "ignore attempts at jamming targets that are not jammable" in { val target = new TrapDeployable(GlobalDefinitions.tank_traps) val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -207,6 +216,7 @@ class DamageableTest extends Specification { val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) target.Faction = player1.Faction val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), SourceEntry(target), target.DamageModel, @@ -261,6 +271,7 @@ class DamageableEntityDamageTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -331,6 +342,7 @@ class DamageableEntityDestroyedTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -404,6 +416,7 @@ class DamageableEntityNotDestroyTwice extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -474,6 +487,7 @@ class DamageableAmenityTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -566,6 +580,7 @@ class DamageableMountableDamageTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -660,6 +675,7 @@ class DamageableMountableDestroyTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -748,6 +764,7 @@ class DamageableWeaponTurretDamageTest extends ActorTest { val projectile = weapon.Projectile val turretSource = SourceEntry(turret) val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -845,6 +862,7 @@ class DamageableWeaponTurretJammerTest extends ActorTest { val projectile = weapon.Projectile val turretSource = SourceEntry(turret) val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -946,6 +964,7 @@ class DamageableWeaponTurretDestructionTest extends ActorTest { val weaponA = Tool(GlobalDefinitions.jammer_grenade) val projectileA = weaponA.Projectile val resolvedA = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectileA, weaponA.Definition, @@ -964,6 +983,7 @@ class DamageableWeaponTurretDestructionTest extends ActorTest { val weaponB = Tool(GlobalDefinitions.phoenix) //decimator val projectileB = weaponB.Projectile val resolvedB = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectileB, weaponB.Definition, @@ -1085,6 +1105,7 @@ class DamageableVehicleDamageTest extends ActorTest { val projectile = weapon.Projectile val vehicleSource = SourceEntry(atv) val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -1216,6 +1237,7 @@ class DamageableVehicleDamageMountedTest extends ActorTest { val projectile = weapon.Projectile val vehicleSource = SourceEntry(lodestar) val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -1363,6 +1385,7 @@ class DamageableVehicleJammeringMountedTest extends ActorTest { val weapon = Tool(GlobalDefinitions.jammer_grenade) val projectile = weapon.Projectile val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -1452,6 +1475,7 @@ class DamageableVehicleDestroyTest extends ActorTest { val projectile = weapon.Projectile val vehicleSource = SourceEntry(atv) val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -1577,6 +1601,7 @@ class DamageableVehicleDestroyMountedTest extends ActorTest { val weaponA = Tool(GlobalDefinitions.jammer_grenade) val projectileA = weaponA.Projectile val resolvedA = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectileA, weaponA.Definition, @@ -1595,6 +1620,7 @@ class DamageableVehicleDestroyMountedTest extends ActorTest { val weaponB = Tool(GlobalDefinitions.phoenix) //decimator val projectileB = weaponB.Projectile val resolvedB = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectileB, weaponB.Definition, diff --git a/src/test/scala/objects/DeployableTest.scala b/src/test/scala/objects/DeployableTest.scala index 63e99a62..39ab6107 100644 --- a/src/test/scala/objects/DeployableTest.scala +++ b/src/test/scala/objects/DeployableTest.scala @@ -334,6 +334,7 @@ class ExplosiveDeployableJammerTest extends ActorTest { val pSource = PlayerSource(player1) val projectile = weapon.Projectile val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), jMineSource, j_mine.DamageModel, @@ -373,7 +374,7 @@ class ExplosiveDeployableJammerTest extends ActorTest { assert( msg_local(2) match { case LocalServiceMessage.Deployables(SupportActor.ClearSpecific(List(target), _zone)) => - (target eq j_mine) && (_zone eq zone) + (j_mine eq target) && (_zone eq zone) case _ => false } ) @@ -431,6 +432,7 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest { val pSource = PlayerSource(player1) val projectile = weapon.Projectile val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), hMineSource, h_mine.DamageModel, @@ -476,7 +478,7 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest { assert( msg_local(3) match { case LocalServiceMessage.Deployables(SupportActor.ClearSpecific(List(target), _zone)) => - (target eq h_mine) && (_zone eq zone) + (h_mine eq target) && (_zone eq zone) case _ => false } ) @@ -540,6 +542,7 @@ class ExplosiveDeployableDestructionTest extends ActorTest { val pSource = PlayerSource(player1) val projectile = weapon.Projectile val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), hMineSource, h_mine.DamageModel, @@ -581,7 +584,7 @@ class ExplosiveDeployableDestructionTest extends ActorTest { assert( msg_local(2) match { case LocalServiceMessage.Deployables(SupportActor.ClearSpecific(List(target), _zone)) => - (target eq h_mine) && (_zone eq zone) + (h_mine eq target) && (_zone eq zone) case _ => false } ) diff --git a/src/test/scala/objects/GeneratorTest.scala b/src/test/scala/objects/GeneratorTest.scala index 48c98756..4d53ae41 100644 --- a/src/test/scala/objects/GeneratorTest.scala +++ b/src/test/scala/objects/GeneratorTest.scala @@ -80,6 +80,7 @@ class GeneratorControlDamageTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -161,6 +162,7 @@ class GeneratorControlCriticalTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -251,6 +253,7 @@ class GeneratorControlDestroyedTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -381,6 +384,7 @@ class GeneratorControlKillsTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -503,6 +507,7 @@ class GeneratorControlNotDestroyTwice extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -592,6 +597,7 @@ class GeneratorControlNotDamageIfExplodingTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, @@ -685,6 +691,7 @@ class GeneratorControlNotRepairIfExplodingTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, weapon.Definition, diff --git a/src/test/scala/objects/PlayerControlTest.scala b/src/test/scala/objects/PlayerControlTest.scala index 06a528f2..56b34460 100644 --- a/src/test/scala/objects/PlayerControlTest.scala +++ b/src/test/scala/objects/PlayerControlTest.scala @@ -378,6 +378,7 @@ class PlayerControlDamageTest extends ActorTest { val projectile = tool.Projectile val playerSource = SourceEntry(player2) val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile( projectile, tool.Definition, @@ -477,6 +478,7 @@ class PlayerControlDeathStandingTest extends ActorTest { val projectile = tool.Projectile val player1Source = SourceEntry(player1) val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile(projectile, tool.Definition, tool.FireMode, player1Source, 0, Vector3(2, 0, 0), Vector3(-1, 0, 0)), SourceEntry(player2), player2.DamageModel, @@ -605,6 +607,7 @@ class PlayerControlDeathSeatedTest extends ActorTest { val projectile = tool.Projectile val player1Source = SourceEntry(player1) val resolved = ResolvedProjectile( + ProjectileResolution.Hit, Projectile(projectile, tool.Definition, tool.FireMode, player1Source, 0, Vector3(2, 0, 0), Vector3(-1, 0, 0)), SourceEntry(player2), player2.DamageModel, diff --git a/src/test/scala/objects/ProjectileTest.scala b/src/test/scala/objects/ProjectileTest.scala index 44638010..4d96ccd1 100644 --- a/src/test/scala/objects/ProjectileTest.scala +++ b/src/test/scala/objects/ProjectileTest.scala @@ -352,6 +352,7 @@ class ProjectileTest extends Specification { "construct" in { val obj = ResolvedProjectile( + ProjectileResolution.Hit, projectile, PlayerSource(player2), fury_dm, diff --git a/src/test/scala/objects/VitalityTest.scala b/src/test/scala/objects/VitalityTest.scala index 750ca986..ee459f06 100644 --- a/src/test/scala/objects/VitalityTest.scala +++ b/src/test/scala/objects/VitalityTest.scala @@ -21,6 +21,7 @@ class VitalityTest extends Specification { val pSource = PlayerSource(player) val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero) val resprojectile = ResolvedProjectile( + ProjectileResolution.Hit, projectile, SourceEntry(player), player.DamageModel, @@ -68,6 +69,7 @@ class VitalityTest extends Specification { val pSource = PlayerSource(player) val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero) val resprojectile = ResolvedProjectile( + ProjectileResolution.Hit, projectile, SourceEntry(player), player.DamageModel, From 66eb3b5b9599aba610ad1db5c561f8ac92d67630 Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 7 Aug 2020 23:32:09 -0400 Subject: [PATCH 07/19] work on starfire damage profile; moved aura behavior into own folder and creates an aura container object; extended aura behavior to vehicles; applied target-control to aggravation effect --- .../objects/ballistics/AggravatedDamage.scala | 63 ++++++++----- .../aggravated}/Aura.scala | 3 +- .../aggravated/AuraContainer.scala | 21 +++++ .../aggravated}/AuraEffectBehavior.scala | 21 +++-- .../psforever/objects/GlobalDefinitions.scala | 91 +++++++++++-------- .../scala/net/psforever/objects/Player.scala | 21 +---- .../scala/net/psforever/objects/Vehicle.scala | 4 +- .../objects/avatar/PlayerControl.scala | 1 + .../objects/equipment/EffectTarget.scala | 20 +++- .../objects/vehicles/VehicleControl.scala | 29 +++++- .../vital/damage/DamageModifiers.scala | 71 ++++++++++++--- 11 files changed, 241 insertions(+), 104 deletions(-) rename common/src/main/scala/net/psforever/objects/{avatar => serverobject/aggravated}/Aura.scala (72%) create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraContainer.scala rename common/src/main/scala/net/psforever/objects/{avatar => serverobject/aggravated}/AuraEffectBehavior.scala (93%) diff --git a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala index e430bcda..a1b39ce3 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala +++ b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala @@ -1,33 +1,52 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.ballistics - -import net.psforever.objects.avatar.Aura +import net.psforever.objects.equipment.TargetValidation +import net.psforever.objects.serverobject.aggravated.Aura import net.psforever.objects.vital.DamageType -final case class AggravatedInfo(damage_type : DamageType.Value, - degradation_percentage : Float, - infliction_rate : Long) { +final case class AggravatedInfo(damage_type: DamageType.Value, + degradation_percentage: Float, + infliction_rate: Long) { assert(damage_type == DamageType.Direct || damage_type == DamageType.Splash, s"aggravated damage is an unsupported type - $damage_type") } -final case class AggravatedDamage(info : List[AggravatedInfo], - effect_type : Aura.Value, - duration : Long, - max_factor : Float, - cumulative_damage_degrade : Boolean, - vanu_aggravated : Boolean) +final case class AggravatedDamage(info: List[AggravatedInfo], + effect_type: Aura.Value, + duration: Long, + max_factor: Float, + cumulative_damage_degrade: Boolean, + vanu_aggravated: Boolean, + targets: List[TargetValidation]) object AggravatedDamage { - def apply(info : AggravatedInfo, - effect_type : Aura.Value, - duration : Long, - max_factor : Float) : AggravatedDamage = - AggravatedDamage(List(info), effect_type, duration, max_factor, cumulative_damage_degrade = true, vanu_aggravated = false) + def apply(info: AggravatedInfo, + effect_type: Aura.Value, + duration: Long, + max_factor: Float, + targets: List[TargetValidation]): AggravatedDamage = + AggravatedDamage( + List(info), + effect_type, + duration, + max_factor, + cumulative_damage_degrade = true, + vanu_aggravated = false, + targets + ) - def apply(info : AggravatedInfo, - effect_type : Aura.Value, - duration : Long, - max_factor : Float, - vanu_aggravated : Boolean) : AggravatedDamage = - AggravatedDamage(List(info), effect_type, duration, max_factor, cumulative_damage_degrade = true, vanu_aggravated) + def apply(info: AggravatedInfo, + effect_type: Aura.Value, + duration: Long, + max_factor: Float, + vanu_aggravated: Boolean, + targets: List[TargetValidation]): AggravatedDamage = + AggravatedDamage( + List(info), + effect_type, + duration, + max_factor, + cumulative_damage_degrade = true, + vanu_aggravated, + targets + ) } diff --git a/common/src/main/scala/net/psforever/objects/avatar/Aura.scala b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/Aura.scala similarity index 72% rename from common/src/main/scala/net/psforever/objects/avatar/Aura.scala rename to common/src/main/scala/net/psforever/objects/serverobject/aggravated/Aura.scala index 935dce50..c6883468 100644 --- a/common/src/main/scala/net/psforever/objects/avatar/Aura.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/Aura.scala @@ -1,5 +1,4 @@ -// Copyright (c) 2020 PSForever -package net.psforever.objects.avatar +package net.psforever.objects.serverobject.aggravated object Aura extends Enumeration { final val None = Value(0) diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraContainer.scala b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraContainer.scala new file mode 100644 index 00000000..e2d617d6 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraContainer.scala @@ -0,0 +1,21 @@ +package net.psforever.objects.serverobject.aggravated + +import net.psforever.objects.serverobject.aggravated.{Aura => AuraEffect} + +trait AuraContainer { + private var aura : Set[AuraEffect.Value] = Set.empty[AuraEffect.Value] + + def Aura : Set[AuraEffect.Value] = aura + + def AddEffectToAura(effect : AuraEffect.Value) : Set[AuraEffect.Value] = { + if(effect != AuraEffect.None) { + aura = aura + effect + } + Aura + } + + def RemoveEffectFromAura(effect : AuraEffect.Value) : Set[AuraEffect.Value] = { + aura = aura - effect + Aura + } +} diff --git a/common/src/main/scala/net/psforever/objects/avatar/AuraEffectBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala similarity index 93% rename from common/src/main/scala/net/psforever/objects/avatar/AuraEffectBehavior.scala rename to common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala index 90da7f59..8bd09b18 100644 --- a/common/src/main/scala/net/psforever/objects/avatar/AuraEffectBehavior.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala @@ -1,16 +1,16 @@ // Copyright (c) 2020 PSForever -package net.psforever.objects.avatar +package net.psforever.objects.serverobject.aggravated import akka.actor.{Actor, Cancellable} -import net.psforever.objects.Player import net.psforever.objects.ballistics._ +import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.damage.Damageable import net.psforever.objects.vital.{DamageType, Vitality} import net.psforever.types.Vector3 import scala.collection.mutable -import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration._ trait AuraEffectBehavior { _ : Actor with Damageable => @@ -22,7 +22,7 @@ trait AuraEffectBehavior { private val entryIdToEntry: mutable.LongMap[AuraEffectBehavior.Entry] = mutable.LongMap.empty[AuraEffectBehavior.Entry] - def AuraTargetObject : Player + def AuraTargetObject : AuraEffectBehavior.Target val auraBehavior : Receive = { case AuraEffectBehavior.Aggravate(id, 0, 0) => @@ -93,7 +93,8 @@ trait AuraEffectBehavior { data.projectile.profile.Aggravated match { case Some(damage) if data.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) && - damage.effect_type != Aura.None => + damage.effect_type != Aura.None && //TODO aphelion starfire + damage.targets.exists(validation => validation.test(AuraTargetObject)) => TryAggravationEffect(damage, data) case _ => ; } @@ -104,7 +105,7 @@ trait AuraEffectBehavior { val obj = AuraTargetObject if(CheckForUniqueUnqueuedProjectile(data.projectile)) { val auraEffects = obj.Aura - if(auraEffects.contains(effect) && aggravation.cumulative_damage_degrade) { //TODO cumulative? + if(auraEffects.contains(effect) && aggravation.cumulative_damage_degrade) { SetupAggravationEntry(aggravation, data) } else if(obj.AddEffectToAura(effect).diff(auraEffects).contains(effect)) { @@ -131,9 +132,9 @@ trait AuraEffectBehavior { case Some(list) => effectToEntryId -> (list :+ id) } //pair id with timer - val inflictionRate = info.infliction_rate + val inflictionRate = 1000 //info.infliction_rate val iterations = (aggravation.duration / inflictionRate).toInt - val leftoverTime = aggravation.duration % inflictionRate + val leftoverTime = aggravation.duration - (iterations * inflictionRate) entryIdToTimer += id -> context.system.scheduler.scheduleOnce(inflictionRate milliseconds, self, AuraEffectBehavior.Aggravate(id, iterations, leftoverTime)) //pair id with entry PairIdWithAggravationEntry(id, effect, inflictionRate, data, data.target, Vector3.Zero) @@ -212,7 +213,7 @@ trait AuraEffectBehavior { UpdateAggravatedEffect(AuraTargetObject) } - def UpdateAggravatedEffect(target: Player) : Unit = { + def UpdateAggravatedEffect(target: AuraEffectBehavior.Target) : Unit = { import services.avatar.{AvatarAction, AvatarServiceMessage} val zone = target.Zone val value = target.Aura.foldLeft(0)(_ + _.id) @@ -225,6 +226,8 @@ trait AuraEffectBehavior { } object AuraEffectBehavior { + type Target = PlanetSideServerObject with Vitality with AuraContainer + private case class Entry(id: Long, effect: Aura.Value, retime: Long, data: ResolvedProjectile) private case class Aggravate(id: Long, iterations: Int, leftover: Long) diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index e33c4dc3..462046fb 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2,13 +2,13 @@ package net.psforever.objects import net.psforever.objects.avatar.Certification -import net.psforever.objects.avatar.Aura import net.psforever.objects.ballistics.{AggravatedDamage, AggravatedInfo, Projectiles} import net.psforever.objects.ce.{DeployableCategory, DeployedItem} import net.psforever.objects.definition._ import net.psforever.objects.definition.converter._ import net.psforever.objects.equipment._ import net.psforever.objects.inventory.InventoryTile +import net.psforever.objects.serverobject.aggravated.Aura import net.psforever.objects.serverobject.doors.DoorDefinition import net.psforever.objects.serverobject.generator.GeneratorDefinition import net.psforever.objects.serverobject.implantmech.ImplantTerminalMechDefinition @@ -2323,7 +2323,8 @@ object GlobalDefinitions { Aura.None, 0, 0f, - true + true, + List(TargetValidation(EffectTarget.Category.Aircraft, EffectTarget.Validation.Aircraft)) ) aphelion_starfire_projectile.ExistsOnRemoteClients = true aphelion_starfire_projectile.RemoteClientData = (39577, 249) //starfire_projectile data @@ -2454,7 +2455,11 @@ object GlobalDefinitions { AggravatedInfo(DamageType.Direct, 0.2f, 500), Aura.Comet, 0, - 10f + 10f, + List( + TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player), + TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.Vehicle) + ) ) comet_projectile.InitialVelocity = 80 comet_projectile.Lifespan = 3.1f @@ -2609,16 +2614,17 @@ object GlobalDefinitions { 5000, 0.1f, false, - false + false, + List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player)) ) flamethrower_fireball.InitialVelocity = 15 flamethrower_fireball.Lifespan = 1.2f ProjectileDefinition.CalculateDerivedFields(flamethrower_fireball) flamethrower_fireball.Modifiers = List( - DamageModifiers.AggravatedDirect, - DamageModifiers.AggravatedDirectBurn, - DamageModifiers.AggravatedSplash, - DamageModifiers.AggravatedSplashBurn, + DamageModifiers.InfantryAggravatedDirect, + DamageModifiers.InfantryAggravatedDirectBurn, + DamageModifiers.InfantryAggravatedSplash, + DamageModifiers.InfantryAggravatedSplashBurn, DamageModifiers.RadialDegrade ) @@ -2638,7 +2644,8 @@ object GlobalDefinitions { 5000, 0.5f, false, - false + false, + List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player)) ) flamethrower_projectile.DegradeDelay = 1.0f flamethrower_projectile.DegradeMultiplier = 0.5f @@ -2646,8 +2653,8 @@ object GlobalDefinitions { flamethrower_projectile.Lifespan = 2.0f ProjectileDefinition.CalculateDerivedFields(flamethrower_projectile) flamethrower_projectile.Modifiers = List( - DamageModifiers.AggravatedDirect, - DamageModifiers.AggravatedDirectBurn, + DamageModifiers.InfantryAggravatedDirect, + DamageModifiers.InfantryAggravatedDirectBurn, DamageModifiers.MaxDistanceCutoff ) @@ -3500,19 +3507,20 @@ object GlobalDefinitions { plasma_cartridge_projectile.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), Aura.Plasma, - 4000, //hard-coded default? + 3000, 1.5f, true, - false + false, + List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player)) ) plasma_cartridge_projectile.InitialVelocity = 30 plasma_cartridge_projectile.Lifespan = 15f ProjectileDefinition.CalculateDerivedFields(plasma_cartridge_projectile) plasma_cartridge_projectile.Modifiers = List( - DamageModifiers.AggravatedDirect, - DamageModifiers.AggravatedDirectBurn, - DamageModifiers.AggravatedSplash, - DamageModifiers.AggravatedSplashBurn, + DamageModifiers.InfantryAggravatedDirect, + DamageModifiers.InfantryAggravatedDirectBurn, + DamageModifiers.InfantryAggravatedSplash, + DamageModifiers.InfantryAggravatedSplashBurn, DamageModifiers.RadialDegrade ) @@ -3527,19 +3535,20 @@ object GlobalDefinitions { plasma_cartridge_projectile_b.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), Aura.Plasma, - 4000, //hard-coded default? + 3000, 1.5f, true, - false + false, + List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player)) ) plasma_cartridge_projectile_b.InitialVelocity = 30 plasma_cartridge_projectile_b.Lifespan = 2f ProjectileDefinition.CalculateDerivedFields(plasma_cartridge_projectile_b) plasma_cartridge_projectile_b.Modifiers = List( - DamageModifiers.AggravatedDirect, - DamageModifiers.AggravatedDirectBurn, - DamageModifiers.AggravatedSplash, - DamageModifiers.AggravatedSplashBurn, + DamageModifiers.InfantryAggravatedDirect, + DamageModifiers.InfantryAggravatedDirectBurn, + DamageModifiers.InfantryAggravatedSplash, + DamageModifiers.InfantryAggravatedSplashBurn, DamageModifiers.RadialDegrade ) @@ -3553,19 +3562,20 @@ object GlobalDefinitions { plasma_grenade_projectile.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), Aura.Plasma, - 4000, //hard-coded default? + 3000, 1.5f, true, - false + false, + List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player)) ) plasma_grenade_projectile.InitialVelocity = 30 plasma_grenade_projectile.Lifespan = 15f ProjectileDefinition.CalculateDerivedFields(plasma_grenade_projectile) plasma_grenade_projectile.Modifiers = List( - DamageModifiers.AggravatedDirect, - DamageModifiers.AggravatedDirectBurn, - DamageModifiers.AggravatedSplash, - DamageModifiers.AggravatedSplashBurn, + DamageModifiers.InfantryAggravatedDirect, + DamageModifiers.InfantryAggravatedDirectBurn, + DamageModifiers.InfantryAggravatedSplash, + DamageModifiers.InfantryAggravatedSplashBurn, DamageModifiers.RadialDegrade ) @@ -3580,19 +3590,20 @@ object GlobalDefinitions { plasma_grenade_projectile_B.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), Aura.Plasma, - 4000, //hard-coded default? + 3000, 1.5f, true, - false + false, + List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player)) ) plasma_grenade_projectile_B.InitialVelocity = 30 plasma_grenade_projectile_B.Lifespan = 3f ProjectileDefinition.CalculateDerivedFields(plasma_grenade_projectile_B) plasma_grenade_projectile_B.Modifiers = List( - DamageModifiers.AggravatedDirect, - DamageModifiers.AggravatedDirectBurn, - DamageModifiers.AggravatedSplash, - DamageModifiers.AggravatedSplashBurn, + DamageModifiers.InfantryAggravatedDirect, + DamageModifiers.InfantryAggravatedDirectBurn, + DamageModifiers.InfantryAggravatedSplash, + DamageModifiers.InfantryAggravatedSplashBurn, DamageModifiers.RadialDegrade ) @@ -3899,9 +3910,10 @@ object GlobalDefinitions { starfire_projectile.Aggravated = AggravatedDamage( AggravatedInfo(DamageType.Direct, 0.25f, 250), Aura.Comet, - 0, + 3000, 0f, - true + true, + List(TargetValidation(EffectTarget.Category.Aircraft, EffectTarget.Validation.Aircraft)) ) starfire_projectile.InitialVelocity = 45 starfire_projectile.Lifespan = 7.8f @@ -3910,6 +3922,11 @@ object GlobalDefinitions { starfire_projectile.AutoLock = true starfire_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(starfire_projectile) + starfire_projectile.Modifiers = List( + DamageModifiers.StarfireAggravated, + DamageModifiers.StarfireAggravatedBurn, + DamageModifiers.RadialDegrade + ) striker_missile_projectile.Name = "striker_missile_projectile" striker_missile_projectile.Damage0 = 35 diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala index aec00096..23b329e1 100644 --- a/src/main/scala/net/psforever/objects/Player.scala +++ b/src/main/scala/net/psforever/objects/Player.scala @@ -3,7 +3,6 @@ package net.psforever.objects import net.psforever.objects.avatar.{ Avatar, - Aura => AuraEffect, LoadoutManager } import net.psforever.objects.definition.{ @@ -15,6 +14,7 @@ import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.affinity.FactionAffinity +import net.psforever.objects.serverobject.aggravated.AuraContainer import net.psforever.objects.vital.resistance.ResistanceProfile import net.psforever.objects.vital.{DamageResistanceModel, Vitality} import net.psforever.objects.zones.ZoneAware @@ -30,7 +30,8 @@ class Player(var avatar: Avatar) with ResistanceProfile with Container with JammableUnit - with ZoneAware { + with ZoneAware + with AuraContainer { Health = 0 //player health is artificially managed as a part of their lifecycle; start entity as dead Destroyed = true //see isAlive private var backpack: Boolean = false @@ -57,8 +58,6 @@ class Player(var avatar: Avatar) private var vehicleSeated: Option[PlanetSideGUID] = None - private var aura : Set[AuraEffect.Value] = Set.empty[AuraEffect.Value] - Continent = "home2" //the zone id var spectator: Boolean = false @@ -356,20 +355,6 @@ class Player(var avatar: Avatar) AwayFromKeyboard } - def Aura : Set[AuraEffect.Value] = aura - - def AddEffectToAura(effect : AuraEffect.Value) : Set[AuraEffect.Value] = { - if(effect != AuraEffect.None) { - aura = aura + effect - } - Aura - } - - def RemoveEffectFromAura(effect : AuraEffect.Value) : Set[AuraEffect.Value] = { - aura = aura - effect - Aura - } - private var usingSpecial: SpecialExoSuitDefinition.Mode.Value => SpecialExoSuitDefinition.Mode.Value = DefaultUsingSpecial diff --git a/src/main/scala/net/psforever/objects/Vehicle.scala b/src/main/scala/net/psforever/objects/Vehicle.scala index d108fad3..b927791f 100644 --- a/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/src/main/scala/net/psforever/objects/Vehicle.scala @@ -7,6 +7,7 @@ import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.affinity.FactionAffinity +import net.psforever.objects.serverobject.aggravated.AuraContainer import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.structures.AmenityOwner @@ -78,7 +79,8 @@ class Vehicle(private val vehicleDef: VehicleDefinition) with StandardResistanceProfile with JammableUnit with CommonNtuContainer - with Container { + with Container + with AuraContainer { private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL private var shields: Int = 0 private var decal: Int = 0 diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index 09d0550c..99b02a39 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -8,6 +8,7 @@ import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile} import net.psforever.objects.equipment._ import net.psforever.objects.inventory.{GridInventory, InventoryItem} import net.psforever.objects.loadouts.Loadout +import net.psforever.objects.serverobject.aggravated.AuraEffectBehavior import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior} import net.psforever.objects.vital.{PlayerSuicide, Vitality} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} diff --git a/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala b/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala index d69ee638..7bfc8f28 100644 --- a/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala +++ b/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala @@ -89,10 +89,10 @@ object EffectTarget { false } - def AMS(target: PlanetSideGameObject): Boolean = + def Vehicle(target: PlanetSideGameObject): Boolean = target match { case v: Vehicle => - v.Health > 0 && v.Definition == GlobalDefinitions.ams + v.Health > 0 case _ => false } @@ -104,5 +104,21 @@ object EffectTarget { case _ => false } + + def AMS(target: PlanetSideGameObject): Boolean = + target match { + case v: Vehicle => + v.Health > 0 && v.Definition == GlobalDefinitions.ams + case _ => + false + } + + def Aircraft(target: PlanetSideGameObject): Boolean = + target match { + case v: Vehicle => + GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 + case _ => + false + } } } diff --git a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala index 1e8a4038..c92689bc 100644 --- a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -10,7 +10,9 @@ import net.psforever.objects.inventory.{GridInventory, InventoryItem} import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} +import net.psforever.objects.serverobject.aggravated.AuraEffectBehavior import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior} +import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.serverobject.damage.DamageableVehicle import net.psforever.objects.serverobject.deploy.Deployment.DeploymentObject import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior} @@ -51,7 +53,8 @@ class VehicleControl(vehicle: Vehicle) with RepairableVehicle with JammableMountedWeapons with ContainableBehavior - with AntTransferBehavior { + with AntTransferBehavior + with AuraEffectBehavior { //make control actors belonging to utilities when making control actor belonging to vehicle vehicle.Utilities.foreach({ case (_, util) => util.Setup }) @@ -74,7 +77,9 @@ class VehicleControl(vehicle: Vehicle) def ChargeTransferObject = vehicle - if (vehicle.Definition == GlobalDefinitions.ant) { + def AuraTargetObject = vehicle + + if(vehicle.Definition == GlobalDefinitions.ant) { findChargeTargetFunc = Vehicles.FindANTChargingSource findDischargeTargetFunc = Vehicles.FindANTDischargingTarget } @@ -95,6 +100,7 @@ class VehicleControl(vehicle: Vehicle) context.stop(util().Actor) util().Actor = Default.Actor } + EndAllEffects() } def Enabled: Receive = @@ -103,6 +109,7 @@ class VehicleControl(vehicle: Vehicle) .orElse(cargoBehavior) .orElse(jammableBehavior) .orElse(takesDamage) + .orElse(auraBehavior) .orElse(canBeRepairedByNanoDispenser) .orElse(containerBehavior) .orElse(antBehavior) @@ -579,6 +586,24 @@ class VehicleControl(vehicle: Vehicle) } out } + + override def DamageAwareness( + target: Target, + cause: ResolvedProjectile, + amount: Int + ): Unit = { + TryAggravationEffect(cause) + super.DamageAwareness(target, cause, amount) + } + + override def DestructionAwareness( + target: Target, + cause: ResolvedProjectile + ): Unit = { + //aura effects cancel + EndAllEffects() + super.DestructionAwareness(target, cause) + } } object VehicleControl { diff --git a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala index bb7aedc9..7b508fb8 100644 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala @@ -1,7 +1,8 @@ // Copyright (c) 2020 PSForever package net.psforever.objects.vital.damage -import net.psforever.objects.ballistics.{PlayerSource, ProjectileResolution, ResolvedProjectile} +import net.psforever.objects.GlobalDefinitions +import net.psforever.objects.ballistics.{PlayerSource, ProjectileResolution, ResolvedProjectile, VehicleSource} import net.psforever.objects.vital.DamageType import net.psforever.types.{ExoSuitType, Vector3} @@ -134,22 +135,22 @@ object DamageModifiers { } } - case object AggravatedDirect extends Mod { + case object InfantryAggravatedDirect extends Mod { def Calculate: DamageModifiers.Format = BaseAggravatedFormula(ProjectileResolution.AggravatedDirect, DamageType.Direct) } - case object AggravatedSplash extends Mod { + case object InfantryAggravatedSplash extends Mod { def Calculate: DamageModifiers.Format = BaseAggravatedFormula(ProjectileResolution.AggravatedSplash, DamageType.Splash) } - case object AggravatedDirectBurn extends Mod { + case object InfantryAggravatedDirectBurn extends Mod { def Calculate: DamageModifiers.Format = BaseAggravatedBurnFormula(ProjectileResolution.AggravatedDirectBurn, DamageType.Direct) } - case object AggravatedSplashBurn extends Mod { + case object InfantryAggravatedSplashBurn extends Mod { def Calculate: DamageModifiers.Format = BaseAggravatedBurnFormula(ProjectileResolution.AggravatedSplashBurn, DamageType.Splash) } @@ -164,14 +165,18 @@ object DamageModifiers { ): Int = { if (data.resolution == resolution) { (data.projectile.profile.Aggravated, data.target) match { - case (Some(aggravation), p: PlayerSource) if p.ExoSuit == ExoSuitType.MAX => - val aggravatedDirectDamage = (aggravation.info.find(_.damage_type == damageType) match { + case (Some(aggravation), p: PlayerSource) => + val aggravatedDamage = aggravation.info.find(_.damage_type == damageType) match { case Some(infos) => - damage * infos.degradation_percentage + damage * infos.degradation_percentage + damage case _ => damage toFloat - }) * aggravation.max_factor - damage + aggravatedDirectDamage toInt + } + if(p.ExoSuit == ExoSuitType.MAX) { + (aggravatedDamage * aggravation.max_factor) toInt + } else { + aggravatedDamage toInt + } case _ => damage } @@ -214,10 +219,54 @@ object DamageModifiers { } } case _ => - damage + 0 } } else { damage } } + + case object StarfireAggravated extends Mod { + def Calculate: DamageModifiers.Format = formula + + private def formula(damage: Int, data: ResolvedProjectile): Int = { + if (data.resolution == ProjectileResolution.AggravatedDirect) { + (data.projectile.profile.Aggravated, data.target) match { + case (Some(aggravation), v : VehicleSource) if GlobalDefinitions.isFlightVehicle(v.Definition) => + aggravation.info.find(_.damage_type == DamageType.Direct) match { + case Some(infos) => + (damage * infos.degradation_percentage + damage) toInt + case _ => + damage + } + case _ => + damage + } + } else { + damage + } + } + } + + case object StarfireAggravatedBurn extends Mod { + def Calculate: DamageModifiers.Format = formula + + private def formula(damage: Int, data: ResolvedProjectile): Int = { + if (data.resolution == ProjectileResolution.AggravatedDirectBurn) { + (data.projectile.profile.Aggravated, data.target) match { + case (Some(aggravation), v : VehicleSource) if GlobalDefinitions.isFlightVehicle(v.Definition) => + aggravation.info.find(_.damage_type == DamageType.Direct) match { + case Some(infos) => + (damage * infos.degradation_percentage) toInt + case _ => + damage + } + case _ => + 0 + } + } else { + damage + } + } + } } From 80c1a34fb0a5f53f177d3a0267885bd90317fe35 Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 10 Aug 2020 08:20:12 -0400 Subject: [PATCH 08/19] woring Starfire damage calculations; projectiles have open-ended quality modifier --- .../objects/ballistics/AggravatedDamage.scala | 8 +- .../serverobject/aggravated/Aura.scala | 19 ++-- .../aggravated/AuraContainer.scala | 9 +- .../aggravated/AuraEffectBehavior.scala | 88 +++++++++++-------- .../psforever/objects/GlobalDefinitions.scala | 7 +- .../objects/ballistics/Projectile.scala | 31 +++++++ .../vital/damage/DamageModifiers.scala | 2 +- 7 files changed, 106 insertions(+), 58 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala index a1b39ce3..e28a2544 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala +++ b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala @@ -1,4 +1,4 @@ -// Copyright (c) 2017 PSForever +// Copyright (c) 2020 PSForever package net.psforever.objects.ballistics import net.psforever.objects.equipment.TargetValidation import net.psforever.objects.serverobject.aggravated.Aura @@ -11,7 +11,7 @@ final case class AggravatedInfo(damage_type: DamageType.Value, } final case class AggravatedDamage(info: List[AggravatedInfo], - effect_type: Aura.Value, + effect_type: Aura, duration: Long, max_factor: Float, cumulative_damage_degrade: Boolean, @@ -20,7 +20,7 @@ final case class AggravatedDamage(info: List[AggravatedInfo], object AggravatedDamage { def apply(info: AggravatedInfo, - effect_type: Aura.Value, + effect_type: Aura, duration: Long, max_factor: Float, targets: List[TargetValidation]): AggravatedDamage = @@ -35,7 +35,7 @@ object AggravatedDamage { ) def apply(info: AggravatedInfo, - effect_type: Aura.Value, + effect_type: Aura, duration: Long, max_factor: Float, vanu_aggravated: Boolean, diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/Aura.scala b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/Aura.scala index c6883468..3283e1fc 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/Aura.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/Aura.scala @@ -1,9 +1,16 @@ +// Copyright (c) 2020 PSForever package net.psforever.objects.serverobject.aggravated -object Aura extends Enumeration { - final val None = Value(0) - final val Plasma = Value(1) - final val Comet = Value(2) - final val Napalm = Value(4) - final val Fire = Value(8) +sealed class Aura(val id: Int) + +object Aura { + final case object None extends Aura(id = 0) + + final case object Plasma extends Aura(id = 1) + + final case object Comet extends Aura(id = 2) + + final case object Napalm extends Aura(id = 4) + + final case object Fire extends Aura(id = 8) } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraContainer.scala b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraContainer.scala index e2d617d6..e57f878b 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraContainer.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraContainer.scala @@ -1,20 +1,21 @@ +// Copyright (c) 2020 PSForever package net.psforever.objects.serverobject.aggravated import net.psforever.objects.serverobject.aggravated.{Aura => AuraEffect} trait AuraContainer { - private var aura : Set[AuraEffect.Value] = Set.empty[AuraEffect.Value] + private var aura : Set[AuraEffect] = Set.empty[AuraEffect] - def Aura : Set[AuraEffect.Value] = aura + def Aura : Set[AuraEffect] = aura - def AddEffectToAura(effect : AuraEffect.Value) : Set[AuraEffect.Value] = { + def AddEffectToAura(effect : AuraEffect) : Set[AuraEffect] = { if(effect != AuraEffect.None) { aura = aura + effect } Aura } - def RemoveEffectFromAura(effect : AuraEffect.Value) : Set[AuraEffect.Value] = { + def RemoveEffectFromAura(effect : AuraEffect) : Set[AuraEffect] = { aura = aura - effect Aura } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala index 8bd09b18..1a2cc3da 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala @@ -6,7 +6,6 @@ import net.psforever.objects.ballistics._ import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.damage.Damageable import net.psforever.objects.vital.{DamageType, Vitality} -import net.psforever.types.Vector3 import scala.collection.mutable import scala.concurrent.ExecutionContext.Implicits.global @@ -15,16 +14,16 @@ import scala.concurrent.duration._ trait AuraEffectBehavior { _ : Actor with Damageable => private var activeEffectIndex: Long = 0 - private val effectToEntryId: mutable.HashMap[Aura.Value, List[Long]] = - mutable.HashMap.empty[Aura.Value, List[Long]] - private val entryIdToTimer: mutable.LongMap[Cancellable] = + private val effectToEntryId: mutable.HashMap[Aura, List[Long]] = + mutable.HashMap.empty[Aura, List[Long]] + private val entryIdToTimer: mutable.LongMap[Cancellable] = mutable.LongMap.empty[Cancellable] private val entryIdToEntry: mutable.LongMap[AuraEffectBehavior.Entry] = mutable.LongMap.empty[AuraEffectBehavior.Entry] - def AuraTargetObject : AuraEffectBehavior.Target + def AuraTargetObject: AuraEffectBehavior.Target - val auraBehavior : Receive = { + val auraBehavior: Receive = { case AuraEffectBehavior.Aggravate(id, 0, 0) => CancelEffectTimer(id) PerformCleanupEffect(id) @@ -33,8 +32,8 @@ trait AuraEffectBehavior { RemoveEffectEntry(id) RetimeEvent(id, iteration = 0, Some(leftoverTime), leftoverTime = 0) - case AuraEffectBehavior.Aggravate(id, iteration, leftover) => ; - RetimeEventAndPerformAggravation(id, iteration - 1, None, leftover) + case AuraEffectBehavior.Aggravate(id, iteration, leftover) => + RetimeEventAndPerformAggravation(id, iteration, None, leftover) } private def RetimeEvent( @@ -45,22 +44,13 @@ trait AuraEffectBehavior { ): Option[AuraEffectBehavior.Entry] = { CancelEffectTimer(id) entryIdToEntry.get(id) match { - case Some(oldEntry) => - val target = SourceEntry(AuraTargetObject) - val entry = PairIdWithAggravationEntry( - id, - oldEntry.effect, - oldEntry.retime, - oldEntry.data, - target, - target.Position - oldEntry.data.target.Position - ) + case out @ Some(oldEntry) => entryIdToTimer += id -> context.system.scheduler.scheduleOnce( - time.getOrElse(entry.retime) milliseconds, + time.getOrElse(oldEntry.retime) milliseconds, self, AuraEffectBehavior.Aggravate(id, iteration, leftoverTime) ) - Some(entry) + out case _ => PerformCleanupEffect(id) None @@ -68,9 +58,9 @@ trait AuraEffectBehavior { } private def RetimeEventAndPerformAggravation(id: Long, iteration: Int, time: Option[Long], leftoverTime: Long) : Unit = { - RetimeEvent(id, iteration, time, leftoverTime) match { + RetimeEvent(id, iteration - 1, time, leftoverTime) match { case Some(entry) => - PerformAggravation(entry) + PerformAggravation(entry, iteration) case _ => ; } } @@ -116,7 +106,7 @@ trait AuraEffectBehavior { } private def CheckForUniqueUnqueuedProjectile(projectile : Projectile) : Boolean = { - !entryIdToEntry.values.exists { entry => entry.data.projectile eq projectile } + !entryIdToEntry.values.exists { entry => entry.data.projectile.id == projectile.id } } private def SetupAggravationEntry(aggravation: AggravatedDamage, data: ResolvedProjectile) : Unit = { @@ -131,38 +121,50 @@ trait AuraEffectBehavior { case None | Some(Nil) => effectToEntryId += effect -> List(id) case Some(list) => effectToEntryId -> (list :+ id) } - //pair id with timer - val inflictionRate = 1000 //info.infliction_rate - val iterations = (aggravation.duration / inflictionRate).toInt - val leftoverTime = aggravation.duration - (iterations * inflictionRate) - entryIdToTimer += id -> context.system.scheduler.scheduleOnce(inflictionRate milliseconds, self, AuraEffectBehavior.Aggravate(id, iterations, leftoverTime)) + //setup timer data + val tick = 1000 //each second + val duration = aggravation.duration + val iterations = (duration / tick).toInt + val leftoverTime = duration - (iterations * tick) + //quality per tick + val totalPower = (duration.toFloat / info.infliction_rate).toInt - 1 + val averagePowerPerTick = math.max(1, totalPower.toFloat / iterations).toInt + val lastTickRemainder = totalPower - averagePowerPerTick * iterations + val qualityPerTick: List[Int] = if (lastTickRemainder > 0) { + 0 +: List.fill[Int](iterations - 1)(averagePowerPerTick) :+ (lastTickRemainder + averagePowerPerTick) + } + else { + 0 +: List.fill[Int](iterations)(averagePowerPerTick) + } //pair id with entry - PairIdWithAggravationEntry(id, effect, inflictionRate, data, data.target, Vector3.Zero) + PairIdWithAggravationEntry(id, effect, tick, data, data.target, qualityPerTick) + //pair id with timer + entryIdToTimer += id -> context.system.scheduler.scheduleOnce(tick milliseconds, self, AuraEffectBehavior.Aggravate(id, iterations, leftoverTime)) case _ => ; } } private def PairIdWithAggravationEntry( id: Long, - effect: Aura.Value, - retime:Long, + effect: Aura, + retime: Long, data: ResolvedProjectile, target: SourceEntry, - offset: Vector3 + powerOffset: List[Int] ): AuraEffectBehavior.Entry = { val aggravatedDamageInfo = ResolvedProjectile( AuraEffectBehavior.burning(data.resolution), data.projectile, target, data.damage_model, - data.hit_pos + offset + data.hit_pos ) - val entry = AuraEffectBehavior.Entry(id, effect, retime, aggravatedDamageInfo) + val entry = AuraEffectBehavior.Entry(id, effect, retime, aggravatedDamageInfo, powerOffset) entryIdToEntry += id -> entry entry } - def RemoveEffectEntry(id: Long) : Aura.Value = { + def RemoveEffectEntry(id: Long) : Aura = { entryIdToEntry.remove(id) match { case Some(entry) => entry.data.projectile.profile.Aggravated.get.effect_type @@ -174,7 +176,7 @@ trait AuraEffectBehavior { } } - def CleanupEffect(id: Long) : Aura.Value = { + def CleanupEffect(id: Long) : Aura = { //remove and cancel timer entryIdToTimer.remove(id) match { case Some(timer) => timer.cancel @@ -220,15 +222,23 @@ trait AuraEffectBehavior { zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(target.GUID, 54, value)) } - private def PerformAggravation(entry: AuraEffectBehavior.Entry) : Unit = { - TakesDamage.apply(Vitality.Damage(entry.data.damage_model.Calculate(entry.data))) + private def PerformAggravation(entry: AuraEffectBehavior.Entry, tick: Int = 0) : Unit = { + val data = entry.data + val info = ResolvedProjectile( + data.resolution, + data.projectile.quality(entry.qualityPerTick(tick).toFloat), + data.target, + data.damage_model, + data.hit_pos + ) + TakesDamage.apply(Vitality.Damage(info.damage_model.Calculate(info))) } } object AuraEffectBehavior { type Target = PlanetSideServerObject with Vitality with AuraContainer - private case class Entry(id: Long, effect: Aura.Value, retime: Long, data: ResolvedProjectile) + private case class Entry(id: Long, effect: Aura, retime: Long, data: ResolvedProjectile, qualityPerTick: List[Int]) private case class Aggravate(id: Long, iterations: Int, leftover: Long) diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 462046fb..f8171f01 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2321,7 +2321,7 @@ object GlobalDefinitions { aphelion_starfire_projectile.Aggravated = AggravatedDamage( AggravatedInfo(DamageType.Direct, 0.25f, 250), Aura.None, - 0, + 2000, 0f, true, List(TargetValidation(EffectTarget.Category.Aircraft, EffectTarget.Validation.Aircraft)) @@ -2454,7 +2454,7 @@ object GlobalDefinitions { comet_projectile.Aggravated = AggravatedDamage( AggravatedInfo(DamageType.Direct, 0.2f, 500), Aura.Comet, - 0, + 2000, 10f, List( TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player), @@ -3910,7 +3910,7 @@ object GlobalDefinitions { starfire_projectile.Aggravated = AggravatedDamage( AggravatedInfo(DamageType.Direct, 0.25f, 250), Aura.Comet, - 3000, + 2000, 0f, true, List(TargetValidation(EffectTarget.Category.Aircraft, EffectTarget.Validation.Aircraft)) @@ -3923,7 +3923,6 @@ object GlobalDefinitions { starfire_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(starfire_projectile) starfire_projectile.Modifiers = List( - DamageModifiers.StarfireAggravated, DamageModifiers.StarfireAggravatedBurn, DamageModifiers.RadialDegrade ) diff --git a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala index 57b6de22..d40d9de8 100644 --- a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala +++ b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala @@ -1,6 +1,8 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.ballistics +import java.util.concurrent.atomic.AtomicLong + import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.definition.{ProjectileDefinition, ToolDefinition} import net.psforever.objects.entity.SimpleWorldEntity @@ -25,6 +27,9 @@ import net.psforever.types.Vector3 * if not, then it is a type of vehicle (and owner should have a positive `seated` field) * @param shot_origin where the projectile started * @param shot_angle in which direction the projectile was aimed when it was discharged + * @param quality na + * @param id an exclusive identifier for this projectile; + * normally generated internally, but can be manually set * @param fire_time when the weapon discharged was recorded; * defaults to `System.nanoTime` */ @@ -36,6 +41,8 @@ final case class Projectile( attribute_to: Int, shot_origin: Vector3, shot_angle: Vector3, + quality: Float = 1f, + id: Long = Projectile.idGenerator.getAndIncrement(), fire_time: Long = System.nanoTime ) extends PlanetSideGameObject { Position = shot_origin @@ -52,6 +59,28 @@ final case class Projectile( val current: SimpleWorldEntity = new SimpleWorldEntity() private var resolved: ProjectileResolution.Value = ProjectileResolution.Unresolved + /** + * Create a copy of this projectile with all the same information + * save for the quality. + * Used mainly for aggravated damage. + * It is important to note that the new projectile shares the (otherwise) exclusive id of the original. + * @param value the new quality + * @return a new `Projectile` entity + */ + def quality(value: Float): Projectile = + Projectile( + profile, + tool_def, + fire_mode, + owner, + attribute_to, + shot_origin, + shot_angle, + value, + id, + fire_time + ) + /** * Mark the projectile as being "encountered" or "managed" at least once. */ @@ -80,6 +109,8 @@ object Projectile { */ final val rangeUID: Int = 40150 + private val idGenerator: AtomicLong = new AtomicLong + /** * Overloaded constructor for an `Unresolved` projectile. * @param profile an explanation of the damage that can be performed by this discharge diff --git a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala index 7b508fb8..1ae9dbc9 100644 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala @@ -257,7 +257,7 @@ object DamageModifiers { case (Some(aggravation), v : VehicleSource) if GlobalDefinitions.isFlightVehicle(v.Definition) => aggravation.info.find(_.damage_type == DamageType.Direct) match { case Some(infos) => - (damage * infos.degradation_percentage) toInt + (math.floor(damage * infos.degradation_percentage) * data.projectile.quality) toInt case _ => damage } From a79fc6bd2f793abcebb19d599b7d55c76e4e2bc1 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 11 Aug 2020 15:17:56 -0400 Subject: [PATCH 09/19] working comet calculations --- .../objects/ballistics/AggravatedDamage.scala | 45 +++++++++++++++++-- .../aggravated/AuraEffectBehavior.scala | 37 +++++++++------ .../psforever/objects/GlobalDefinitions.scala | 20 +++++---- .../serverobject/damage/Damageable.scala | 2 +- .../vital/damage/DamageModifiers.scala | 29 ++++++++++++ 5 files changed, 107 insertions(+), 26 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala index e28a2544..81c4fedb 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala +++ b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala @@ -4,6 +4,14 @@ import net.psforever.objects.equipment.TargetValidation import net.psforever.objects.serverobject.aggravated.Aura import net.psforever.objects.vital.DamageType +final case class AggravatedTiming(duration: Long, ticks: Option[Int]) + +object AggravatedTiming { + def apply(duration: Long): AggravatedTiming = AggravatedTiming(duration, None) + + def apply(duration: Long, ticks: Int): AggravatedTiming = AggravatedTiming(duration, Some(ticks)) +} + final case class AggravatedInfo(damage_type: DamageType.Value, degradation_percentage: Float, infliction_rate: Long) { @@ -12,13 +20,44 @@ final case class AggravatedInfo(damage_type: DamageType.Value, final case class AggravatedDamage(info: List[AggravatedInfo], effect_type: Aura, - duration: Long, + timing: AggravatedTiming, max_factor: Float, cumulative_damage_degrade: Boolean, vanu_aggravated: Boolean, targets: List[TargetValidation]) object AggravatedDamage { + def apply(info: AggravatedInfo, + effect_type: Aura, + timing: AggravatedTiming, + max_factor: Float, + targets: List[TargetValidation]): AggravatedDamage = + AggravatedDamage( + List(info), + effect_type, + timing, + max_factor, + cumulative_damage_degrade = true, + vanu_aggravated = false, + targets + ) + + def apply(info: AggravatedInfo, + effect_type: Aura, + timing: AggravatedTiming, + max_factor: Float, + vanu_aggravated: Boolean, + targets: List[TargetValidation]): AggravatedDamage = + AggravatedDamage( + List(info), + effect_type, + timing, + max_factor, + cumulative_damage_degrade = true, + vanu_aggravated, + targets + ) + def apply(info: AggravatedInfo, effect_type: Aura, duration: Long, @@ -27,7 +66,7 @@ object AggravatedDamage { AggravatedDamage( List(info), effect_type, - duration, + AggravatedTiming(duration), max_factor, cumulative_damage_degrade = true, vanu_aggravated = false, @@ -43,7 +82,7 @@ object AggravatedDamage { AggravatedDamage( List(info), effect_type, - duration, + AggravatedTiming(duration), max_factor, cumulative_damage_degrade = true, vanu_aggravated, diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala index 1a2cc3da..9f774525 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala @@ -122,19 +122,27 @@ trait AuraEffectBehavior { case Some(list) => effectToEntryId -> (list :+ id) } //setup timer data - val tick = 1000 //each second - val duration = aggravation.duration - val iterations = (duration / tick).toInt - val leftoverTime = duration - (iterations * tick) + val timing = aggravation.timing + val duration = timing.duration + val (tick: Long, iterations: Int) = timing.ticks match { + case Some(n) if n < 1 => + val rate = info.infliction_rate + (rate, (duration / rate).toInt) + case Some(ticks) => + (duration / ticks, ticks) + case None => + (1000, (duration / 1000).toInt) + } + val leftoverTime = duration - (tick * iterations) //quality per tick val totalPower = (duration.toFloat / info.infliction_rate).toInt - 1 - val averagePowerPerTick = math.max(1, totalPower.toFloat / iterations).toInt + val averagePowerPerTick = totalPower.toFloat / iterations val lastTickRemainder = totalPower - averagePowerPerTick * iterations - val qualityPerTick: List[Int] = if (lastTickRemainder > 0) { - 0 +: List.fill[Int](iterations - 1)(averagePowerPerTick) :+ (lastTickRemainder + averagePowerPerTick) + val qualityPerTick: List[Float] = if (lastTickRemainder > 0) { + 0f +: List.fill[Float](iterations - 1)(averagePowerPerTick) :+ (lastTickRemainder + averagePowerPerTick) } else { - 0 +: List.fill[Int](iterations)(averagePowerPerTick) + 0f +: List.fill[Float](iterations)(averagePowerPerTick) } //pair id with entry PairIdWithAggravationEntry(id, effect, tick, data, data.target, qualityPerTick) @@ -150,7 +158,7 @@ trait AuraEffectBehavior { retime: Long, data: ResolvedProjectile, target: SourceEntry, - powerOffset: List[Int] + powerOffset: List[Float] ): AuraEffectBehavior.Entry = { val aggravatedDamageInfo = ResolvedProjectile( AuraEffectBehavior.burning(data.resolution), @@ -224,21 +232,22 @@ trait AuraEffectBehavior { private def PerformAggravation(entry: AuraEffectBehavior.Entry, tick: Int = 0) : Unit = { val data = entry.data - val info = ResolvedProjectile( + val model = data.damage_model + val aggravatedProjectileData = ResolvedProjectile( data.resolution, - data.projectile.quality(entry.qualityPerTick(tick).toFloat), + data.projectile.quality(entry.qualityPerTick(tick)), data.target, - data.damage_model, + model, data.hit_pos ) - TakesDamage.apply(Vitality.Damage(info.damage_model.Calculate(info))) + TakesDamage.apply(Vitality.Damage(model.Calculate(aggravatedProjectileData))) } } object AuraEffectBehavior { type Target = PlanetSideServerObject with Vitality with AuraContainer - private case class Entry(id: Long, effect: Aura, retime: Long, data: ResolvedProjectile, qualityPerTick: List[Int]) + private case class Entry(id: Long, effect: Aura, retime: Long, data: ResolvedProjectile, qualityPerTick: List[Float]) private case class Aggravate(id: Long, iterations: Int, leftover: Long) diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index f8171f01..e9295cb1 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2,7 +2,7 @@ package net.psforever.objects import net.psforever.objects.avatar.Certification -import net.psforever.objects.ballistics.{AggravatedDamage, AggravatedInfo, Projectiles} +import net.psforever.objects.ballistics.{AggravatedDamage, AggravatedInfo, AggravatedTiming, Projectiles} import net.psforever.objects.ce.{DeployableCategory, DeployedItem} import net.psforever.objects.definition._ import net.psforever.objects.definition.converter._ @@ -2454,7 +2454,7 @@ object GlobalDefinitions { comet_projectile.Aggravated = AggravatedDamage( AggravatedInfo(DamageType.Direct, 0.2f, 500), Aura.Comet, - 2000, + AggravatedTiming(2000, 4), 10f, List( TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player), @@ -2464,6 +2464,10 @@ object GlobalDefinitions { comet_projectile.InitialVelocity = 80 comet_projectile.Lifespan = 3.1f ProjectileDefinition.CalculateDerivedFields(comet_projectile) + comet_projectile.Modifiers = List( + DamageModifiers.CometAggravated, + DamageModifiers.CometAggravatedBurn + ) dualcycler_projectile.Name = "dualcycler_projectile" dualcycler_projectile.Damage0 = 18 @@ -2611,7 +2615,7 @@ object GlobalDefinitions { flamethrower_fireball.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.9f, 500), AggravatedInfo(DamageType.Splash, 0.9f, 500)), Aura.Fire, - 5000, + AggravatedTiming(5000), 0.1f, false, false, @@ -2641,7 +2645,7 @@ object GlobalDefinitions { flamethrower_projectile.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.5f, 500)), Aura.Fire, - 5000, + AggravatedTiming(5000), 0.5f, false, false, @@ -3507,7 +3511,7 @@ object GlobalDefinitions { plasma_cartridge_projectile.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), Aura.Plasma, - 3000, + AggravatedTiming(3000), 1.5f, true, false, @@ -3535,7 +3539,7 @@ object GlobalDefinitions { plasma_cartridge_projectile_b.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), Aura.Plasma, - 3000, + AggravatedTiming(3000), 1.5f, true, false, @@ -3562,7 +3566,7 @@ object GlobalDefinitions { plasma_grenade_projectile.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), Aura.Plasma, - 3000, + AggravatedTiming(3000), 1.5f, true, false, @@ -3590,7 +3594,7 @@ object GlobalDefinitions { plasma_grenade_projectile_B.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)), Aura.Plasma, - 3000, + AggravatedTiming(3000), 1.5f, true, false, diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala b/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala index 5dc87756..8fcde16c 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala @@ -59,7 +59,7 @@ object Damageable { */ def CanDamage(obj: Vitality with FactionAffinity, damage: Int, data: ResolvedProjectile): Boolean = { val definition = obj.Definition - damage > 0 && + (damage > 0 || data.projectile.profile.Aggravated.nonEmpty) && definition.Damageable && (definition.DamageableByFriendlyFire || (data.projectile.owner.Faction != obj.Faction || diff --git a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala index 1ae9dbc9..d544e526 100644 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala @@ -269,4 +269,33 @@ object DamageModifiers { } } } + + case object CometAggravated extends Mod { + def Calculate: DamageModifiers.Format = formula + + private def formula(damage: Int, data: ResolvedProjectile): Int = { + if (data.resolution == ProjectileResolution.AggravatedDirect) { + 0 + } else { + damage + } + } + } + + case object CometAggravatedBurn extends Mod { + def Calculate: DamageModifiers.Format = formula + + private def formula(damage: Int, data: ResolvedProjectile): Int = { + if (data.resolution == ProjectileResolution.AggravatedDirectBurn) { + data.projectile.profile.Aggravated match { + case Some(_) => + (damage * data.projectile.quality) toInt + case _ => + 0 + } + } else { + damage + } + } + } } From 89d7aea633c6b2e038130173797c5aef8d5bb2cc Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 12 Aug 2020 20:59:13 -0400 Subject: [PATCH 10/19] separated aggravation behavior and uara management behavior; moved files into packages and deleted old files --- .../objects/ballistics/AggravatedDamage.scala | 2 +- .../aggravated/AggravatedBehavior.scala | 223 ++++++++++++++ .../serverobject/aggravated/Aura.scala | 16 -- .../aggravated/AuraEffectBehavior.scala | 272 ------------------ .../objects/serverobject/aura/Aura.scala | 33 +++ .../{aggravated => aura}/AuraContainer.scala | 9 +- .../aura/AuraEffectBehavior.scala | 153 ++++++++++ .../objects/vital/StandardDamages.scala | 176 ------------ .../vital/damage/DamageSelection.scala | 35 --- .../psforever/objects/GlobalDefinitions.scala | 2 +- .../scala/net/psforever/objects/Player.scala | 2 +- .../scala/net/psforever/objects/Vehicle.scala | 2 +- .../objects/avatar/PlayerControl.scala | 16 +- .../objects/vehicles/VehicleControl.scala | 18 +- .../psforever/packet/GamePacketOpcode.scala | 2 +- src/test/scala/objects/DamageModelTests.scala | 4 +- 16 files changed, 451 insertions(+), 514 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/aggravated/AggravatedBehavior.scala delete mode 100644 common/src/main/scala/net/psforever/objects/serverobject/aggravated/Aura.scala delete mode 100644 common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/aura/Aura.scala rename common/src/main/scala/net/psforever/objects/serverobject/{aggravated => aura}/AuraContainer.scala (54%) create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala delete mode 100644 common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala delete mode 100644 common/src/main/scala/net/psforever/objects/vital/damage/DamageSelection.scala diff --git a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala index 81c4fedb..cf445115 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala +++ b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala @@ -1,7 +1,7 @@ // Copyright (c) 2020 PSForever package net.psforever.objects.ballistics import net.psforever.objects.equipment.TargetValidation -import net.psforever.objects.serverobject.aggravated.Aura +import net.psforever.objects.serverobject.aura.Aura import net.psforever.objects.vital.DamageType final case class AggravatedTiming(duration: Long, ticks: Option[Int]) diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AggravatedBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AggravatedBehavior.scala new file mode 100644 index 00000000..0e1feff3 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AggravatedBehavior.scala @@ -0,0 +1,223 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.serverobject.aggravated + +import akka.actor.{Actor, Cancellable} +import net.psforever.objects.ballistics._ +import net.psforever.objects.serverobject.aura.{Aura, AuraEffectBehavior} +import net.psforever.objects.serverobject.damage.Damageable +import net.psforever.objects.vital.{DamageType, Vitality} + +import scala.collection.mutable +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration._ + +trait AggravatedBehavior { + _ : Actor with Damageable => + private val entryIdToEntry: mutable.LongMap[AggravatedBehavior.Entry] = + mutable.LongMap.empty[AggravatedBehavior.Entry] + private val aggravationToTimer: mutable.LongMap[Cancellable] = + mutable.LongMap.empty[Cancellable] + + def AggravatedObject: AggravatedBehavior.Target + + def TryAggravationEffect(data: ResolvedProjectile): Option[AggravatedDamage] = { + data.projectile.profile.Aggravated match { + case Some(damage) + if data.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) && + damage.effect_type != Aura.Nothing && + damage.targets.exists(validation => validation.test(AggravatedObject)) => + TryAggravationEffect(damage, data) + case _ => + None + } + } + + private def TryAggravationEffect(aggravation: AggravatedDamage, data: ResolvedProjectile): Option[AggravatedDamage] = { + val effect = aggravation.effect_type + val obj = AggravatedObject + if(CheckForUniqueUnqueuedProjectile(data.projectile)) { + val auraEffects = obj.Aura + if(auraEffects.contains(effect) && aggravation.cumulative_damage_degrade) { + SetupAggravationEntry(aggravation, data) + Some(aggravation) + } + else if(obj.AddEffectToAura(effect).diff(auraEffects).contains(effect)) { + SetupAggravationEntry(aggravation, data) + Some(aggravation) + } + else { + None + } + } + else { + None + } + } + + private def CheckForUniqueUnqueuedProjectile(projectile : Projectile): Boolean = { + !entryIdToEntry.values.exists { entry => entry.data.projectile.id == projectile.id } + } + + private def SetupAggravationEntry(aggravation: AggravatedDamage, data: ResolvedProjectile): Boolean = { + val effect = aggravation.effect_type + aggravation.info.find(_.damage_type == AggravatedBehavior.basicDamageType(data.resolution)) match { + case Some(info) => + val timing = aggravation.timing + val duration = timing.duration + //setup effect + val id = data.projectile.id + //setup timer data + val (tick: Long, iterations: Int) = timing.ticks match { + case Some(n) if n < 1 => + val rate = info.infliction_rate + (rate, (duration / rate).toInt) + case Some(ticks) => + (duration / ticks, ticks) + case None => + (1000L, (duration / 1000).toInt) + } + //val leftoverTime = duration - (tick * iterations) + //quality per tick + val totalPower = (duration.toFloat / info.infliction_rate).toInt - 1 + val averagePowerPerTick = totalPower.toFloat / iterations + val lastTickRemainder = totalPower - averagePowerPerTick * iterations + val qualityPerTick: List[Float] = if (lastTickRemainder > 0) { + 0f +: List.fill[Float](iterations - 1)(averagePowerPerTick) :+ (lastTickRemainder + averagePowerPerTick) + } + else { + 0f +: List.fill[Float](iterations)(averagePowerPerTick) + } + //pair id with entry + PairIdWithAggravationEntry(id, effect, tick, data, data.target, qualityPerTick) + //pair id with timer + aggravationToTimer += id -> context.system.scheduler.scheduleOnce(tick milliseconds, self, AggravatedBehavior.Aggravate(id, iterations)) + true + case _ => + false + } + } + + private def PairIdWithAggravationEntry( + id: Long, + effect: Aura, + retime: Long, + data: ResolvedProjectile, + target: SourceEntry, + powerOffset: List[Float] + ): AggravatedBehavior.Entry = { + val aggravatedDamageInfo = ResolvedProjectile( + AggravatedBehavior.burning(data.resolution), + data.projectile, + target, + data.damage_model, + data.hit_pos + ) + val entry = AggravatedBehavior.Entry(id, effect, retime, aggravatedDamageInfo, powerOffset) + entryIdToEntry += id -> entry + entry + } + + val aggravatedBehavior: Receive = { + case AggravatedBehavior.Aggravate(id, 0) => + AggravationCleanup(id) + + case AggravatedBehavior.Aggravate(id, iteration) => + RetimeEventAndPerformAggravation(id, iteration, None) + } + + private def RetimeEventAndPerformAggravation(id: Long, iteration: Int, time: Option[Long]) : Unit = { + RetimeAggravation(id, iteration - 1, time) match { + case Some(entry) => + PerformAggravation(entry, iteration) + case _ => ; + } + } + + private def RetimeAggravation( + id: Long, + iteration: Int, + time: Option[Long] + ): Option[AggravatedBehavior.Entry] = { + CleanupAggravationTimer(id) + entryIdToEntry.get(id) match { + case out @ Some(oldEntry) => + aggravationToTimer += id -> context.system.scheduler.scheduleOnce( + time.getOrElse(oldEntry.retime) milliseconds, + self, + AggravatedBehavior.Aggravate(id, iteration) + ) + out + case _ => + AggravationCleanup(id) + None + } + } + + def RemoveAggravatedEntry(id: Long): Aura = { + entryIdToEntry.remove(id) match { + case Some(entry) => + entry.data.projectile.profile.Aggravated.get.effect_type + case _ => + Aura.Nothing + } + } + + def CleanupAggravationTimer(id: Long): Unit = { + //remove and cancel timer + aggravationToTimer.remove(id) match { + case Some(timer) => timer.cancel + case _ => ; + } + } + + def AggravationCleanup(id: Long): Unit = { + RemoveAggravatedEntry(id) + CleanupAggravationTimer(id) + } + + def EndAllAggravation(): Unit = { + entryIdToEntry.clear + aggravationToTimer.values.foreach { _.cancel } + aggravationToTimer.clear + } + + private def PerformAggravation(entry: AggravatedBehavior.Entry, tick: Int = 0): Unit = { + val data = entry.data + val model = data.damage_model + val aggravatedProjectileData = ResolvedProjectile( + data.resolution, + data.projectile.quality(entry.qualityPerTick(tick)), + data.target, + model, + data.hit_pos + ) + TakesDamage.apply(Vitality.Damage(model.Calculate(aggravatedProjectileData))) + } +} + +object AggravatedBehavior { + type Target = AuraEffectBehavior.Target with Vitality + + private case class Entry(id: Long, effect: Aura, retime: Long, data: ResolvedProjectile, qualityPerTick: List[Float]) + + private case class Aggravate(id: Long, iterations: Int) + + private def burning(resolution: ProjectileResolution.Value): ProjectileResolution.Value = { + resolution match { + case ProjectileResolution.AggravatedDirect => ProjectileResolution.AggravatedDirectBurn + case ProjectileResolution.AggravatedSplash => ProjectileResolution.AggravatedSplashBurn + case _ => resolution + } + } + + private def basicDamageType(resolution: ProjectileResolution.Value): DamageType.Value = { + resolution match { + case ProjectileResolution.AggravatedDirect | ProjectileResolution.AggravatedDirectBurn => + DamageType.Direct + case ProjectileResolution.AggravatedSplash | ProjectileResolution.AggravatedSplashBurn => + DamageType.Splash + case _ => + DamageType.None + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/Aura.scala b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/Aura.scala deleted file mode 100644 index 3283e1fc..00000000 --- a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/Aura.scala +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2020 PSForever -package net.psforever.objects.serverobject.aggravated - -sealed class Aura(val id: Int) - -object Aura { - final case object None extends Aura(id = 0) - - final case object Plasma extends Aura(id = 1) - - final case object Comet extends Aura(id = 2) - - final case object Napalm extends Aura(id = 4) - - final case object Fire extends Aura(id = 8) -} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala deleted file mode 100644 index 9f774525..00000000 --- a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) 2020 PSForever -package net.psforever.objects.serverobject.aggravated - -import akka.actor.{Actor, Cancellable} -import net.psforever.objects.ballistics._ -import net.psforever.objects.serverobject.PlanetSideServerObject -import net.psforever.objects.serverobject.damage.Damageable -import net.psforever.objects.vital.{DamageType, Vitality} - -import scala.collection.mutable -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration._ - -trait AuraEffectBehavior { - _ : Actor with Damageable => - private var activeEffectIndex: Long = 0 - private val effectToEntryId: mutable.HashMap[Aura, List[Long]] = - mutable.HashMap.empty[Aura, List[Long]] - private val entryIdToTimer: mutable.LongMap[Cancellable] = - mutable.LongMap.empty[Cancellable] - private val entryIdToEntry: mutable.LongMap[AuraEffectBehavior.Entry] = - mutable.LongMap.empty[AuraEffectBehavior.Entry] - - def AuraTargetObject: AuraEffectBehavior.Target - - val auraBehavior: Receive = { - case AuraEffectBehavior.Aggravate(id, 0, 0) => - CancelEffectTimer(id) - PerformCleanupEffect(id) - - case AuraEffectBehavior.Aggravate(id, 0, leftoverTime) => - RemoveEffectEntry(id) - RetimeEvent(id, iteration = 0, Some(leftoverTime), leftoverTime = 0) - - case AuraEffectBehavior.Aggravate(id, iteration, leftover) => - RetimeEventAndPerformAggravation(id, iteration, None, leftover) - } - - private def RetimeEvent( - id: Long, - iteration: Int, - time: Option[Long], - leftoverTime: Long - ): Option[AuraEffectBehavior.Entry] = { - CancelEffectTimer(id) - entryIdToEntry.get(id) match { - case out @ Some(oldEntry) => - entryIdToTimer += id -> context.system.scheduler.scheduleOnce( - time.getOrElse(oldEntry.retime) milliseconds, - self, - AuraEffectBehavior.Aggravate(id, iteration, leftoverTime) - ) - out - case _ => - PerformCleanupEffect(id) - None - } - } - - private def RetimeEventAndPerformAggravation(id: Long, iteration: Int, time: Option[Long], leftoverTime: Long) : Unit = { - RetimeEvent(id, iteration - 1, time, leftoverTime) match { - case Some(entry) => - PerformAggravation(entry, iteration) - case _ => ; - } - } - - def CancelEffectTimer(id: Long) : Unit = { - entryIdToTimer.remove(id) match { - case Some(timer) => timer.cancel - case _ => ; - } - } - - def PerformCleanupEffect(id: Long) : Unit = { - CleanupEffect(id) match { - case Aura.None => ; - case _ => UpdateAggravatedEffect(AuraTargetObject) - } - } - - def TryAggravationEffect(data: ResolvedProjectile) : Unit = { - data.projectile.profile.Aggravated match { - case Some(damage) - if data.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) && - damage.effect_type != Aura.None && //TODO aphelion starfire - damage.targets.exists(validation => validation.test(AuraTargetObject)) => - TryAggravationEffect(damage, data) - case _ => ; - } - } - - private def TryAggravationEffect(aggravation: AggravatedDamage, data: ResolvedProjectile) : Unit = { - val effect = aggravation.effect_type - val obj = AuraTargetObject - if(CheckForUniqueUnqueuedProjectile(data.projectile)) { - val auraEffects = obj.Aura - if(auraEffects.contains(effect) && aggravation.cumulative_damage_degrade) { - SetupAggravationEntry(aggravation, data) - } - else if(obj.AddEffectToAura(effect).diff(auraEffects).contains(effect)) { - SetupAggravationEntry(aggravation, data) - UpdateAggravatedEffect(obj) - } - } - } - - private def CheckForUniqueUnqueuedProjectile(projectile : Projectile) : Boolean = { - !entryIdToEntry.values.exists { entry => entry.data.projectile.id == projectile.id } - } - - private def SetupAggravationEntry(aggravation: AggravatedDamage, data: ResolvedProjectile) : Unit = { - val effect = aggravation.effect_type - aggravation.info.find(_.damage_type == AuraEffectBehavior.basicDamageType(data.resolution)) match { - case Some(info) => - //get unused id - val id = activeEffectIndex - activeEffectIndex += 1 - //pair aura effect with id - effectToEntryId.get(effect) match { - case None | Some(Nil) => effectToEntryId += effect -> List(id) - case Some(list) => effectToEntryId -> (list :+ id) - } - //setup timer data - val timing = aggravation.timing - val duration = timing.duration - val (tick: Long, iterations: Int) = timing.ticks match { - case Some(n) if n < 1 => - val rate = info.infliction_rate - (rate, (duration / rate).toInt) - case Some(ticks) => - (duration / ticks, ticks) - case None => - (1000, (duration / 1000).toInt) - } - val leftoverTime = duration - (tick * iterations) - //quality per tick - val totalPower = (duration.toFloat / info.infliction_rate).toInt - 1 - val averagePowerPerTick = totalPower.toFloat / iterations - val lastTickRemainder = totalPower - averagePowerPerTick * iterations - val qualityPerTick: List[Float] = if (lastTickRemainder > 0) { - 0f +: List.fill[Float](iterations - 1)(averagePowerPerTick) :+ (lastTickRemainder + averagePowerPerTick) - } - else { - 0f +: List.fill[Float](iterations)(averagePowerPerTick) - } - //pair id with entry - PairIdWithAggravationEntry(id, effect, tick, data, data.target, qualityPerTick) - //pair id with timer - entryIdToTimer += id -> context.system.scheduler.scheduleOnce(tick milliseconds, self, AuraEffectBehavior.Aggravate(id, iterations, leftoverTime)) - case _ => ; - } - } - - private def PairIdWithAggravationEntry( - id: Long, - effect: Aura, - retime: Long, - data: ResolvedProjectile, - target: SourceEntry, - powerOffset: List[Float] - ): AuraEffectBehavior.Entry = { - val aggravatedDamageInfo = ResolvedProjectile( - AuraEffectBehavior.burning(data.resolution), - data.projectile, - target, - data.damage_model, - data.hit_pos - ) - val entry = AuraEffectBehavior.Entry(id, effect, retime, aggravatedDamageInfo, powerOffset) - entryIdToEntry += id -> entry - entry - } - - def RemoveEffectEntry(id: Long) : Aura = { - entryIdToEntry.remove(id) match { - case Some(entry) => - entry.data.projectile.profile.Aggravated.get.effect_type - case None => - effectToEntryId.find { case (_, values) => values.contains(id) } match { - case Some((effect, _)) => effect - case _ => Aura.None - } - } - } - - def CleanupEffect(id: Long) : Aura = { - //remove and cancel timer - entryIdToTimer.remove(id) match { - case Some(timer) => timer.cancel - case _ => ; - } - //remove entry and cache effect - val out = RemoveEffectEntry(id) - //remove id and, if now unsupported, effect - (effectToEntryId.get(out) match { - case Some(list) => (out, list.filterNot(_ == id)) - case _ => (Aura.None, Nil) - }) match { - case (Aura.None, _) => - Aura.None - case (effect, Nil) => - AuraTargetObject.RemoveEffectFromAura(effect) - effectToEntryId.remove(effect) - effect - case (effect, list) => - effectToEntryId += effect -> list - Aura.None - } - } - - def EndAllEffects() : Unit = { - entryIdToEntry.clear - entryIdToTimer.values.foreach { _.cancel } - entryIdToTimer.clear - effectToEntryId.clear - val obj = AuraTargetObject - obj.Aura.foreach { obj.RemoveEffectFromAura } - } - - def EndAllEffectsAndUpdate() : Unit = { - EndAllEffects() - UpdateAggravatedEffect(AuraTargetObject) - } - - def UpdateAggravatedEffect(target: AuraEffectBehavior.Target) : Unit = { - import services.avatar.{AvatarAction, AvatarServiceMessage} - val zone = target.Zone - val value = target.Aura.foldLeft(0)(_ + _.id) - zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(target.GUID, 54, value)) - } - - private def PerformAggravation(entry: AuraEffectBehavior.Entry, tick: Int = 0) : Unit = { - val data = entry.data - val model = data.damage_model - val aggravatedProjectileData = ResolvedProjectile( - data.resolution, - data.projectile.quality(entry.qualityPerTick(tick)), - data.target, - model, - data.hit_pos - ) - TakesDamage.apply(Vitality.Damage(model.Calculate(aggravatedProjectileData))) - } -} - -object AuraEffectBehavior { - type Target = PlanetSideServerObject with Vitality with AuraContainer - - private case class Entry(id: Long, effect: Aura, retime: Long, data: ResolvedProjectile, qualityPerTick: List[Float]) - - private case class Aggravate(id: Long, iterations: Int, leftover: Long) - - private def burning(resolution: ProjectileResolution.Value): ProjectileResolution.Value = { - resolution match { - case ProjectileResolution.AggravatedDirect => ProjectileResolution.AggravatedDirectBurn - case ProjectileResolution.AggravatedSplash => ProjectileResolution.AggravatedSplashBurn - case _ => resolution - } - } - - private def basicDamageType(resolution: ProjectileResolution.Value): DamageType.Value = { - resolution match { - case ProjectileResolution.AggravatedDirect | ProjectileResolution.AggravatedDirectBurn => - DamageType.Direct - case ProjectileResolution.AggravatedSplash | ProjectileResolution.AggravatedSplashBurn => - DamageType.Splash - case _ => - DamageType.None - } - } -} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aura/Aura.scala b/common/src/main/scala/net/psforever/objects/serverobject/aura/Aura.scala new file mode 100644 index 00000000..f78867e0 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/aura/Aura.scala @@ -0,0 +1,33 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.serverobject.aura + +/** + * An effect that can be emitted by a target game object entity. + */ +sealed class Aura() + +/** + * Visual effects emitted by a target, usually a `Player` entity. + * Most often paired with aggravated damage. + * Unrelated to the effects emitted by obtaining and transporting + * the lattice logic unit, or a facility module, or the rabbit ball. + */ +object Aura { + /** Since `None` is an actual effect, the "no effect" default is repurposed as "Nothing". */ + final case object Nothing extends Aura + + /** Conferred by the `aphelion_starfire_projectile`. */ + final case object None extends Aura + + /** A green emission. */ + final case object Plasma extends Aura + + /** A purple emission. */ + final case object Comet extends Aura + + /** A white and yellow starburst emission. */ + final case object Napalm extends Aura + + /** A red and orange emission. */ + final case object Fire extends Aura +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraContainer.scala b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraContainer.scala similarity index 54% rename from common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraContainer.scala rename to common/src/main/scala/net/psforever/objects/serverobject/aura/AuraContainer.scala index e57f878b..78d42069 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraContainer.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraContainer.scala @@ -1,8 +1,11 @@ -// Copyright (c) 2020 PSForever -package net.psforever.objects.serverobject.aggravated +package net.psforever.objects.serverobject.aura -import net.psforever.objects.serverobject.aggravated.{Aura => AuraEffect} +import net.psforever.objects.serverobject.aura.{Aura => AuraEffect} +/** + * An entity that can display specific special effects that decorate its model. + * These animations confer information about the nature of some status that is affecting the target entity. + */ trait AuraContainer { private var aura : Set[AuraEffect] = Set.empty[AuraEffect] diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala new file mode 100644 index 00000000..5c528dc8 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala @@ -0,0 +1,153 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.serverobject.aura + +import akka.actor.{Actor, Cancellable} +import net.psforever.objects.serverobject.PlanetSideServerObject + +import scala.collection.mutable +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration._ + +trait AuraEffectBehavior { + _ : Actor => + private var activeEffectIndex: Long = 0 + private val effectToEntryId: mutable.HashMap[Aura, List[Long]] = + mutable.HashMap.empty[Aura, List[Long]] + private val effectIdToTimer: mutable.LongMap[Cancellable] = + mutable.LongMap.empty[Cancellable] + + def AuraTargetObject: AuraEffectBehavior.Target + + val auraBehavior: Receive = { + case AuraEffectBehavior.StartEffect(effect, duration) => + StartAuraEffect(effect, duration) + + case AuraEffectBehavior.EndEffect(Some(id), None) => + EndAuraEffect(id) + + case AuraEffectBehavior.EndEffect(None, Some(effect)) => + EndAuraEffect(effect) + + case AuraEffectBehavior.EndAllEffects() => + EndAllEffectsAndUpdate() + } + + final def GetUnusedEffectId: Long = { + val id = activeEffectIndex + activeEffectIndex += 1 + id + } + + def StartAuraEffect(effect: Aura, duration: Long): Long = { + StartAuraEffect(GetUnusedEffectId, effect, duration) + } + + def StartAuraEffect(id: Long, effect: Aura, duration: Long): Long = { + //pair aura effect with id + effectToEntryId.get(effect) match { + case None | Some(Nil) => effectToEntryId += effect -> List(id) + case Some(list) => effectToEntryId -> (list :+ id) + } + //pair id with timer + effectIdToTimer += id -> context.system.scheduler.scheduleOnce(duration milliseconds, self, AuraEffectBehavior.EndEffect(id)) + //update visuals + UpdateAuraEffect(AuraTargetObject) + id + } + + def EndAuraEffect(id: Long): Unit = { + EndActiveEffect(id) match { + case Aura.Nothing => ; + case effect => + CancelEffectTimer(id) + val obj = AuraTargetObject + obj.RemoveEffectFromAura(effect) + UpdateAuraEffect(obj) + } + } + + def EndActiveEffect(id: Long): Aura = { + effectToEntryId.find { case (_, ids) => ids.contains(id) } match { + case Some((effect, ids)) if ids.size == 1 => + effectToEntryId.remove(effect) + effect + case Some((effect, ids)) => + effectToEntryId += effect -> ids.filterNot(_ == id) + Aura.Nothing + case None => + Aura.Nothing + } + } + + def CancelEffectTimer(id: Long) : Unit = { + effectIdToTimer.remove(id) match { + case Some(timer) => timer.cancel + case _ => ; + } + } + + def EndAuraEffect(effect: Aura): Unit = { + effectToEntryId.remove(effect) match { + case Some(idList) => + idList.foreach { id => + val obj = AuraTargetObject + CancelEffectTimer(id) + obj.RemoveEffectFromAura(effect) + UpdateAuraEffect(obj) + } + case _ => ; + } + } + + def EndAllEffects() : Unit = { + effectIdToTimer.values.foreach { _.cancel } + effectIdToTimer.clear + effectToEntryId.clear + val obj = AuraTargetObject + obj.Aura.foreach { obj.RemoveEffectFromAura } + } + + def EndAllEffectsAndUpdate() : Unit = { + EndAllEffects() + UpdateAuraEffect(AuraTargetObject) + } + + def UpdateAuraEffect(target: AuraEffectBehavior.Target) : Unit = { + import services.avatar.{AvatarAction, AvatarServiceMessage} + val zone = target.Zone + val value = target.Aura.foldLeft(0)(_ + AuraEffectBehavior.effectToAttributeValue(_)) + zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(target.GUID, 54, value)) + } + + def TestForEffect(id: Long): Aura = { + effectToEntryId.find { case (_, ids) => ids.contains(id) } match { + case Some((effect, _)) => effect + case _ => Aura.Nothing + } + } +} + +object AuraEffectBehavior { + type Target = PlanetSideServerObject with AuraContainer + + final case class StartEffect(effect: Aura, duration: Long) + + final case class EndEffect(id: Option[Long], aura: Option[Aura]) + + object EndEffect { + def apply(id: Long): EndEffect = EndEffect(Some(id), None) + + def apply(aura: Aura): EndEffect = EndEffect(None, Some(aura)) + } + + final case class EndAllEffects() + + private def effectToAttributeValue(effect: Aura): Int = effect match { + case Aura.None => 0 + case Aura.Plasma => 1 + case Aura.Comet => 2 + case Aura.Napalm => 4 + case Aura.Fire => 8 + case _ => Int.MinValue + } +} diff --git a/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala b/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala deleted file mode 100644 index 7e460c58..00000000 --- a/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) 2017 PSForever -//package net.psforever.objects.vital -// -//import net.psforever.objects.vital.StandardAmenityDamage.None -//import net.psforever.objects.vital.StandardDeployableDamage.None -//import net.psforever.objects.vital.damage._ -//import net.psforever.objects.vital.damage.DamageCalculations._ -//import net.psforever.objects.vital.projectile.ProjectileCalculations -// -///** -// * A protected super class for calculating "no damage." -// * Used for `NoDamage` but also for the base of `*LashDamage` calculation objects -// * to maintain the polymorphic identity of `DamageCalculations`. -// */ -//protected class NoDamageBase -// extends DamageCalculations( -// DamageCalculations.NoDamage, -// DamageWithModifiers(NoDamageAgainst), -// TooFar -// ) -// -//object NoDamage extends NoDamageBase -// -//object InfantryHitDamage -// extends DamageCalculations( -// DirectHitDamageWithDegrade, -// DamageWithModifiers(DamageAgainstExoSuit), -// DistanceBetweenTargetandSource -// ) -// -//object MaxHitDamage -// extends DamageCalculations( -// DirectHitDamageWithDegrade, -// DamageWithModifiers(DamageAgainstMaxSuit), -// DistanceBetweenTargetandSource -// ) -// -//object VehicleHitDamage -// extends DamageCalculations( -// DirectHitDamageWithDegrade, -// DamageWithModifiers(DamageAgainstVehicle), -// DistanceBetweenTargetandSource -// ) -// -//object AircraftHitDamage -// extends DamageCalculations( -// DirectHitDamageWithDegrade, -// DamageWithModifiers(DamageAgainstAircraft), -// DistanceBetweenTargetandSource -// ) -// -//object InfantrySplashDamage -// extends DamageCalculations( -// SplashDamageWithRadialDegrade, -// DamageWithModifiers(DamageAgainstExoSuit), -// DistanceFromExplosionToTarget -// ) -// -//object MaxSplashDamage -// extends DamageCalculations( -// SplashDamageWithRadialDegrade, -// DamageWithModifiers(DamageAgainstMaxSuit), -// DistanceFromExplosionToTarget -// ) -// -//object VehicleSplashDamage -// extends DamageCalculations( -// SplashDamageWithRadialDegrade, -// DamageWithModifiers(DamageAgainstVehicle), -// DistanceFromExplosionToTarget -// ) -// -//object AircraftSplashDamage -// extends DamageCalculations( -// SplashDamageWithRadialDegrade, -// DamageWithModifiers(DamageAgainstAircraft), -// DistanceFromExplosionToTarget -// ) -// -//object InfantrySplashDamageDirect -// extends DamageCalculations( -// SplashDamageWithRadialDegrade, -// DamageWithModifiers(DamageAgainstAircraft), -// NoDistance -// ) -// -//object InfantryLashDamage -// extends DamageCalculations( -// LashDamage, -// DamageWithModifiers(DamageAgainstExoSuit), -// DistanceBetweenTargetandSource -// ) -// -//object MaxLashDamage -// extends DamageCalculations( -// LashDamage, -// DamageWithModifiers(DamageAgainstMaxSuit), -// DistanceBetweenTargetandSource -// ) -// -//object VehicleLashDamage -// extends DamageCalculations( -// LashDamage, -// DamageWithModifiers(DamageAgainstVehicle), -// DistanceBetweenTargetandSource -// ) -// -//object AircraftLashDamage -// extends DamageCalculations( -// LashDamage, -// DamageWithModifiers(DamageAgainstAircraft), -// DistanceBetweenTargetandSource -// ) -// -//object AmenityHitDamage -// extends DamageCalculations( -// DirectHitDamageWithDegrade, -// DamageWithModifiers(DamageAgainstVehicle), -// DistanceBetweenTargetandSource -// ) -// -//object AmenitySplashDamage -// extends DamageCalculations( -// SplashDamageWithRadialDegrade, -// DamageWithModifiers(DamageAgainstVehicle), -// DistanceFromExplosionToTarget -// ) -// -//object NoDamageSelection extends DamageSelection { -// def Direct = None -// def Splash = None -// def Lash = None -// def Aggravated : ProjectileCalculations.Form = None -//} -// -//object StandardInfantryDamage extends DamageSelection { -// def Direct : ProjectileCalculations.Form = InfantryHitDamage.Calculate -// def Splash : ProjectileCalculations.Form = InfantrySplashDamage.Calculate -// def Lash : ProjectileCalculations.Form = InfantryLashDamage.Calculate -// def Aggravated : ProjectileCalculations.Form = InfantrySplashDamage.Calculate -//} -// -//object StandardMaxDamage extends DamageSelection { -// def Direct : ProjectileCalculations.Form = MaxHitDamage.Calculate -// def Splash : ProjectileCalculations.Form = MaxSplashDamage.Calculate -// def Lash : ProjectileCalculations.Form = MaxLashDamage.Calculate -// def Aggravated : ProjectileCalculations.Form = None -//} -// -//object StandardVehicleDamage extends DamageSelection { -// def Direct : ProjectileCalculations.Form = VehicleHitDamage.Calculate -// def Splash : ProjectileCalculations.Form = VehicleSplashDamage.Calculate -// def Lash : ProjectileCalculations.Form = VehicleLashDamage.Calculate -// def Aggravated : ProjectileCalculations.Form = None -//} -// -//object StandardAircraftDamage extends DamageSelection { -// def Direct : ProjectileCalculations.Form = AircraftHitDamage.Calculate -// def Splash : ProjectileCalculations.Form = AircraftSplashDamage.Calculate -// def Lash : ProjectileCalculations.Form = AircraftLashDamage.Calculate -// def Aggravated : ProjectileCalculations.Form = None -//} -// -//object StandardDeployableDamage extends DamageSelection { -// def Direct : ProjectileCalculations.Form = VehicleHitDamage.Calculate -// def Splash : ProjectileCalculations.Form = VehicleSplashDamage.Calculate -// def Lash : ProjectileCalculations.Form = NoDamage.Calculate -// def Aggravated : ProjectileCalculations.Form = None -//} -// -//object StandardAmenityDamage extends DamageSelection { -// def Direct : ProjectileCalculations.Form = AmenityHitDamage.Calculate -// def Splash : ProjectileCalculations.Form = AmenitySplashDamage.Calculate -// def Lash : ProjectileCalculations.Form = NoDamage.Calculate -// def Aggravated : ProjectileCalculations.Form = None -//} diff --git a/common/src/main/scala/net/psforever/objects/vital/damage/DamageSelection.scala b/common/src/main/scala/net/psforever/objects/vital/damage/DamageSelection.scala deleted file mode 100644 index 8b15c500..00000000 --- a/common/src/main/scala/net/psforever/objects/vital/damage/DamageSelection.scala +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2017 PSForever -//package net.psforever.objects.vital.damage -// -//import net.psforever.objects.ballistics.ResolvedProjectile -//import net.psforever.objects.vital.{DamageType, NoDamage} -//import net.psforever.objects.vital.projectile.ProjectileCalculations -// -///** -// * Maintain information about three primary forms of damage calculation -// * and a means to test which calculation is valid in a given situation. -// */ -//trait DamageSelection { -// final def None: ProjectileCalculations.Form = NoDamage -// -// def Direct : ProjectileCalculations.Form -// def Splash : ProjectileCalculations.Form -// def Lash : ProjectileCalculations.Form -// def Aggravated : ProjectileCalculations.Form -// -// def apply(data : ResolvedProjectile) : ProjectileCalculations.Form = data.projectile.profile.ProjectileDamageType match { -// case DamageType.Direct => Direct -// case DamageType.Splash => Splash -// case DamageType.Lash => Lash -// case DamageType.Aggravated => Aggravated -// case _ => None -// } -// -// def apply(res : DamageType.Value) : ProjectileCalculations.Form = res match { -// case DamageType.Direct => Direct -// case DamageType.Splash => Splash -// case DamageType.Lash => Lash -// case DamageType.Aggravated => Aggravated -// case _ => None -// } -//} diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index e9295cb1..af02396e 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -8,7 +8,7 @@ import net.psforever.objects.definition._ import net.psforever.objects.definition.converter._ import net.psforever.objects.equipment._ import net.psforever.objects.inventory.InventoryTile -import net.psforever.objects.serverobject.aggravated.Aura +import net.psforever.objects.serverobject.aura.Aura import net.psforever.objects.serverobject.doors.DoorDefinition import net.psforever.objects.serverobject.generator.GeneratorDefinition import net.psforever.objects.serverobject.implantmech.ImplantTerminalMechDefinition diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala index 23b329e1..d7585f01 100644 --- a/src/main/scala/net/psforever/objects/Player.scala +++ b/src/main/scala/net/psforever/objects/Player.scala @@ -14,7 +14,7 @@ import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.affinity.FactionAffinity -import net.psforever.objects.serverobject.aggravated.AuraContainer +import net.psforever.objects.serverobject.aura.AuraContainer import net.psforever.objects.vital.resistance.ResistanceProfile import net.psforever.objects.vital.{DamageResistanceModel, Vitality} import net.psforever.objects.zones.ZoneAware diff --git a/src/main/scala/net/psforever/objects/Vehicle.scala b/src/main/scala/net/psforever/objects/Vehicle.scala index b927791f..b1a47202 100644 --- a/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/src/main/scala/net/psforever/objects/Vehicle.scala @@ -7,7 +7,7 @@ import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.affinity.FactionAffinity -import net.psforever.objects.serverobject.aggravated.AuraContainer +import net.psforever.objects.serverobject.aura.AuraContainer import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.structures.AmenityOwner diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index 99b02a39..ef7b8225 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -8,7 +8,8 @@ import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile} import net.psforever.objects.equipment._ import net.psforever.objects.inventory.{GridInventory, InventoryItem} import net.psforever.objects.loadouts.Loadout -import net.psforever.objects.serverobject.aggravated.AuraEffectBehavior +import net.psforever.objects.serverobject.aggravated.AggravatedBehavior +import net.psforever.objects.serverobject.aura.AuraEffectBehavior import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior} import net.psforever.objects.vital.{PlayerSuicide, Vitality} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} @@ -32,6 +33,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm with JammableBehavior with Damageable with ContainableBehavior + with AggravatedBehavior with AuraEffectBehavior { def JammableObject = player @@ -39,6 +41,8 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm def ContainerObject = player + def AggravatedObject = player + def AuraTargetObject = player private[this] val log = org.log4s.getLogger(player.Name) @@ -58,11 +62,13 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm lockerControlAgent ! akka.actor.PoisonPill player.avatar.locker.Actor = Default.Actor EndAllEffects() + EndAllAggravation() } def receive: Receive = jammableBehavior .orElse(takesDamage) + .orElse(aggravatedBehavior) .orElse(auraBehavior) .orElse(containerBehavior) .orElse { @@ -554,7 +560,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm if (Damageable.CanJammer(target, cause)) { TryJammerEffectActivate(target, cause) } - TryAggravationEffect(cause) + TryAggravationEffect(cause) match { + case Some(aggravation) => + StartAuraEffect(aggravation.effect_type, aggravation.timing.duration) + case _ => ; + } } else { DestructionAwareness(target, Some(cause)) } @@ -609,6 +619,8 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm target.Die //aura effects cancel EndAllEffects() + //aggravation cancel + EndAllAggravation() //unjam CancelJammeredSound(target) CancelJammeredStatus(target) diff --git a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala index c92689bc..5f6dee3d 100644 --- a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -10,7 +10,8 @@ import net.psforever.objects.inventory.{GridInventory, InventoryItem} import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} -import net.psforever.objects.serverobject.aggravated.AuraEffectBehavior +import net.psforever.objects.serverobject.aggravated.AggravatedBehavior +import net.psforever.objects.serverobject.aura.AuraEffectBehavior import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior} import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.serverobject.damage.DamageableVehicle @@ -54,11 +55,12 @@ class VehicleControl(vehicle: Vehicle) with JammableMountedWeapons with ContainableBehavior with AntTransferBehavior + with AggravatedBehavior with AuraEffectBehavior { //make control actors belonging to utilities when making control actor belonging to vehicle vehicle.Utilities.foreach({ case (_, util) => util.Setup }) - + def MountableObject = vehicle def CargoObject = vehicle @@ -79,6 +81,8 @@ class VehicleControl(vehicle: Vehicle) def AuraTargetObject = vehicle + def AggravatedObject = vehicle + if(vehicle.Definition == GlobalDefinitions.ant) { findChargeTargetFunc = Vehicles.FindANTChargingSource findDischargeTargetFunc = Vehicles.FindANTDischargingTarget @@ -101,6 +105,7 @@ class VehicleControl(vehicle: Vehicle) util().Actor = Default.Actor } EndAllEffects() + EndAllAggravation() } def Enabled: Receive = @@ -109,6 +114,7 @@ class VehicleControl(vehicle: Vehicle) .orElse(cargoBehavior) .orElse(jammableBehavior) .orElse(takesDamage) + .orElse(aggravatedBehavior) .orElse(auraBehavior) .orElse(canBeRepairedByNanoDispenser) .orElse(containerBehavior) @@ -592,7 +598,11 @@ class VehicleControl(vehicle: Vehicle) cause: ResolvedProjectile, amount: Int ): Unit = { - TryAggravationEffect(cause) + TryAggravationEffect(cause) match { + case Some(aggravation) => + StartAuraEffect(aggravation.effect_type, aggravation.timing.duration) + case _ => ; + } super.DamageAwareness(target, cause, amount) } @@ -602,6 +612,8 @@ class VehicleControl(vehicle: Vehicle) ): Unit = { //aura effects cancel EndAllEffects() + //aggravation cancel + EndAllAggravation() super.DestructionAwareness(target, cause) } } diff --git a/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index b77e183b..a3a7256a 100644 --- a/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -255,7 +255,7 @@ object GamePacketOpcode extends Enumeration { // 0x68 case 0x68 => game.DroppodFreefallingMessage.decode case 0x69 => game.AvatarFirstTimeEventMessage.decode - case 0x6a => noDecoder(AggravatedDamageMessage) + case 0x6a => game.AggravatedDamageMessage.decode case 0x6b => game.TriggerSoundMessage.decode case 0x6c => game.LootItemMessage.decode case 0x6d => game.VehicleSubStateMessage.decode diff --git a/src/test/scala/objects/DamageModelTests.scala b/src/test/scala/objects/DamageModelTests.scala index 7e6935cd..8da2e8fc 100644 --- a/src/test/scala/objects/DamageModelTests.scala +++ b/src/test/scala/objects/DamageModelTests.scala @@ -451,8 +451,8 @@ class DamageModelTests extends Specification { ) val func: Any => ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile) func(tplayer) - tplayer.Health mustEqual 54 - tplayer.Armor mustEqual 46 + tplayer.Health mustEqual 65 + tplayer.Armor mustEqual 35 } "resolve infantry targets in a specific way" in { From fc89355acf247c6c225d3e1cc48696a2faefe96d Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 18 Aug 2020 13:32:43 -0400 Subject: [PATCH 11/19] moved aggravation damage into damage implementations of turrets and vehicles, rather than directly into ther immediate control agencies; revamp projectile quality modifiers; comet now does initial damage and one less tick of aggravation; the implementation progression of damageable entities is different now --- .../ballistics/ProjectileQuality.scala | 36 +++ .../aura/AuraEffectBehavior.scala | 16 +- .../AggravatedBehavior.scala | 43 ++-- .../actors/session/SessionActor.scala | 15 +- .../objects/ExplosiveDeployable.scala | 28 ++- .../psforever/objects/GlobalDefinitions.scala | 7 +- .../objects/ShieldGeneratorDeployable.scala | 15 +- .../psforever/objects/TurretDeployable.scala | 5 + .../objects/avatar/PlayerControl.scala | 209 +++++++++++------- .../objects/ballistics/Projectile.scala | 10 +- .../net/psforever/objects/ce/Deployable.scala | 2 +- .../serverobject/damage/Damageable.scala | 34 ++- .../damage/DamageableEntity.scala | 53 ++--- .../damage/DamageableMountable.scala | 14 +- .../damage/DamageableVehicle.scala | 205 ++++++++++------- .../damage/DamageableWeaponTurret.scala | 81 ++++++- .../generator/GeneratorControl.scala | 8 +- .../ImplantTerminalMechControl.scala | 8 +- .../turret/FacilityTurretControl.scala | 5 + .../objects/vehicles/VehicleControl.scala | 41 +--- .../vital/damage/DamageModifiers.scala | 34 ++- .../game/DamageWithPositionMessage.scala | 16 +- 22 files changed, 570 insertions(+), 315 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/ballistics/ProjectileQuality.scala rename common/src/main/scala/net/psforever/objects/serverobject/{aggravated => damage}/AggravatedBehavior.scala (82%) diff --git a/common/src/main/scala/net/psforever/objects/ballistics/ProjectileQuality.scala b/common/src/main/scala/net/psforever/objects/ballistics/ProjectileQuality.scala new file mode 100644 index 00000000..8fe9d38c --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/ballistics/ProjectileQuality.scala @@ -0,0 +1,36 @@ +//Copyright (c) 2020 PSForever +package net.psforever.objects.ballistics + +/** + * Projectile quality is an external aspect of projectiles + * that is not dependent on hard-coded definitions of the entities + * used to compose the projectile such as the knowlegde of the emitting `Tool` (weapon). + * A flag or a damage modifier, depending on use. + * To the extent that it can be used as a numeric modifier, + * insists on defining a numeric modifier component rather to what it is trying to express. + * That numeric modifier does not have to be used for anything. + */ +sealed trait ProjectileQuality { + def mod: Float +} + +/** + * Implement the numeric modifier with as one. + */ +sealed trait SameAsQuality extends ProjectileQuality { + def mod: Float = 1f +} + +object ProjectileQuality { + /** Standard projectile quality. More of a flag than a modifier. */ + case object Normal extends SameAsQuality + + /** Quality that flags the first stage of aggravation (setup). */ + case object AggravatesTarget extends SameAsQuality + + /** The complete lack of quality. Even the numeric modifier is zeroed. */ + case object Zeroed extends ProjectileQuality { def mod = 0f } + + /** Assign a custom numeric qualifier value, usually to be applied to damage calculations. */ + case class Modified(mod: Float) extends ProjectileQuality +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala index 5c528dc8..6dc233a5 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala @@ -38,8 +38,18 @@ trait AuraEffectBehavior { id } - def StartAuraEffect(effect: Aura, duration: Long): Long = { - StartAuraEffect(GetUnusedEffectId, effect, duration) + def StartAuraEffect(effect: Aura, duration: Long): Option[Long] = { + val obj = AuraTargetObject + val auraEffects = obj.Aura + if (obj.Aura.contains(effect)) { + effectToEntryId.getOrElse(effect, List[Long](AuraEffectBehavior.InvalidEffectId)).headOption //grab an available active effect id + } + else if(obj.AddEffectToAura(effect).diff(auraEffects).contains(effect)) { + Some(StartAuraEffect(GetUnusedEffectId, effect, duration)) + } + else { + None + } } def StartAuraEffect(id: Long, effect: Aura, duration: Long): Long = { @@ -130,6 +140,8 @@ trait AuraEffectBehavior { object AuraEffectBehavior { type Target = PlanetSideServerObject with AuraContainer + final val InvalidEffectId = -1 + final case class StartEffect(effect: Aura, duration: Long) final case class EndEffect(id: Option[Long], aura: Option[Aura]) diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AggravatedBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala similarity index 82% rename from common/src/main/scala/net/psforever/objects/serverobject/aggravated/AggravatedBehavior.scala rename to common/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala index 0e1feff3..e2a477e9 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AggravatedBehavior.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala @@ -1,10 +1,9 @@ // Copyright (c) 2020 PSForever -package net.psforever.objects.serverobject.aggravated +package net.psforever.objects.serverobject.damage import akka.actor.{Actor, Cancellable} import net.psforever.objects.ballistics._ -import net.psforever.objects.serverobject.aura.{Aura, AuraEffectBehavior} -import net.psforever.objects.serverobject.damage.Damageable +import net.psforever.objects.serverobject.aura.Aura import net.psforever.objects.vital.{DamageType, Vitality} import scala.collection.mutable @@ -17,31 +16,33 @@ trait AggravatedBehavior { mutable.LongMap.empty[AggravatedBehavior.Entry] private val aggravationToTimer: mutable.LongMap[Cancellable] = mutable.LongMap.empty[Cancellable] + /** ongoing flag to indicate whether the target is being afflicted by any form of aggravated damage */ + private var ongoingAggravated: Boolean = false def AggravatedObject: AggravatedBehavior.Target - def TryAggravationEffect(data: ResolvedProjectile): Option[AggravatedDamage] = { - data.projectile.profile.Aggravated match { + def TryAggravationEffectActivate(data: ResolvedProjectile): Option[AggravatedDamage] = { + val projectile = data.projectile + projectile.profile.Aggravated match { case Some(damage) - if data.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) && + if projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) && damage.effect_type != Aura.Nothing && - damage.targets.exists(validation => validation.test(AggravatedObject)) => - TryAggravationEffect(damage, data) + (projectile.quality == ProjectileQuality.AggravatesTarget || + damage.targets.exists(validation => validation.test(AggravatedObject))) => + TryAggravationEffectActivate(damage, data) case _ => None } } - private def TryAggravationEffect(aggravation: AggravatedDamage, data: ResolvedProjectile): Option[AggravatedDamage] = { + private def TryAggravationEffectActivate( + aggravation: AggravatedDamage, + data: ResolvedProjectile + ): Option[AggravatedDamage] = { val effect = aggravation.effect_type - val obj = AggravatedObject if(CheckForUniqueUnqueuedProjectile(data.projectile)) { - val auraEffects = obj.Aura - if(auraEffects.contains(effect) && aggravation.cumulative_damage_degrade) { - SetupAggravationEntry(aggravation, data) - Some(aggravation) - } - else if(obj.AddEffectToAura(effect).diff(auraEffects).contains(effect)) { + val sameEffect = entryIdToEntry.values.filter(entry => entry.effect == effect) + if(sameEffect.isEmpty || sameEffect.nonEmpty && aggravation.cumulative_damage_degrade) { SetupAggravationEntry(aggravation, data) Some(aggravation) } @@ -91,6 +92,7 @@ trait AggravatedBehavior { PairIdWithAggravationEntry(id, effect, tick, data, data.target, qualityPerTick) //pair id with timer aggravationToTimer += id -> context.system.scheduler.scheduleOnce(tick milliseconds, self, AggravatedBehavior.Aggravate(id, iterations)) + ongoingAggravated = true true case _ => false @@ -156,6 +158,7 @@ trait AggravatedBehavior { def RemoveAggravatedEntry(id: Long): Aura = { entryIdToEntry.remove(id) match { case Some(entry) => + ongoingAggravated = entryIdToEntry.nonEmpty entry.data.projectile.profile.Aggravated.get.effect_type case _ => Aura.Nothing @@ -181,22 +184,24 @@ trait AggravatedBehavior { aggravationToTimer.clear } + def AggravatedReaction: Boolean = ongoingAggravated + private def PerformAggravation(entry: AggravatedBehavior.Entry, tick: Int = 0): Unit = { val data = entry.data val model = data.damage_model val aggravatedProjectileData = ResolvedProjectile( data.resolution, - data.projectile.quality(entry.qualityPerTick(tick)), + data.projectile.quality(ProjectileQuality.Modified(entry.qualityPerTick(tick))), data.target, model, data.hit_pos ) - TakesDamage.apply(Vitality.Damage(model.Calculate(aggravatedProjectileData))) + takesDamage.apply(Vitality.Damage(model.Calculate(aggravatedProjectileData))) } } object AggravatedBehavior { - type Target = AuraEffectBehavior.Target with Vitality + type Target = Damageable.Target private case class Entry(id: Long, effect: Aura, retime: Long, data: ResolvedProjectile, qualityPerTick: List[Float]) diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index 23e5d283..5d4dae49 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -7750,7 +7750,20 @@ class SessionActor extends Actor with MDCContextAware { None } else { projectile.Resolve() - Some(ResolvedProjectile(resolution, projectile, SourceEntry(target), target.DamageModel, pos)) + val outProjectile = if(projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated)) { + val quality = projectile.profile.Aggravated match { + case Some(aggravation) + if aggravation.targets.exists(validation => validation.test(target)) => + ProjectileQuality.AggravatesTarget + case _ => + ProjectileQuality.Normal + } + projectile.quality(quality) + } + else { + projectile + } + Some(ResolvedProjectile(resolution, outProjectile, SourceEntry(target), target.DamageModel, pos)) } } diff --git a/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala b/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala index 66c261ab..252d3e0c 100644 --- a/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala +++ b/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala @@ -9,7 +9,9 @@ import net.psforever.objects.definition.converter.SmallDeployableConverter import net.psforever.objects.equipment.JammableUnit import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.damage.Damageable -import net.psforever.objects.vital.{StandardResolutions, Vitality} +import net.psforever.objects.serverobject.damage.Damageable.Target +import net.psforever.objects.vital.resolution.ResolutionCalculations.Output +import net.psforever.objects.vital.StandardResolutions import net.psforever.objects.zones.Zone import net.psforever.types.{PlanetSideGUID, Vector3} import net.psforever.services.Service @@ -63,18 +65,20 @@ class ExplosiveDeployableControl(mine: ExplosiveDeployable) extends Actor with D case _ => ; } - protected def TakesDamage: Receive = { - case Vitality.Damage(applyDamageTo) => - if (mine.CanDamage) { - val originalHealth = mine.Health - val cause = applyDamageTo(mine) - val damage = originalHealth - mine.Health - if (Damageable.CanDamageOrJammer(mine, damage, cause)) { - ExplosiveDeployableControl.DamageResolution(mine, cause, damage) - } else { - mine.Health = originalHealth - } + override protected def PerformDamage( + target: Target, + applyDamageTo: Output + ): Unit = { + if (mine.CanDamage) { + val originalHealth = mine.Health + val cause = applyDamageTo(mine) + val damage = originalHealth - mine.Health + if (Damageable.CanDamageOrJammer(mine, damage, cause)) { + ExplosiveDeployableControl.DamageResolution(mine, cause, damage) + } else { + mine.Health = originalHealth } + } } } diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index af02396e..8d20e52d 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2452,13 +2452,14 @@ object GlobalDefinitions { comet_projectile.DamageRadius = 1.0f comet_projectile.ProjectileDamageType = DamageType.Aggravated comet_projectile.Aggravated = AggravatedDamage( - AggravatedInfo(DamageType.Direct, 0.2f, 500), + AggravatedInfo(DamageType.Direct, 0.25f, 500), //originally, .2 Aura.Comet, - AggravatedTiming(2000, 4), + AggravatedTiming(2000, 3), 10f, List( TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player), - TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.Vehicle) + TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.Vehicle), + TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret) ) ) comet_projectile.InitialVelocity = 80 diff --git a/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala b/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala index feb1f129..75a375ec 100644 --- a/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala +++ b/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala @@ -44,7 +44,6 @@ class ShieldGeneratorControl(gen: ShieldGeneratorDeployable) def JammableObject = gen def DamageableObject = gen def RepairableObject = gen - private var handleDamageToShields: Boolean = false def receive: Receive = jammableBehavior @@ -90,18 +89,20 @@ class ShieldGeneratorControl(gen: ShieldGeneratorDeployable) target, s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields" ) - handleDamageToShields = damageToShields > 0 - HandleDamage(target, cause, damageToHealth) + HandleDamage(target, cause, (damageToHealth, damageToShields)) } else { gen.Health = originalHealth gen.Shields = originalShields } } - override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Unit = { - super.DamageAwareness(target, cause, amount) - ShieldGeneratorControl.DamageAwareness(gen, cause, handleDamageToShields) - handleDamageToShields = false + override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Any): Unit = { + val (damageToHealth, damageToShields) = amount match { + case (a: Int, b: Int) => (a, b) + case _ => (0, 0) + } + super.DamageAwareness(target, cause, damageToHealth) + ShieldGeneratorControl.DamageAwareness(gen, cause, damageToShields > 0) } override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = { diff --git a/src/main/scala/net/psforever/objects/TurretDeployable.scala b/src/main/scala/net/psforever/objects/TurretDeployable.scala index 2c3b5e8d..fd9f8569 100644 --- a/src/main/scala/net/psforever/objects/TurretDeployable.scala +++ b/src/main/scala/net/psforever/objects/TurretDeployable.scala @@ -75,6 +75,11 @@ class TurretControl(turret: TurretDeployable) def DamageableObject = turret def RepairableObject = turret + override def postStop(): Unit = { + super.postStop() + damageableWeaponTurretPostStop() + } + def receive: Receive = checkBehavior .orElse(jammableBehavior) diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index ef7b8225..e9a3b956 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -8,16 +8,17 @@ import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile} import net.psforever.objects.equipment._ import net.psforever.objects.inventory.{GridInventory, InventoryItem} import net.psforever.objects.loadouts.Loadout -import net.psforever.objects.serverobject.aggravated.AggravatedBehavior import net.psforever.objects.serverobject.aura.AuraEffectBehavior import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior} -import net.psforever.objects.vital.{PlayerSuicide, Vitality} +import net.psforever.objects.serverobject.damage.Damageable.Target +import net.psforever.objects.vital.PlayerSuicide import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} -import net.psforever.objects.serverobject.damage.Damageable +import net.psforever.objects.serverobject.damage.{AggravatedBehavior, Damageable} import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.repair.Repairable import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.vital._ +import net.psforever.objects.vital.resolution.ResolutionCalculations.Output import net.psforever.objects.zones.Zone import net.psforever.packet.game._ import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent @@ -491,29 +492,31 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm case _ => ; } - protected def TakesDamage: Receive = { - case Vitality.Damage(applyDamageTo) => - if (player.isAlive && !player.spectator) { - val originalHealth = player.Health - val originalArmor = player.Armor - val originalStamina = player.avatar.stamina - val originalCapacitor = player.Capacitor.toInt - val cause = applyDamageTo(player) - val health = player.Health - val armor = player.Armor - val stamina = player.avatar.stamina - val capacitor = player.Capacitor.toInt - val damageToHealth = originalHealth - health - val damageToArmor = originalArmor - armor - val damageToStamina = originalStamina - stamina - val damageToCapacitor = originalCapacitor - capacitor - HandleDamage(player, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor) - if (damageToHealth > 0 || damageToArmor > 0 || damageToStamina > 0 || damageToCapacitor > 0) { - damageLog.info( - s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalStamina/$originalCapacitor, AFTER=$health/$armor/$stamina/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToStamina/$damageToCapacitor" - ) - } + override protected def PerformDamage( + target: Target, + applyDamageTo: Output + ): Unit = { + if (player.isAlive && !player.spectator) { + val originalHealth = player.Health + val originalArmor = player.Armor + val originalStamina = player.avatar.stamina + val originalCapacitor = player.Capacitor.toInt + val cause = applyDamageTo(player) + val health = player.Health + val armor = player.Armor + val stamina = player.avatar.stamina + val capacitor = player.Capacitor.toInt + val damageToHealth = originalHealth - health + val damageToArmor = originalArmor - armor + val damageToStamina = originalStamina - stamina + val damageToCapacitor = originalCapacitor - capacitor + HandleDamage(player, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor) + if (damageToHealth > 0 || damageToArmor > 0 || damageToStamina > 0 || damageToCapacitor > 0) { + damageLog.info( + s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalStamina/$originalCapacitor, AFTER=$health/$armor/$stamina/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToStamina/$damageToCapacitor" + ) } + } } /** @@ -521,76 +524,110 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm * @param target na */ def HandleDamage( - target: Player, - cause: ResolvedProjectile, - damageToHealth: Int, - damageToArmor: Int, - damageToStamina: Int, - damageToCapacitor: Int - ): Unit = { - val targetGUID = target.GUID - val zone = target.Zone - val zoneId = zone.id - val events = zone.AvatarEvents - val health = target.Health + target: Player, + cause: ResolvedProjectile, + damageToHealth: Int, + damageToArmor: Int, + damageToStamina: Int, + damageToCapacitor: Int + ): Unit = { + //always do armor update if (damageToArmor > 0) { - events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor)) + val zone = target.Zone + zone.AvatarEvents ! AvatarServiceMessage( + zone.id, + AvatarAction.PlanetsideAttributeToAll(target.GUID, 4, target.Armor) + ) } - if (health > 0) { - if (damageToCapacitor > 0) { - events ! AvatarServiceMessage( - target.Name, - AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong) - ) - } - if (damageToHealth > 0 || damageToStamina > 0) { - target.History(cause) - if (damageToHealth > 0) { - events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health)) - } - if (damageToStamina > 0) { - avatarActor ! AvatarActor.ConsumeStamina(damageToStamina) - } - //activity on map - zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) - //alert damage source - DamageAwareness(target, cause) - } - //special effects - if (Damageable.CanJammer(target, cause)) { - TryJammerEffectActivate(target, cause) - } - TryAggravationEffect(cause) match { - case Some(aggravation) => - StartAuraEffect(aggravation.effect_type, aggravation.timing.duration) - case _ => ; - } + //choose + if (target.Health > 0) { + DamageAwareness(target, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor) } else { DestructionAwareness(target, Some(cause)) } } - /** - * na - * @param target na - * @param cause na - */ - def DamageAwareness(target: Player, cause: ResolvedProjectile): Unit = { - val zone = target.Zone - zone.AvatarEvents ! AvatarServiceMessage( - target.Name, - cause.projectile.owner match { - case pSource: PlayerSource => //player damage - val name = pSource.Name - zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match { - case Some(tplayer) => AvatarAction.HitHint(tplayer.GUID, target.GUID) - case None => - AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, pSource.Position)) + def DamageAwareness( + target: Player, + cause: ResolvedProjectile, + damageToHealth: Int, + damageToArmor: Int, + damageToStamina: Int, + damageToCapacitor: Int + ): Unit = { + val targetGUID = target.GUID + val zone = target.Zone + val zoneId = zone.id + val events = zone.AvatarEvents + val health = target.Health + var announceConfrontation = damageToArmor > 0 + //special effects + if (Damageable.CanJammer(target, cause)) { + TryJammerEffectActivate(target, cause) + } + val aggravated: Boolean = TryAggravationEffectActivate(cause) match { + case Some(aggravation) => + StartAuraEffect(aggravation.effect_type, aggravation.timing.duration) + announceConfrontation = true //useful if initial damage (to anything) is zero + //initial damage for aggravation, but never treat as "aggravated" + false + case _ => + cause.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) + } + //log historical event + target.History(cause) + //stat changes + if (damageToCapacitor > 0) { + events ! AvatarServiceMessage( + target.Name, + AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong) + ) + announceConfrontation = true + } + if (damageToStamina > 0) { + avatarActor ! AvatarActor.ConsumeStamina(damageToStamina) + announceConfrontation = true + } + if (damageToHealth > 0) { + events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health)) + announceConfrontation = true + } + val countableDamage = damageToHealth + damageToArmor + if(announceConfrontation) { + if (!aggravated) { + //activity on map + zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) + //alert to damage source + zone.AvatarEvents ! AvatarServiceMessage( + target.Name, + cause.projectile.owner match { + case pSource: PlayerSource => //player damage + val name = pSource.Name + zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match { + case Some(tplayer) => + AvatarAction.HitHint(tplayer.GUID, target.GUID) + case None => + AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, pSource.Position)) + } + case source => + AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, source.Position)) } - case source => - AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, source.Position)) + ) } - ) + else { + //general alert + zone.AvatarEvents ! AvatarServiceMessage( + target.Name, + AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, Vector3.Zero)) + ) + } + } + if (aggravated) { + events ! AvatarServiceMessage( + zoneId, + AvatarAction.SendResponse(Service.defaultPlayerGUID, AggravatedDamageMessage(targetGUID, countableDamage)) + ) + } } /** diff --git a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala index d40d9de8..a00b79e4 100644 --- a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala +++ b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala @@ -41,7 +41,7 @@ final case class Projectile( attribute_to: Int, shot_origin: Vector3, shot_angle: Vector3, - quality: Float = 1f, + quality: ProjectileQuality = ProjectileQuality.Normal, id: Long = Projectile.idGenerator.getAndIncrement(), fire_time: Long = System.nanoTime ) extends PlanetSideGameObject { @@ -67,8 +67,8 @@ final case class Projectile( * @param value the new quality * @return a new `Projectile` entity */ - def quality(value: Float): Projectile = - Projectile( + def quality(value: ProjectileQuality): Projectile = { + val projectile = Projectile( profile, tool_def, fire_mode, @@ -80,6 +80,10 @@ final case class Projectile( id, fire_time ) + if(isMiss) projectile.Miss() + else if(isResolved) projectile.Resolve() + projectile + } /** * Mark the projectile as being "encountered" or "managed" at least once. diff --git a/src/main/scala/net/psforever/objects/ce/Deployable.scala b/src/main/scala/net/psforever/objects/ce/Deployable.scala index 6448daf8..56736d35 100644 --- a/src/main/scala/net/psforever/objects/ce/Deployable.scala +++ b/src/main/scala/net/psforever/objects/ce/Deployable.scala @@ -33,7 +33,7 @@ object Deployable { def Includes(category: DeployableCategory.Value): List[DeployedItem.Value] = { (for { - (ce, cat) <- deployablesToCategories + (ce: DeployedItem.Value, cat: DeployableCategory.Value) <- deployablesToCategories if cat == category } yield ce) toList } diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala b/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala index 8fcde16c..e36b3c95 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala @@ -8,6 +8,7 @@ import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.vital.Vitality +import net.psforever.objects.vital.resolution.ResolutionCalculations /** * The base "control" `Actor` mixin for damage-handling code. @@ -24,16 +25,35 @@ trait Damageable { */ def DamageableObject: Damageable.Target - /** the official mixin hook; `orElse` onto the "control" `Actor` `receive` */ - final val takesDamage: Receive = TakesDamage + /** the official mixin hook; + * `orElse` onto the "control" `Actor` `receive`; or, + * cite the `originalTakesDamage` protocol during inheritance overrides */ + val takesDamage: Receive = { + case Vitality.Damage(damage_func) => + val obj = DamageableObject + if (obj.CanDamage) { + PerformDamage(obj, damage_func) + } + } + + /** a duplicate of the core implementation for the default mixin hook, for use in overriding */ + final val originalTakesDamage: Receive = { + case Vitality.Damage(damage_func) => + val obj = DamageableObject + if (obj.CanDamage) { + PerformDamage(obj, damage_func) + } + } /** - * Implementation of the mixin hook will be provided by a child class. - * Override this method only when directly implementing. - * @see `takesDamage` - * @see `DamageableAmenity.PerformDamage` + * Assess the vital statistics of the target, apply the damage, and determine if any of those statistics changed. + * By default, only take an interest in the change of "health". + * If implementing custom damage with no new message handling, override this method. + * @see `ResolutionCalculations.Output` + * @param target the entity to be damaged + * @param applyDamageTo the function that applies the damage to the target in a target-tailored fashion */ - protected def TakesDamage: Receive + protected def PerformDamage(target: Damageable.Target, applyDamageTo: ResolutionCalculations.Output): Unit } object Damageable { diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala index e4591747..a2db8e3c 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala @@ -1,10 +1,8 @@ //Copyright (c) 2020 PSForever package net.psforever.objects.serverobject.damage -import akka.actor.Actor.Receive import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.equipment.JammableUnit -import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.resolution.ResolutionCalculations import net.psforever.objects.zones.Zone import net.psforever.types.PlanetSideGUID @@ -42,23 +40,6 @@ trait DamageableEntity extends Damageable { DamageLog(s"${name.substring(slashPoint + 1, name.length - 1)}: $msg") } - /** - * Catch the expected damage message and apply checks to the target. - * If adding custom message handling in an future child implementation, - * override this method and call `super.TakesDamage.orElse { ... }`. - * @see `Damageable.TakesDamage` - * @see `ResolutionCalcultions.Output` - * @see `Vitality.CanDamage` - * @see `Vitality.Damage` - */ - protected def TakesDamage: Receive = { - case Vitality.Damage(damage_func) => - val obj = DamageableObject - if (obj.CanDamage) { - PerformDamage(obj, damage_func) - } - } - /** * Assess the vital statistics of the target, apply the damage, and determine if any of those statistics changed. * By default, only take an interest in the change of "health". @@ -108,7 +89,7 @@ trait DamageableEntity extends Damageable { * @param cause historical information about the damage * @param damage the amount of damage */ - protected def HandleDamage(target: Damageable.Target, cause: ResolvedProjectile, damage: Int): Unit = { + protected def HandleDamage(target: Damageable.Target, cause: ResolvedProjectile, damage: Any): Unit = { if (!target.Destroyed && target.Health <= target.Definition.DamageDestroysAt) { DestructionAwareness(target, cause) } else { @@ -122,8 +103,12 @@ trait DamageableEntity extends Damageable { * @param cause historical information about the damage * @param amount the amount of damage */ - protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Unit = { - DamageableEntity.DamageAwareness(target, cause, amount) + protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Any): Unit = { + amount match { + case value: Int => + DamageableEntity.DamageAwareness(target, cause, value) + case _ => ; + } } /** @@ -162,16 +147,22 @@ object DamageableEntity { if (Damageable.CanJammer(target, cause)) { target.Actor ! JammableUnit.Jammered(cause) } - if (amount > 0) { + if (DamageToHealth(target, cause, amount)) { + target.Zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) + } + } + + def DamageToHealth(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Boolean = { + if (amount > 0 && !target.Destroyed) { val zone = target.Zone - if (!target.Destroyed) { - val tguid = target.GUID - zone.AvatarEvents ! AvatarServiceMessage( - zone.id, - AvatarAction.PlanetsideAttributeToAll(tguid, 0, target.Health) - ) - } - zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) + zone.AvatarEvents ! AvatarServiceMessage( + zone.id, + AvatarAction.PlanetsideAttributeToAll(target.GUID, 0, target.Health) + ) + true + } + else { + false } } diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala index cc3e5a8b..abfb7922 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala @@ -26,8 +26,13 @@ object DamageableMountable { * @see `Zone.LivePlayers` * @param target the entity being damaged * @param cause historical information about the damage + * @param countableDamage the amount of damage being done, translating to the intensity of the damage indicator */ - def DamageAwareness(target: Damageable.Target with Mountable, cause: ResolvedProjectile): Unit = { + def DamageAwareness( + target: Damageable.Target with Mountable, + cause: ResolvedProjectile, + countableDamage: Int + ): Unit = { val zone = target.Zone val events = zone.AvatarEvents val occupants = target.Seats.values.collect { @@ -38,9 +43,10 @@ object DamageableMountable { case pSource: PlayerSource => //player damage val name = pSource.Name (zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match { - case Some(player) => AvatarAction.HitHint(player.GUID, player.GUID) + case Some(player) => + AvatarAction.HitHint(player.GUID, player.GUID) case None => - AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, pSource.Position)) + AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, pSource.Position)) }) match { case AvatarAction.HitHint(_, guid) => occupants.map { tplayer => (tplayer.Name, AvatarAction.HitHint(guid, tplayer.GUID)) } @@ -48,7 +54,7 @@ object DamageableMountable { occupants.map { tplayer => (tplayer.Name, msg) } } case source => //object damage - val msg = AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, source.Position)) + val msg = AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, source.Position)) occupants.map { tplayer => (tplayer.Name, msg) } }).foreach { case (channel, msg) => diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala index c3d26a3d..ceea8af0 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala @@ -1,43 +1,55 @@ //Copyright (c) 2020 PSForever package net.psforever.objects.serverobject.damage -import akka.actor.Actor.Receive +import akka.actor.Actor import net.psforever.objects.{Vehicle, Vehicles} import net.psforever.objects.ballistics.ResolvedProjectile +import net.psforever.objects.equipment.JammableUnit import net.psforever.objects.serverobject.damage.Damageable.Target +import net.psforever.objects.vital.DamageType import net.psforever.objects.vital.resolution.ResolutionCalculations import net.psforever.services.Service import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} +import net.psforever.objects.zones.Zone +import net.psforever.packet.game.DamageWithPositionMessage +import net.psforever.types.Vector3 import scala.concurrent.duration._ /** * The "control" `Actor` mixin for damage-handling code for `Vehicle` objects. */ -trait DamageableVehicle extends DamageableEntity { +trait DamageableVehicle + extends DamageableEntity + with AggravatedBehavior { + _ : Actor => - /** vehicles (may) have shields; they need to be handled */ - private var handleDamageToShields: Boolean = false + def damageableVehiclePostStop(): Unit = { + EndAllAggravation() + } /** whether or not the vehicle has been damaged directly, report that damage has occurred */ private var reportDamageToVehicle: Boolean = false def DamageableObject: Vehicle + def AggravatedObject : Vehicle = DamageableObject - override protected def TakesDamage: Receive = - super.TakesDamage.orElse { - case DamageableVehicle.Damage(cause, damage) => - //cargo vehicles inherit feedback from carrier - reportDamageToVehicle = damage > 0 - DamageAwareness(DamageableObject, cause, amount = 0) + override val takesDamage: Receive = + originalTakesDamage + .orElse(aggravatedBehavior) + .orElse { + case DamageableVehicle.Damage(cause, damage) => + //cargo vehicles inherit feedback from carrier + reportDamageToVehicle = damage > 0 + DamageAwareness(DamageableObject, cause, amount = 0) - case DamageableVehicle.Destruction(cause) => - //cargo vehicles are destroyed when carrier is destroyed - val obj = DamageableObject - obj.Health = 0 - obj.History(cause) - DestructionAwareness(obj, cause) - } + case DamageableVehicle.Destruction(cause) => + //cargo vehicles are destroyed when carrier is destroyed + val obj = DamageableObject + obj.Health = 0 + obj.History(cause) + DestructionAwareness(obj, cause) + } /** * Vehicles may have charged shields that absorb damage before the vehicle's own health is affected. @@ -62,53 +74,13 @@ trait DamageableVehicle extends DamageableEntity { target, s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields" ) - handleDamageToShields = damageToShields > 0 - HandleDamage(target, cause, damageToHealth + damageToShields) + HandleDamage(target, cause, (damageToHealth, damageToShields)) } else { obj.Health = originalHealth obj.Shields = originalShields } } - override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Int): Unit = { - val obj = DamageableObject - val handleShields = handleDamageToShields - handleDamageToShields = false - val handleReport = reportDamageToVehicle || amount > 0 - reportDamageToVehicle = false - if (Damageable.CanDamageOrJammer(target, amount, cause)) { - super.DamageAwareness(target, cause, amount) - } - if (handleReport) { - DamageableMountable.DamageAwareness(obj, cause) - } - DamageableVehicle.DamageAwareness(obj, cause, amount, handleShields) - } - - override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = { - super.DestructionAwareness(target, cause) - val obj = DamageableObject - DamageableMountable.DestructionAwareness(obj, cause) - DamageableVehicle.DestructionAwareness(obj, cause) - DamageableWeaponTurret.DestructionAwareness(obj, cause) - } -} - -object DamageableVehicle { - - /** - * Message for instructing the target's cargo vehicles about a damage source affecting their carrier. - * @param cause historical information about damage - */ - private case class Damage(cause: ResolvedProjectile, amount: Int) - - /** - * Message for instructing the target's cargo vehicles that their carrier is destroyed, - * and they should be destroyed too. - * @param cause historical information about damage - */ - private case class Destruction(cause: ResolvedProjectile) - /** * Most all vehicles and the weapons mounted to them can jam * if the projectile that strikes (near) them has jammering properties. @@ -121,25 +93,76 @@ object DamageableVehicle { * @see `VehicleServiceMessage` * @param target the entity being destroyed * @param cause historical information about the damage - * @param damage how much damage was performed - * @param damageToShields dispatch a shield strength update + * @param amount how much damage was performed */ - def DamageAwareness(target: Vehicle, cause: ResolvedProjectile, damage: Int, damageToShields: Boolean): Unit = { - //alert cargo occupants to damage source - target.CargoHolds.values.foreach(hold => { - hold.Occupant match { - case Some(cargo) => - cargo.Actor ! DamageableVehicle.Damage(cause, damage + (if (damageToShields) 1 else 0)) - case None => ; + override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Any): Unit = { + val obj = DamageableObject + val zone = target.Zone + val events = zone.VehicleEvents + val targetGUID = target.GUID + val zoneId = zone.id + val vehicleChannel = s"${obj.Actor}" + val (damageToHealth, damageToShields, totalDamage) = amount match { + case (a: Int, b: Int) => (a, b, a+b) + case _ => (0, 0, 0) + } + var announceConfrontation: Boolean = reportDamageToVehicle || totalDamage > 0 + val aggravated = TryAggravationEffectActivate(cause) match { + case Some(_) => + announceConfrontation = true + false + case _ => + cause.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) + } + reportDamageToVehicle = false + + //log historical event + target.History(cause) + //damage + if (Damageable.CanDamageOrJammer(target, totalDamage, cause)) { + //jammering + if (Damageable.CanJammer(target, cause)) { + target.Actor ! JammableUnit.Jammered(cause) } - }) - //shields - if (damageToShields) { - val zone = target.Zone - zone.VehicleEvents ! VehicleServiceMessage( - s"${target.Actor}", - VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, target.Shields) - ) + //stat changes + if (damageToShields > 0) { + events ! VehicleServiceMessage( + vehicleChannel, + VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 68, obj.Shields) + ) + announceConfrontation = true + } + if (damageToHealth > 0) { + events ! VehicleServiceMessage( + zoneId, + VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, obj.Health) + ) + announceConfrontation = true + } + } + if (announceConfrontation) { + if (aggravated) { + val msg = VehicleAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(totalDamage, Vector3.Zero)) + obj.Seats.values + .collect { case seat if seat.Occupant.nonEmpty => seat.Occupant.get.Name } + .foreach { channel => + events ! VehicleServiceMessage(channel, msg) + } + } + else { + //activity on map + zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) + //alert to damage source + DamageableMountable.DamageAwareness(obj, cause, totalDamage) + } + //alert cargo occupants to damage source + obj.CargoHolds.values.foreach(hold => { + hold.Occupant match { + case Some(cargo) => + cargo.Actor ! DamageableVehicle.Damage(cause, totalDamage) + case None => ; + } + }) } } @@ -164,10 +187,15 @@ object DamageableVehicle { * @param target the entity being destroyed * @param cause historical information about the damage */ - def DestructionAwareness(target: Vehicle, cause: ResolvedProjectile): Unit = { + override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = { + super.DestructionAwareness(target, cause) + val obj = DamageableObject + DamageableMountable.DestructionAwareness(obj, cause) val zone = target.Zone + //aggravation cancel + EndAllAggravation() //cargo vehicles die with us - target.CargoHolds.values.foreach(hold => { + obj.CargoHolds.values.foreach(hold => { hold.Occupant match { case Some(cargo) => cargo.Actor ! DamageableVehicle.Destruction(cause) @@ -175,10 +203,10 @@ object DamageableVehicle { } }) //special considerations for certain vehicles - Vehicles.BeforeUnloadVehicle(target, zone) + Vehicles.BeforeUnloadVehicle(obj, zone) //shields - if (target.Shields > 0) { - target.Shields = 0 + if (obj.Shields > 0) { + obj.Shields = 0 zone.VehicleEvents ! VehicleServiceMessage( zone.id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, 0) @@ -186,5 +214,22 @@ object DamageableVehicle { } target.Actor ! Vehicle.Deconstruct(Some(1 minute)) target.ClearHistory() + DamageableWeaponTurret.DestructionAwareness(obj, cause) } } + +object DamageableVehicle { + + /** + * Message for instructing the target's cargo vehicles about a damage source affecting their carrier. + * @param cause historical information about damage + */ + private case class Damage(cause: ResolvedProjectile, amount: Int) + + /** + * Message for instructing the target's cargo vehicles that their carrier is destroyed, + * and they should be destroyed too. + * @param cause historical information about damage + */ + private case class Destruction(cause: ResolvedProjectile) +} diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala index b7fab97a..f4ff4b2c 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala @@ -1,30 +1,97 @@ //Copyright (c) 2020 PSForever package net.psforever.objects.serverobject.damage +import akka.actor.Actor import net.psforever.objects.ballistics.ResolvedProjectile +import net.psforever.objects.equipment.JammableUnit import net.psforever.objects.serverobject.turret.{TurretUpgrade, WeaponTurret} import net.psforever.objects.vehicles.MountedWeapons +import net.psforever.objects.vital.DamageType +import net.psforever.objects.zones.Zone +import net.psforever.packet.game.DamageWithPositionMessage +import net.psforever.types.Vector3 import net.psforever.services.Service import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.vehicle.support.TurretUpgrader -import net.psforever.services.vehicle.VehicleServiceMessage +import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} /** * The "control" `Actor` mixin for damage-handling code for `WeaponTurret` objects. */ -trait DamageableWeaponTurret extends DamageableEntity { - def DamageableObject: Damageable.Target with WeaponTurret +trait DamageableWeaponTurret + extends DamageableEntity + with AggravatedBehavior { + _: Actor => - override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Unit = { - super.DamageAwareness(target, cause, amount) - if (amount > 0) { - DamageableMountable.DamageAwareness(DamageableObject, cause) + def damageableWeaponTurretPostStop(): Unit = { + EndAllAggravation() + } + + def DamageableObject: Damageable.Target with WeaponTurret + def AggravatedObject: Damageable.Target with WeaponTurret = DamageableObject + + override val takesDamage: Receive = originalTakesDamage.orElse(aggravatedBehavior) + + override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Any): Unit = { + val obj = DamageableObject + val zone = target.Zone + val events = zone.VehicleEvents + val targetGUID = target.GUID + val zoneId = zone.id + val damageToHealth = amount match { + case a: Int => a + case _ => 0 + } + var announceConfrontation: Boolean = damageToHealth > 0 + val aggravated = TryAggravationEffectActivate(cause) match { + case Some(_) => + announceConfrontation = true + false + case _ => + cause.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) + } + + //log historical event + target.History(cause) + //damage + if (Damageable.CanDamageOrJammer(target, damageToHealth, cause)) { + //jammering + if (Damageable.CanJammer(target, cause)) { + target.Actor ! JammableUnit.Jammered(cause) + } + //stat changes + //TODO some turrets have shields + if (damageToHealth > 0) { + DamageableMountable.DamageAwareness(DamageableObject, cause, damageToHealth) + events ! VehicleServiceMessage( + zoneId, + VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, obj.Health) + ) + announceConfrontation = true + } + } + if (announceConfrontation) { + if (aggravated) { + val msg = VehicleAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(damageToHealth, Vector3.Zero)) + obj.Seats.values + .collect { case seat if seat.Occupant.nonEmpty => seat.Occupant.get.Name } + .foreach { channel => + events ! VehicleServiceMessage(channel, msg) + } + } + else { + //activity on map + zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) + //alert to damage source + DamageableMountable.DamageAwareness(obj, cause, damageToHealth) + } } } override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = { super.DestructionAwareness(target, cause) val obj = DamageableObject + EndAllAggravation() DamageableWeaponTurret.DestructionAwareness(obj, cause) DamageableMountable.DestructionAwareness(obj, cause) } diff --git a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala index 799b339f..e4258dba 100644 --- a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala @@ -88,9 +88,13 @@ class GeneratorControl(gen: Generator) !imminentExplosion && super.WillAffectTarget(target, damage, cause) } - override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Int): Unit = { + override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Any): Unit = { super.DamageAwareness(target, cause, amount) - GeneratorControl.DamageAwareness(gen, cause, amount) + val damageTo = amount match { + case a: Int => a + case _ => 0 + } + GeneratorControl.DamageAwareness(gen, cause, damageTo) } override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = { diff --git a/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala b/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala index ec5b7346..2bbb5ed4 100644 --- a/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala @@ -72,9 +72,13 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech) } } - override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Int): Unit = { + override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Any): Unit = { super.DamageAwareness(target, cause, amount) - DamageableMountable.DamageAwareness(DamageableObject, cause) + val damageTo = amount match { + case a: Int => a + case _ => 0 + } + DamageableMountable.DamageAwareness(DamageableObject, cause, damageTo) } override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = { diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala index 44a85f83..a7e3436c 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala @@ -44,6 +44,11 @@ class FacilityTurretControl(turret: FacilityTurret) // Used for timing ammo recharge for vanu turrets in caves var weaponAmmoRechargeTimer = Default.Cancellable + override def postStop(): Unit = { + super.postStop() + damageableWeaponTurretPostStop() + } + def receive: Receive = checkBehavior .orElse(jammableBehavior) diff --git a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala index 5f6dee3d..355698d4 100644 --- a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -10,11 +10,8 @@ import net.psforever.objects.inventory.{GridInventory, InventoryItem} import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} -import net.psforever.objects.serverobject.aggravated.AggravatedBehavior -import net.psforever.objects.serverobject.aura.AuraEffectBehavior import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior} -import net.psforever.objects.serverobject.damage.Damageable.Target -import net.psforever.objects.serverobject.damage.DamageableVehicle +import net.psforever.objects.serverobject.damage.{AggravatedBehavior, DamageableVehicle} import net.psforever.objects.serverobject.deploy.Deployment.DeploymentObject import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior} import net.psforever.objects.serverobject.hackable.GenericHackables @@ -55,8 +52,7 @@ class VehicleControl(vehicle: Vehicle) with JammableMountedWeapons with ContainableBehavior with AntTransferBehavior - with AggravatedBehavior - with AuraEffectBehavior { + with AggravatedBehavior { //make control actors belonging to utilities when making control actor belonging to vehicle vehicle.Utilities.foreach({ case (_, util) => util.Setup }) @@ -79,10 +75,6 @@ class VehicleControl(vehicle: Vehicle) def ChargeTransferObject = vehicle - def AuraTargetObject = vehicle - - def AggravatedObject = vehicle - if(vehicle.Definition == GlobalDefinitions.ant) { findChargeTargetFunc = Vehicles.FindANTChargingSource findDischargeTargetFunc = Vehicles.FindANTDischargingTarget @@ -98,14 +90,13 @@ class VehicleControl(vehicle: Vehicle) override def postStop(): Unit = { super.postStop() + damageableVehiclePostStop() decaying = false decayTimer.cancel() vehicle.Utilities.values.foreach { util => context.stop(util().Actor) util().Actor = Default.Actor } - EndAllEffects() - EndAllAggravation() } def Enabled: Receive = @@ -114,8 +105,6 @@ class VehicleControl(vehicle: Vehicle) .orElse(cargoBehavior) .orElse(jammableBehavior) .orElse(takesDamage) - .orElse(aggravatedBehavior) - .orElse(auraBehavior) .orElse(canBeRepairedByNanoDispenser) .orElse(containerBehavior) .orElse(antBehavior) @@ -592,30 +581,6 @@ class VehicleControl(vehicle: Vehicle) } out } - - override def DamageAwareness( - target: Target, - cause: ResolvedProjectile, - amount: Int - ): Unit = { - TryAggravationEffect(cause) match { - case Some(aggravation) => - StartAuraEffect(aggravation.effect_type, aggravation.timing.duration) - case _ => ; - } - super.DamageAwareness(target, cause, amount) - } - - override def DestructionAwareness( - target: Target, - cause: ResolvedProjectile - ): Unit = { - //aura effects cancel - EndAllEffects() - //aggravation cancel - EndAllAggravation() - super.DestructionAwareness(target, cause) - } } object VehicleControl { diff --git a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala index d544e526..e1be38de 100644 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala @@ -2,7 +2,7 @@ package net.psforever.objects.vital.damage import net.psforever.objects.GlobalDefinitions -import net.psforever.objects.ballistics.{PlayerSource, ProjectileResolution, ResolvedProjectile, VehicleSource} +import net.psforever.objects.ballistics._ import net.psforever.objects.vital.DamageType import net.psforever.types.{ExoSuitType, Vector3} @@ -163,7 +163,8 @@ object DamageModifiers { damage: Int, data: ResolvedProjectile ): Int = { - if (data.resolution == resolution) { + if (data.resolution == resolution && + data.projectile.quality == ProjectileQuality.AggravatesTarget) { (data.projectile.profile.Aggravated, data.target) match { case (Some(aggravation), p: PlayerSource) => val aggravatedDamage = aggravation.info.find(_.damage_type == damageType) match { @@ -230,7 +231,8 @@ object DamageModifiers { def Calculate: DamageModifiers.Format = formula private def formula(damage: Int, data: ResolvedProjectile): Int = { - if (data.resolution == ProjectileResolution.AggravatedDirect) { + if (data.resolution == ProjectileResolution.AggravatedDirect && + data.projectile.quality == ProjectileQuality.AggravatesTarget) { (data.projectile.profile.Aggravated, data.target) match { case (Some(aggravation), v : VehicleSource) if GlobalDefinitions.isFlightVehicle(v.Definition) => aggravation.info.find(_.damage_type == DamageType.Direct) match { @@ -257,7 +259,7 @@ object DamageModifiers { case (Some(aggravation), v : VehicleSource) if GlobalDefinitions.isFlightVehicle(v.Definition) => aggravation.info.find(_.damage_type == DamageType.Direct) match { case Some(infos) => - (math.floor(damage * infos.degradation_percentage) * data.projectile.quality) toInt + (math.floor(damage * infos.degradation_percentage) * data.projectile.quality.mod) toInt case _ => damage } @@ -274,8 +276,19 @@ object DamageModifiers { def Calculate: DamageModifiers.Format = formula private def formula(damage: Int, data: ResolvedProjectile): Int = { - if (data.resolution == ProjectileResolution.AggravatedDirect) { - 0 + if (data.resolution == ProjectileResolution.AggravatedDirect && + data.projectile.quality == ProjectileQuality.AggravatesTarget) { + data.projectile.profile.Aggravated match { + case Some(aggravation) => + aggravation.info.find(_.damage_type == DamageType.Direct) match { + case Some(infos) => + damage - (damage * infos.degradation_percentage) toInt + case _ => + damage + } + case _ => + damage + } } else { damage } @@ -288,8 +301,13 @@ object DamageModifiers { private def formula(damage: Int, data: ResolvedProjectile): Int = { if (data.resolution == ProjectileResolution.AggravatedDirectBurn) { data.projectile.profile.Aggravated match { - case Some(_) => - (damage * data.projectile.quality) toInt + case Some(aggravation) => + aggravation.info.find(_.damage_type == DamageType.Direct) match { + case Some(infos) => + damage - (damage * infos.degradation_percentage) toInt + case _ => + damage + } case _ => 0 } diff --git a/src/main/scala/net/psforever/packet/game/DamageWithPositionMessage.scala b/src/main/scala/net/psforever/packet/game/DamageWithPositionMessage.scala index 8f2a09e1..3e6406b4 100644 --- a/src/main/scala/net/psforever/packet/game/DamageWithPositionMessage.scala +++ b/src/main/scala/net/psforever/packet/game/DamageWithPositionMessage.scala @@ -5,10 +5,13 @@ import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacke import net.psforever.types.Vector3 import scodec.Codec import scodec.codecs._ +import shapeless.{::, HNil} /** * Dispatched by the server to indicate a source of damage affecting the player. - * Unlike `HitHint` the damage source is defined by an actual coordinate location rather than a physical target.
+ * Unlike `HitHint` the damage source is defined by an actual coordinate location rather than a physical target. + * Setting the position to the world origin, however, + * can cause the damage tick mark to point towards the previous damaging entity in some situations.
*
* The player will be shown a fading, outwards drifting, red tick mark. * The location will indicate a general direction towards the source. @@ -27,5 +30,14 @@ object DamageWithPositionMessage extends Marshallable[DamageWithPositionMessage] implicit val codec: Codec[DamageWithPositionMessage] = ( ("unk" | uint8L) :: ("pos" | Vector3.codec_pos) - ).as[DamageWithPositionMessage] + ).xmap[DamageWithPositionMessage] ( + { + case unk :: pos :: HNil => + DamageWithPositionMessage(math.min(0, math.max(unk, 255)), pos) + }, + { + case DamageWithPositionMessage(unk, pos) => + unk :: pos :: HNil + } + ) } From f627571f0efbb5f9ecf0e689d4b4d01435ce3fd4 Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 19 Aug 2020 20:43:50 -0400 Subject: [PATCH 12/19] eliminated multiple timers for a single aura effect; added comments; added tests; fixed tests --- .../objects/ballistics/AggravatedDamage.scala | 68 +++++ .../ballistics/ProjectileQuality.scala | 4 +- .../aura/AuraEffectBehavior.scala | 217 ++++++++-------- .../packet/game/AggravatedDamageMessage.scala | 15 +- common/src/test/scala/objects/AuraTest.scala | 237 ++++++++++++++++++ .../objects/avatar/PlayerControl.scala | 33 ++- .../vital/damage/DamageModifiers.scala | 108 +++++++- .../packet/game/DamageFeedbackMessage.scala | 9 +- .../game/DamageWithPositionMessage.scala | 2 +- src/test/scala/objects/DamageableTest.scala | 101 ++++---- .../scala/objects/PlayerControlTest.scala | 7 +- 11 files changed, 616 insertions(+), 185 deletions(-) create mode 100644 common/src/test/scala/objects/AuraTest.scala diff --git a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala index cf445115..7fad9e15 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala +++ b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala @@ -4,20 +4,54 @@ import net.psforever.objects.equipment.TargetValidation import net.psforever.objects.serverobject.aura.Aura import net.psforever.objects.vital.DamageType +/** + * In what manner of pacing the aggravated damage ticks are applied. + * @param duration for how long the over-all effect is applied + * @param ticks a custom number of damage applications, + * as opposed to whatever calculations normally estimate the number of applications + */ final case class AggravatedTiming(duration: Long, ticks: Option[Int]) object AggravatedTiming { + /** + * Overloaded constructor that only defines the duration. + * @param duration for how long the over-all effect lasts + * @return an `AggravatedTiming` object + */ def apply(duration: Long): AggravatedTiming = AggravatedTiming(duration, None) + /** + * Overloaded constructor. + * @param duration for how long the over-all effect lasts + * @param ticks a custom number of damage applications + * @return an `AggravatedTiming` object + */ def apply(duration: Long, ticks: Int): AggravatedTiming = AggravatedTiming(duration, Some(ticks)) } +/** + * Aggravation damage has components that are mainly divided by the `DamageType` they inflict. + * Only `Direct` and `Splash` are valid damage types, however. + * @param damage_type the type of damage + * @param degradation_percentage by how much the damage is degraded + * @param infliction_rate how often the damage is inflicted (ms) + */ final case class AggravatedInfo(damage_type: DamageType.Value, degradation_percentage: Float, infliction_rate: Long) { assert(damage_type == DamageType.Direct || damage_type == DamageType.Splash, s"aggravated damage is an unsupported type - $damage_type") } +/** + * Information related to the aggravated damage. + * @param info the specific kinds of aggravation damage available + * @param effect_type what effect is exhibited by this aggravated damage + * @param timing the timing for the damage application + * @param max_factor na (if the target is a mechanized assault exo-suit?) + * @param cumulative_damage_degrade na (can multiple instances of this type of aggravated damage apply to the same target at once?) + * @param vanu_aggravated na (search me) + * @param targets validation information indicating whether a certain entity is applicable for aggravation + */ final case class AggravatedDamage(info: List[AggravatedInfo], effect_type: Aura, timing: AggravatedTiming, @@ -27,6 +61,14 @@ final case class AggravatedDamage(info: List[AggravatedInfo], targets: List[TargetValidation]) object AggravatedDamage { + /** + * Overloaded constructor. + * @param info the specific kinds of aggravation damage available + * @param effect_type what effect is exhibited by this aggravated damage + * @param timing the timing for the damage application + * @param max_factor na + * @param targets validation information indicating whether a certain entity is applicable for aggravation + */ def apply(info: AggravatedInfo, effect_type: Aura, timing: AggravatedTiming, @@ -42,6 +84,15 @@ object AggravatedDamage { targets ) + /** + * Overloaded constructor. + * @param info the specific kinds of aggravation damage available + * @param effect_type what effect is exhibited by this aggravated damage + * @param timing the timing for the damage application + * @param max_factor na + * @param vanu_aggravated na + * @param targets validation information indicating whether a certain entity is applicable for aggravation + */ def apply(info: AggravatedInfo, effect_type: Aura, timing: AggravatedTiming, @@ -58,6 +109,14 @@ object AggravatedDamage { targets ) + /** + * Overloaded constructor. + * @param info the specific kinds of aggravation damage available + * @param effect_type what effect is exhibited by this aggravated damage + * @param duration for how long the over-all effect is applied + * @param max_factor na + * @param targets validation information indicating whether a certain entity is applicable for aggravation + */ def apply(info: AggravatedInfo, effect_type: Aura, duration: Long, @@ -73,6 +132,15 @@ object AggravatedDamage { targets ) + /** + * Overloaded constructor. + * @param info the specific kinds of aggravation damage available + * @param effect_type what effect is exhibited by this aggravated damage + * @param duration for how long the over-all effect is applied + * @param max_factor na + * @param vanu_aggravated na + * @param targets validation information indicating whether a certain entity is applicable for aggravation + */ def apply(info: AggravatedInfo, effect_type: Aura, duration: Long, diff --git a/common/src/main/scala/net/psforever/objects/ballistics/ProjectileQuality.scala b/common/src/main/scala/net/psforever/objects/ballistics/ProjectileQuality.scala index 8fe9d38c..9a630845 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/ProjectileQuality.scala +++ b/common/src/main/scala/net/psforever/objects/ballistics/ProjectileQuality.scala @@ -15,7 +15,7 @@ sealed trait ProjectileQuality { } /** - * Implement the numeric modifier with as one. + * Implement the numeric modifier with the value as one. */ sealed trait SameAsQuality extends ProjectileQuality { def mod: Float = 1f @@ -25,7 +25,7 @@ object ProjectileQuality { /** Standard projectile quality. More of a flag than a modifier. */ case object Normal extends SameAsQuality - /** Quality that flags the first stage of aggravation (setup). */ + /** Quality that flags the first stage of aggravation (initial damage). */ case object AggravatesTarget extends SameAsQuality /** The complete lack of quality. Even the numeric modifier is zeroed. */ diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala index 6dc233a5..51683d18 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala @@ -2,19 +2,28 @@ package net.psforever.objects.serverobject.aura import akka.actor.{Actor, Cancellable} +import net.psforever.objects.Default import net.psforever.objects.serverobject.PlanetSideServerObject import scala.collection.mutable import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ +/** + * A mixin that governs the addition, display, and removal of aura particle effects + * on a target with control agency. + * @see `Aura` + * @see `AuraContainer` + * @see `PlayerControl` + */ trait AuraEffectBehavior { _ : Actor => - private var activeEffectIndex: Long = 0 - private val effectToEntryId: mutable.HashMap[Aura, List[Long]] = - mutable.HashMap.empty[Aura, List[Long]] - private val effectIdToTimer: mutable.LongMap[Cancellable] = - mutable.LongMap.empty[Cancellable] + /** active aura effects are monotonic, but the timer will be updated for continuing and cancelling effects as well
+ * only effects that are initialized to this mapping are approved for display on this target
+ * key - aura effect; value - the timer for that effect + * @see `ApplicableEffect` + */ + private val effectToTimer: mutable.HashMap[Aura, Cancellable] = mutable.HashMap.empty[Aura, Cancellable] def AuraTargetObject: AuraEffectBehavior.Target @@ -22,144 +31,142 @@ trait AuraEffectBehavior { case AuraEffectBehavior.StartEffect(effect, duration) => StartAuraEffect(effect, duration) - case AuraEffectBehavior.EndEffect(Some(id), None) => - EndAuraEffect(id) - - case AuraEffectBehavior.EndEffect(None, Some(effect)) => - EndAuraEffect(effect) + case AuraEffectBehavior.EndEffect(effect) => + EndAuraEffectAndUpdate(effect) case AuraEffectBehavior.EndAllEffects() => EndAllEffectsAndUpdate() } - final def GetUnusedEffectId: Long = { - val id = activeEffectIndex - activeEffectIndex += 1 - id + /** + * Only pre-apporved aura effects will be emitted by this target. + * @param effect the aura effect + */ + def ApplicableEffect(effect: Aura): Unit = { + //create entry + effectToTimer += effect -> Default.Cancellable } - def StartAuraEffect(effect: Aura, duration: Long): Option[Long] = { + /** + * An aura particle effect is to be emitted by the target. + * If the effect was not previously applied to the target in an ongoing manner, + * animate it appropriately. + * @param effect the effect to be emitted + * @param duration for how long the effect will be emitted + * @return the active effect index number + */ + def StartAuraEffect(effect: Aura, duration: Long): Unit = { val obj = AuraTargetObject - val auraEffects = obj.Aura - if (obj.Aura.contains(effect)) { - effectToEntryId.getOrElse(effect, List[Long](AuraEffectBehavior.InvalidEffectId)).headOption //grab an available active effect id - } - else if(obj.AddEffectToAura(effect).diff(auraEffects).contains(effect)) { - Some(StartAuraEffect(GetUnusedEffectId, effect, duration)) - } - else { - None + val auraEffectsBefore = obj.Aura.size + if(StartAuraTimer(effect, duration) && obj.AddEffectToAura(effect).size > auraEffectsBefore) { + //new effect; update visuals + UpdateAuraEffect(AuraTargetObject) } } - def StartAuraEffect(id: Long, effect: Aura, duration: Long): Long = { + /** + * As long as the effect has been approved for this target, + * the timer will either start if it is stopped or has never been started, + * or the timer will stop and be recreated with the new duration if is currently running. + * @param effect the effect to be emitted + * @param duration for how long the effect will be emitted + * @return `true`, if the timer was started or restarted; + * `false`, otherwise + */ + private def StartAuraTimer(effect: Aura, duration: Long): Boolean = { //pair aura effect with id - effectToEntryId.get(effect) match { - case None | Some(Nil) => effectToEntryId += effect -> List(id) - case Some(list) => effectToEntryId -> (list :+ id) - } - //pair id with timer - effectIdToTimer += id -> context.system.scheduler.scheduleOnce(duration milliseconds, self, AuraEffectBehavior.EndEffect(id)) - //update visuals - UpdateAuraEffect(AuraTargetObject) - id - } - - def EndAuraEffect(id: Long): Unit = { - EndActiveEffect(id) match { - case Aura.Nothing => ; - case effect => - CancelEffectTimer(id) - val obj = AuraTargetObject - obj.RemoveEffectFromAura(effect) - UpdateAuraEffect(obj) - } - } - - def EndActiveEffect(id: Long): Aura = { - effectToEntryId.find { case (_, ids) => ids.contains(id) } match { - case Some((effect, ids)) if ids.size == 1 => - effectToEntryId.remove(effect) - effect - case Some((effect, ids)) => - effectToEntryId += effect -> ids.filterNot(_ == id) - Aura.Nothing + (effectToTimer.get(effect) match { case None => - Aura.Nothing + None + case Some(timer) => + timer.cancel() + Some(effect) + }) match { + case None => + false + case Some(_) => + //paired id with timer; retime + effectToTimer(effect) = + context.system.scheduler.scheduleOnce(duration milliseconds, self, AuraEffectBehavior.EndEffect(effect)) + true } } - def CancelEffectTimer(id: Long) : Unit = { - effectIdToTimer.remove(id) match { - case Some(timer) => timer.cancel - case _ => ; - } - } - - def EndAuraEffect(effect: Aura): Unit = { - effectToEntryId.remove(effect) match { - case Some(idList) => - idList.foreach { id => - val obj = AuraTargetObject - CancelEffectTimer(id) - obj.RemoveEffectFromAura(effect) - UpdateAuraEffect(obj) - } - case _ => ; + /** + * Stop the target entity from emitting the aura particle effect, if it currently is. + * @param effect the target effect + * @return `true`, if the effect was being emitted but has been stopped + * `false`, if the effect was not approved or is not being emitted + */ + def EndAuraEffect(effect: Aura): Boolean = { + effectToTimer.get(effect) match { + case Some(timer) if !timer.isCancelled => + timer.cancel() + effectToTimer(effect) = Default.Cancellable + AuraTargetObject.RemoveEffectFromAura(effect) + true + case _ => + false } } + /** + * Stop the target entity from emitting all aura particle effects. + */ def EndAllEffects() : Unit = { - effectIdToTimer.values.foreach { _.cancel } - effectIdToTimer.clear - effectToEntryId.clear + effectToTimer.keysIterator.foreach { effect => + effectToTimer(effect).cancel() + effectToTimer(effect) = Default.Cancellable + } val obj = AuraTargetObject obj.Aura.foreach { obj.RemoveEffectFromAura } } + /** + * Stop the target entity from emitting the aura particle effect, if it currently is. + * If the effect has been stopped, animate the new particle effect state. + */ + def EndAuraEffectAndUpdate(effect: Aura) : Unit = { + if(EndAuraEffect(effect)) { + UpdateAuraEffect(AuraTargetObject) + } + } + + /** + * Stop the target entity from emitting all aura particle effects. + * Animate the new particle effect state. + */ def EndAllEffectsAndUpdate() : Unit = { EndAllEffects() UpdateAuraEffect(AuraTargetObject) } - def UpdateAuraEffect(target: AuraEffectBehavior.Target) : Unit = { - import services.avatar.{AvatarAction, AvatarServiceMessage} - val zone = target.Zone - val value = target.Aura.foldLeft(0)(_ + AuraEffectBehavior.effectToAttributeValue(_)) - zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(target.GUID, 54, value)) - } - - def TestForEffect(id: Long): Aura = { - effectToEntryId.find { case (_, ids) => ids.contains(id) } match { - case Some((effect, _)) => effect - case _ => Aura.Nothing + /** + * Is the target entity emitting the aura effect? + * @param effect the effect being tested + * @return `true`, if the effect is currently being emitted; + * `false`, otherwise + */ + def TestForEffect(effect: Aura): Boolean = { + effectToTimer.get(effect) match { + case None => false + case Some(timer) => timer.isCancelled } } + + /** + * An override callback to display aura effects emitted. + * @param target the entity from which the aura effects are being emitted + */ + def UpdateAuraEffect(target: AuraEffectBehavior.Target) : Unit } object AuraEffectBehavior { type Target = PlanetSideServerObject with AuraContainer - final val InvalidEffectId = -1 - final case class StartEffect(effect: Aura, duration: Long) - final case class EndEffect(id: Option[Long], aura: Option[Aura]) - - object EndEffect { - def apply(id: Long): EndEffect = EndEffect(Some(id), None) - - def apply(aura: Aura): EndEffect = EndEffect(None, Some(aura)) - } + final case class EndEffect(aura: Aura) final case class EndAllEffects() - - private def effectToAttributeValue(effect: Aura): Int = effect match { - case Aura.None => 0 - case Aura.Plasma => 1 - case Aura.Comet => 2 - case Aura.Napalm => 4 - case Aura.Fire => 8 - case _ => Int.MinValue - } } diff --git a/common/src/main/scala/net/psforever/packet/game/AggravatedDamageMessage.scala b/common/src/main/scala/net/psforever/packet/game/AggravatedDamageMessage.scala index faa48528..2456bee2 100644 --- a/common/src/main/scala/net/psforever/packet/game/AggravatedDamageMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/AggravatedDamageMessage.scala @@ -7,12 +7,17 @@ import scodec.Codec import scodec.codecs._ /** - * na - * @param guid na - * @param unk na + * Dispatched from the server to cause a damage reaction from a specific target. + * Infantry targets should be the primary target of this packet, as indicated by their identifier. + * Infantry targets display their flinch animation. + * All targets yelp in agony. + * Infantry targets use their assigned voice. + * Non-infantry targets use the "grizzled"(?) voice. + * @param guid the target entity's global unique identifier + * @param damage the amount of damsge being simulated */ final case class AggravatedDamageMessage(guid : PlanetSideGUID, - unk : Long) + damage : Long) extends PlanetSideGamePacket { type Packet = AggravatedDamageMessage def opcode = GamePacketOpcode.AggravatedDamageMessage @@ -22,6 +27,6 @@ final case class AggravatedDamageMessage(guid : PlanetSideGUID, object AggravatedDamageMessage extends Marshallable[AggravatedDamageMessage] { implicit val codec : Codec[AggravatedDamageMessage] = ( ("guid" | PlanetSideGUID.codec) :: - ("unk" | uint32L) + ("damage" | uint32L) ).as[AggravatedDamageMessage] } diff --git a/common/src/test/scala/objects/AuraTest.scala b/common/src/test/scala/objects/AuraTest.scala new file mode 100644 index 00000000..a4af3983 --- /dev/null +++ b/common/src/test/scala/objects/AuraTest.scala @@ -0,0 +1,237 @@ +// Copyright (c) 2020 PSForever +package objects + +import akka.actor.{Actor, ActorRef, Props} +import akka.testkit.TestProbe +import base.ActorTest +import net.psforever.objects.serverobject.PlanetSideServerObject +import net.psforever.objects.serverobject.aura.AuraEffectBehavior.Target +import net.psforever.objects.serverobject.aura.{Aura, AuraContainer, AuraEffectBehavior} +import net.psforever.types.PlanetSideEmpire +import org.specs2.mutable.Specification + +import scala.concurrent.duration._ + +class AuraContainerTest extends Specification { + "AuraContainer" should { + "have no default effects" in { + new AuraTest.Entity().Aura.isEmpty mustEqual true + } + + "add effects" in { + val obj = new AuraTest.Entity() + obj.Aura.size mustEqual 0 + obj.Aura.contains(Aura.Plasma) mustEqual false + obj.AddEffectToAura(Aura.Plasma) + obj.Aura.size mustEqual 1 + obj.Aura.contains(Aura.Plasma) mustEqual true + } + + "do nothing if adding repeated effects" in { + val obj = new AuraTest.Entity() + obj.Aura.size mustEqual 0 + obj.AddEffectToAura(Aura.Plasma) + obj.Aura.size mustEqual 1 + obj.AddEffectToAura(Aura.Plasma) + obj.Aura.size mustEqual 1 + } + + "remove effects" in { + val obj = new AuraTest.Entity() + obj.Aura.size mustEqual 0 + obj.Aura.contains(Aura.Plasma) mustEqual false + obj.AddEffectToAura(Aura.Plasma) + obj.Aura.size mustEqual 1 + obj.Aura.contains(Aura.Plasma) mustEqual true + obj.RemoveEffectFromAura(Aura.Plasma) + obj.Aura.size mustEqual 0 + obj.Aura.contains(Aura.Plasma) mustEqual false + } + + "do nothing if no effects" in { + val obj = new AuraTest.Entity() + obj.Aura.size mustEqual 0 + obj.Aura.contains(Aura.Plasma) mustEqual false + obj.RemoveEffectFromAura(Aura.Plasma) + obj.Aura.size mustEqual 0 + obj.Aura.contains(Aura.Plasma) mustEqual false + } + + "do nothing if trying to remove wrong effect" in { + val obj = new AuraTest.Entity() + obj.Aura.size mustEqual 0 + obj.Aura.contains(Aura.Plasma) mustEqual false + obj.Aura.contains(Aura.Fire) mustEqual false + obj.AddEffectToAura(Aura.Plasma) + obj.Aura.size mustEqual 1 + obj.Aura.contains(Aura.Plasma) mustEqual true + obj.Aura.contains(Aura.Fire) mustEqual false + obj.RemoveEffectFromAura(Aura.Fire) + obj.Aura.size mustEqual 1 + obj.Aura.contains(Aura.Plasma) mustEqual true + obj.Aura.contains(Aura.Fire) mustEqual false + } + } +} + +class AuraEffectBehaviorInitTest extends ActorTest { + val obj = new AuraTest.Entity() + + "AuraEffectBehavior" should { + "init" in { + obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, ActorRef.noSender), "aura-test-actor") + expectNoMessage(500 milliseconds) + } + } +} + +class AuraEffectBehaviorStartEffectTest extends ActorTest { + val obj = new AuraTest.Entity() + val updateProbe = new TestProbe(system) + obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor") + + "AuraEffectBehavior" should { + "start effect (ends naturally)" in { + assert(obj.Aura.isEmpty) + obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500) + val msg1 = updateProbe.receiveOne(100 milliseconds) + assert( + msg1 match { + case AuraTest.DoUpdateAuraEffect() => true + case _ => false + } + ) + assert(obj.Aura.contains(Aura.Plasma)) + expectNoMessage(2000 milliseconds) + assert(obj.Aura.contains(Aura.Plasma)) + val msg2 = updateProbe.receiveOne(750 milliseconds) + assert( + msg2 match { + case AuraTest.DoUpdateAuraEffect() => true + case _ => false + } + ) + assert(obj.Aura.isEmpty) + } + } +} + +class AuraEffectBehaviorNoRedundantStartEffectTest extends ActorTest { + val obj = new AuraTest.Entity() + val updateProbe = new TestProbe(system) + obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor") + + "AuraEffectBehavior" should { + "not start an effect if already active" in { + assert(obj.Aura.isEmpty) + obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500) + val msg1 = updateProbe.receiveOne(100 milliseconds) + assert( + msg1 match { + case AuraTest.DoUpdateAuraEffect() => true + case _ => false + } + ) + assert(obj.Aura.contains(Aura.Plasma)) + expectNoMessage(1000 milliseconds) //wait for half of the effect's duration + assert(obj.Aura.contains(Aura.Plasma)) + obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500) + updateProbe.expectNoMessage(1500 milliseconds) + //effect has not ended naturally + assert(obj.Aura.contains(Aura.Plasma)) + } + } +} + +class AuraEffectBehaviorNoStartUnsupportedEffectTest extends ActorTest { + val obj = new AuraTest.Entity() + val updateProbe = new TestProbe(system) + obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor") //supports Plasma only + + "AuraEffectBehavior" should { + "not start an effect that is not approved" in { + assert(obj.Aura.isEmpty) + obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Fire, 2500) + assert(obj.Aura.isEmpty) + updateProbe.expectNoMessage(2000 milliseconds) + } + } +} + + + +class AuraEffectBehaviorEndEarlyTest extends ActorTest { + val obj = new AuraTest.Entity() + val updateProbe = new TestProbe(system) + obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor") + + "AuraEffectBehavior" should { + "start effect (ends early)" in { + assert(obj.Aura.isEmpty) + obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500) + val msg1 = updateProbe.receiveOne(100 milliseconds) + assert( + msg1 match { + case AuraTest.DoUpdateAuraEffect() => true + case _ => false + } + ) + assert(obj.Aura.contains(Aura.Plasma)) + obj.Actor ! AuraEffectBehavior.EndEffect(Aura.Plasma) + val msg2 = updateProbe.receiveOne(100 milliseconds) + assert( + msg2 match { + case AuraTest.DoUpdateAuraEffect() => true + case _ => false + } + ) + assert(obj.Aura.isEmpty) + } + } +} + +class AuraEffectBehaviorEndNothingTest extends ActorTest { + val obj = new AuraTest.Entity() + val updateProbe = new TestProbe(system) + obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor") + + "AuraEffectBehavior" should { + "can not end an effect that is not supported (hence, not started)" in { + assert(obj.Aura.isEmpty) + obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500) + val msg1 = updateProbe.receiveOne(100 milliseconds) + assert( + msg1 match { + case AuraTest.DoUpdateAuraEffect() => true + case _ => false + } + ) + assert(obj.Aura.size == 1) + obj.Actor ! AuraEffectBehavior.EndEffect(Aura.Fire) + updateProbe.expectNoMessage(1000 milliseconds) + assert(obj.Aura.size == 1) + } + } +} + +object AuraTest { + class Agency(obj: AuraEffectBehavior.Target, updateRef: ActorRef) extends Actor with AuraEffectBehavior { + def AuraTargetObject : Target = obj + ApplicableEffect(Aura.Plasma) + + def receive: Receive = auraBehavior.orElse { + case _ => ; + } + + def UpdateAuraEffect(target : Target) : Unit = { + updateRef ! DoUpdateAuraEffect() + } + } + + class Entity extends PlanetSideServerObject with AuraContainer { + def Faction = PlanetSideEmpire.NEUTRAL + def Definition = null + } + + final case class DoUpdateAuraEffect() +} diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index e9a3b956..bed0afbb 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -8,7 +8,7 @@ import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile} import net.psforever.objects.equipment._ import net.psforever.objects.inventory.{GridInventory, InventoryItem} import net.psforever.objects.loadouts.Loadout -import net.psforever.objects.serverobject.aura.AuraEffectBehavior +import net.psforever.objects.serverobject.aura.{Aura, AuraEffectBehavior} import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior} import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.vital.PlayerSuicide @@ -36,13 +36,18 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm with ContainableBehavior with AggravatedBehavior with AuraEffectBehavior { - def JammableObject = player + + def JammableObject = player def DamageableObject = player def ContainerObject = player def AggravatedObject = player + ApplicableEffect(Aura.Plasma) + ApplicableEffect(Aura.Napalm) + ApplicableEffect(Aura.Comet) + ApplicableEffect(Aura.Fire) def AuraTargetObject = player @@ -922,4 +927,28 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item.GUID) ) } + + def UpdateAuraEffect(target: AuraEffectBehavior.Target) : Unit = { + import services.avatar.{AvatarAction, AvatarServiceMessage} + val zone = target.Zone + val value = target.Aura.foldLeft(0)(_ + PlayerControl.auraEffectToAttributeValue(_)) + zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.PlanetsideAttributeToAll(target.GUID, 54, value)) + } +} + +object PlayerControl { + /** + * Transform an applicable Aura effect into its `PlanetsideAttributeMessage` value. + * @see `Aura` + * @see `PlanetsideAttributeMessage` + * @param effect the aura effect + * @return the attribute value for that effect + */ + private def auraEffectToAttributeValue(effect: Aura): Int = effect match { + case Aura.Plasma => 1 + case Aura.Comet => 2 + case Aura.Napalm => 4 + case Aura.Fire => 8 + case _ => 0 + } } diff --git a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala index e1be38de..e0009f1e 100644 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala @@ -1,7 +1,6 @@ // Copyright (c) 2020 PSForever package net.psforever.objects.vital.damage -import net.psforever.objects.GlobalDefinitions import net.psforever.objects.ballistics._ import net.psforever.objects.vital.DamageType import net.psforever.types.{ExoSuitType, Vector3} @@ -135,26 +134,68 @@ object DamageModifiers { } } + /* + Below this point are the calculations for sources of aggravated damage. + For the most part, these calculations are individualistic and arbitrary. + They exist in their current form to satisfy observed shots to kill (STK) of specific weapon systems + according to 2012 standards of the Youtube video series by TheLegendaryNarwhal. + */ + /** + * The initial application of aggravated damage against an infantry target + * where the specific damage component is `Direct`. + */ case object InfantryAggravatedDirect extends Mod { def Calculate: DamageModifiers.Format = BaseAggravatedFormula(ProjectileResolution.AggravatedDirect, DamageType.Direct) } + /** + * The initial application of aggravated damage against an infantry target + * where the specific damage component is `Splash`. + */ case object InfantryAggravatedSplash extends Mod { def Calculate: DamageModifiers.Format = BaseAggravatedFormula(ProjectileResolution.AggravatedSplash, DamageType.Splash) } + /** + * The ongoing application of aggravated damage ticks against an infantry target + * where the specific damage component is `Direct`. + * This is called "burning" regardless of what the active aura effect actually is. + */ case object InfantryAggravatedDirectBurn extends Mod { def Calculate: DamageModifiers.Format = BaseAggravatedBurnFormula(ProjectileResolution.AggravatedDirectBurn, DamageType.Direct) } + /** + * The ongoing application of aggravated damage ticks against an infantry target + * where the specific damage component is `Splash`. + * This is called "burning" regardless of what the active aura effect actually is. + */ case object InfantryAggravatedSplashBurn extends Mod { def Calculate: DamageModifiers.Format = BaseAggravatedBurnFormula(ProjectileResolution.AggravatedSplashBurn, DamageType.Splash) } + /** + * For damage application that involves aggravation of a particular damage type, + * calculate that initial damage application for infantry targets + * and produce the modified damage value. + * Infantry wearing mechanized assault exo-suits (MAX) incorporate an additional modifier. + * @see `AggravatedDamage` + * @see `ExoSuitType` + * @see `InfantryAggravatedDirect` + * @see `InfantryAggravatedSplash` + * @see `PlayerSource` + * @see `ProjectileTarget.AggravatesTarget` + * @see `ResolvedProjectile` + * @param resolution the projectile resolution to match against + * @param damageType the damage type to find in as a component of aggravated information + * @param damage the base damage value + * @param data historical information related to the damage interaction + * @return the modified damage + */ private def BaseAggravatedFormula( resolution: ProjectileResolution.Value, damageType : DamageType.Value @@ -186,6 +227,24 @@ object DamageModifiers { } } + /** + * For damage application that involves aggravation of a particular damage type, + * calculate that damage application burn for each tick for infantry targets + * and produce the modified damage value. + * Infantry wearing mechanized assault exo-suits (MAX) incorporate an additional modifier. + * Vanilla infantry incorporate their resistance value into a slightly different calculation than usual. + * @see `AggravatedDamage` + * @see `ExoSuitType` + * @see `InfantryAggravatedDirectBurn` + * @see `InfantryAggravatedSplashBurn` + * @see `PlayerSource` + * @see `ResolvedProjectile` + * @param resolution the projectile resolution to match against + * @param damageType the damage type to find in as a component of aggravated information + * @param damage the base damage value + * @param data historical information related to the damage interaction + * @return the modified damage + */ private def BaseAggravatedBurnFormula( resolution: ProjectileResolution.Value, damageType : DamageType.Value @@ -207,16 +266,11 @@ object DamageModifiers { (damage * degradation * aggravation.max_factor) toInt } else { val resist = data.damage_model.ResistUsing(data)(data) + //add resist to offset resist subtraction later if (damage > resist) { ((damage - resist) * degradation).toInt + resist } else { - val degradedDamage = damage * degradation - if (degradedDamage > resist) { - degradedDamage toInt - } - else { - damage - } + (damage * degradation).toInt + resist } } case _ => @@ -227,14 +281,21 @@ object DamageModifiers { } } + /** + * The initial application of aggravated damage against an aircraft target. + * Primarily for use in the starfire weapon system. + * @see `AggravatedDamage` + * @see `ProjectileQuality.AggravatesTarget` + * @see `ResolvedProjectile` + */ case object StarfireAggravated extends Mod { def Calculate: DamageModifiers.Format = formula private def formula(damage: Int, data: ResolvedProjectile): Int = { if (data.resolution == ProjectileResolution.AggravatedDirect && data.projectile.quality == ProjectileQuality.AggravatesTarget) { - (data.projectile.profile.Aggravated, data.target) match { - case (Some(aggravation), v : VehicleSource) if GlobalDefinitions.isFlightVehicle(v.Definition) => + data.projectile.profile.Aggravated match { + case Some(aggravation) => aggravation.info.find(_.damage_type == DamageType.Direct) match { case Some(infos) => (damage * infos.degradation_percentage + damage) toInt @@ -250,13 +311,21 @@ object DamageModifiers { } } + /** + * The ongoing application of aggravated damage ticks against an aircraft target. + * Primarily for use in the starfire weapon system. + * This is called "burning" regardless of what the active aura effect actually is. + * @see `AggravatedDamage` + * @see `ProjectileQuality` + * @see `ResolvedProjectile` + */ case object StarfireAggravatedBurn extends Mod { def Calculate: DamageModifiers.Format = formula private def formula(damage: Int, data: ResolvedProjectile): Int = { if (data.resolution == ProjectileResolution.AggravatedDirectBurn) { - (data.projectile.profile.Aggravated, data.target) match { - case (Some(aggravation), v : VehicleSource) if GlobalDefinitions.isFlightVehicle(v.Definition) => + data.projectile.profile.Aggravated match { + case Some(aggravation) => aggravation.info.find(_.damage_type == DamageType.Direct) match { case Some(infos) => (math.floor(damage * infos.degradation_percentage) * data.projectile.quality.mod) toInt @@ -272,6 +341,13 @@ object DamageModifiers { } } + /** + * The initial application of aggravated damage against a target. + * Primarily for use in the comet weapon system. + * @see `AggravatedDamage` + * @see `ProjectileQuality.AggravatesTarget` + * @see `ResolvedProjectile` + */ case object CometAggravated extends Mod { def Calculate: DamageModifiers.Format = formula @@ -295,6 +371,14 @@ object DamageModifiers { } } + /** + * The ongoing application of aggravated damage ticks against a target. + * Primarily for use in the comet weapon system. + * This is called "burning" regardless of what the active aura effect actually is. + * @see `AggravatedDamage` + * @see `ProjectileQuality` + * @see `ResolvedProjectile` + */ case object CometAggravatedBurn extends Mod { def Calculate: DamageModifiers.Format = formula diff --git a/src/main/scala/net/psforever/packet/game/DamageFeedbackMessage.scala b/src/main/scala/net/psforever/packet/game/DamageFeedbackMessage.scala index 181a67b2..f88bcf03 100644 --- a/src/main/scala/net/psforever/packet/game/DamageFeedbackMessage.scala +++ b/src/main/scala/net/psforever/packet/game/DamageFeedbackMessage.scala @@ -19,7 +19,7 @@ import shapeless.{::, HNil} * @param unk3b if no global unique identifier (above), the name of the entity absorbing the damage * @param unk3c if no global unique identifier (above), the object type of the entity absorbing the damage * @param unk3d na - * @param unk4 na + * @param unk4 an indicator for the target-specific vital statistic being affected * @param unk5 the amount of damage * @param unk6 na */ @@ -66,6 +66,13 @@ final case class DamageFeedbackMessage( } object DamageFeedbackMessage extends Marshallable[DamageFeedbackMessage] { + def apply(unk1: Int, + unk2: PlanetSideGUID, + unk3: PlanetSideGUID, + unk4: Int, + unk5: Long): DamageFeedbackMessage = + DamageFeedbackMessage(unk1, true, Some(unk2), None, None, true, Some(unk3), None, None, None, unk4, unk5, 0) + implicit val codec: Codec[DamageFeedbackMessage] = ( ("unk1" | uint4) :: (bool >>:~ { u2 => diff --git a/src/main/scala/net/psforever/packet/game/DamageWithPositionMessage.scala b/src/main/scala/net/psforever/packet/game/DamageWithPositionMessage.scala index 3e6406b4..d6e4a2df 100644 --- a/src/main/scala/net/psforever/packet/game/DamageWithPositionMessage.scala +++ b/src/main/scala/net/psforever/packet/game/DamageWithPositionMessage.scala @@ -33,7 +33,7 @@ object DamageWithPositionMessage extends Marshallable[DamageWithPositionMessage] ).xmap[DamageWithPositionMessage] ( { case unk :: pos :: HNil => - DamageWithPositionMessage(math.min(0, math.max(unk, 255)), pos) + DamageWithPositionMessage(math.max(0, math.min(unk, 255)), pos) }, { case DamageWithPositionMessage(unk, pos) => diff --git a/src/test/scala/objects/DamageableTest.scala b/src/test/scala/objects/DamageableTest.scala index 63c59290..d5973874 100644 --- a/src/test/scala/objects/DamageableTest.scala +++ b/src/test/scala/objects/DamageableTest.scala @@ -626,7 +626,7 @@ class DamageableMountableDamageTest extends ActorTest { msg1_3(1) match { case AvatarServiceMessage( "TestCharacter2", - AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 2, 2))) + AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(_, Vector3(2, 2, 2))) ) => true case _ => false @@ -737,8 +737,10 @@ class DamageableWeaponTurretDamageTest extends ActorTest { } val activityProbe = TestProbe() val avatarProbe = TestProbe() + val vehicleProbe = TestProbe() zone.Activity = activityProbe.ref zone.AvatarEvents = avatarProbe.ref + zone.VehicleEvents = vehicleProbe.ref val turret = new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) //2 turret.Actor = system.actorOf(Props(classOf[TurretControl], turret), "turret-control") turret.Zone = zone @@ -787,16 +789,17 @@ class DamageableWeaponTurretDamageTest extends ActorTest { assert(turret.Health == turret.Definition.DefaultHealth) turret.Actor ! Vitality.Damage(applyDamageTo) - val msg1_3 = avatarProbe.receiveN(2, 500 milliseconds) - val msg2 = activityProbe.receiveOne(500 milliseconds) + val msg12 = vehicleProbe.receiveOne(500 milliseconds) + val msg3 = activityProbe.receiveOne(500 milliseconds) + val msg4 = avatarProbe.receiveOne(500 milliseconds) assert( - msg1_3.head match { - case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true - case _ => false + msg12 match { + case VehicleServiceMessage("test", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(2), 0, _)) => true + case _ => false } ) assert( - msg2 match { + msg3 match { case activity: Zone.HotSpot.Activity => activity.attacker == PlayerSource(player1) && activity.defender == turretSource && @@ -805,10 +808,10 @@ class DamageableWeaponTurretDamageTest extends ActorTest { } ) assert( - msg1_3(1) match { + msg4 match { case AvatarServiceMessage( "TestCharacter2", - AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 2, 2))) + AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(_, Vector3(2, 2, 2))) ) => true case _ => false @@ -1130,40 +1133,36 @@ class DamageableVehicleDamageTest extends ActorTest { assert(atv.Shields == 1) atv.Actor ! Vitality.Damage(applyDamageTo) - val msg1_3 = avatarProbe.receiveN(2, 500 milliseconds) - val msg2 = activityProbe.receiveOne(200 milliseconds) - val msg4 = vehicleProbe.receiveOne(200 milliseconds) + val msg12 = vehicleProbe.receiveN(2, 200 milliseconds) + val msg3 = activityProbe.receiveOne(200 milliseconds) + val msg4 = avatarProbe.receiveOne(200 milliseconds) assert( - msg1_3.head match { - case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(1), 0, _)) => true - case _ => false + msg12.head match { + case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _)) => true + case _ => false } ) assert( - msg2 match { + msg12(1) match { + case VehicleServiceMessage("test", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 0, _)) => true + case _ => false + } + ) + assert( + msg3 match { case activity: Zone.HotSpot.Activity => activity.attacker == PlayerSource(player1) && - activity.defender == vehicleSource && - activity.location == Vector3(1, 0, 0) - case _ => false - } - ) - assert( - msg1_3(1) match { - case AvatarServiceMessage( - "TestCharacter2", - AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 0, 0))) - ) => - true + activity.defender == vehicleSource && + activity.location == Vector3(1, 0, 0) case _ => false } ) assert( msg4 match { - case VehicleServiceMessage( - channel, - VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _) - ) if channel.equals(atv.Actor.toString) => + case AvatarServiceMessage( + "TestCharacter2", + AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(9, Vector3(2, 0, 0))) + ) => true case _ => false } @@ -1264,17 +1263,23 @@ class DamageableVehicleDamageMountedTest extends ActorTest { assert(atv.Shields == 1) lodestar.Actor ! Vitality.Damage(applyDamageTo) - val msg1_35 = avatarProbe.receiveN(3, 500 milliseconds) - val msg2 = activityProbe.receiveOne(200 milliseconds) - val msg4 = vehicleProbe.receiveOne(200 milliseconds) + val msg12 = vehicleProbe.receiveN(2, 200 milliseconds) + val msg3 = activityProbe.receiveOne(200 milliseconds) + val msg45 = avatarProbe.receiveN(2,200 milliseconds) assert( - msg1_35.head match { - case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(1), 0, _)) => true - case _ => false + msg12.head match { + case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _)) => true + case _ => false } ) assert( - msg2 match { + msg12(1) match { + case VehicleServiceMessage("test", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 0, _)) => true + case _ => false + } + ) + assert( + msg3 match { case activity: Zone.HotSpot.Activity => activity.attacker == PlayerSource(player1) && activity.defender == vehicleSource && @@ -1283,30 +1288,20 @@ class DamageableVehicleDamageMountedTest extends ActorTest { } ) assert( - msg1_35(1) match { + msg45.head match { case AvatarServiceMessage( "TestCharacter2", - AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 0, 0))) + AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(400, Vector3(2, 0, 0))) ) => true case _ => false } ) assert( - msg4 match { - case VehicleServiceMessage( - channel, - VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _) - ) if channel.equals(lodestar.Actor.toString) => - true - case _ => false - } - ) - assert( - msg1_35(2) match { + msg45(1) match { case AvatarServiceMessage( "TestCharacter3", - AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 0, 0))) + AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(0, Vector3(2, 0, 0))) ) => true case _ => false diff --git a/src/test/scala/objects/PlayerControlTest.scala b/src/test/scala/objects/PlayerControlTest.scala index 56b34460..9b261bdf 100644 --- a/src/test/scala/objects/PlayerControlTest.scala +++ b/src/test/scala/objects/PlayerControlTest.scala @@ -413,14 +413,13 @@ class PlayerControlDamageTest extends ActorTest { ) assert( msg_avatar(1) match { - case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true + case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => true case _ => false } ) assert( msg_avatar(2) match { - case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => - true + case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true case _ => false } ) @@ -437,7 +436,7 @@ class PlayerControlDamageTest extends ActorTest { msg_avatar(3) match { case AvatarServiceMessage( "TestCharacter2", - AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 0, 0))) + AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(17, Vector3(2, 0, 0))) ) => true case _ => false From ac5e26f37aa3edee0ad12ba11fae29dcd4d9395a Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 20 Aug 2020 09:22:22 -0400 Subject: [PATCH 13/19] aura effects that will run for longer will not get replaced by aura effects that would end sooner --- .../aura/AuraEffectBehavior.scala | 36 +++++++++----- common/src/test/scala/objects/AuraTest.scala | 48 +++++++++++++++++++ 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala index 51683d18..96b6f2c6 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala @@ -23,7 +23,7 @@ trait AuraEffectBehavior { * key - aura effect; value - the timer for that effect * @see `ApplicableEffect` */ - private val effectToTimer: mutable.HashMap[Aura, Cancellable] = mutable.HashMap.empty[Aura, Cancellable] + private val effectToTimer: mutable.HashMap[Aura, AuraEffectBehavior.Entry] = mutable.HashMap.empty[Aura, AuraEffectBehavior.Entry] def AuraTargetObject: AuraEffectBehavior.Target @@ -44,7 +44,7 @@ trait AuraEffectBehavior { */ def ApplicableEffect(effect: Aura): Unit = { //create entry - effectToTimer += effect -> Default.Cancellable + effectToTimer += effect -> AuraEffectBehavior.Entry() } /** @@ -67,27 +67,29 @@ trait AuraEffectBehavior { /** * As long as the effect has been approved for this target, * the timer will either start if it is stopped or has never been started, - * or the timer will stop and be recreated with the new duration if is currently running. + * or the timer will stop and be recreated with the new duration if is currently running for a shoreter amount of time. * @param effect the effect to be emitted * @param duration for how long the effect will be emitted * @return `true`, if the timer was started or restarted; * `false`, otherwise */ private def StartAuraTimer(effect: Aura, duration: Long): Boolean = { - //pair aura effect with id + //pair aura effect with entry (effectToTimer.get(effect) match { - case None => - None - case Some(timer) => + case Some(timer) if timer.start + timer.duration < System.currentTimeMillis() + duration => timer.cancel() Some(effect) + case _ => + None }) match { case None => false case Some(_) => - //paired id with timer; retime - effectToTimer(effect) = + //retime + effectToTimer(effect) = AuraEffectBehavior.Entry( + duration, context.system.scheduler.scheduleOnce(duration milliseconds, self, AuraEffectBehavior.EndEffect(effect)) + ) true } } @@ -102,7 +104,7 @@ trait AuraEffectBehavior { effectToTimer.get(effect) match { case Some(timer) if !timer.isCancelled => timer.cancel() - effectToTimer(effect) = Default.Cancellable + //effectToTimer(effect) = Default.Cancellable AuraTargetObject.RemoveEffectFromAura(effect) true case _ => @@ -116,7 +118,7 @@ trait AuraEffectBehavior { def EndAllEffects() : Unit = { effectToTimer.keysIterator.foreach { effect => effectToTimer(effect).cancel() - effectToTimer(effect) = Default.Cancellable + //effectToTimer(effect) = Default.Cancellable } val obj = AuraTargetObject obj.Aura.foreach { obj.RemoveEffectFromAura } @@ -164,6 +166,18 @@ trait AuraEffectBehavior { object AuraEffectBehavior { type Target = PlanetSideServerObject with AuraContainer + case class Entry(duration: Long, timer: Cancellable) extends Cancellable { + val start: Long = System.currentTimeMillis() + + override def isCancelled : Boolean = timer.isCancelled + + override def cancel: Boolean = timer.cancel + } + + object Entry { + def apply(): Entry = Entry(0, Default.Cancellable) + } + final case class StartEffect(effect: Aura, duration: Long) final case class EndEffect(aura: Aura) diff --git a/common/src/test/scala/objects/AuraTest.scala b/common/src/test/scala/objects/AuraTest.scala index a4af3983..40aec57c 100644 --- a/common/src/test/scala/objects/AuraTest.scala +++ b/common/src/test/scala/objects/AuraTest.scala @@ -116,6 +116,31 @@ class AuraEffectBehaviorStartEffectTest extends ActorTest { } } +class AuraEffectBehaviorStartLongerEffectTest extends ActorTest { + val obj = new AuraTest.Entity() + val updateProbe = new TestProbe(system) + obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor") + + "AuraEffectBehavior" should { + "replace a shorter effect with a longer one" in { + assert(obj.Aura.isEmpty) + obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 500) + val msg1 = updateProbe.receiveOne(100 milliseconds) + assert( + msg1 match { + case AuraTest.DoUpdateAuraEffect() => true + case _ => false + } + ) + assert(obj.Aura.contains(Aura.Plasma)) + obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500) + updateProbe.expectNoMessage(2000 milliseconds) + //first effect has not ended naturally (yet) + assert(obj.Aura.contains(Aura.Plasma)) + } + } +} + class AuraEffectBehaviorNoRedundantStartEffectTest extends ActorTest { val obj = new AuraTest.Entity() val updateProbe = new TestProbe(system) @@ -137,6 +162,29 @@ class AuraEffectBehaviorNoRedundantStartEffectTest extends ActorTest { assert(obj.Aura.contains(Aura.Plasma)) obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500) updateProbe.expectNoMessage(1500 milliseconds) + } + } +} + +class AuraEffectBehaviorNoOverrideStartEffectTest extends ActorTest { + val obj = new AuraTest.Entity() + val updateProbe = new TestProbe(system) + obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor") + + "AuraEffectBehavior" should { + "not replace a long-running effect with a short-running effect" in { + assert(obj.Aura.isEmpty) + obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500) + val msg1 = updateProbe.receiveOne(100 milliseconds) + assert( + msg1 match { + case AuraTest.DoUpdateAuraEffect() => true + case _ => false + } + ) + assert(obj.Aura.contains(Aura.Plasma)) + obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 500) + updateProbe.expectNoMessage(1500 milliseconds) //effect has not ended naturally assert(obj.Aura.contains(Aura.Plasma)) } From e27e8275521b447d2b3abb66e0f7bbc77772eacb Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 25 Aug 2020 01:12:53 -0400 Subject: [PATCH 14/19] merge rebase; accommodation for suppressing aura where no aura should be displayed; new radial degrade calculations --- .../objects/ballistics/AggravatedDamage.scala | 19 +++ .../aura/AuraEffectBehavior.scala | 2 +- .../damage/AggravatedBehavior.scala | 35 ++---- .../actors/session/SessionActor.scala | 113 ++++-------------- .../psforever/objects/GlobalDefinitions.scala | 6 +- .../definition/ProjectileDefinition.scala | 41 ++++++- .../vital/damage/DamageModifiers.scala | 7 +- 7 files changed, 98 insertions(+), 125 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala index 7fad9e15..91c9c3c2 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala +++ b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala @@ -156,4 +156,23 @@ object AggravatedDamage { vanu_aggravated, targets ) + + def burning(resolution: ProjectileResolution.Value): ProjectileResolution.Value = { + resolution match { + case ProjectileResolution.AggravatedDirect => ProjectileResolution.AggravatedDirectBurn + case ProjectileResolution.AggravatedSplash => ProjectileResolution.AggravatedSplashBurn + case _ => resolution + } + } + + def basicDamageType(resolution: ProjectileResolution.Value): DamageType.Value = { + resolution match { + case ProjectileResolution.AggravatedDirect | ProjectileResolution.AggravatedDirectBurn => + DamageType.Direct + case ProjectileResolution.AggravatedSplash | ProjectileResolution.AggravatedSplashBurn => + DamageType.Splash + case _ => + DamageType.None + } + } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala index 96b6f2c6..1a041b62 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala @@ -171,7 +171,7 @@ object AuraEffectBehavior { override def isCancelled : Boolean = timer.isCancelled - override def cancel: Boolean = timer.cancel + override def cancel(): Boolean = timer.cancel() } object Entry { diff --git a/common/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala index e2a477e9..ad211cdb 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala @@ -26,7 +26,8 @@ trait AggravatedBehavior { projectile.profile.Aggravated match { case Some(damage) if projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) && - damage.effect_type != Aura.Nothing && + damage.info.exists(_.damage_type == AggravatedDamage.basicDamageType(data.resolution)) && + damage.effect_type != Aura.Nothing && (projectile.quality == ProjectileQuality.AggravatesTarget || damage.targets.exists(validation => validation.test(AggravatedObject))) => TryAggravationEffectActivate(damage, data) @@ -61,7 +62,7 @@ trait AggravatedBehavior { private def SetupAggravationEntry(aggravation: AggravatedDamage, data: ResolvedProjectile): Boolean = { val effect = aggravation.effect_type - aggravation.info.find(_.damage_type == AggravatedBehavior.basicDamageType(data.resolution)) match { + aggravation.info.find(_.damage_type == AggravatedDamage.basicDamageType(data.resolution)) match { case Some(info) => val timing = aggravation.timing val duration = timing.duration @@ -77,7 +78,6 @@ trait AggravatedBehavior { case None => (1000L, (duration / 1000).toInt) } - //val leftoverTime = duration - (tick * iterations) //quality per tick val totalPower = (duration.toFloat / info.infliction_rate).toInt - 1 val averagePowerPerTick = totalPower.toFloat / iterations @@ -108,7 +108,7 @@ trait AggravatedBehavior { powerOffset: List[Float] ): AggravatedBehavior.Entry = { val aggravatedDamageInfo = ResolvedProjectile( - AggravatedBehavior.burning(data.resolution), + AggravatedDamage.burning(data.resolution), data.projectile, target, data.damage_model, @@ -168,7 +168,7 @@ trait AggravatedBehavior { def CleanupAggravationTimer(id: Long): Unit = { //remove and cancel timer aggravationToTimer.remove(id) match { - case Some(timer) => timer.cancel + case Some(timer) => timer.cancel() case _ => ; } } @@ -179,9 +179,9 @@ trait AggravatedBehavior { } def EndAllAggravation(): Unit = { - entryIdToEntry.clear - aggravationToTimer.values.foreach { _.cancel } - aggravationToTimer.clear + entryIdToEntry.clear() + aggravationToTimer.values.foreach { _.cancel() } + aggravationToTimer.clear() } def AggravatedReaction: Boolean = ongoingAggravated @@ -206,23 +206,4 @@ object AggravatedBehavior { private case class Entry(id: Long, effect: Aura, retime: Long, data: ResolvedProjectile, qualityPerTick: List[Float]) private case class Aggravate(id: Long, iterations: Int) - - private def burning(resolution: ProjectileResolution.Value): ProjectileResolution.Value = { - resolution match { - case ProjectileResolution.AggravatedDirect => ProjectileResolution.AggravatedDirectBurn - case ProjectileResolution.AggravatedSplash => ProjectileResolution.AggravatedSplashBurn - case _ => resolution - } - } - - private def basicDamageType(resolution: ProjectileResolution.Value): DamageType.Value = { - resolution match { - case ProjectileResolution.AggravatedDirect | ProjectileResolution.AggravatedDirectBurn => - DamageType.Direct - case ProjectileResolution.AggravatedSplash | ProjectileResolution.AggravatedSplashBurn => - DamageType.Splash - case _ => - DamageType.None - } - } } diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index 5d4dae49..fdd13f9d 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -1,26 +1,26 @@ package net.psforever.actors.session -import java.util.concurrent.TimeUnit -import akka.actor.MDCContextAware.Implicits._ +//language imports import akka.actor.typed import akka.actor.typed.receptionist.Receptionist +import akka.actor.typed.scaladsl.adapter._ import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware} -import java.util.concurrent.atomic.AtomicInteger +import akka.pattern.ask +import akka.util.Timeout +import java.util.concurrent.TimeUnit +import MDCContextAware.Implicits._ import org.log4s.MDC -import scala.collection.mutable.LongMap +import scala.collection.mutable import scala.concurrent.{Await, Future} import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global -import scala.util.{Failure, Success} +import scala.util.Success import scodec.bits.ByteVector -import services.properties.PropertyOverrideManager -import org.joda.time.{LocalDateTime, Period} -import csr.{CSRWarp, CSRZone, Traveler} -import MDCContextAware.Implicits._ -import net.psforever.objects.{GlobalDefinitions, _} -import net.psforever.objects.avatar.{Avatar, Certification, DeployableToolbox} +//project imports +import net.psforever.login.{DropCryptoSession, DropSession, HelloFriend, RawPacket} +import net.psforever.login.WorldSession._ import net.psforever.objects._ -import net.psforever.objects.avatar.{Certification, DeployableToolbox, FirstTimeEvents} +import net.psforever.objects.avatar.{Avatar, Certification, Cosmetic, DeployableToolbox} import net.psforever.objects.ballistics._ import net.psforever.objects.ce._ import net.psforever.objects.definition._ @@ -29,6 +29,7 @@ import net.psforever.objects.entity.{SimpleWorldEntity, WorldEntity} import net.psforever.objects.equipment.{EffectTarget, Equipment, FireModeSwitch, JammableUnit} import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} import net.psforever.objects.inventory.{Container, InventoryItem} +import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.containable.Containable import net.psforever.objects.serverobject.damage.Damageable @@ -48,7 +49,6 @@ import net.psforever.objects.serverobject.terminals._ import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.serverobject.turret.{FacilityTurret, WeaponTurret} import net.psforever.objects.serverobject.zipline.ZipLinePath -import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.teamwork.Squad import net.psforever.objects.vehicles._ import net.psforever.objects.vehicles.Utility.InternalTelepad @@ -56,21 +56,12 @@ import net.psforever.objects.vital._ import net.psforever.objects.zones.{Zone, ZoneHotSpotProjector, Zoning} import net.psforever.packet._ import net.psforever.packet.control._ -import net.psforever.packet.game.objectcreate._ import net.psforever.packet.game.{HotSpotInfo => PacketHotSpotInfo, _} -import net.psforever.objects.zones.{InterstellarCluster, Zone, ZoneHotSpotProjector, Zoning} -import net.psforever.packet._ -import net.psforever.packet.control._ -import net.psforever.packet.game._ import net.psforever.packet.game.objectcreate._ -import net.psforever.packet.game.{HotSpotInfo => PacketHotSpotInfo} -import net.psforever.persistence -import net.psforever.types._ -import org.log4s.MDC -import scodec.bits.ByteVector import net.psforever.services.ServiceManager.LookupResult import net.psforever.services.account.{AccountPersistenceService, PlayerToken, ReceiveAccountData, RetrieveAccountData} import net.psforever.services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage, AvatarServiceResponse} +import net.psforever.services.chat.ChatService import net.psforever.services.galaxy.{GalaxyAction, GalaxyResponse, GalaxyServiceMessage, GalaxyServiceResponse} import net.psforever.services.local.support.RouterTelepadActivation import net.psforever.services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse} @@ -84,20 +75,8 @@ import net.psforever.services.teamwork.{ } import net.psforever.services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse} import net.psforever.services.{InterstellarClusterService, RemoverActor, Service, ServiceManager} -import net.psforever.login.{DropCryptoSession, DropSession, HelloFriend, RawPacket} +import net.psforever.types._ import net.psforever.util.{Config, DefinitionUtil} -import net.psforever.login.WorldSession._ -import net.psforever.zones.Zones -import net.psforever.services.chat.ChatService -import net.psforever.objects.avatar.Cosmetic -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration._ -import scala.concurrent.{Await, Future} -import scala.util.Success -import akka.actor.typed.scaladsl.adapter._ -import akka.pattern.ask -import akka.util.Timeout -import scala.collection.mutable object SessionActor { @@ -1301,49 +1280,6 @@ class SessionActor extends Actor with MDCContextAware { taskResolver ! RegisterNewAvatar(player) } - case msg @ Zoning.InstantAction.Located(zone, _, spawn_point) => - //in between subsequent reply messages, it does not matter if the destination changes - //so long as there is at least one destination at all (including the fallback) - if (ContemplateZoningResponse(Zoning.InstantAction.Request(player.Faction), cluster)) { - val (pos, ori) = spawn_point.SpecificPoint(player) - SpawnThroughZoningProcess(zone, pos, ori) - } else if (zoningStatus != Zoning.Status.None) { - instantActionFallbackDestination = Some(msg) - } - - case Zoning.InstantAction.NotLocated() => - instantActionFallbackDestination match { - case Some(Zoning.InstantAction.Located(zone, _, spawn_point)) - if spawn_point.Owner.Faction == player.Faction && !spawn_point.Offline => - if (ContemplateZoningResponse(Zoning.InstantAction.Request(player.Faction), cluster)) { - val (pos, ori) = spawn_point.SpecificPoint(player) - SpawnThroughZoningProcess(zone, pos, ori) - } else if (zoningCounter == 0) { - CancelZoningProcessWithReason("@InstantActionNoHotspotsAvailable") - } - case _ => - //no instant action available - CancelZoningProcessWithReason("@InstantActionNoHotspotsAvailable") - } - - case Zoning.Recall.Located(zone, spawn_point) => - if (ContemplateZoningResponse(Zoning.Recall.Request(player.Faction, zone.Id), cluster)) { - val (pos, ori) = spawn_point.SpecificPoint(player) - SpawnThroughZoningProcess(zone, pos, ori) - } - - case Zoning.Recall.Denied(reason) => - CancelZoningProcessWithReason(s"@norecall_sanctuary_$reason", Some(ChatMessageType.CMT_QUIT)) - - case Zoning.Quit() => - if (ContemplateZoningResponse(Zoning.Quit(), self)) { - log.info("Good-bye") - ImmediateDisconnect() - } - - case ZoningReset() => - CancelZoningProcess() - case NewPlayerLoaded(tplayer) => //new zone log.info(s"Player ${tplayer.Name} has been loaded") @@ -3792,7 +3728,7 @@ class SessionActor extends Actor with MDCContextAware { } } continent.VehicleEvents ! VehicleServiceMessage( - continent.Id, + continent.id, VehicleAction.UpdateAmsSpawnPoint(continent) ) upstreamMessageCount = 0 @@ -7753,7 +7689,8 @@ class SessionActor extends Actor with MDCContextAware { val outProjectile = if(projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated)) { val quality = projectile.profile.Aggravated match { case Some(aggravation) - if aggravation.targets.exists(validation => validation.test(target)) => + if aggravation.targets.exists(validation => validation.test(target)) && + aggravation.info.exists(_.damage_type == AggravatedDamage.basicDamageType(resolution)) => ProjectileQuality.AggravatesTarget case _ => ProjectileQuality.Normal @@ -8013,12 +7950,12 @@ class SessionActor extends Actor with MDCContextAware { * @return `true`, if the desired certification requirements are met; `false`, otherwise */ def ConstructionItemPermissionComparison( - sample: Set[CertificationType.Value], - test: Set[CertificationType.Value] + sample: Set[Certification], + test: Set[Certification] ): Boolean = { - import CertificationType._ - val engineeringCerts: Set[CertificationType.Value] = Set(AssaultEngineering, FortificationEngineering) - val testDiff: Set[CertificationType.Value] = test diff (engineeringCerts ++ Set(AdvancedEngineering)) + import Certification._ + val engineeringCerts: Set[Certification] = Set(AssaultEngineering, FortificationEngineering) + val testDiff: Set[Certification] = test diff (engineeringCerts ++ Set(AdvancedEngineering)) //substitute `AssaultEngineering` and `FortificationEngineering` for `AdvancedEngineering` val sampleIntersect = if (sample contains AdvancedEngineering) { engineeringCerts @@ -8602,7 +8539,7 @@ class SessionActor extends Actor with MDCContextAware { ) { //do not delete if vehicle has passengers or cargo continent.VehicleEvents ! VehicleServiceMessage( - continent.Id, + continent.id, VehicleAction.UnloadVehicle(pguid, continent, vehicle, topLevel) ) None @@ -9438,7 +9375,7 @@ class SessionActor extends Actor with MDCContextAware { ) taskResolver ! (if (projectile.HasGUID) { continent.AvatarEvents ! AvatarServiceMessage( - continent.Id, + continent.id, AvatarAction.ProjectileExplodes( player.GUID, projectile.GUID, diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 8d20e52d..b31e95cb 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -3918,7 +3918,11 @@ object GlobalDefinitions { 2000, 0f, true, - List(TargetValidation(EffectTarget.Category.Aircraft, EffectTarget.Validation.Aircraft)) + List( + TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player), + TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.Vehicle), + TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret) + ) ) starfire_projectile.InitialVelocity = 45 starfire_projectile.Lifespan = 7.8f diff --git a/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala b/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala index 3f68364c..6c225fb0 100644 --- a/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala @@ -16,33 +16,68 @@ class ProjectileDefinition(objectId: Int) with JammingUnit with StandardDamageProfile with DamageModifiers { + /** ascertain that this object is a valid projectile type */ private val projectileType: Projectiles.Value = Projectiles(objectId) //let throw NoSuchElementException + /** how much faster (or slower) the projectile moves (m/s^2^) */ private var acceleration: Int = 0 + /** when the acceleration stops being applied (s) */ private var accelerationUntil: Float = 0f + /** the type of damage that the projectile causes */ private var damageType: DamageType.Value = DamageType.None + /** an auxillary type of damage that the projectile causes */ private var damageTypeSecondary: DamageType.Value = DamageType.None + /** against Infantry targets, this projectile does not do armor damage */ private var damageToHealthOnly: Boolean = false + /** number of seconds before an airborne projectile's damage begins to degrade (s) */ private var degradeDelay: Float = 1f + /** the rate of degrade of projectile damage after the degrade delay */ private var degradeMultiplier: Float = 1f + /** the out-of-the-muzzle speed of a projectile (m/s) */ private var initialVelocity: Int = 1 + /** for how long the projectile exists (s) */ private var lifespan: Float = 1f + /** for radial damage, how much damage has been lost the further away from the impact point (m) */ private var damageAtEdge: Float = 1f + /** for radial damage, the radial distance of the explosion effect (m) */ private var damageRadius: Float = 1f + /** for lashing damage, how far away a target will be affected by the projectile (m) */ private var lashRadius : Float = 0f + /** use a specific modifier as a part of damage calculations */ private var useDamage1Subtract: Boolean = false - private var existsOnRemoteClients: Boolean = false //`true` spawns a server-managed object - private var remoteClientData: (Int, Int) = - (0, 0) //artificial values; for ObjectCreateMessage packet (oicw_little_buddy is undefined) + /** the projectile is represented by a server-side entity + * that is updated by the projectile owner + * and transmitted to all projectile observers; + * `true` spawns a server-managed object */ + private var existsOnRemoteClients: Boolean = false + /** the values used by the `ObjectCreateMessage` packet for construction of the server-managed projectile + * `0, 0` are artificial values; + * the oicw_little_buddy is undefined for these values */ + private var remoteClientData: (Int, Int) = (0, 0) + /** some other entity confers projectile damage; + * a set value should not `None` and not `0` but is preferred to be the damager's uid */ private var damageProxy: Option[Int] = None + /** this projectile follows its target, after a fashion */ private var autoLock: Boolean = false + /** na; + * currently used with jammer properties only */ private var additionalEffect: Boolean = false + /** the projectile tries to confer the jammered status effect to its target(s) */ private var jammerProjectile: Boolean = false + /** projectile takes the form of a type of "grenade"; + * grenades arc with gravity rather than travel in a relatively straight path */ private var grenade_projectile : Boolean = false + /** projectile tries to confers aggravated damage burn to its target */ private var aggravated_damage : Option[AggravatedDamage] = None //derived calculations + /** the calculated distance at which the projectile have traveled far enough to despawn (m); + * typically handled as the projectile no longer performing damage; + * occasionally, this value is purely mathematical as opposed to realistic, e.g., the melee weapons */ private var distanceMax: Float = 0f + /** how far the projectile will travel while accelerating (m) */ private var distanceFromAcceleration: Float = 0f + /** how far the projectile will travel while no degrading (m) */ private var distanceNoDegrade: Float = 0f + /** after acceleration, if any, what is the final speed of the projectile (m/s) */ private var finalVelocity: Float = 0f Name = "projectile" Modifiers = DamageModifiers.DistanceDegrade diff --git a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala index e0009f1e..fff0a754 100644 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala @@ -97,14 +97,11 @@ object DamageModifiers { def Calculate: DamageModifiers.Format = function private def function(damage: Int, data: ResolvedProjectile): Int = { - val projectile = data.projectile - val profile = projectile.profile + val profile = data.projectile.profile val distance = Vector3.Distance(data.hit_pos, data.target.Position) val radius = profile.DamageRadius if (distance <= radius) { - val base: Float = profile.DamageAtEdge - val degrade: Float = (1 - base) * ((radius - distance) / radius) + base - (damage * degrade).toInt + damage * (1f - (profile.DamageAtEdge * distance / radius)).toInt } else { 0 } From bb3c0f5d91a5b628395778b0874dfbd1d92dc730 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 25 Aug 2020 15:24:26 -0400 Subject: [PATCH 15/19] test repairs --- .../actor/service/AvatarServiceTest.scala | 25 ++++++++++++------ .../vital/damage/DamageModifiers.scala | 2 +- .../net/psforever/objects/zones/Zone.scala | 2 +- src/test/scala/objects/DamageModelTests.scala | 20 +++++++------- src/test/scala/objects/ZoneTest.scala | 26 ++++++++++++++++--- 5 files changed, 51 insertions(+), 24 deletions(-) diff --git a/server/src/test/scala/actor/service/AvatarServiceTest.scala b/server/src/test/scala/actor/service/AvatarServiceTest.scala index f912c0f0..4f9d2365 100644 --- a/server/src/test/scala/actor/service/AvatarServiceTest.scala +++ b/server/src/test/scala/actor/service/AvatarServiceTest.scala @@ -5,7 +5,7 @@ import akka.actor.Props import akka.routing.RandomPool import actor.base.ActorTest import net.psforever.objects._ -import net.psforever.objects.guid.{GUIDTask, TaskResolver} +import net.psforever.objects.guid.{NumberPoolHub, TaskResolver} import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectClass, ObjectCreateMessageParent, PlacementData} import net.psforever.packet.game.{ObjectCreateMessage, PlayerStateMessageUpstream} @@ -17,6 +17,7 @@ import scala.concurrent.duration._ import akka.actor.typed.scaladsl.adapter._ import net.psforever.actors.zone.ZoneActor import net.psforever.objects.avatar.Avatar +import net.psforever.objects.guid.source.LimitedNumberSource class AvatarService1Test extends ActorTest { "AvatarService" should { @@ -511,10 +512,13 @@ class AvatarReleaseTest extends ActorTest { val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = { AddPool("dynamic", 1 to 10) } } + val guid1: NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(100)) + zone.GUID(guid1) val service = system.actorOf(Props(classOf[AvatarService], zone), "release-test-service") - val taskResolver = system.actorOf(Props[TaskResolver](), "release-test-resolver") zone.actor = system.spawn(ZoneActor(zone), "release-test-zone") val obj = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1)) + guid1.register(obj) + guid1.register(obj.Slot(5).Equipment.get) obj.Continent = "test" obj.Release @@ -523,7 +527,6 @@ class AvatarReleaseTest extends ActorTest { expectNoMessage(100 milliseconds) //spacer service ! Service.Join("test") - taskResolver ! GUIDTask.RegisterObjectTask(obj)(zone.GUID) assert(zone.Corpses.isEmpty) zone.Population ! Zone.Corpse.Add(obj) expectNoMessage(200 milliseconds) //spacer @@ -561,10 +564,13 @@ class AvatarReleaseEarly1Test extends ActorTest { val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = { AddPool("dynamic", 1 to 10) } } + val guid1: NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(100)) + zone.GUID(guid1) val service = system.actorOf(Props(classOf[AvatarService], zone), "release-test-service") - val taskResolver = system.actorOf(Props[TaskResolver](), "release-test-resolver") zone.actor = system.spawn(ZoneActor(zone), "release-test-zone") val obj = Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1)) + guid1.register(obj) + guid1.register(obj.Slot(5).Equipment.get) obj.Continent = "test" obj.Release @@ -573,7 +579,6 @@ class AvatarReleaseEarly1Test extends ActorTest { expectNoMessage(100 milliseconds) //spacer service ! Service.Join("test") - taskResolver ! GUIDTask.RegisterObjectTask(obj)(zone.GUID) assert(zone.Corpses.isEmpty) zone.Population ! Zone.Corpse.Add(obj) expectNoMessage(200 milliseconds) //spacer @@ -612,14 +617,19 @@ class AvatarReleaseEarly2Test extends ActorTest { val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = { AddPool("dynamic", 1 to 10) } } + val guid1: NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(100)) + zone.GUID(guid1) val service = system.actorOf(Props(classOf[AvatarService], zone), "release-test-service") - val taskResolver = system.actorOf(Props[TaskResolver](), "release-test-resolver") zone.actor = system.spawn(ZoneActor(zone), "release-test-zone") val objAlt = Player( Avatar(0, "TestCharacter2", PlanetSideEmpire.NC, CharacterGender.Male, 1, CharacterVoice.Voice1) ) //necessary clutter - val obj = Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1)) + objAlt.GUID = PlanetSideGUID(3) + objAlt.Slot(5).Equipment.get.GUID = PlanetSideGUID(4) + val obj = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1)) + guid1.register(obj) + guid1.register(obj.Slot(5).Equipment.get) obj.Continent = "test" obj.Release @@ -628,7 +638,6 @@ class AvatarReleaseEarly2Test extends ActorTest { expectNoMessage(100 milliseconds) //spacer service ! Service.Join("test") - taskResolver ! GUIDTask.RegisterObjectTask(obj)(zone.GUID) assert(zone.Corpses.isEmpty) zone.Population ! Zone.Corpse.Add(obj) expectNoMessage(200 milliseconds) //spacer diff --git a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala index fff0a754..2b9739d5 100644 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala @@ -101,7 +101,7 @@ object DamageModifiers { val distance = Vector3.Distance(data.hit_pos, data.target.Position) val radius = profile.DamageRadius if (distance <= radius) { - damage * (1f - (profile.DamageAtEdge * distance / radius)).toInt + damage - (damage * profile.DamageAtEdge * distance / radius).toInt } else { 0 } diff --git a/src/main/scala/net/psforever/objects/zones/Zone.scala b/src/main/scala/net/psforever/objects/zones/Zone.scala index 5f17e46b..debbf627 100644 --- a/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -451,7 +451,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) { * `false`, if the new pool can not be created because the system has already been started */ def AddPool(name: String, pool: Seq[Int]): Boolean = { - if (accessor == Default.Actor) { + if (accessor == Default.Actor || accessor == null) { guid.AddPool(name, pool.toList) true } else { diff --git a/src/test/scala/objects/DamageModelTests.scala b/src/test/scala/objects/DamageModelTests.scala index 8da2e8fc..656e91fc 100644 --- a/src/test/scala/objects/DamageModelTests.scala +++ b/src/test/scala/objects/DamageModelTests.scala @@ -64,14 +64,7 @@ class DamageCalculationsTests extends Specification { } "degrade over distance damage modifier (no degrade)" in { - val resprojectile2 = ResolvedProjectile( - ProjectileResolution.Splash, - projectile, - SourceEntry(target), - target.DamageModel, - Vector3(10, 0, 0) - ) - DamageModifiers.DistanceDegrade.Calculate(100, resprojectile2) == 100 mustEqual true + DamageModifiers.DistanceDegrade.Calculate(100, resprojectile) == 100 mustEqual true } "degrade over distance damage modifier (some degrade)" in { @@ -109,7 +102,14 @@ class DamageCalculationsTests extends Specification { } "degrade at radial distance damage modifier (some degrade)" in { - val damage = DamageModifiers.RadialDegrade.Calculate(100, resprojectile) + val resprojectile2 = ResolvedProjectile( + ProjectileResolution.Splash, + projectile, + SourceEntry(target), + target.DamageModel, + Vector3(12, 0, 0) + ) + val damage = DamageModifiers.RadialDegrade.Calculate(100, resprojectile2) damage < 100 && damage > 0 mustEqual true } @@ -119,7 +119,7 @@ class DamageCalculationsTests extends Specification { projectile, SourceEntry(target), target.DamageModel, - Vector3(1000, 0, 0) + Vector3(100, 0, 0) ) DamageModifiers.RadialDegrade.Calculate(100, resprojectile2) == 0 mustEqual true } diff --git a/src/test/scala/objects/ZoneTest.scala b/src/test/scala/objects/ZoneTest.scala index 520ad0c4..7ac0988e 100644 --- a/src/test/scala/objects/ZoneTest.scala +++ b/src/test/scala/objects/ZoneTest.scala @@ -110,7 +110,8 @@ class ZoneTest extends Specification { zone.AddPool("pool2", (51 to 75).toList) val obj = new TestObject() - guid1.register(obj, "pool2").isSuccess mustEqual true + val registration = guid1.register(obj, "pool2") + registration.isSuccess mustEqual true guid1.WhichPool(obj).contains("pool2") mustEqual true zone.GUID(new NumberPoolHub(new LimitedNumberSource(150))) mustEqual false @@ -214,13 +215,15 @@ class ZonePopulationTest extends ActorTest { override def SetupNumberPools() = {} } val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) + val player = Player(avatar) + player.GUID = PlanetSideGUID(1) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) assert(zone.Players.isEmpty) assert(zone.LivePlayers.isEmpty) zone.Population ! Zone.Population.Join(avatar) - zone.Population ! Zone.Population.Spawn(avatar, Player(avatar), null) + zone.Population ! Zone.Population.Spawn(avatar, player, null) expectNoMessage(Duration.create(200, "ms")) assert(zone.Players.size == 1) assert(zone.Players.head == avatar) @@ -232,10 +235,12 @@ class ZonePopulationTest extends ActorTest { override def SetupNumberPools() = {} } val avatar = Avatar(1, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) + val player = Player(avatar) + player.GUID = PlanetSideGUID(1) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) receiveOne(Duration.create(200, "ms")) //consume zone.Population ! Zone.Population.Join(avatar) - zone.Population ! Zone.Population.Spawn(avatar, Player(avatar), null) + zone.Population ! Zone.Population.Spawn(avatar, player, null) expectNoMessage(Duration.create(100, "ms")) assert(zone.Players.size == 1) @@ -251,6 +256,7 @@ class ZonePopulationTest extends ActorTest { val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val player = Player(avatar) + player.GUID = PlanetSideGUID(1) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) zone.Population ! Zone.Population.Join(avatar) @@ -271,6 +277,7 @@ class ZonePopulationTest extends ActorTest { val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val player = Player(avatar) + player.GUID = PlanetSideGUID(1) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) zone.Population ! Zone.Population.Join(avatar) @@ -318,7 +325,9 @@ class ZonePopulationTest extends ActorTest { val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val player1 = Player(avatar) + player1.GUID = PlanetSideGUID(1) val player2 = Player(avatar) + player2.GUID = PlanetSideGUID(2) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) zone.Population ! Zone.Population.Join(avatar) @@ -344,6 +353,7 @@ class ZonePopulationTest extends ActorTest { val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val player = Player(avatar) + player.GUID = PlanetSideGUID(1) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) @@ -364,10 +374,12 @@ class ZonePopulationTest extends ActorTest { override def SetupNumberPools() = {} } val avatar = Avatar(2, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) + val player = Player(avatar) + player.GUID = PlanetSideGUID(1) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) zone.Population ! Zone.Population.Join(avatar) - zone.Population ! Zone.Population.Spawn(avatar, Player(avatar), null) + zone.Population ! Zone.Population.Spawn(avatar, player, null) expectNoMessage(Duration.create(100, "ms")) assert(zone.Players.size == 1) @@ -388,6 +400,7 @@ class ZonePopulationTest extends ActorTest { override def SetupNumberPools() = {} } val player = Player(Avatar(3, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) + player.GUID = PlanetSideGUID(1) player.Release zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) @@ -404,6 +417,7 @@ class ZonePopulationTest extends ActorTest { override def SetupNumberPools() = {} } val player = Player(Avatar(4, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) + player.GUID = PlanetSideGUID(1) player.Release zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) @@ -422,10 +436,13 @@ class ZonePopulationTest extends ActorTest { override def SetupNumberPools() = {} } val player1 = Player(Avatar(5, "Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) + player1.GUID = PlanetSideGUID(1) player1.Release val player2 = Player(Avatar(6, "Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) + player2.GUID = PlanetSideGUID(2) player2.Release val player3 = Player(Avatar(7, "Chord3", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) + player3.GUID = PlanetSideGUID(3) player3.Release zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) @@ -450,6 +467,7 @@ class ZonePopulationTest extends ActorTest { override def SetupNumberPools() = {} } val player = Player(Avatar(8, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) + player.GUID = PlanetSideGUID(1) //player.Release !!important zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) From 89a7f180dd5205c060a66c4d2ac76fa086e48e13 Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 26 Aug 2020 14:40:16 -0400 Subject: [PATCH 16/19] merge rebase hell --- src/main/scala/net/psforever/actors/session/SessionActor.scala | 1 + src/main/scala/net/psforever/objects/avatar/PlayerControl.scala | 2 +- .../net/psforever/objects/ballistics/AggravatedDamage.scala | 0 .../net/psforever/objects/ballistics/ProjectileQuality.scala | 0 .../scala/net/psforever/objects/serverobject/aura/Aura.scala | 0 .../net/psforever/objects/serverobject/aura/AuraContainer.scala | 0 .../objects/serverobject/aura/AuraEffectBehavior.scala | 0 .../objects/serverobject/damage/AggravatedBehavior.scala | 0 .../net/psforever/packet/game/AggravatedDamageMessage.scala | 0 .../test/scala/game/AggravatedDamageMessageTest.scala | 2 +- {common/src => src}/test/scala/objects/AuraTest.scala | 0 11 files changed, 3 insertions(+), 2 deletions(-) rename {common/src => src}/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala (100%) rename {common/src => src}/main/scala/net/psforever/objects/ballistics/ProjectileQuality.scala (100%) rename {common/src => src}/main/scala/net/psforever/objects/serverobject/aura/Aura.scala (100%) rename {common/src => src}/main/scala/net/psforever/objects/serverobject/aura/AuraContainer.scala (100%) rename {common/src => src}/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala (100%) rename {common/src => src}/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala (100%) rename {common/src => src}/main/scala/net/psforever/packet/game/AggravatedDamageMessage.scala (100%) rename {common/src => src}/test/scala/game/AggravatedDamageMessageTest.scala (100%) rename {common/src => src}/test/scala/objects/AuraTest.scala (100%) diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index fdd13f9d..3e5f5407 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -77,6 +77,7 @@ import net.psforever.services.vehicle.{VehicleAction, VehicleResponse, VehicleSe import net.psforever.services.{InterstellarClusterService, RemoverActor, Service, ServiceManager} import net.psforever.types._ import net.psforever.util.{Config, DefinitionUtil} +import net.psforever.zones.Zones object SessionActor { diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index bed0afbb..ffdd20cb 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -929,7 +929,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm } def UpdateAuraEffect(target: AuraEffectBehavior.Target) : Unit = { - import services.avatar.{AvatarAction, AvatarServiceMessage} + import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} val zone = target.Zone val value = target.Aura.foldLeft(0)(_ + PlayerControl.auraEffectToAttributeValue(_)) zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.PlanetsideAttributeToAll(target.GUID, 54, value)) diff --git a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala b/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala similarity index 100% rename from common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala rename to src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala diff --git a/common/src/main/scala/net/psforever/objects/ballistics/ProjectileQuality.scala b/src/main/scala/net/psforever/objects/ballistics/ProjectileQuality.scala similarity index 100% rename from common/src/main/scala/net/psforever/objects/ballistics/ProjectileQuality.scala rename to src/main/scala/net/psforever/objects/ballistics/ProjectileQuality.scala diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aura/Aura.scala b/src/main/scala/net/psforever/objects/serverobject/aura/Aura.scala similarity index 100% rename from common/src/main/scala/net/psforever/objects/serverobject/aura/Aura.scala rename to src/main/scala/net/psforever/objects/serverobject/aura/Aura.scala diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraContainer.scala b/src/main/scala/net/psforever/objects/serverobject/aura/AuraContainer.scala similarity index 100% rename from common/src/main/scala/net/psforever/objects/serverobject/aura/AuraContainer.scala rename to src/main/scala/net/psforever/objects/serverobject/aura/AuraContainer.scala diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala b/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala similarity index 100% rename from common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala rename to src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala diff --git a/common/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala b/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala similarity index 100% rename from common/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala rename to src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala diff --git a/common/src/main/scala/net/psforever/packet/game/AggravatedDamageMessage.scala b/src/main/scala/net/psforever/packet/game/AggravatedDamageMessage.scala similarity index 100% rename from common/src/main/scala/net/psforever/packet/game/AggravatedDamageMessage.scala rename to src/main/scala/net/psforever/packet/game/AggravatedDamageMessage.scala diff --git a/common/src/test/scala/game/AggravatedDamageMessageTest.scala b/src/test/scala/game/AggravatedDamageMessageTest.scala similarity index 100% rename from common/src/test/scala/game/AggravatedDamageMessageTest.scala rename to src/test/scala/game/AggravatedDamageMessageTest.scala index 61529e56..7cc9783a 100644 --- a/common/src/test/scala/game/AggravatedDamageMessageTest.scala +++ b/src/test/scala/game/AggravatedDamageMessageTest.scala @@ -1,10 +1,10 @@ // Copyright (c) 2020 PSForever package game -import org.specs2.mutable._ import net.psforever.packet._ import net.psforever.packet.game._ import net.psforever.types.PlanetSideGUID +import org.specs2.mutable._ import scodec.bits._ class AggravatedDamageMessageTest extends Specification { diff --git a/common/src/test/scala/objects/AuraTest.scala b/src/test/scala/objects/AuraTest.scala similarity index 100% rename from common/src/test/scala/objects/AuraTest.scala rename to src/test/scala/objects/AuraTest.scala From 285d5c94ae8df20fc39244279b501489a58dc622 Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 26 Aug 2020 19:10:43 -0400 Subject: [PATCH 17/19] dragon fireball burn damage modification --- .../psforever/objects/GlobalDefinitions.scala | 9 ++++----- .../vital/damage/DamageModifiers.scala | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index b31e95cb..a68fbcf6 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2616,7 +2616,7 @@ object GlobalDefinitions { flamethrower_fireball.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.9f, 500), AggravatedInfo(DamageType.Splash, 0.9f, 500)), Aura.Fire, - AggravatedTiming(5000), + AggravatedTiming(5000, 10), 0.1f, false, false, @@ -2627,9 +2627,8 @@ object GlobalDefinitions { ProjectileDefinition.CalculateDerivedFields(flamethrower_fireball) flamethrower_fireball.Modifiers = List( DamageModifiers.InfantryAggravatedDirect, - DamageModifiers.InfantryAggravatedDirectBurn, DamageModifiers.InfantryAggravatedSplash, - DamageModifiers.InfantryAggravatedSplashBurn, + DamageModifiers.FireballAggravatedBurn, DamageModifiers.RadialDegrade ) @@ -2646,7 +2645,7 @@ object GlobalDefinitions { flamethrower_projectile.Aggravated = AggravatedDamage( List(AggravatedInfo(DamageType.Direct, 0.5f, 500)), Aura.Fire, - AggravatedTiming(5000), + AggravatedTiming(5000, 10), 0.5f, false, false, @@ -2659,7 +2658,7 @@ object GlobalDefinitions { ProjectileDefinition.CalculateDerivedFields(flamethrower_projectile) flamethrower_projectile.Modifiers = List( DamageModifiers.InfantryAggravatedDirect, - DamageModifiers.InfantryAggravatedDirectBurn, + DamageModifiers.FireballAggravatedBurn, DamageModifiers.MaxDistanceCutoff ) diff --git a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala index 2b9739d5..24c7fdca 100644 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala @@ -278,6 +278,25 @@ object DamageModifiers { } } + /** + * For damage application that involves aggravation of a fireball (Dragon secondary fire mode), + * perform 1 damage. + * @see `ResolvedProjectile` + */ + case object FireballAggravatedBurn extends Mod { + def Calculate: DamageModifiers.Format = formula + + private def formula(damage: Int, data: ResolvedProjectile): Int = { + if (data.resolution == ProjectileResolution.AggravatedDirectBurn || + data.resolution == ProjectileResolution.AggravatedSplashBurn) { + //add resist to offset resist subtraction later + 1 + data.damage_model.ResistUsing(data)(data) + } else { + damage + } + } + } + /** * The initial application of aggravated damage against an aircraft target. * Primarily for use in the starfire weapon system. From 37af460a89f09e198c500aa733b150e2e69c1868 Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 26 Aug 2020 22:42:07 -0400 Subject: [PATCH 18/19] fireball burn effect should always be 1 as long as it does damage, despite radial degrade --- src/main/scala/net/psforever/objects/GlobalDefinitions.scala | 4 ++-- .../net/psforever/objects/vital/damage/DamageModifiers.scala | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index a68fbcf6..49a85a07 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2628,8 +2628,8 @@ object GlobalDefinitions { flamethrower_fireball.Modifiers = List( DamageModifiers.InfantryAggravatedDirect, DamageModifiers.InfantryAggravatedSplash, - DamageModifiers.FireballAggravatedBurn, - DamageModifiers.RadialDegrade + DamageModifiers.RadialDegrade, + DamageModifiers.FireballAggravatedBurn ) flamethrower_projectile.Name = "flamethrower_projectile" diff --git a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala index 24c7fdca..8c7ed433 100644 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala @@ -287,7 +287,8 @@ object DamageModifiers { def Calculate: DamageModifiers.Format = formula private def formula(damage: Int, data: ResolvedProjectile): Int = { - if (data.resolution == ProjectileResolution.AggravatedDirectBurn || + if (damage > 0 && + data.resolution == ProjectileResolution.AggravatedDirectBurn || data.resolution == ProjectileResolution.AggravatedSplashBurn) { //add resist to offset resist subtraction later 1 + data.damage_model.ResistUsing(data)(data) From 2b6ec727f2a0ed034f949feb1b0f7ec15ce78878 Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 28 Aug 2020 00:30:23 -0400 Subject: [PATCH 19/19] revert radial damage calculations --- .../net/psforever/objects/vital/damage/DamageModifiers.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala index 8c7ed433..82a874ea 100644 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala @@ -101,7 +101,8 @@ object DamageModifiers { val distance = Vector3.Distance(data.hit_pos, data.target.Position) val radius = profile.DamageRadius if (distance <= radius) { - damage - (damage * profile.DamageAtEdge * distance / radius).toInt + val base: Float = profile.DamageAtEdge + (damage * ((1 - base) * ((radius - distance) / radius) + base)).toInt } else { 0 }