* initial spiker logic for charging mechanic; it's functional but doesn't work exactly by the numbers say it should

* no damage degrade for radial damage; charge mode features, including damage on the projectile and fire mode on the weapon; the Spiker's damage output is pretty close to accurate

* ammunition drain timer works correctly; no need for the progress completion function; new formatting sucks

* dial back on fire mode changes; stop excessive weapon discharge case; comments

* master merge; test fix (when did it change?)

* test repair; fixed unintentional side-effect of instantiation of StanDamProf
This commit is contained in:
Fate-JH 2020-09-05 09:08:18 -04:00 committed by GitHub
parent b573530b25
commit 78f970a4ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 552 additions and 167 deletions

View file

@ -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)

View file

@ -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

View file

@ -239,3 +239,5 @@ object Tool {
def Definition: FireModeDefinition = fdef
}
}

View file

@ -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
}
}
}

View file

@ -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
)

View file

@ -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

View file

@ -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)

View file

@ -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.<br>
* <br>
@ -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.<br>
@ -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.<br>
* <br>
* 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
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}
}
}

View file

@ -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) =

View file

@ -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)

View file

@ -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
}
}
}

View file

@ -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
}