Rudimentary SOI & Pain field functionality (#274)

* Rudimentary SOI & Pain field functionality

* Tweaks to painbox positioning and size
This commit is contained in:
Mazo 2019-10-06 19:09:25 +01:00 committed by Fate-JH
parent 242cc4f7c2
commit 9ea78e2801
30 changed files with 2803 additions and 780 deletions

View file

@ -12,9 +12,11 @@ import net.psforever.objects.serverobject.implantmech.ImplantTerminalMechDefinit
import net.psforever.objects.serverobject.locks.IFFLockDefinition
import net.psforever.objects.serverobject.mblocker.LockerDefinition
import net.psforever.objects.serverobject.pad.VehicleSpawnPadDefinition
import net.psforever.objects.serverobject.painbox.PainboxDefinition
import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.tube.SpawnTubeDefinition
import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition
import net.psforever.objects.serverobject.structures.SphereOfInfluence
import net.psforever.objects.serverobject.turret.{TurretDefinition, TurretUpgrade}
import net.psforever.objects.vehicles.{DestroyedVehicle, SeatArmorRestriction, UtilityType}
import net.psforever.objects.vital.{DamageType, StandardMaxDamage, StandardResolutions}
@ -1029,18 +1031,24 @@ object GlobalDefinitions {
val ground_rearm_terminal = new OrderTerminalDefinition(384)
val manned_turret = new TurretDefinition(480)
val painbox = new PainboxDefinition(622)
val painbox_continuous = new PainboxDefinition(623)
val painbox_door_radius = new PainboxDefinition(624)
val painbox_door_radius_continuous = new PainboxDefinition(625)
val painbox_radius = new PainboxDefinition(626)
val painbox_radius_continuous = new PainboxDefinition(627)
initMiscellaneous()
/*
Buildings
*/
val building : ObjectDefinition = new ObjectDefinition(474) { Name = "building" } //borrows object id of entity mainbase1
val warpgate : ObjectDefinition with SpawnPointDefinition = new ObjectDefinition(993) with SpawnPointDefinition
warpgate.Name = "warpgate"
warpgate.UseRadius = 301.8713f
warpgate.VehicleAllowance = true
warpgate.SpecificPointFunc = SpawnPoint.Gate
val amp_station : ObjectDefinition = new ObjectDefinition(45) with SphereOfInfluence { Name = "amp_station"; SOIRadius = 300 }
val comm_station : ObjectDefinition = new ObjectDefinition(211) with SphereOfInfluence { Name = "comm_station"; SOIRadius = 300 }
val comm_station_dsp : ObjectDefinition = new ObjectDefinition(212) with SphereOfInfluence { Name = "comm_station_dsp"; SOIRadius = 300 }
val cryo_facility : ObjectDefinition = new ObjectDefinition(215) with SphereOfInfluence { Name = "cryo_facility"; SOIRadius = 300 }
val hst : ObjectDefinition with SpawnPointDefinition = new ObjectDefinition(402) with SpawnPointDefinition
hst.Name = "hst"
@ -1057,12 +1065,56 @@ object GlobalDefinitions {
//hst.NoWarp += peregrine_flight
hst.SpecificPointFunc = SpawnPoint.Gate
val mainbase1 : ObjectDefinition = new ObjectDefinition(474) { Name = "mainbase1" }
val mainbase2 : ObjectDefinition = new ObjectDefinition(475) { Name = "mainbase2" }
val mainbase3 : ObjectDefinition = new ObjectDefinition(476) { Name = "mainbase3" }
val meeting_center_nc : ObjectDefinition = new ObjectDefinition(537) { Name = "meeting_center_nc" }
val meeting_center_tr : ObjectDefinition = new ObjectDefinition(538) { Name = "meeting_center_tr" }
val meeting_center_vs : ObjectDefinition = new ObjectDefinition(539) { Name = "meeting_center_vs" }
val minibase1 : ObjectDefinition = new ObjectDefinition(557) { Name = "minibase1" }
val minibase2 : ObjectDefinition = new ObjectDefinition(558) { Name = "minibase2" }
val minibase3 : ObjectDefinition = new ObjectDefinition(559) { Name = "minibase3" }
val redoubt : ObjectDefinition = new ObjectDefinition(726) { Name = "redoubt" }
val tech_plant : ObjectDefinition = new ObjectDefinition(852) with SphereOfInfluence { Name = "tech_plant"; SOIRadius = 300 }
val tower_a : ObjectDefinition = new ObjectDefinition(869) with SphereOfInfluence { Name = "tower_a"; SOIRadius = 50 }
val tower_b : ObjectDefinition = new ObjectDefinition(870) with SphereOfInfluence { Name = "tower_b"; SOIRadius = 50 }
val tower_c : ObjectDefinition = new ObjectDefinition(871) with SphereOfInfluence { Name = "tower_c"; SOIRadius = 50 }
val vanu_control_point : ObjectDefinition = new ObjectDefinition(931) { Name = "vanu_control_point" }
val vanu_vehicle_station : ObjectDefinition = new ObjectDefinition(948) { Name = "vanu_vehicle_station" }
val warpgate : ObjectDefinition with SpawnPointDefinition = new ObjectDefinition(993) with SpawnPointDefinition
warpgate.Name = "warpgate"
warpgate.UseRadius = 301.8713f
warpgate.VehicleAllowance = true
warpgate.SpecificPointFunc = SpawnPoint.Gate
val warpgate_cavern : ObjectDefinition with SpawnPointDefinition = new ObjectDefinition(994) with SpawnPointDefinition
warpgate_cavern.Name = "warpgate_cavern"
warpgate_cavern.UseRadius = 55.0522f
warpgate_cavern.UseRadius = 51.0522f
warpgate_cavern.VehicleAllowance = true
warpgate_cavern.SpecificPointFunc = SpawnPoint.Gate
val warpgate_small : ObjectDefinition with SpawnPointDefinition = new ObjectDefinition(995) with SpawnPointDefinition
warpgate_small.Name = "warpgate_small"
warpgate_small.UseRadius = 103f
warpgate_small.VehicleAllowance = true
warpgate_small.SpecificPointFunc = SpawnPoint.Gate
val bunker_gauntlet : ObjectDefinition = new ObjectDefinition(150) { Name = "bunker_gauntlet" }
val bunker_lg : ObjectDefinition = new ObjectDefinition(151) { Name = "bunker_lg" }
val bunker_sm : ObjectDefinition = new ObjectDefinition(152) { Name = "bunker_sm" }
val orbital_building_nc : ObjectDefinition = new ObjectDefinition(605) { Name = "orbital_building_nc" }
val orbital_building_tr : ObjectDefinition = new ObjectDefinition(606) { Name = "orbital_building_tr" }
val orbital_building_vs : ObjectDefinition = new ObjectDefinition(607) { Name = "orbital_building_vs" }
val VT_building_nc : ObjectDefinition = new ObjectDefinition(978) { Name = "VT_building_nc" }
val VT_building_tr : ObjectDefinition = new ObjectDefinition(979) { Name = "VT_building_tr" }
val VT_building_vs : ObjectDefinition = new ObjectDefinition(980) { Name = "VT_building_vs" }
val vt_dropship : ObjectDefinition = new ObjectDefinition(981) { Name = "vt_dropship" }
val vt_spawn : ObjectDefinition = new ObjectDefinition(984) { Name = "vt_spawn" }
val vt_vehicle : ObjectDefinition = new ObjectDefinition(985) { Name = "vt_vehicle" }
/**
* Given a faction, provide the standard assault melee weapon.
* @param faction the faction

View file

@ -0,0 +1,25 @@
package net.psforever.objects.serverobject.painbox
import akka.actor.{ActorContext, Props}
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.types.Vector3
class Painbox(tdef : PainboxDefinition) extends Amenity {
def Definition = tdef
}
object Painbox {
final case class Tick()
def apply(tdef : PainboxDefinition) : Painbox = {
new Painbox(tdef)
}
def Constructor(pos : Vector3, tdef : PainboxDefinition)(id : Int, context : ActorContext) : Painbox = {
val obj = Painbox(tdef)
obj.Position = pos + tdef.SphereOffset
obj.Actor = context.actorOf(Props(classOf[PainboxControl], obj), s"${obj.Definition.Name}_$id")
obj
}
}

View file

@ -0,0 +1,62 @@
package net.psforever.objects.serverobject.painbox
import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects.{DefaultCancellable, GlobalDefinitions}
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.structures.Building
import net.psforever.types.{PlanetSideEmpire, Vector3}
import services.ServiceManager
import services.ServiceManager.Lookup
import services.avatar.{AvatarAction, AvatarServiceMessage}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
class PainboxControl(painbox: Painbox) extends Actor {
var avatarService : ActorRef = Actor.noSender
private[this] val log = org.log4s.getLogger(s"Painbox")
private var painboxTick: Cancellable = DefaultCancellable.obj
private var nearestDoor : Door = null
def receive : Receive = {
case "startup" =>
ServiceManager.serviceManager ! Lookup("avatar")
nearestDoor = painbox.Owner.asInstanceOf[Building].Amenities.filter(x => x.isInstanceOf[Door])
.map(x => x.asInstanceOf[Door])
.sortBy(door => Vector3.DistanceSquared(painbox.Position, door.Position))
.head
case ServiceManager.LookupResult("avatar", endpoint) =>
avatarService = endpoint
log.trace("PainboxControl: " + painbox.GUID + " Got avatar service " + endpoint)
context.become(Processing)
painboxTick = context.system.scheduler.schedule(0 seconds,1 second, self, Painbox.Tick())
case _ => ;
}
def Processing : Receive = {
case Painbox.Tick() =>
//todo: Account for overlapping pain fields
if(painbox.Owner.Faction == PlanetSideEmpire.NEUTRAL) return null
if(painbox.Definition.HasNearestDoorDependency && nearestDoor.Open.isEmpty) return null
val playersToCheck = painbox.Owner.asInstanceOf[Building].PlayersInSOI
if(playersToCheck.length == 0) return null
// todo: Disable if no base power
val playersInRange = playersToCheck.filter(p =>
p.Faction != painbox.Owner.Faction
&& p.Health > 0
&& Math.pow(p.Position.x - painbox.Position.x, 2) + Math.pow(p.Position.y - painbox.Position.y, 2) + Math.pow(p.Position.z - painbox.Position.z, 2) < Math.pow(painbox.Definition.Radius, 2)
)
// Make 'em hurt.
playersInRange.foreach({ p =>
avatarService ! AvatarServiceMessage(p.Name, AvatarAction.EnvironmentalDamage(p.GUID, painbox.Definition.Damage))
// todo: Pain module
// todo: REK boosting
})
}
}

View file

@ -0,0 +1,50 @@
package net.psforever.objects.serverobject.painbox
import net.psforever.objects.definition.ObjectDefinition
import net.psforever.types.Vector3
class PainboxDefinition(objectId : Int) extends ObjectDefinition(objectId) {
private var alwaysOn : Boolean = true
private var radius : Float = 0f
private var damage : Int = 10
private var sphereOffset = Vector3(0f, 0f, -0.4f)
private var hasNearestDoorDependency = false
objectId match {
case 622 =>
Name = "painbox"
alwaysOn = false
radius = 10f // Guess - not in game_objects.adb.lst - probably not radius based but it will have to do for the moment
damage = 0
case 623 =>
Name = "painbox_continuous"
radius = 10f // Guess - not in game_objects.adb.lst - probably not radius based but it will have to do for the moment
case 624 =>
Name = "painbox_door_radius"
alwaysOn = false
radius = 10f * 0.6928f
hasNearestDoorDependency = true
damage = 0
case 625 =>
Name = "painbox_door_radius_continuous"
radius = 10f * 0.6928f
hasNearestDoorDependency = true
case 626 =>
Name = "painbox_radius"
alwaysOn = false
radius = 10f * 0.6928f
damage = 0
case 627 =>
Name = "painbox_radius_continuous"
radius = 8.55f
sphereOffset = Vector3.Zero
case _ =>
throw new IllegalArgumentException(s"${objectId} is not a valid painbox object id")
}
def Radius : Float = radius
def AlwaysOn : Boolean = alwaysOn
def Damage : Int = damage
def SphereOffset : Vector3 = sphereOffset
def HasNearestDoorDependency : Boolean = hasNearestDoorDependency
}

View file

@ -4,7 +4,7 @@ package net.psforever.objects.serverobject.structures
import java.util.concurrent.TimeUnit
import akka.actor.ActorContext
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.{GlobalDefinitions, Player}
import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.hackable.Hackable
@ -22,6 +22,8 @@ class Building(private val building_guid : Int, private val map_id : Int, privat
*/
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
private var amenities : List[Amenity] = List.empty
private var playersInSOI : List[Player] = List.empty
GUID = PlanetSideGUID(building_guid)
def MapId : Int = map_id
@ -49,6 +51,13 @@ class Building(private val building_guid : Int, private val map_id : Int, privat
}
}
def PlayersInSOI : List[Player] = playersInSOI
def PlayersInSOI_=(list : List[Player]) : List[Player] = {
playersInSOI = list
playersInSOI
}
def Zone : Zone = zone
def Info : (
@ -136,6 +145,14 @@ object Building {
new Building(guid, map_id, zone, buildingType, GlobalDefinitions.building)
}
def Structure(buildingType : StructureType.Value, location : Vector3, definition: ObjectDefinition)(guid : Int, map_id : Int, zone : Zone, context : ActorContext) : Building = {
import akka.actor.Props
val obj = new Building(guid, map_id, zone, buildingType, definition)
obj.Position = location
obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$map_id-$buildingType-building")
obj
}
def Structure(buildingType : StructureType.Value, location : Vector3)(guid : Int, map_id : Int, zone : Zone, context : ActorContext) : Building = {
import akka.actor.Props
val obj = new Building(guid, map_id, zone, buildingType, GlobalDefinitions.building)

View file

@ -0,0 +1,14 @@
package net.psforever.objects.serverobject.structures
import net.psforever.objects.Player
trait SphereOfInfluence {
private var soiRadius : Int = 0
def SOIRadius : Int = soiRadius
def SOIRadius_=(radius : Int) : Int = {
soiRadius = radius
SOIRadius
}
}

View file

@ -0,0 +1,45 @@
package net.psforever.objects.zones;
import akka.actor.{Actor, Cancellable}
import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.serverobject.structures.{Building, SphereOfInfluence}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
class SphereOfInfluenceActor(zone: Zone) extends Actor {
def receive : Receive = Established
private var populateTick: Cancellable = context.system.scheduler.scheduleOnce(5 seconds, self, SOI.Populate())
private[this] val log = org.log4s.getLogger(s"${zone.Id.capitalize}-SphereOfInfluenceActor")
def Established : Receive = {
case SOI.Populate() =>
UpdateSOI()
}
def UpdateSOI(): Unit = {
val players = zone.LivePlayers
zone.Buildings.foreach({
case (_, building : Building) =>
building.Definition match {
case _ : ObjectDefinition with SphereOfInfluence =>
// todo: overlapping soi (e.g. tower soi in base soi) order by smallest soi first?
val playersInSoi = players.filter(p => Math.pow(p.Position.x - building.Position.x, 2) + Math.pow(p.Position.y - building.Position.y, 2) < Math.pow(300, 2) )
if(playersInSoi.length > 0) {
// log.info(s"Building ${building.GUID} players in soi: ${playersInSoi.toString()}" )
}
building.PlayersInSOI = playersInSoi
case _ => ;
}
})
populateTick = context.system.scheduler.scheduleOnce(5 seconds, self, SOI.Populate())
}
}
object SOI {
/** Populate the list of players within a SOI **/
final case class Populate()
}

View file

@ -14,6 +14,7 @@ import net.psforever.objects.guid.selector.RandomSelector
import net.psforever.objects.guid.source.LimitedNumberSource
import net.psforever.objects.inventory.Container
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.painbox.{Painbox, PainboxDefinition}
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.{Amenity, Building, WarpGate}
import net.psforever.objects.serverobject.terminals.ProximityUnit
@ -50,6 +51,9 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
/** Governs general synchronized external requests. */
private var actor = ActorRef.noSender
/** Actor that handles SOI related functionality, for example if a player is in a SOI **/
private var soi = ActorRef.noSender
/** Used by the globally unique identifier system to coordinate requests. */
private var accessor : ActorRef = ActorRef.noSender
/** The basic support structure for the globally unique number system used by this `Zone`. */
@ -114,6 +118,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"$Id-vehicles")
population = context.actorOf(Props(classOf[ZonePopulationActor], this, players, corpses), s"$Id-players")
projector = context.actorOf(Props(classOf[ZoneHotSpotProjector], this), s"$Id-hotpots")
soi = context.actorOf(Props(classOf[SphereOfInfluenceActor], this), s"$Id-soi")
BuildLocalObjects(context, guid)
BuildSupportObjects()
@ -389,6 +394,12 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
case o : PlanetSideServerObject =>
o.Actor ! Service.Startup()
}
buildings.values
.flatMap(_.Amenities.filter(_.Definition.isInstanceOf[PainboxDefinition]))
.collect {
case painbox : Painbox =>
painbox.Actor ! "startup"
}
}
private def CreateSpawnGroups() : Unit = {

View file

@ -25,7 +25,7 @@ import shapeless.{::, HNil}
* 60, 61, 62, 63 - Displays "This facility's generator is under attack!"
* 64, 65, 66, 67 - Displays "Generator has Overloaded! Evacuate Generator Room Immediately!"
* 68, 69, 70, 71 - Displays "This facility's generator is back on line"
* 88, 89, 90, 91 - ???? (Has been seen on vehicle pad objects)
* 88, 89, 90, 91 - ???? (Has been seen on vehicle pad objects, possibly some sort of reset flag after base faction flip / hack clear?)
* 92, 93, 94, 95 - Plays vehicle pad animation moving downwards
* 96, 97, 98, 99 - Makes the vehicle bounce slightly. Have seen this in packet captures after taking a vehicle through a warpgate
* 200, 201, 202, 203 - For aircraft - client shows "THe bailing mechanism failed! To fix the mechanism, land and repair the vehicle!"

View file

@ -23,6 +23,7 @@ object AvatarAction {
final case class ChangeFireState_Start(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
final case class ChangeFireState_Stop(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Action
final case class EnvironmentalDamage(player_guid : PlanetSideGUID, amont: Int) extends Action
final case class Damage(player_guid : PlanetSideGUID, target : Player, resolution_function : (Any)=>Unit) extends Action
final case class DeployItem(player_guid : PlanetSideGUID, item : PlanetSideGameObject with Deployable) extends Action
final case class Destroy(victim : PlanetSideGUID, killer : PlanetSideGUID, weapon : PlanetSideGUID, pos : Vector3) extends Action

View file

@ -18,6 +18,7 @@ object AvatarResponse {
final case class ChangeFireState_Start(weapon_guid : PlanetSideGUID) extends Response
final case class ChangeFireState_Stop(weapon_guid : PlanetSideGUID) extends Response
final case class ConcealPlayer() extends Response
final case class EnvironmentalDamage(target : PlanetSideGUID, amount : Int) extends Response
final case class DamageResolution(target : Player, resolution_function : (Any)=>Unit) extends Response
final case class Destroy(victim : PlanetSideGUID, killer : PlanetSideGUID, weapon : PlanetSideGUID, pos : Vector3) extends Response
final case class DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int) extends Response

View file

@ -64,6 +64,10 @@ class AvatarService extends Actor {
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ConcealPlayer())
)
case AvatarAction.EnvironmentalDamage(player_guid, amount) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EnvironmentalDamage(player_guid, amount))
)
case AvatarAction.Damage(player_guid, target, resolution_function) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.DamageResolution(target, resolution_function))