mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-19 18:14:44 +00:00
259 lines
8.8 KiB
Scala
259 lines
8.8 KiB
Scala
// Copyright (c) 2017 PSForever
|
|
package net.psforever.objects
|
|
|
|
import net.psforever.objects.definition.{AmmoBoxDefinition, ProjectileDefinition, ToolDefinition}
|
|
import net.psforever.objects.equipment._
|
|
import net.psforever.objects.ballistics.Projectiles
|
|
|
|
import scala.annotation.tailrec
|
|
|
|
/**
|
|
* A type of `Equipment` that can be wielded and loaded with certain other game elements.<br>
|
|
* <br>
|
|
* "Tool" is a very mechanical name while this class is intended for various weapons and support items.
|
|
* The primary trait of a `Tool` is that it has something that counts as an "ammunition,"
|
|
* depleted as the `Tool` is used, replaceable as long as one has an appropriate type of `AmmoBox` object.
|
|
* (The former is always called "consuming;" the latter, "reloading.")
|
|
* Some weapons Chainblade have ammunition but do not consume it.
|
|
* @param toolDef the `ObjectDefinition` that constructs this item and maintains some of its immutable fields
|
|
*/
|
|
class Tool(private val toolDef: ToolDefinition)
|
|
extends Equipment
|
|
with FireModeSwitch[FireModeDefinition]
|
|
with JammableUnit {
|
|
private var tdef = toolDef
|
|
/** index of the current fire mode on the `ToolDefinition`'s list of fire modes */
|
|
private var fireModeIndex: Int = tdef.DefaultFireModeIndex
|
|
/** current ammunition slot being used by this fire mode */
|
|
private var ammoSlots: List[Tool.FireModeSlot] = List.empty
|
|
var lastDischarge: Long = 0
|
|
|
|
Tool.LoadDefinition(tool = this)
|
|
|
|
def FireModeIndex: Int = fireModeIndex
|
|
|
|
def FireModeIndex_=(index: Int): Int = {
|
|
fireModeIndex = index % Definition.FireModes.length
|
|
FireModeIndex
|
|
}
|
|
|
|
def FireMode: FireModeDefinition = Definition.FireModes(fireModeIndex)
|
|
|
|
def NextFireMode: FireModeDefinition = {
|
|
FireModeIndex = Definition.NextFireModeIndex(FireModeIndex)
|
|
AmmoSlot.Chamber = FireMode.Chamber
|
|
FireMode
|
|
}
|
|
|
|
def ToFireMode: Int = Definition.NextFireModeIndex(FireModeIndex)
|
|
|
|
def ToFireMode_=(index: Int): FireModeDefinition = {
|
|
FireModeIndex = index
|
|
AmmoSlot.Chamber = FireMode.Chamber
|
|
FireMode
|
|
}
|
|
|
|
def AmmoTypeIndex: Int = FireMode.AmmoTypeIndices(AmmoSlot.AmmoTypeIndex)
|
|
|
|
def AmmoTypeIndex_=(index: Int): Int = {
|
|
AmmoSlot.AmmoTypeIndex = index % FireMode.AmmoTypeIndices.length
|
|
AmmoTypeIndex
|
|
}
|
|
|
|
def AmmoType: Ammo.Value = Definition.AmmoTypes(AmmoTypeIndex).AmmoType
|
|
|
|
def NextAmmoType: Ammo.Value = {
|
|
AmmoSlot.AmmoTypeIndex = AmmoSlot.AmmoTypeIndex + 1
|
|
AmmoType
|
|
}
|
|
|
|
def Projectile: ProjectileDefinition = {
|
|
Definition.ProjectileTypes({
|
|
val projIndices = FireMode.ProjectileTypeIndices
|
|
if (projIndices.isEmpty) {
|
|
AmmoTypeIndex //e.g., bullet_9mm -> bullet_9mm_projectile, bullet_9mm_AP -> bullet_9mm_AP_projectile
|
|
} else {
|
|
projIndices(AmmoSlot.AmmoTypeIndex) //e.g., pulsar: f.mode1 -> pulsar_projectile, f.mode2 = pulsar_ap_projectile
|
|
}
|
|
})
|
|
}
|
|
|
|
def ProjectileType: Projectiles.Types.Value = Projectile.ProjectileType
|
|
|
|
def Magazine: Int = AmmoSlot.Magazine
|
|
|
|
def Magazine_=(mag: Int): Int = {
|
|
//AmmoSlot.Magazine = Math.min(Math.max(0, mag), MaxMagazine)
|
|
AmmoSlot.Magazine = Math.max(0, mag)
|
|
Magazine
|
|
}
|
|
|
|
def MaxMagazine: Int = {
|
|
val fmode = FireMode
|
|
fmode.CustomMagazine.get(AmmoType) match {
|
|
case Some(magSize) =>
|
|
magSize
|
|
case None =>
|
|
fmode.Magazine
|
|
}
|
|
}
|
|
|
|
def Discharge(rounds: Option[Int] = None): Int = {
|
|
lastDischarge = System.nanoTime()
|
|
Magazine = FireMode.Discharge(this, rounds)
|
|
}
|
|
|
|
def LastDischarge: Long = {
|
|
lastDischarge
|
|
}
|
|
|
|
def AmmoSlot: Tool.FireModeSlot = ammoSlots(FireMode.AmmoSlotIndex)
|
|
|
|
def AmmoSlots: List[Tool.FireModeSlot] = ammoSlots
|
|
|
|
def MaxAmmoSlot: Int = ammoSlots.length
|
|
|
|
def Definition: ToolDefinition = tdef
|
|
|
|
override def toString: String = Tool.toString(this)
|
|
}
|
|
|
|
//AmmoType = Definition.AmmoTypes( (Definition.FireModes(fireModeIndex)).AmmoTypeIndices( (ammoSlots((Definition.FireModes(fireModeIndex)).AmmoSlotIndex)).AmmoTypeIndex) ).AmmoType
|
|
|
|
object Tool {
|
|
def apply(toolDef: ToolDefinition): Tool = {
|
|
new Tool(toolDef)
|
|
}
|
|
|
|
/**
|
|
* Use the `*Definition` that was provided to this object to initialize its fields and settings.
|
|
* @param tool the `Tool` being initialized
|
|
*/
|
|
def LoadDefinition(tool: Tool): Unit = {
|
|
val tdef = tool.Definition
|
|
val maxSlot = tdef.FireModes.maxBy(fmode => fmode.AmmoSlotIndex).AmmoSlotIndex
|
|
tool.ammoSlots = buildFireModes(tdef, (0 to maxSlot).iterator, tdef.FireModes.toList)
|
|
tool.fireModeIndex = tdef.DefaultFireModeIndex
|
|
}
|
|
/**
|
|
* Substitute this `Definition` for the one that was originally provided for this entity.
|
|
* Calling this will not reconstruct the internal fields of the entity.
|
|
* @param tool the `Tool` being modified
|
|
* @param tdef the definition used to override the definition that was previously assigned this `Tool`;
|
|
* WILL override the assignment in the original constructor
|
|
*/
|
|
def LoadDefinition(tool: Tool, tdef: ToolDefinition): Unit = {
|
|
tool.tdef = tdef
|
|
}
|
|
|
|
@tailrec private def buildFireModes(
|
|
tdef: ToolDefinition,
|
|
iter: Iterator[Int],
|
|
fmodes: List[FireModeDefinition],
|
|
list: List[FireModeSlot] = Nil
|
|
): List[FireModeSlot] = {
|
|
if (!iter.hasNext) {
|
|
list
|
|
} else {
|
|
val index = iter.next()
|
|
fmodes.filter(fmode => fmode.AmmoSlotIndex == index) match {
|
|
case fmode :: _ =>
|
|
buildFireModes(tdef, iter, fmodes, list :+ new FireModeSlot(tdef, fmode))
|
|
case Nil =>
|
|
throw new IllegalArgumentException(
|
|
s"tool ${tdef.Name} ammo slot #$index is missing a fire mode specification; do not skip"
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
def toString(obj: Tool): String = {
|
|
s"${obj.Definition.Name} (mode=${obj.FireModeIndex}-${obj.AmmoType})(${obj.Magazine}/${obj.MaxMagazine})"
|
|
}
|
|
|
|
/**
|
|
* The `FireModeSlot` can be called the "magazine feed," an abstracted "ammunition slot."
|
|
* Most weapons will have only one ammunition slot and swap different ammunition into it as needed.
|
|
* In general to swap ammunition means to unload the onld ammunition and load the new ammunition.
|
|
* Many weapons also have one ammunition slot and multiple fire modes using the same list of ammunition
|
|
* This slot manages either of two ammunitions where one does not need to unload to be swapped to the other;
|
|
* however, the fire mod has most likely been changed.
|
|
* The Punisher -
|
|
* six ammunition types in total,
|
|
* two uniquely different types without unloading,
|
|
* two exclusive groups of ammunition divided into 2 cycled types and 4 cycled types -
|
|
* is an example of a weapon that benefits from this implementation.
|
|
*/
|
|
class FireModeSlot(private val tdef: ToolDefinition, private val fdef: FireModeDefinition) {
|
|
|
|
/**
|
|
* if this fire mode has multiple types of ammunition
|
|
* this is the index of the fire mode's ammo List, not a reference to the tool's ammo List
|
|
*/
|
|
private var ammoTypeIndex: Int = 0
|
|
|
|
/** a reference to the actual `AmmoBox` of this slot */
|
|
private var box: AmmoBox = AmmoBox(AmmoDefinition, MaxMagazine())
|
|
private var chamber = fdef.Chamber
|
|
|
|
def AmmoTypeIndex: Int = ammoTypeIndex
|
|
|
|
def AmmoTypeIndex_=(index: Int): Int = {
|
|
ammoTypeIndex = index % fdef.AmmoTypeIndices.length
|
|
AmmoTypeIndex
|
|
}
|
|
|
|
private def AmmoDefinition: AmmoBoxDefinition = {
|
|
tdef.AmmoTypes(fdef.AmmoTypeIndices(ammoTypeIndex))
|
|
}
|
|
|
|
/**
|
|
* This is a reference to the `Ammo.Value` whose `AmmoBoxDefinition` should be loaded into `box`.
|
|
* It may not be the correct `Ammo.Value` whose `AmmoBoxDefinition` is loaded into `box` such as is the case during ammunition swaps.
|
|
* Generally, convert from this index, to the index in the fire mode's ammunition list, to the index in the `ToolDefinition`'s ammunition list.
|
|
* @return the `Ammo` type that should be loaded into the magazine right now
|
|
*/
|
|
def AmmoType: Ammo.Value = AmmoDefinition.AmmoType
|
|
|
|
def AllAmmoTypes: List[Ammo.Value] = {
|
|
fdef.AmmoTypeIndices.map(index => tdef.AmmoTypes(fdef.AmmoTypeIndices(index)).AmmoType).toList
|
|
}
|
|
|
|
def Magazine: Int = box.Capacity
|
|
|
|
def Magazine_=(mag: Int): Int = {
|
|
box.Capacity = mag
|
|
Magazine
|
|
}
|
|
|
|
def Chamber: Int = chamber
|
|
|
|
def Chamber_=(chmbr: Int): Int = {
|
|
chamber = math.min(math.max(0, chmbr), fdef.Chamber)
|
|
Chamber
|
|
}
|
|
|
|
def MaxMagazine(): Int = {
|
|
fdef.CustomMagazine.get(AmmoType) match {
|
|
case Some(value) => value
|
|
case None => fdef.DefaultMagazine
|
|
}
|
|
}
|
|
|
|
def Box: AmmoBox = box
|
|
|
|
def Box_=(toBox: AmmoBox): Option[AmmoBox] = {
|
|
if (toBox.AmmoType == AmmoType) {
|
|
box = toBox
|
|
Some(Box)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
def Tool: ToolDefinition = tdef
|
|
|
|
def Definition: FireModeDefinition = fdef
|
|
}
|
|
}
|