diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index f8c14fe1..88e7aaef 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -26,7 +26,7 @@ import net.psforever.objects.ce._ import net.psforever.objects.definition._ import net.psforever.objects.definition.converter.{CorpseConverter, DestroyedVehicleConverter} import net.psforever.objects.entity.{SimpleWorldEntity, WorldEntity} -import net.psforever.objects.equipment.{EffectTarget, Equipment, FireModeSwitch, JammableUnit} +import net.psforever.objects.equipment.{ChargeFireModeDefinition, 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} @@ -127,8 +127,10 @@ object SessionActor { * must be a positive value * @param completionAction a finalizing action performed once the progress reaches 100(%) * @param tickAction an action that is performed for each increase of progress + * @param tickTime how long between each `tickAction` (ms); + * defaults to 250 milliseconds */ - final case class ProgressEvent(delta: Float, completionAction: () => Unit, tickAction: Float => Boolean) + final case class ProgressEvent(delta: Float, completionAction: () => Unit, tickAction: Float => Boolean, tickTime: Long = 250) private final val zoningCountdownMessages: Seq[Int] = Seq(5, 10, 20) @@ -175,6 +177,8 @@ class SessionActor extends Actor with MDCContextAware { var progressBarValue: Option[Float] = None var shooting: Option[PlanetSideGUID] = None //ChangeFireStateMessage_Start var prefire: Option[PlanetSideGUID] = None //if WeaponFireMessage precedes ChangeFireStateMessage_Start + var shootingStart: Long = 0 + var shootingStop: Long = 0 var shotsWhileDead: Int = 0 var accessedContainer: Option[PlanetSideGameObject with Container] = None var connectionState: Int = 25 @@ -543,8 +547,8 @@ class SessionActor extends Actor with MDCContextAware { self ! ProgressEvent(rate, finishedAction, stepAction) } - case ProgressEvent(delta, finishedAction, stepAction) => - HandleProgressChange(delta, finishedAction, stepAction) + case ProgressEvent(delta, finishedAction, stepAction, tick) => + HandleProgressChange(delta, finishedAction, stepAction, tick) case Door.DoorMessage(tplayer, msg, order) => HandleDoorMessage(tplayer, msg, order) @@ -2986,7 +2990,7 @@ class SessionActor extends Actor with MDCContextAware { * @param tickAction an optional action is is performed for each tick of progress; * also performs a continuity check to determine if the process has been disrupted */ - def HandleProgressChange(delta: Float, completionAction: () => Unit, tickAction: Float => Boolean): Unit = { + def HandleProgressChange(delta: Float, completionAction: () => Unit, tickAction: Float => Boolean, tick: Long): Unit = { progressBarUpdate.cancel() progressBarValue match { case Some(value) => @@ -3015,9 +3019,9 @@ class SessionActor extends Actor with MDCContextAware { progressBarValue = Some(next) import scala.concurrent.ExecutionContext.Implicits.global progressBarUpdate = context.system.scheduler.scheduleOnce( - 250 milliseconds, + tick milliseconds, self, - ProgressEvent(delta, completionAction, tickAction) + ProgressEvent(delta, completionAction, tickAction, tick) ) } else { progressBarValue = None @@ -4051,6 +4055,7 @@ class SessionActor extends Actor with MDCContextAware { if (tool.Magazine > 0 || prefire.contains(item_guid)) { prefire = None shooting = Some(item_guid) + shootingStart = System.currentTimeMillis() //special case - suppress the decimator's alternate fire mode, by projectile if (tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile) { continent.AvatarEvents ! AvatarServiceMessage( @@ -4058,6 +4063,17 @@ class SessionActor extends Actor with MDCContextAware { AvatarAction.ChangeFireState_Start(player.GUID, item_guid) ) } + //charge ammunition drain + tool.FireMode match { + case mode: ChargeFireModeDefinition => + progressBarValue = Some(0f) + progressBarUpdate = context.system.scheduler.scheduleOnce( + (mode.Time + mode.DrainInterval) milliseconds, + self, + ProgressEvent(1f, () => {}, Tools.ChargeFireMode(player, tool), mode.DrainInterval) + ) + case _ => ; + } } else { log.warn( s"ChangeFireState_Start: ${tool.Definition.Name} magazine is empty before trying to shoot bullet" @@ -4067,6 +4083,7 @@ class SessionActor extends Actor with MDCContextAware { case Some(_) => //permissible, for now prefire = None shooting = Some(item_guid) + shootingStart = System.currentTimeMillis() continent.AvatarEvents ! AvatarServiceMessage( continent.id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid) @@ -4079,6 +4096,7 @@ class SessionActor extends Actor with MDCContextAware { case msg @ ChangeFireStateMessage_Stop(item_guid) => log.trace("ChangeFireState_Stop: " + msg) prefire = None + shootingStop = System.currentTimeMillis() val weapon: Option[Equipment] = if (shooting.contains(item_guid)) { shooting = None continent.AvatarEvents ! AvatarServiceMessage( @@ -4099,6 +4117,7 @@ class SessionActor extends Actor with MDCContextAware { continent.id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid) ) + shootingStart = System.currentTimeMillis() - 1L } continent.AvatarEvents ! AvatarServiceMessage( continent.id, @@ -4118,6 +4137,11 @@ class SessionActor extends Actor with MDCContextAware { } weapon match { case Some(tool: Tool) => + tool.FireMode match { + case mode : ChargeFireModeDefinition => + sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, tool.Magazine)) + case _ => ; + } if (tool.Magazine == 0) { FireCycleCleanup(tool) } @@ -4201,7 +4225,7 @@ class SessionActor extends Actor with MDCContextAware { log.warn(s"ReloadMessage: no ammunition could be found for $item_guid") case x :: xs => val (deleteFunc, modifyFunc): (Equipment => Future[Any], (AmmoBox, Int) => Unit) = obj match { - case (veh: Vehicle) => + case veh: Vehicle => (RemoveOldEquipmentFromInventory(veh, taskResolver), ModifyAmmunitionInVehicle(veh)) case o: PlanetSideServerObject with Container => (RemoveOldEquipmentFromInventory(o, taskResolver), ModifyAmmunition(o)) @@ -5244,7 +5268,7 @@ class SessionActor extends Actor with MDCContextAware { unk6, unk7 ) => - //log.info(s"WeaponFire: $msg") + log.info(s"WeaponFire: $msg") HandleWeaponFire(weapon_guid, projectile_guid, shot_origin) case msg @ WeaponLazeTargetPositionMessage(weapon, pos1, pos2) => @@ -6811,6 +6835,7 @@ class SessionActor extends Actor with MDCContextAware { ) prefire = None shooting = None + shootingStop = System.currentTimeMillis() case None => ; } if (session.flying) { @@ -9345,8 +9370,7 @@ class SessionActor extends Actor with MDCContextAware { case _ => (obj.Orientation, obj.Definition.ObjectId, 300f) } - val distanceToOwner = - Vector3.DistanceSquared(shotOrigin, player.Position) + val distanceToOwner = Vector3.DistanceSquared(shotOrigin, player.Position) if (distanceToOwner <= acceptableDistanceToOwner) { val projectile_info = tool.Projectile val projectile = @@ -9357,9 +9381,15 @@ class SessionActor extends Actor with MDCContextAware { player, attribution, shotOrigin, - angle + angle, ) - projectiles(projectileIndex) = Some(projectile) + val initialQuality = tool.FireMode match { + case mode: ChargeFireModeDefinition => + ProjectileQuality.Modified((projectile.fire_time - shootingStart) / mode.Time.toFloat) + case _ => + ProjectileQuality.Normal + } + projectiles(projectileIndex) = Some(projectile.quality(initialQuality)) if (projectile_info.ExistsOnRemoteClients) { log.trace( s"WeaponFireMessage: ${projectile_info.Name} is a remote projectile" @@ -9421,9 +9451,9 @@ class SessionActor extends Actor with MDCContextAware { avatarActor ! AvatarActor.ConsumeStamina(avatar.stamina) } avatarActor ! AvatarActor.SuspendStaminaRegeneration(3.seconds) + prefire = shooting.orElse(Some(weaponGUID)) + tool.Discharge() } - prefire = shooting.orElse(Some(weaponGUID)) - tool.Discharge() //always out case _ => (None, None) diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 8e1d65ff..a1ef9755 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, AggravatedTiming, Projectiles} +import net.psforever.objects.ballistics._ import net.psforever.objects.ce.{DeployableCategory, DeployedItem} import net.psforever.objects.definition._ import net.psforever.objects.definition.converter._ @@ -23,8 +23,9 @@ import net.psforever.objects.serverobject.structures.{BuildingDefinition, WarpGa import net.psforever.objects.serverobject.turret.{FacilityTurretDefinition, TurretUpgrade} import net.psforever.objects.vehicles.{DestroyedVehicle, InternalTelepadDefinition, SeatArmorRestriction, UtilityType} import net.psforever.objects.vital.damage.{DamageCalculations, DamageModifiers} -import net.psforever.objects.vital.{DamageType, StandardResolutions} +import net.psforever.objects.vital.{DamageType, StandardDamageProfile, StandardResolutions} import net.psforever.types.{ExoSuitType, ImplantType, PlanetSideEmpire, Vector3} + import scala.collection.mutable import scala.concurrent.duration._ @@ -3865,19 +3866,20 @@ object GlobalDefinitions { sparrow_secondary_projectile.Modifiers = DamageModifiers.RadialDegrade spiker_projectile.Name = "spiker_projectile" - // spiker_projectile.Damage0 = 75 - spiker_projectile.Damage0 = 20 - // spiker_projectile.Damage0_min = 20 - // spiker_projectile.Damage1 = 75 - spiker_projectile.Damage1 = 20 - // spiker_projectile.Damage1_min = 20 + spiker_projectile.Charging = ChargeDamage(4, StandardDamageProfile(damage0 = Some(20), damage1 = Some(20))) + spiker_projectile.Damage0 = 75 + spiker_projectile.Damage1 = 75 spiker_projectile.DamageAtEdge = 0.1f spiker_projectile.DamageRadius = 5f - spiker_projectile.DamageRadius = 1f + spiker_projectile.DamageRadiusMin = 1f spiker_projectile.ProjectileDamageType = DamageType.Splash spiker_projectile.InitialVelocity = 40 spiker_projectile.Lifespan = 5f ProjectileDefinition.CalculateDerivedFields(spiker_projectile) + spiker_projectile.Modifiers = List( + DamageModifiers.SpikerChargeDamage, + DamageModifiers.RadialDegrade + ) spitfire_aa_ammo_projectile.Name = "spitfire_aa_ammo_projectile" spitfire_aa_ammo_projectile.Damage0 = 5 @@ -4214,8 +4216,8 @@ object GlobalDefinitions { isp.FireModes.head.AmmoTypeIndices += 0 isp.FireModes.head.AmmoTypeIndices += 1 isp.FireModes.head.AmmoSlotIndex = 0 + isp.FireModes.head.Chamber = 6 //8 shells x 6 pellets = 36 isp.FireModes.head.Magazine = 8 - isp.FireModes.head.Chamber = 6 //8 shells x 6 pellets = 48 isp.FireModes.head.Add.Damage0 = 1 isp.FireModes.head.Add.Damage2 = 1 isp.FireModes.head.Add.Damage3 = 1 @@ -4417,7 +4419,7 @@ object GlobalDefinitions { spiker.Size = EquipmentSize.Pistol spiker.AmmoTypes += ancient_ammo_combo spiker.ProjectileTypes += spiker_projectile - spiker.FireModes += new FireModeDefinition + spiker.FireModes += new ChargeFireModeDefinition(time = 1000, drainInterval = 500) spiker.FireModes.head.AmmoTypeIndices += 0 spiker.FireModes.head.AmmoSlotIndex = 0 spiker.FireModes.head.Magazine = 25 @@ -4682,8 +4684,8 @@ object GlobalDefinitions { pellet_gun.FireModes += new PelletFireModeDefinition pellet_gun.FireModes.head.AmmoTypeIndices += 0 pellet_gun.FireModes.head.AmmoSlotIndex = 0 - pellet_gun.FireModes.head.Magazine = 1 - pellet_gun.FireModes.head.Chamber = 8 //1 shells * 8 pellets = 8 + pellet_gun.FireModes.head.Magazine = 1 //what is this? + pellet_gun.FireModes.head.Chamber = 8 //1 shell * 8 pellets = 8 pellet_gun.Tile = InventoryTile.Tile63 six_shooter.Name = "six_shooter" @@ -4773,17 +4775,17 @@ object GlobalDefinitions { nchev_scattercannon.FireModes.head.AmmoTypeIndices += 0 nchev_scattercannon.FireModes.head.AmmoSlotIndex = 0 nchev_scattercannon.FireModes.head.Magazine = 40 - nchev_scattercannon.FireModes.head.Chamber = 10 + nchev_scattercannon.FireModes.head.Chamber = 10 //40 shells * 10 pellets = 400 nchev_scattercannon.FireModes += new PelletFireModeDefinition nchev_scattercannon.FireModes(1).AmmoTypeIndices += 0 nchev_scattercannon.FireModes(1).AmmoSlotIndex = 0 nchev_scattercannon.FireModes(1).Magazine = 40 - nchev_scattercannon.FireModes(1).Chamber = 10 + nchev_scattercannon.FireModes(1).Chamber = 10 //40 shells * 10 pellets = 400 nchev_scattercannon.FireModes += new PelletFireModeDefinition nchev_scattercannon.FireModes(2).AmmoTypeIndices += 0 nchev_scattercannon.FireModes(2).AmmoSlotIndex = 0 nchev_scattercannon.FireModes(2).Magazine = 40 - nchev_scattercannon.FireModes(2).Chamber = 10 + nchev_scattercannon.FireModes(2).Chamber = 10 //40 shells * 10 pellets = 400 nchev_falcon.Name = "nchev_falcon" nchev_falcon.Size = EquipmentSize.Max @@ -5526,7 +5528,7 @@ object GlobalDefinitions { energy_gun_nc.FireModes.head.AmmoTypeIndices += 0 energy_gun_nc.FireModes.head.AmmoSlotIndex = 0 energy_gun_nc.FireModes.head.Magazine = 35 - energy_gun_nc.FireModes.head.Chamber = 9 + energy_gun_nc.FireModes.head.Chamber = 8 //35 shots * 8 pellets = 280 energy_gun_tr.Name = "energy_gun_tr" energy_gun_tr.Size = EquipmentSize.BaseTurretWeapon diff --git a/src/main/scala/net/psforever/objects/Tool.scala b/src/main/scala/net/psforever/objects/Tool.scala index 114d08cf..22284143 100644 --- a/src/main/scala/net/psforever/objects/Tool.scala +++ b/src/main/scala/net/psforever/objects/Tool.scala @@ -239,3 +239,5 @@ object Tool { def Definition: FireModeDefinition = fdef } } + + diff --git a/src/main/scala/net/psforever/objects/Tools.scala b/src/main/scala/net/psforever/objects/Tools.scala new file mode 100644 index 00000000..6744550b --- /dev/null +++ b/src/main/scala/net/psforever/objects/Tools.scala @@ -0,0 +1,37 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects + +import net.psforever.objects.equipment.ChargeFireModeDefinition +import net.psforever.packet.game.QuantityUpdateMessage +import net.psforever.services.Service +import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} + +object Tools { + /** + * + * @param player the player performing the revive action + * @param tool the tool being used to execute the attack; + * should have a selected chargeable fire mode + * @param progress the current progress value + * @see `ChargeFireModeDefinition` + * @see `QuantityUpdateMessage` + * @return `true`, if the next cycle of progress should occur; + * `false`, otherwise + */ + def ChargeFireMode(player: Player, tool: Tool)(progress: Float): Boolean = { + tool.FireMode match { + case mode: ChargeFireModeDefinition if tool.Magazine > 0 => + val magazine = tool.Magazine -= mode.RoundsPerInterval + player.Zone.AvatarEvents ! AvatarServiceMessage( + player.Name, + AvatarAction.SendResponse( + Service.defaultPlayerGUID, + QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, magazine) + ) + ) + player.isAlive + case _ => + false + } + } +} diff --git a/src/main/scala/net/psforever/objects/ballistics/ChargeDamage.scala b/src/main/scala/net/psforever/objects/ballistics/ChargeDamage.scala new file mode 100644 index 00000000..c3633dca --- /dev/null +++ b/src/main/scala/net/psforever/objects/ballistics/ChargeDamage.scala @@ -0,0 +1,9 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.ballistics + +import net.psforever.objects.vital.StandardDamageProfile + +final case class ChargeDamage( + effect_count: Int, + min: StandardDamageProfile + ) diff --git a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala index a00b79e4..8f000b70 100644 --- a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala +++ b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala @@ -43,7 +43,7 @@ final case class Projectile( shot_angle: Vector3, quality: ProjectileQuality = ProjectileQuality.Normal, id: Long = Projectile.idGenerator.getAndIncrement(), - fire_time: Long = System.nanoTime + fire_time: Long = System.currentTimeMillis() ) extends PlanetSideGameObject { Position = shot_origin Orientation = shot_angle diff --git a/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala b/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala index 6c225fb0..f4294e6d 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.{AggravatedDamage, Projectiles} +import net.psforever.objects.ballistics.{AggravatedDamage, ChargeDamage, Projectiles} import net.psforever.objects.equipment.JammingUnit import net.psforever.objects.vital.damage.DamageModifiers import net.psforever.objects.vital.{DamageType, StandardDamageProfile} @@ -38,8 +38,10 @@ class ProjectileDefinition(objectId: Int) 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 radial damage, the distance of the explosion effect (m) */ + private var damageRadius: Float = 0f + /** for radial damage, the distance before degradation of the explosion effect (m) */ + private var damageRadiusMin: 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 */ @@ -65,9 +67,11 @@ class ProjectileDefinition(objectId: Int) 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 + private var grenade_projectile: Boolean = false /** projectile tries to confers aggravated damage burn to its target */ - private var aggravated_damage : Option[AggravatedDamage] = None + private var aggravated_damage: Option[AggravatedDamage] = None + /** */ + private var charging: Option[ChargeDamage] = 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; @@ -75,7 +79,8 @@ class ProjectileDefinition(objectId: Int) 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) */ + /** how far the projectile will travel while not degrading (m); + * this field is not to be used in the place of minimum radial damage */ private var distanceNoDegrade: Float = 0f /** after acceleration, if any, what is the final speed of the projectile (m/s) */ private var finalVelocity: Float = 0f @@ -172,6 +177,13 @@ class ProjectileDefinition(objectId: Int) DamageRadius } + def DamageRadiusMin: Float = damageRadiusMin + + def DamageRadiusMin_=(damageRadius: Float): Float = { + this.damageRadiusMin = damageRadius + DamageRadiusMin + } + def LashRadius: Float = lashRadius def LashRadius_=(radius: Float): Float = { @@ -239,6 +251,15 @@ class ProjectileDefinition(objectId: Int) Aggravated } + def Charging : Option[ChargeDamage] = charging + + def Charging_=(damage : ChargeDamage) : Option[ChargeDamage] = Charging_=(Some(damage)) + + def Charging_=(damage : Option[ChargeDamage]) : Option[ChargeDamage] = { + charging = damage + Charging + } + def DistanceMax : Float = distanceMax //accessor only def DistanceFromAcceleration: Float = distanceFromAcceleration //accessor only @@ -253,6 +274,13 @@ object ProjectileDefinition { new ProjectileDefinition(projectileType.id) } + /** + * Calculate the secondary fields of the projectile's damage. + * Depending on whether the appropriate fields are defined, + * it may calculate for "damage over distance", typically associated with straight-fire direct hit projectiles, + * or for "radial damage", typically associated with explosive splash projectiles. + * @param pdef the projectile's definition, often called its profile + */ def CalculateDerivedFields(pdef: ProjectileDefinition): Unit = { val (distanceMax, distanceFromAcceleration, finalVelocity): (Float, Float, Float) = if (pdef.Acceleration == 0) { (pdef.InitialVelocity * pdef.Lifespan, 0, pdef.InitialVelocity.toFloat) diff --git a/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala b/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala index 2772417d..3fc46bba 100644 --- a/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala +++ b/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala @@ -2,7 +2,8 @@ package net.psforever.objects.equipment import net.psforever.objects.Tool -import net.psforever.objects.vital.damage.{DamageModifiers, DamageProfile} +import net.psforever.objects.vital.SpecificDamageProfile +import net.psforever.objects.vital.damage.DamageModifiers import scala.collection.mutable @@ -41,7 +42,7 @@ class FireModeDefinition extends DamageModifiers { private var chamber: Int = 1 /** modifiers for each damage type */ - private val modifiers: FireModeDamageModifiers = new FireModeDamageModifiers + private val modifiers: SpecificDamageProfile = new SpecificDamageProfile def AmmoSlotIndex: Int = ammoSlotIndex @@ -91,7 +92,7 @@ class FireModeDefinition extends DamageModifiers { Chamber } - def Add: FireModeDamageModifiers = modifiers + def Add: SpecificDamageProfile = modifiers /** * Shoot a weapon, remove an anticipated amount of ammunition. @@ -108,8 +109,8 @@ class FireModeDefinition extends DamageModifiers { } } -class PelletFireModeDefinition extends FireModeDefinition { - +class PelletFireModeDefinition + extends FireModeDefinition { /** * Shoot a weapon, remove an anticipated amount of ammunition.
*
@@ -132,7 +133,8 @@ class PelletFireModeDefinition extends FireModeDefinition { } } -class InfiniteFireModeDefinition extends FireModeDefinition { +class InfiniteFireModeDefinition + extends FireModeDefinition { /** * Shoot a weapon, remove an anticipated amount of ammunition.
@@ -150,45 +152,19 @@ class InfiniteFireModeDefinition extends FireModeDefinition { override def Discharge(weapon: Tool, rounds: Option[Int] = None): Int = 1 } -class FireModeDamageModifiers extends DamageProfile { - private var damage0: Int = 0 - private var damage1: Int = 0 - private var damage2: Int = 0 - private var damage3: Int = 0 - private var damage4: Int = 0 +/** + * Shoot a weapon, remove an anticipated amount of ammunition.
+ *
+ * Hold down the fire trigger to create a damage multiplier. + * After the multiplier has reach complete/full, expend additional ammunition to sustain it. + * @param time the duration until the charge is full (milliseconds) + * @param drainInterval the curation between ticks of ammunition depletion after "full charge" + */ +class ChargeFireModeDefinition(private val time: Long, private val drainInterval: Long, private val roundsPerInterval: Int = 1) + extends FireModeDefinition { + def Time: Long = time - def Damage0: Int = damage0 + def DrainInterval: Long = drainInterval - def Damage0_=(damage: Int): Int = { - damage0 = damage - Damage0 - } - - def Damage1: Int = damage1 - - def Damage1_=(damage: Int): Int = { - damage1 = damage - Damage1 - } - - def Damage2: Int = damage2 - - def Damage2_=(damage: Int): Int = { - damage2 = damage - Damage2 - } - - def Damage3: Int = damage3 - - def Damage3_=(damage: Int): Int = { - damage3 = damage - Damage3 - } - - def Damage4: Int = damage4 - - def Damage4_=(damage: Int): Int = { - damage4 = damage - Damage4 - } + def RoundsPerInterval: Int = roundsPerInterval } diff --git a/src/main/scala/net/psforever/objects/vital/SpecificDamageProfile.scala b/src/main/scala/net/psforever/objects/vital/SpecificDamageProfile.scala new file mode 100644 index 00000000..33a4c23f --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/SpecificDamageProfile.scala @@ -0,0 +1,65 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.vital + +import net.psforever.objects.vital.damage.DamageProfile + +class SpecificDamageProfile extends DamageProfile { + private var damage0: Int = 0 + private var damage1: Int = 0 + private var damage2: Int = 0 + private var damage3: Int = 0 + private var damage4: Int = 0 + + def Damage0: Int = damage0 + + def Damage0_=(damage: Int): Int = { + damage0 = damage + Damage0 + } + + def Damage1: Int = damage1 + + def Damage1_=(damage: Int): Int = { + damage1 = damage + Damage1 + } + + def Damage2: Int = damage2 + + def Damage2_=(damage: Int): Int = { + damage2 = damage + Damage2 + } + + def Damage3: Int = damage3 + + def Damage3_=(damage: Int): Int = { + damage3 = damage + Damage3 + } + + def Damage4: Int = damage4 + + def Damage4_=(damage: Int): Int = { + damage4 = damage + Damage4 + } +} + +object SpecificDamageProfile { + def apply( + damage0: Int = 0, + damage1: Int = 0, + damage2: Int = 0, + damage3: Int = 0, + damage4: Int = 0 + ): SpecificDamageProfile = { + val obj = new SpecificDamageProfile + obj.Damage0 = damage0 + obj.Damage1 = damage1 + obj.Damage2 = damage2 + obj.Damage3 = damage3 + obj.Damage4 = damage4 + obj + } +} diff --git a/src/main/scala/net/psforever/objects/vital/StandardDamageProfile.scala b/src/main/scala/net/psforever/objects/vital/StandardDamageProfile.scala index 62995960..87b0681f 100644 --- a/src/main/scala/net/psforever/objects/vital/StandardDamageProfile.scala +++ b/src/main/scala/net/psforever/objects/vital/StandardDamageProfile.scala @@ -61,3 +61,21 @@ trait StandardDamageProfile extends DamageProfile { Damage4 } } + +object StandardDamageProfile { + def apply( + damage0: Option[Int] = None, + damage1: Option[Int] = None, + damage2: Option[Int] = None, + damage3: Option[Int] = None, + damage4: Option[Int] = None + ): StandardDamageProfile = { + val obj = new StandardDamageProfile { } + obj.Damage0 = damage0 + obj.Damage1 = damage1 + obj.Damage2 = damage2 + obj.Damage3 = damage3 + obj.Damage4 = damage4 + obj + } +} 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 82a874ea..089a41cc 100644 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala @@ -2,6 +2,7 @@ package net.psforever.objects.vital.damage import net.psforever.objects.ballistics._ +import net.psforever.objects.equipment.ChargeFireModeDefinition import net.psforever.objects.vital.DamageType import net.psforever.types.{ExoSuitType, Vector3} @@ -97,12 +98,17 @@ object DamageModifiers { def Calculate: DamageModifiers.Format = function private def function(damage: Int, data: ResolvedProjectile): Int = { - 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 - (damage * ((1 - base) * ((radius - distance) / radius) + base)).toInt + val profile = data.projectile.profile + val distance = Vector3.Distance(data.hit_pos, data.target.Position) + val radius = profile.DamageRadius + val radiusMin = profile.DamageRadiusMin + if (distance <= radiusMin) { + damage + } else if (distance <= radius) { + //damage - (damage * profile.DamageAtEdge * (distance - radiusMin) / (radius - radiusMin)).toInt + val base = profile.DamageAtEdge + val radi = radius - radiusMin + (damage * ((1 - base) * ((radi - (distance - radiusMin)) / radi) + base)).toInt } else { 0 } @@ -418,4 +424,19 @@ object DamageModifiers { } } } + + case object SpikerChargeDamage extends Mod { + def Calculate: DamageModifiers.Format = formula + + private def formula(damage: Int, data: ResolvedProjectile): Int = { + val projectile = data.projectile + (projectile.fire_mode, projectile.profile.Charging) match { + case (_: ChargeFireModeDefinition, Some(info: ChargeDamage)) => + val chargeQuality = math.max(0f, math.min(projectile.quality.mod, 1f)) + data.damage_model.DamageUsing(info.min) + (damage * chargeQuality).toInt + case _ => + damage + } + } + } } diff --git a/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index a3a7256a..ad006ed2 100644 --- a/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -18,107 +18,280 @@ object GamePacketOpcode extends Enumeration { type Type = Value val // OPCODES 0x00-0f - Unknown0, // PPT_NULL in beta client - LoginMessage, LoginRespMessage, ConnectToWorldRequestMessage, // found by searching for 83 F8 03 89 in IDA - ConnectToWorldMessage, VNLWorldStatusMessage, UnknownMessage6, // PPT_TRANSFERTOWORLDREQUEST - UnknownMessage7, // PPT_TRANSFERTOWORLDRESPONSE + Unknown0, // PPT_NULL in beta client + LoginMessage, + LoginRespMessage, + ConnectToWorldRequestMessage, // found by searching for 83 F8 03 89 in IDA + ConnectToWorldMessage, + VNLWorldStatusMessage, + UnknownMessage6, // PPT_TRANSFERTOWORLDREQUEST + UnknownMessage7, // PPT_TRANSFERTOWORLDRESPONSE // 0x08 - PlayerStateMessage, HitMessage, HitHint, DamageMessage, DestroyMessage, ReloadMessage, MountVehicleMsg, - DismountVehicleMsg, + PlayerStateMessage, + HitMessage, + HitHint, + DamageMessage, + DestroyMessage, + ReloadMessage, + MountVehicleMsg, + DismountVehicleMsg, // OPCODES 0x10-1f - UseItemMessage, MoveItemMessage, ChatMsg, CharacterNoRecordMessage, CharacterInfoMessage, - UnknownMessage21, // PPT_DISCONNECT - BindPlayerMessage, ObjectCreateMessage_Duplicate, // PPT_OBJECTCREATE + UseItemMessage, + MoveItemMessage, + ChatMsg, + CharacterNoRecordMessage, + CharacterInfoMessage, + UnknownMessage21, // PPT_DISCONNECT + BindPlayerMessage, + ObjectCreateMessage_Duplicate, // PPT_OBJECTCREATE // 0x18 ObjectCreateMessage, // PPT_OBJECTCREATEDETAILED - ObjectDeleteMessage, PingMsg, VehicleStateMessage, FrameVehicleStateMessage, GenericObjectStateMsg, - ChildObjectStateMessage, ActionResultMessage, + ObjectDeleteMessage, + PingMsg, + VehicleStateMessage, + FrameVehicleStateMessage, + GenericObjectStateMsg, + ChildObjectStateMessage, + ActionResultMessage, // OPCODES 0x20-2f UnknownMessage32, // PPT_ACTIONBEGIN - ActionProgressMessage, ActionCancelMessage, ActionCancelAcknowledgeMessage, SetEmpireMessage, EmoteMsg, - UnuseItemMessage, ObjectDetachMessage, + ActionProgressMessage, + ActionCancelMessage, + ActionCancelAcknowledgeMessage, + SetEmpireMessage, + EmoteMsg, + UnuseItemMessage, + ObjectDetachMessage, // 0x28 - CreateShortcutMessage, ChangeShortcutBankMessage, ObjectAttachMessage, UnknownMessage43, // PPT_OBJECTEMPTY - PlanetsideAttributeMessage, RequestDestroyMessage, UnknownMessage46, // PPT_EQUIPITEM + CreateShortcutMessage, + ChangeShortcutBankMessage, + ObjectAttachMessage, + UnknownMessage43, // PPT_OBJECTEMPTY + PlanetsideAttributeMessage, + RequestDestroyMessage, + UnknownMessage46, // PPT_EQUIPITEM CharacterCreateRequestMessage, // OPCODES 0x30-3f - CharacterRequestMessage, LoadMapMessage, SetCurrentAvatarMessage, ObjectHeldMessage, WeaponFireMessage, - AvatarJumpMessage, PickupItemMessage, DropItemMessage, + CharacterRequestMessage, + LoadMapMessage, + SetCurrentAvatarMessage, + ObjectHeldMessage, + WeaponFireMessage, + AvatarJumpMessage, + PickupItemMessage, + DropItemMessage, // 0x38 - InventoryStateMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, UnknownMessage59, - GenericCollisionMsg, QuantityUpdateMessage, ArmorChangedMessage, ProjectileStateMessage, + InventoryStateMessage, + ChangeFireStateMessage_Start, + ChangeFireStateMessage_Stop, + UnknownMessage59, + GenericCollisionMsg, + QuantityUpdateMessage, + ArmorChangedMessage, + ProjectileStateMessage, // OPCODES 0x40-4f - MountVehicleCargoMsg, DismountVehicleCargoMsg, CargoMountPointStatusMessage, BeginZoningMessage, - ItemTransactionMessage, ItemTransactionResultMessage, ChangeFireModeMessage, ChangeAmmoMessage, + MountVehicleCargoMsg, + DismountVehicleCargoMsg, + CargoMountPointStatusMessage, + BeginZoningMessage, + ItemTransactionMessage, + ItemTransactionResultMessage, + ChangeFireModeMessage, + ChangeAmmoMessage, // 0x48 - TimeOfDayMessage, UnknownMessage73, // PPT_PROJECTILE_EVENT_BLOCK - SpawnRequestMessage, DeployRequestMessage, UnknownMessage76, // PPT_BUILDINGSTATECHANGED - RepairMessage, ServerVehicleOverrideMsg, LashMessage, + TimeOfDayMessage, + UnknownMessage73, // PPT_PROJECTILE_EVENT_BLOCK + SpawnRequestMessage, + DeployRequestMessage, + UnknownMessage76, // PPT_BUILDINGSTATECHANGED + RepairMessage, + ServerVehicleOverrideMsg, + LashMessage, // OPCODES 0x50-5f - TargetingInfoMessage, TriggerEffectMessage, WeaponDryFireMessage, DroppodLaunchRequestMessage, HackMessage, - DroppodLaunchResponseMessage, GenericObjectActionMessage, AvatarVehicleTimerMessage, + TargetingInfoMessage, + TriggerEffectMessage, + WeaponDryFireMessage, + DroppodLaunchRequestMessage, + HackMessage, + DroppodLaunchResponseMessage, + GenericObjectActionMessage, + AvatarVehicleTimerMessage, // 0x58 - AvatarImplantMessage, UnknownMessage89, // PPT_SEARCHMESSAGE - DelayedPathMountMsg, OrbitalShuttleTimeMsg, AIDamage, DeployObjectMessage, FavoritesRequest, FavoritesResponse, + AvatarImplantMessage, + UnknownMessage89, // PPT_SEARCHMESSAGE + DelayedPathMountMsg, + OrbitalShuttleTimeMsg, + AIDamage, + DeployObjectMessage, + FavoritesRequest, + FavoritesResponse, // OPCODES 0x60-6f - FavoritesMessage, ObjectDetectedMessage, SplashHitMessage, SetChatFilterMessage, AvatarSearchCriteriaMessage, - AvatarSearchResponse, WeaponJammedMessage, LinkDeadAwarenessMsg, + FavoritesMessage, + ObjectDetectedMessage, + SplashHitMessage, + SetChatFilterMessage, + AvatarSearchCriteriaMessage, + AvatarSearchResponse, + WeaponJammedMessage, + LinkDeadAwarenessMsg, // 0x68 - DroppodFreefallingMessage, AvatarFirstTimeEventMessage, AggravatedDamageMessage, TriggerSoundMessage, LootItemMessage, - VehicleSubStateMessage, SquadMembershipRequest, SquadMembershipResponse, + DroppodFreefallingMessage, + AvatarFirstTimeEventMessage, + AggravatedDamageMessage, + TriggerSoundMessage, + LootItemMessage, + VehicleSubStateMessage, + SquadMembershipRequest, + SquadMembershipResponse, // OPCODES 0x70-7f - SquadMemberEvent, PlatoonEvent, FriendsRequest, FriendsResponse, TriggerEnvironmentalDamageMessage, - TrainingZoneMessage, DeployableObjectsInfoMessage, SquadState, + SquadMemberEvent, + PlatoonEvent, + FriendsRequest, + FriendsResponse, + TriggerEnvironmentalDamageMessage, + TrainingZoneMessage, + DeployableObjectsInfoMessage, + SquadState, // 0x78 - OxygenStateMessage, TradeMessage, UnknownMessage122, DamageFeedbackMessage, DismountBuildingMsg, - UnknownMessage125, // PPT_MOUNTBUILDING - UnknownMessage126, // PPT_INTENDEDDROPZONE + OxygenStateMessage, + TradeMessage, + UnknownMessage122, + DamageFeedbackMessage, + DismountBuildingMsg, + UnknownMessage125, // PPT_MOUNTBUILDING + UnknownMessage126, // PPT_INTENDEDDROPZONE AvatarStatisticsMessage, // OPCODES 0x80-8f - GenericObjectAction2Message, DestroyDisplayMessage, TriggerBotAction, SquadWaypointRequest, SquadWaypointEvent, - OffshoreVehicleMessage, ObjectDeployedMessage, ObjectDeployedCountMessage, + GenericObjectAction2Message, + DestroyDisplayMessage, + TriggerBotAction, + SquadWaypointRequest, + SquadWaypointEvent, + OffshoreVehicleMessage, + ObjectDeployedMessage, + ObjectDeployedCountMessage, // 0x88 - WeaponDelayFireMessage, BugReportMessage, PlayerStasisMessage, UnknownMessage139, OutfitMembershipRequest, - OutfitMembershipResponse, OutfitRequest, OutfitEvent, + WeaponDelayFireMessage, + BugReportMessage, + PlayerStasisMessage, + UnknownMessage139, + OutfitMembershipRequest, + OutfitMembershipResponse, + OutfitRequest, + OutfitEvent, // OPCODES 0x90-9f - OutfitMemberEvent, OutfitMemberUpdate, PlanetsideStringAttributeMessage, DataChallengeMessage, - DataChallengeMessageResp, WeatherMessage, SimDataChallenge, SimDataChallengeResp, + OutfitMemberEvent, + OutfitMemberUpdate, + PlanetsideStringAttributeMessage, + DataChallengeMessage, + DataChallengeMessageResp, + WeatherMessage, + SimDataChallenge, + SimDataChallengeResp, // 0x98 - OutfitListEvent, EmpireIncentivesMessage, InvalidTerrainMessage, SyncMessage, DebugDrawMessage, SoulMarkMessage, - UplinkPositionEvent, HotSpotUpdateMessage, + OutfitListEvent, + EmpireIncentivesMessage, + InvalidTerrainMessage, + SyncMessage, + DebugDrawMessage, + SoulMarkMessage, + UplinkPositionEvent, + HotSpotUpdateMessage, // OPCODES 0xa0-af - BuildingInfoUpdateMessage, FireHintMessage, UplinkRequest, UplinkResponse, WarpgateRequest, WarpgateResponse, - DamageWithPositionMessage, GenericActionMessage, + BuildingInfoUpdateMessage, + FireHintMessage, + UplinkRequest, + UplinkResponse, + WarpgateRequest, + WarpgateResponse, + DamageWithPositionMessage, + GenericActionMessage, // 0xa8 - ContinentalLockUpdateMessage, AvatarGrenadeStateMessage, UnknownMessage170, UnknownMessage171, - ReleaseAvatarRequestMessage, AvatarDeadStateMessage, CSAssistMessage, CSAssistCommentMessage, + ContinentalLockUpdateMessage, + AvatarGrenadeStateMessage, + UnknownMessage170, + UnknownMessage171, + ReleaseAvatarRequestMessage, + AvatarDeadStateMessage, + CSAssistMessage, + CSAssistCommentMessage, // OPCODES 0xb0-bf - VoiceHostRequest, VoiceHostKill, VoiceHostInfo, BattleplanMessage, BattleExperienceMessage, TargetingImplantRequest, - ZonePopulationUpdateMessage, DisconnectMessage, + VoiceHostRequest, + VoiceHostKill, + VoiceHostInfo, + BattleplanMessage, + BattleExperienceMessage, + TargetingImplantRequest, + ZonePopulationUpdateMessage, + DisconnectMessage, // 0xb8 - ExperienceAddedMessage, OrbitalStrikeWaypointMessage, KeepAliveMessage, MapObjectStateBlockMessage, SnoopMsg, - PlayerStateMessageUpstream, PlayerStateShiftMessage, ZipLineMessage, + ExperienceAddedMessage, + OrbitalStrikeWaypointMessage, + KeepAliveMessage, + MapObjectStateBlockMessage, + SnoopMsg, + PlayerStateMessageUpstream, + PlayerStateShiftMessage, + ZipLineMessage, // OPCODES 0xc0-cf - CaptureFlagUpdateMessage, VanuModuleUpdateMessage, FacilityBenefitShieldChargeRequestMessage, - ProximityTerminalUseMessage, QuantityDeltaUpdateMessage, ChainLashMessage, ZoneInfoMessage, - LongRangeProjectileInfoMessage, + CaptureFlagUpdateMessage, + VanuModuleUpdateMessage, + FacilityBenefitShieldChargeRequestMessage, + ProximityTerminalUseMessage, + QuantityDeltaUpdateMessage, + ChainLashMessage, + ZoneInfoMessage, + LongRangeProjectileInfoMessage, // 0xc8 - WeaponLazeTargetPositionMessage, ModuleLimitsMessage, OutfitBenefitMessage, EmpireChangeTimeMessage, - ClockCalibrationMessage, DensityLevelUpdateMessage, ActOfGodMessage, AvatarAwardMessage, + WeaponLazeTargetPositionMessage, + ModuleLimitsMessage, + OutfitBenefitMessage, + EmpireChangeTimeMessage, + ClockCalibrationMessage, + DensityLevelUpdateMessage, + ActOfGodMessage, + AvatarAwardMessage, // OPCODES 0xd0-df - UnknownMessage208, DisplayedAwardMessage, RespawnAMSInfoMessage, ComponentDamageMessage, - GenericObjectActionAtPositionMessage, PropertyOverrideMessage, WarpgateLinkOverrideMessage, EmpireBenefitsMessage, + UnknownMessage208, + DisplayedAwardMessage, + RespawnAMSInfoMessage, + ComponentDamageMessage, + GenericObjectActionAtPositionMessage, + PropertyOverrideMessage, + WarpgateLinkOverrideMessage, + EmpireBenefitsMessage, // 0xd8 - ForceEmpireMessage, BroadcastWarpgateUpdateMessage, UnknownMessage218, SquadMainTerminalMessage, - SquadMainTerminalResponseMessage, SquadOrderMessage, SquadOrderResponse, ZoneLockInfoMessage, + ForceEmpireMessage, + BroadcastWarpgateUpdateMessage, + UnknownMessage218, + SquadMainTerminalMessage, + SquadMainTerminalResponseMessage, + SquadOrderMessage, + SquadOrderResponse, + ZoneLockInfoMessage, // OPCODES 0xe0-ef - SquadBindInfoMessage, AudioSequenceMessage, SquadFacilityBindInfoMessage, ZoneForcedCavernConnectionsMessage, - MissionActionMessage, MissionKillTriggerMessage, ReplicationStreamMessage, SquadDefinitionActionMessage, + SquadBindInfoMessage, + AudioSequenceMessage, + SquadFacilityBindInfoMessage, + ZoneForcedCavernConnectionsMessage, + MissionActionMessage, + MissionKillTriggerMessage, + ReplicationStreamMessage, + SquadDefinitionActionMessage, // 0xe8 - SquadDetailDefinitionUpdateMessage, TacticsMessage, RabbitUpdateMessage, SquadInvitationRequestMessage, - CharacterKnowledgeMessage, GameScoreUpdateMessage, UnknownMessage238, OrderTerminalBugMessage, + SquadDetailDefinitionUpdateMessage, + TacticsMessage, + RabbitUpdateMessage, + SquadInvitationRequestMessage, + CharacterKnowledgeMessage, + GameScoreUpdateMessage, + UnknownMessage238, + OrderTerminalBugMessage, // OPCODES 0xf0-f3 - QueueTimedHelpMessage, MailMessage, GameVarUpdate, ClientCheatedMessage // last known message type (243, 0xf3) + QueueTimedHelpMessage, + MailMessage, + GameVarUpdate, + ClientCheatedMessage // last known message type (243, 0xf3) = Value private def noDecoder(opcode: GamePacketOpcode.Type) = diff --git a/src/main/scala/net/psforever/util/DefinitionUtil.scala b/src/main/scala/net/psforever/util/DefinitionUtil.scala index 0a3dfa8f..a41a7bae 100644 --- a/src/main/scala/net/psforever/util/DefinitionUtil.scala +++ b/src/main/scala/net/psforever/util/DefinitionUtil.scala @@ -3,6 +3,7 @@ package net.psforever.util import net.psforever.objects.definition.BasicDefinition import net.psforever.objects.{AmmoBox, GlobalDefinitions, Player, SimpleItem, Tool} import net.psforever.types.ExoSuitType + import scala.reflect.runtime.universe // TODO definitions should be in an iterable format @@ -225,10 +226,10 @@ object DefinitionUtil { def applyDefaultLoadout(player: Player): Unit = { val faction = player.Faction player.ExoSuit = ExoSuitType.Standard - player.Slot(0).Equipment = Tool(GlobalDefinitions.StandardPistol(faction)) + player.Slot(0).Equipment = Tool(GlobalDefinitions.spiker)//StandardPistol(faction)) player.Slot(2).Equipment = Tool(GlobalDefinitions.suppressor) player.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(faction)) - player.Slot(6).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm) + player.Slot(6).Equipment = AmmoBox(GlobalDefinitions.ancient_ammo_combo) player.Slot(9).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm) player.Slot(12).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm) player.Slot(33).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm_AP) diff --git a/src/test/scala/objects/FireModeTest.scala b/src/test/scala/objects/FireModeTest.scala index ca3470cd..04f30755 100644 --- a/src/test/scala/objects/FireModeTest.scala +++ b/src/test/scala/objects/FireModeTest.scala @@ -3,12 +3,7 @@ package objects import net.psforever.objects.definition.ToolDefinition import net.psforever.objects.{GlobalDefinitions, Tool} -import net.psforever.objects.equipment.{ - EquipmentSize, - FireModeDefinition, - InfiniteFireModeDefinition, - PelletFireModeDefinition -} +import net.psforever.objects.equipment._ import org.specs2.mutable._ class FireModeTest extends Specification { @@ -129,4 +124,32 @@ class FireModeTest extends Specification { obj.Magazine mustEqual 1 } } + + "ChargeFireModeDefinition" should { + "construct" in { + val obj = new ChargeFireModeDefinition(1000, 500) + obj.AmmoTypeIndices mustEqual Nil + obj.AmmoSlotIndex mustEqual 0 + obj.Magazine mustEqual 1 + obj.RoundsPerShot mustEqual 1 + obj.Chamber mustEqual 1 + obj.Time mustEqual 1000L + obj.DrainInterval mustEqual 500L + } + + "discharge" in { + val obj = Tool(GlobalDefinitions.spiker) + obj.FireMode.isInstanceOf[ChargeFireModeDefinition] mustEqual true + obj.Magazine mustEqual 25 + obj.FireMode.RoundsPerShot mustEqual 1 + obj.FireMode.Chamber mustEqual 1 + + obj.Magazine mustEqual 25 + obj.Discharge() + obj.Magazine mustEqual 24 + obj.Discharge() + obj.Discharge() + obj.Magazine mustEqual 22 + } + } } diff --git a/src/test/scala/objects/ProjectileTest.scala b/src/test/scala/objects/ProjectileTest.scala index 4d96ccd1..17f81493 100644 --- a/src/test/scala/objects/ProjectileTest.scala +++ b/src/test/scala/objects/ProjectileTest.scala @@ -46,7 +46,7 @@ class ProjectileTest extends Specification { obj.InitialVelocity mustEqual 1 obj.Lifespan mustEqual 1f obj.DamageAtEdge mustEqual 1f - obj.DamageRadius mustEqual 1f + obj.DamageRadius mustEqual 0f obj.UseDamage1Subtract mustEqual false } @@ -269,7 +269,7 @@ class ProjectileTest extends Specification { obj.attribute_to mustEqual obj.tool_def.ObjectId obj.shot_origin mustEqual Vector3(1.2f, 3.4f, 5.6f) obj.shot_angle mustEqual Vector3(0.2f, 0.4f, 0.6f) - obj.fire_time <= System.nanoTime mustEqual true + obj.fire_time <= System.currentTimeMillis() mustEqual true obj.isResolved mustEqual false }