early reivision for v010; recording ongoing shots fired and landed

This commit is contained in:
Fate-JH 2023-08-17 23:55:17 -04:00
parent ac0892ab34
commit c869cdc8a0
16 changed files with 736 additions and 95 deletions

View file

@ -0,0 +1,335 @@
/* changes to objects from V008__Scoring.sql */
ALTER TABLE killactivity
ADD COLUMN victim_mounted INT NO NULL DEFAULT 0;
DROP PROCEDURE IF EXISTS proc_sessionnumber_initAndOrIncrease;
CREATE OR REPLACE PROCEDURE proc_sessionnumber_initAndOrIncreasePerHour
(avatarId IN Int, number OUT Int, nextNumber OUT Int)
AS
$$
DECLARE time TIMESTAMP;
BEGIN
SELECT MAX(session_id) INTO number
FROM sessionnumber
WHERE avatar_id = avatarId;
SELECT COALESCE(timestamp) INTO time
FROM sessionnumber
WHERE avatar_id = avatarId AND session_id = number;
IF (time IS null) THEN
number := 0;
nextNumber := 1;
INSERT INTO sessionnumber
VALUES (avatarId, nextNumber);
ELSIF (CURRENT_TIMESTAMP > DATE_TRUNC('hour', time) + interval '1' hour) THEN
nextNumber := number + 1;
INSERT INTO sessionnumber
VALUES (avatarId, nextNumber);
ELSE
nextNumber := number;
UPDATE sessionnumber
SET timestamp = CURRENT_TIMESTAMP
WHERE avatar_id = avatarId AND session_id = number;
END IF;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE PROCEDURE proc_sessionnumber_get
(avatarId IN Int, number OUT Int)
AS
$$
DECLARE sessionId Int;
BEGIN
SELECT COALESCE(session_id) INTO sessionId
FROM account
WHERE avatar_logged_in = avatarId;
IF sessionId IS NULL THEN
number := 0;
ELSE
number := sessionId;
END IF;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE PROCEDURE proc_weaponstat_addEntryIfNone
(avatarId IN Int, weaponId IN Int)
AS
$$
BEGIN
IF NOT EXISTS(
SELECT avatar_id, weapon_id
FROM weaponstat
WHERE avatar_id = avatarId AND weapon_id = weaponId
GROUP BY avatar_id, weapon_id
) THEN
INSERT INTO weaponstat (avatar_id, weapon_id)
VALUES (avatarId, weaponId);
END IF;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE PROCEDURE proc_weaponstatsession_addEntryIfNoneWithSessionId
(avatarId IN Int, sessionId IN Int, weaponId IN Int)
AS
$$
BEGIN
IF NOT EXISTS(
SELECT avatar_id, session_id, weapon_id
FROM weaponstatsession
WHERE avatar_id = avatarId AND weapon_id = weaponId AND session_id = sessionId
GROUP BY avatar_id, session_id, weapon_id
) THEN
INSERT INTO weaponstatsession (avatar_id, session_id, weapon_id)
VALUES (avatarId, sessionId, weaponId);
END IF;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE PROCEDURE proc_weaponstatsession_addEntryIfNone
(avatarId IN Int, weaponId IN Int)
AS
$$
DECLARE sessionId Int;
BEGIN
CALL proc_sessionnumber_get(avatarId, sessionId);
CALL proc_weaponstatsession_addEntryIfNoneWithSessionId(avatarId, sessionId, weaponId);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION fn_account_newSession()
RETURNS TRIGGER
AS
$$
DECLARE avatarId Int;
DECLARE sessionId Int;
DECLARE oldSessionId Int;
BEGIN
avatarId := NEW.avatar_logged_in;
CALL proc_sessionnumber_initAndOrIncreasePerHour(avatarId, oldSessionId, sessionId);
IF (sessionId > oldSessionId) THEN
BEGIN
UPDATE account
SET session_id = sessionId
WHERE id = OLD.id;
INSERT INTO kdasession (avatar_id, session_id)
VALUES (avatarId, sessionId);
END;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE TRIGGER psf_account_newSession
AFTER UPDATE
OF avatar_logged_in
ON account
FOR EACH ROW
WHEN (OLD.avatar_logged_in = 0 AND NEW.avatar_logged_in > 0)
EXECUTE FUNCTION fn_account_newSession();
CREATE OR REPLACE FUNCTION fn_killactivity_updateRelatedStats()
RETURNS TRIGGER
AS
$$
DECLARE killerSessionId Int;
DECLARE victimSessionId Int;
DECLARE killerId Int;
DECLARE victimId Int;
DECLARE weaponId Int;
BEGIN
killerId := NEW.killer_id;
victimId := NEW.victim_id;
weaponId := NEW.weapon_id;
CALL proc_sessionnumber_get(killerId, killerSessionId);
CALL proc_sessionnumber_get(victimId, victimSessionId);
BEGIN
UPDATE kdasession
SET kills = kills + 1
WHERE avatar_id = killerId AND session_id = killerSessionId;
END;
BEGIN
UPDATE kdasession
SET deaths = deaths + 1
WHERE avatar_id = victimId AND session_id = victimSessionId;
END;
BEGIN
CALL proc_weaponstatsession_addEntryIfNoneWithSessionId(killerId, killerSessionId, weaponId);
UPDATE weaponstatsession
SET kills = kills + 1
WHERE avatar_id = killerId AND session_id = killerSessionId AND weapon_id = weaponId;
END;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION fn_weaponstatsession_addEntryIfNone()
RETURNS TRIGGER
AS
$$
DECLARE avatarId Int;
DECLARE weaponId Int;
BEGIN
avatarId := NEW.avatar_id;
weaponId := NEW.weapon_id;
CALL proc_weaponstatsession_addEntryIfNone(avatarId, weaponId);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION fn_kda_addEntryIfNone()
RETURNS TRIGGER
AS
$$
DECLARE avatarId Int;
BEGIN
avatarId := NEW.avatar_id;
CALL proc_kda_addEntryIfNone(avatarId);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION fn_weaponstat_addEntryIfNone()
RETURNS TRIGGER
AS
$$
DECLARE avatarId Int;
DECLARE weaponId Int;
BEGIN
avatarId := NEW.avatar_id;
weaponId := NEW.weapon_id;
CALL proc_weaponstat_addEntryIfNone(avatarId, weaponId);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION fn_kdasession_updateOnDelete()
RETURNS TRIGGER
AS
$$
DECLARE avatarId Int;
DECLARE oldKills Int;
DECLARE oldDeaths Int;
DECLARE oldAssists Int;
BEGIN
avatarId := OLD.avatar_id;
oldKills := OLD.kills;
oldDeaths := OLD.deaths;
oldAssists := OLD.assists;
CALL proc_kda_addEntryIfNone(avatarId);
UPDATE kda
SET kills = kills + oldKills,
deaths = deaths + oldDeaths,
assists = assists + oldAssists
WHERE avatar_id = avatarId;
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION fn_weaponstatsession_updateOnDelete()
RETURNS TRIGGER
AS
$$
DECLARE avatarId Int;
DECLARE weaponId Int;
DECLARE oldKills Int;
DECLARE oldAssists Int;
DECLARE oldFired Int;
DECLARE oldLanded Int;
BEGIN
avatarId := OLD.avatar_id;
weaponId := OLD.weapon_id;
oldKills := OLD.kills;
oldAssists := OLD.assists;
oldFired := OLD.shots_fired;
oldLanded := OLD.shots_landed;
CALL proc_weaponstat_addEntryIfNone(avatarId, weaponId);
UPDATE weaponstat
SET kills = kills + oldKills,
assists = assists + oldAssists,
shots_fired = shots_fired + oldFired,
shots_landed = shots_landed + oldLanded
WHERE avatar_id = avatarId AND weapon_id = weaponId;
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
/* new objects */
CREATE OR REPLACE TABLE machinedestroyed (
"index" SERIAL PRIMARY KEY NOT NULL,
"avatar_id" INT NOT NULL REFERENCES avatar (id),
"weapon_id" INT NOT NULL,
"machine_type" INT NOT NULL,
"machine_faction" SMALLINT NOT NULL,
"hacked_faction" SMALLINT NOT NULL,
"as_cargo" BOOLEAN NOT NULL,
"zone_num" SMALLINT NOT NULL,
"px" INT NOT NULL,
"py" INT NOT NULL,
"pz" INT NOT NULL,
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
/*
expectations:
heal player with medapp: no intermediate_type, implement_type is the medapp (no, yes)
revive player with medapp: intermediate_type is 121, implement_type is the medapp (yes, yes)
repair max with bank: no intermediate_type, implement_type is the bank (no, yes)
repair foo with gluegun: intermediate_type is the foo object type, implement_type is the gluegun (yes, yes)
bail from vehicle: intermediate_type is the vehicle object type, no implement_type (yes, no)
cargo from vehicle: intermediate_type is the vehicle object type, implement_type is the cargo vehicle object type (yes, yes)
damage from vehicle (collision): no intermediate_type, implement_type is vehicle object type (no, yes)
damage from vehicle (explosion): intermediate_type is vehicle object type, implement_type is intermediate_type (yes, yes)
always ignore target_id = user_id
*/
CREATE OR REPLACE TABLE supportactivity (
"index" SERIAL PRIMARY KEY NOT NULL,
"user_id" INT NOT NULL REFERENCES avatar (id), -- player that provides the support
"target_id" INT NOT NULL REFERENCES avatar (id), -- benefactor of the support
"interaction_type" SMALLINT NOT NULL, -- description of support
"intermediate_type" INT DEFAULT 0, -- through what medium user_id supports target_id
"implement_type" INT DEFAULT 0, -- tool utilized by user_id to support target_id, potentially via interaction with intermediate_type
"bep" INT NOT NULL,
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
/*
Before inserting a value into the weaponstatsession table to session id -1,
correct the session id to the most current session id,
and invalidate the attempted insertion at that -1 session id.
*/
CREATE OR REPLACE FUNCTION fn_weaponstatsession_beforeInsert()
RETURNS TRIGGER
AS
$$
DECLARE avatarId Int;
DECLARE weaponId Int;
DECLARE sessionId Int;
DECLARE oldAssists Int;
DECLARE oldFired Int;
DECLARE oldLanded Int;
BEGIN
avatarId := NEW.avatar_id;
weaponId := NEW.weapon_id;
oldAssists := NEW.assists;
oldFired := NEW.shots_fired;
oldLanded := NEW.shots_landed;
CALL proc_sessionnumber_get(avatarId, sessionId);
CALL proc_weaponstatsession_addEntryIfNoneWithSessionId(avatarId, sessionId, weaponId);
BEGIN
UPDATE weaponstatsession
SET
assists = assists + oldAssists,
shots_fired = shots_fired + oldFired,
shots_landed = shots_landed + oldLanded
WHERE avatar_id = avatarId AND session_id = sessionId AND weapon_id = weaponId;
END;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE TRIGGER psf_weaponstatsession_beforeInsert
BEFORE INSERT
ON weaponstatsession
FOR EACH ROW
WHEN (NEW.session_id = -1)
EXECUTE FUNCTION fn_weaponstatsession_beforeInsert();

View file

@ -137,12 +137,10 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne
bcryptedPassword bcryptedPassword
} }
def getAccountLogin(username: String, password: Option[String], token: Option[String]): Unit = { def getAccountLogin(username: String, passwordOpt: Option[String], tokenOpt: Option[String]): Unit = {
tokenOpt match {
if (token.isDefined) { case Some(token) => accountLoginWithToken(token)
accountLoginWithToken(token.getOrElse("")) case None => accountLogin(username, passwordOpt.getOrElse(""))
} else {
accountLogin(username, password.getOrElse(""))
} }
} }
@ -164,7 +162,8 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne
accountOption <- accountsExact.headOption orElse accountsLower.headOption match { accountOption <- accountsExact.headOption orElse accountsLower.headOption match {
// account found // account found
case Some(account) => Future.successful(Some(account)) case Some(account) =>
Future.successful(Some(account))
// create new account // create new account
case None => case None =>

View file

@ -9,6 +9,7 @@ import java.util.concurrent.atomic.AtomicInteger
import net.psforever.actors.zone.ZoneActor import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.avatar.scoring.{Assist, Death, EquipmentStat, KDAStat, Kill} import net.psforever.objects.avatar.scoring.{Assist, Death, EquipmentStat, KDAStat, Kill}
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.sourcing.VehicleSource
import net.psforever.objects.vital.InGameHistory import net.psforever.objects.vital.InGameHistory
import net.psforever.objects.vehicles.MountedWeapons import net.psforever.objects.vehicles.MountedWeapons
import org.joda.time.{LocalDateTime, Seconds} import org.joda.time.{LocalDateTime, Seconds}
@ -840,6 +841,15 @@ object AvatarActor {
out.future out.future
} }
def avatarNoLongerLoggedIn(accountId: Long): Unit = {
import ctx._
ctx.run(
query[persistence.Account]
.filter(_.id == lift(accountId))
.update(_.avatarLoggedIn -> lift(0L))
)
}
def toAvatar(avatar: persistence.Avatar): Avatar = { def toAvatar(avatar: persistence.Avatar): Avatar = {
val bep = avatar.bep val bep = avatar.bep
val convertedCosmetics = if (BattleRank.showCosmetics(bep)) { val convertedCosmetics = if (BattleRank.showCosmetics(bep)) {
@ -1044,13 +1054,13 @@ class AvatarActor(
} yield true } yield true
inits.onComplete { inits.onComplete {
case Success(_) => case Success(_) =>
performAvatarLogin(avatarId, account.id, replyTo) performAvatarLoginTest(avatarId, account.id, replyTo)
case Failure(e) => case Failure(e) =>
log.error(e)("db failure") log.error(e)("db failure")
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 6)) sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 6))
} }
} else { } else {
performAvatarLogin(avatarId, account.id, replyTo) performAvatarLoginTest(avatarId, account.id, replyTo)
} }
case Success(_) => case Success(_) =>
//TODO this may not be an actual failure, but don't know what to do //TODO this may not be an actual failure, but don't know what to do
@ -1069,6 +1079,11 @@ class AvatarActor(
buffer.stash(other) buffer.stash(other)
Behaviors.same Behaviors.same
} }
.receiveSignal {
case (_, PostStop) =>
AvatarActor.avatarNoLongerLoggedIn(account.id)
Behaviors.same
}
} }
def postCharacterSelectBehaviour(): Behavior[Command] = { def postCharacterSelectBehaviour(): Behavior[Command] = {
@ -1828,6 +1843,7 @@ class AvatarActor(
} }
.receiveSignal { .receiveSignal {
case (_, PostStop) => case (_, PostStop) =>
AvatarActor.avatarNoLongerLoggedIn(account.get.id)
AvatarActor.saveAvatarData(avatar) AvatarActor.saveAvatarData(avatar)
staminaRegenTimer.cancel() staminaRegenTimer.cancel()
implantTimers.values.foreach(_.cancel()) implantTimers.values.foreach(_.cancel())
@ -1848,9 +1864,25 @@ class AvatarActor(
Future.failed(ex).asInstanceOf[Future[Loadout]] Future.failed(ex).asInstanceOf[Future[Loadout]]
} }
def performAvatarLoginTest(avatarId: Long, accountId: Long, replyTo: ActorRef[AvatarLoginResponse]): Unit = {
import ctx._
val blockLogInIfNot = for {
out <- ctx.run(query[persistence.Account].filter(_.id == lift(accountId)))
} yield out
blockLogInIfNot.onComplete {
case Success(account)
//TODO test against acceptable player factions
if account.exists { _.avatarLoggedIn == 0 } =>
//accept
performAvatarLogin(avatarId, accountId, replyTo)
case _ =>
//refuse
//TODO refuse
}
}
def performAvatarLogin(avatarId: Long, accountId: Long, replyTo: ActorRef[AvatarLoginResponse]): Unit = { def performAvatarLogin(avatarId: Long, accountId: Long, replyTo: ActorRef[AvatarLoginResponse]): Unit = {
import ctx._ import ctx._
val result = for { val result = for {
//log this login //log this login
_ <- ctx.run( _ <- ctx.run(
@ -1862,7 +1894,10 @@ class AvatarActor(
_ <- ctx.run( _ <- ctx.run(
query[persistence.Account] query[persistence.Account]
.filter(_.id == lift(accountId)) .filter(_.id == lift(accountId))
.update(_.lastFactionId -> lift(avatar.faction.id)) .update(
_.lastFactionId -> lift(avatar.faction.id),
_.avatarLoggedIn -> lift(avatarId)
)
) )
//retrieve avatar data //retrieve avatar data
loadouts <- initializeAllLoadouts() loadouts <- initializeAllLoadouts()
@ -2995,8 +3030,26 @@ class AvatarActor(
player.HistoryAndContributions() player.HistoryAndContributions()
} }
zone.actor ! ZoneActor.RewardOurSupporters(playerSource, historyTranscript, kill, exp) zone.actor ! ZoneActor.RewardOurSupporters(playerSource, historyTranscript, kill, exp)
case _: Assist => val target = kill.info.targetAfter.asInstanceOf[PlayerSource]
() val targetMounted = target.seatedIn.map { case (v: VehicleSource, seat) =>
val definition = v.Definition
definition.ObjectId * 10 + Vehicles.SeatPermissionGroup(definition, seat).map { _.id }.getOrElse(0)
}.getOrElse(0)
zones.exp.ToDatabase.reportKillBy(
avatar.id.toLong,
target.CharId,
target.ExoSuit.id,
targetMounted,
kill.info.interaction.cause.attribution,
player.Zone.Number,
target.Position,
kill.experienceEarned
)
case assist: Assist =>
val avatarId = avatar.id.toLong
assist.weapons.foreach {
zones.exp.ToDatabase.reportAssistKills(avatarId, _, assists = 1)
}
case _: Death => case _: Death =>
zone.AvatarEvents ! AvatarServiceMessage( zone.AvatarEvents ! AvatarServiceMessage(
player.Name, player.Name,
@ -3019,6 +3072,7 @@ class AvatarActor(
def updateToolDischarge(stats: EquipmentStat): Unit = { def updateToolDischarge(stats: EquipmentStat): Unit = {
avatar.scorecard.rate(stats) avatar.scorecard.rate(stats)
zones.exp.ToDatabase.reportToolDischarge(avatar.id.toLong, stats)
} }
def createAvatar( def createAvatar(

View file

@ -542,6 +542,7 @@ class SessionAvatarHandlers(
sessionData.terminals.CancelAllProximityUnits() sessionData.terminals.CancelAllProximityUnits()
sessionData.zoning sessionData.zoning
AvatarActor.savePlayerLocation(player) AvatarActor.savePlayerLocation(player)
sessionData.shooting.reportOngoingShots(sessionData.shooting.reportOngoingShotsToDatabase)
sessionData.zoning.spawn.shiftPosition = Some(player.Position) sessionData.zoning.spawn.shiftPosition = Some(player.Position)
//respawn //respawn

View file

@ -2,6 +2,8 @@
package net.psforever.actors.session.support package net.psforever.actors.session.support
import akka.actor.{ActorContext, typed} import akka.actor.{ActorContext, typed}
import net.psforever.objects.zones.exp.ToDatabase
import scala.collection.mutable import scala.collection.mutable
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future import scala.concurrent.Future
@ -43,7 +45,8 @@ private[support] class WeaponAndProjectileOperations(
var prefire: mutable.Set[PlanetSideGUID] = mutable.Set.empty //if WeaponFireMessage precedes ChangeFireStateMessage_Start var prefire: mutable.Set[PlanetSideGUID] = mutable.Set.empty //if WeaponFireMessage precedes ChangeFireStateMessage_Start
private[support] var shootingStart: mutable.HashMap[PlanetSideGUID, Long] = mutable.HashMap[PlanetSideGUID, Long]() private[support] var shootingStart: mutable.HashMap[PlanetSideGUID, Long] = mutable.HashMap[PlanetSideGUID, Long]()
private[support] var shootingStop: mutable.HashMap[PlanetSideGUID, Long] = mutable.HashMap[PlanetSideGUID, Long]() private[support] var shootingStop: mutable.HashMap[PlanetSideGUID, Long] = mutable.HashMap[PlanetSideGUID, Long]()
private var ongoingShotsFired: Int = 0 private val shotsFired: mutable.HashMap[Int,Int] = mutable.HashMap[Int,Int]()
private val shotsLanded: mutable.HashMap[Int,Int] = mutable.HashMap[Int,Int]()
private[support] var shotsWhileDead: Int = 0 private[support] var shotsWhileDead: Int = 0
private val projectiles: Array[Option[Projectile]] = private val projectiles: Array[Option[Projectile]] =
Array.fill[Option[Projectile]](Projectile.rangeUID - Projectile.baseUID)(None) Array.fill[Option[Projectile]](Projectile.rangeUID - Projectile.baseUID)(None)
@ -157,7 +160,7 @@ private[support] class WeaponAndProjectileOperations(
fireStateStopPlayerMessages(item_guid) fireStateStopPlayerMessages(item_guid)
case Some(_) => case Some(_) =>
fireStateStopMountedMessages(item_guid) fireStateStopMountedMessages(item_guid)
case _ => () case _ =>
log.warn(s"ChangeFireState_Stop: can not find $item_guid") log.warn(s"ChangeFireState_Stop: can not find $item_guid")
} }
sessionData.progressBarUpdate.cancel() sessionData.progressBarUpdate.cancel()
@ -275,8 +278,8 @@ private[support] class WeaponAndProjectileOperations(
val LongRangeProjectileInfoMessage(guid, _, _) = pkt val LongRangeProjectileInfoMessage(guid, _, _) = pkt
FindContainedWeapon match { FindContainedWeapon match {
case (Some(_: Vehicle), weapons) case (Some(_: Vehicle), weapons)
if weapons.exists { _.GUID == guid } => ; //now what? if weapons.exists { _.GUID == guid } => () //now what?
case _ => ; case _ => ()
} }
} }
@ -329,13 +332,11 @@ private[support] class WeaponAndProjectileOperations(
_: Vector3, _: Vector3,
hitPos: Vector3 hitPos: Vector3
) => ) =>
ResolveProjectileInteraction(proj, DamageResolution.Hit, target, hitPos) match { ResolveProjectileInteraction(proj, DamageResolution.Hit, target, hitPos).collect { resprojectile =>
case Some(resprojectile) => addShotsLanded(resprojectile.cause.attribution, shots = 1)
avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(resprojectile.cause.attribution,0,1,0))
sessionData.handleDealingDamage(target, resprojectile) sessionData.handleDealingDamage(target, resprojectile)
case None => ;
} }
case _ => ; case _ => ()
} }
case None => case None =>
log.warn(s"ResolveProjectile: expected projectile, but ${projectile_guid.guid} not found") log.warn(s"ResolveProjectile: expected projectile, but ${projectile_guid.guid} not found")
@ -368,26 +369,22 @@ private[support] class WeaponAndProjectileOperations(
sessionData.validObject(direct_victim_uid, decorator = "SplashHit/direct_victim") match { sessionData.validObject(direct_victim_uid, decorator = "SplashHit/direct_victim") match {
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) => case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
CheckForHitPositionDiscrepancy(projectile_guid, target.Position, target) CheckForHitPositionDiscrepancy(projectile_guid, target.Position, target)
ResolveProjectileInteraction(projectile, resolution1, target, target.Position) match { ResolveProjectileInteraction(projectile, resolution1, target, target.Position).collect { resprojectile =>
case Some(resprojectile) => addShotsLanded(resprojectile.cause.attribution, shots = 1)
avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(resprojectile.cause.attribution,0,1,0))
sessionData.handleDealingDamage(target, resprojectile) sessionData.handleDealingDamage(target, resprojectile)
case None => ;
} }
case _ => ; case _ => ()
} }
//other victims //other victims
targets.foreach(elem => { targets.foreach(elem => {
sessionData.validObject(elem.uid, decorator = "SplashHit/other_victims") match { sessionData.validObject(elem.uid, decorator = "SplashHit/other_victims") match {
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) => case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target) CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target)
ResolveProjectileInteraction(projectile, resolution2, target, explosion_pos) match { ResolveProjectileInteraction(projectile, resolution2, target, explosion_pos).collect { resprojectile =>
case Some(resprojectile) => addShotsLanded(resprojectile.cause.attribution, shots = 1)
avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(resprojectile.cause.attribution,0,1,0))
sessionData.handleDealingDamage(target, resprojectile) sessionData.handleDealingDamage(target, resprojectile)
case None => ;
} }
case _ => ; case _ => ()
} }
}) })
//... //...
@ -413,7 +410,7 @@ private[support] class WeaponAndProjectileOperations(
continent.Projectile ! ZoneProjectile.Remove(projectile.GUID) continent.Projectile ! ZoneProjectile.Remove(projectile.GUID)
} }
} }
case None => ; case None => ()
} }
} }
@ -422,13 +419,12 @@ private[support] class WeaponAndProjectileOperations(
sessionData.validObject(victim_guid, decorator = "Lash") match { sessionData.validObject(victim_guid, decorator = "Lash") match {
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) => case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
CheckForHitPositionDiscrepancy(projectile_guid, hit_pos, target) CheckForHitPositionDiscrepancy(projectile_guid, hit_pos, target)
ResolveProjectileInteraction(projectile_guid, DamageResolution.Lash, target, hit_pos) match { ResolveProjectileInteraction(projectile_guid, DamageResolution.Lash, target, hit_pos).foreach {
case Some(resprojectile) => resprojectile =>
avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(resprojectile.cause.attribution,0,1,0)) addShotsLanded(resprojectile.cause.attribution, shots = 1)
sessionData.handleDealingDamage(target, resprojectile) sessionData.handleDealingDamage(target, resprojectile)
case None => ;
} }
case _ => ; case _ => ()
} }
} }
@ -524,7 +520,7 @@ private[support] class WeaponAndProjectileOperations(
obj match { obj match {
case turret: FacilityTurret if turret.Definition == GlobalDefinitions.vanu_sentry_turret => case turret: FacilityTurret if turret.Definition == GlobalDefinitions.vanu_sentry_turret =>
turret.Actor ! FacilityTurret.WeaponDischarged() turret.Actor ! FacilityTurret.WeaponDischarged()
case _ => ; case _ => ()
} }
} else { } else {
log.warn( log.warn(
@ -532,7 +528,7 @@ private[support] class WeaponAndProjectileOperations(
) )
} }
case _ => ; case _ => ()
} }
} }
@ -552,7 +548,7 @@ private[support] class WeaponAndProjectileOperations(
case Some(v: Vehicle) => case Some(v: Vehicle) =>
//assert subsystem states //assert subsystem states
v.SubsystemMessages().foreach { sendResponse } v.SubsystemMessages().foreach { sendResponse }
case _ => ; case _ => ()
} }
} }
if (enabledTools.nonEmpty) { if (enabledTools.nonEmpty) {
@ -577,8 +573,9 @@ private[support] class WeaponAndProjectileOperations(
avatarActor ! AvatarActor.ConsumeStamina(avatar.stamina) avatarActor ! AvatarActor.ConsumeStamina(avatar.stamina)
} }
avatarActor ! AvatarActor.SuspendStaminaRegeneration(3.seconds) avatarActor ! AvatarActor.SuspendStaminaRegeneration(3.seconds)
tool.Discharge()
prefire += weaponGUID prefire += weaponGUID
ongoingShotsFired = ongoingShotsFired + tool.Discharge() addShotsFired(tool.Definition.ObjectId, tool.AmmoSlot.Chamber)
} }
(o, Some(tool)) (o, Some(tool))
} }
@ -635,7 +632,7 @@ private[support] class WeaponAndProjectileOperations(
continent.GUID(weapon_guid) match { continent.GUID(weapon_guid) match {
case Some(tool: Tool) => case Some(tool: Tool) =>
EmptyMagazine(weapon_guid, tool) EmptyMagazine(weapon_guid, tool)
case _ => ; case _ => ()
} }
} }
@ -719,8 +716,7 @@ private[support] class WeaponAndProjectileOperations(
*/ */
def ModifyAmmunitionInMountable(obj: PlanetSideServerObject with Container)(box: AmmoBox, reloadValue: Int): Unit = { def ModifyAmmunitionInMountable(obj: PlanetSideServerObject with Container)(box: AmmoBox, reloadValue: Int): Unit = {
ModifyAmmunition(obj)(box, reloadValue) ModifyAmmunition(obj)(box, reloadValue)
obj.Find(box) match { obj.Find(box).collect { index =>
case Some(index) =>
continent.VehicleEvents ! VehicleServiceMessage( continent.VehicleEvents ! VehicleServiceMessage(
s"${obj.Actor}", s"${obj.Actor}",
VehicleAction.InventoryState( VehicleAction.InventoryState(
@ -731,7 +727,6 @@ private[support] class WeaponAndProjectileOperations(
box.Definition.Packet.DetailedConstructorData(box).get box.Definition.Packet.DetailedConstructorData(box).get
) )
) )
case None => ;
} }
} }
@ -849,7 +844,7 @@ private[support] class WeaponAndProjectileOperations(
sessionData.normalItemDrop(player, continent)(previousBox) sessionData.normalItemDrop(player, continent)(previousBox)
} }
AmmoBox.Split(previousBox) match { AmmoBox.Split(previousBox) match {
case Nil | List(_) => ; //done (the former case is technically not possible) case Nil | List(_) => () //done (the former case is technically not possible)
case _ :: toUpdate => case _ :: toUpdate =>
modifyFunc(previousBox, 0) //update to changed capacity value modifyFunc(previousBox, 0) //update to changed capacity value
toUpdate.foreach(box => { TaskWorkflow.execute(stowNewFunc(box)) }) toUpdate.foreach(box => { TaskWorkflow.execute(stowNewFunc(box)) })
@ -1151,7 +1146,6 @@ private[support] class WeaponAndProjectileOperations(
prefire -= itemGuid prefire -= itemGuid
shooting += itemGuid shooting += itemGuid
shootingStart += itemGuid -> System.currentTimeMillis() shootingStart += itemGuid -> System.currentTimeMillis()
ongoingShotsFired = 0
} }
private def fireStateStartChargeMode(tool: Tool): Unit = { private def fireStateStartChargeMode(tool: Tool): Unit = {
@ -1220,11 +1214,10 @@ private[support] class WeaponAndProjectileOperations(
used by ChangeFireStateMessage_Stop handling used by ChangeFireStateMessage_Stop handling
*/ */
private def fireStateStopUpdateChargeAndCleanup(tool: Tool): Unit = { private def fireStateStopUpdateChargeAndCleanup(tool: Tool): Unit = {
avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(tool.Definition.ObjectId, ongoingShotsFired, 0, 0))
tool.FireMode match { tool.FireMode match {
case _: ChargeFireModeDefinition => case _: ChargeFireModeDefinition =>
sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, tool.Magazine)) sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, tool.Magazine))
case _ => ; case _ => ()
} }
if (tool.Magazine == 0) { if (tool.Magazine == 0) {
FireCycleCleanup(tool) FireCycleCleanup(tool)
@ -1365,6 +1358,49 @@ private[support] class WeaponAndProjectileOperations(
) )
} }
private def addShotsFired(weaponId: Int, shots: Int): Unit = {
addShotsToMap(shotsFired, weaponId, shots)
}
private def addShotsLanded(weaponId: Int, shots: Int): Unit = {
addShotsToMap(shotsLanded, weaponId, shots)
}
private def addShotsToMap(map: mutable.HashMap[Int, Int], weaponId: Int, shots: Int): Unit = {
map.put(
weaponId,
map.get(weaponId) match {
case Some(previousShots) => previousShots + shots
case None => shots
}
)
}
private[support] def reportOngoingShots(reportFunc: (Long, Int, Int, Int) => Unit): Unit = {
reportOngoingShots(player.CharId, reportFunc)
}
private[support] def reportOngoingShots(avatarId: Long, reportFunc: (Long, Int, Int, Int) => Unit): Unit = {
//only shots that have been reported as fired count
//if somehow shots had reported as landed but never reported as fired, they are ignored
//these are just raw counts; there's only numeric connection between the entries of fired and of landed
shotsFired.foreach { case (weaponId, fired) =>
val landed = math.min(shotsLanded.getOrElse(weaponId, 0), fired)
reportFunc(avatarId, weaponId, fired, landed)
}
shotsFired.clear()
shotsLanded.clear()
}
//noinspection ScalaUnusedSymbol
private[support] def reportOngoingShotsToAvatar(avatarId: Long, weaponId: Int, fired: Int, landed: Int): Unit = {
avatarActor ! AvatarActor.UpdateToolDischarge(EquipmentStat(weaponId, fired, landed, 0, 0))
}
private[support] def reportOngoingShotsToDatabase(avatarId: Long, weaponId: Int, fired: Int, landed: Int): Unit = {
ToDatabase.reportToolDischarge(avatarId, EquipmentStat(weaponId, fired, landed, 0, 0))
}
override protected[session] def stop(): Unit = { override protected[session] def stop(): Unit = {
if (player != null && player.HasGUID) { if (player != null && player.HasGUID) {
(prefire ++ shooting).foreach { guid => (prefire ++ shooting).foreach { guid =>
@ -1373,6 +1409,7 @@ private[support] class WeaponAndProjectileOperations(
fireStateStopMountedMessages(guid) fireStateStopMountedMessages(guid)
} }
projectiles.indices.foreach { projectiles.update(_, None) } projectiles.indices.foreach { projectiles.update(_, None) }
reportOngoingShots(reportOngoingShotsToDatabase)
} }
} }
} }

View file

@ -267,30 +267,7 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
} }
def SeatPermissionGroup(seatNumber: Int): Option[AccessPermissionGroup.Value] = { def SeatPermissionGroup(seatNumber: Int): Option[AccessPermissionGroup.Value] = {
if (seatNumber == 0) { //valid in almost all cases Vehicles.SeatPermissionGroup(this.Definition, seatNumber)
Some(AccessPermissionGroup.Driver)
} else {
Seat(seatNumber) match {
case Some(_) =>
Definition.controlledWeapons().get(seatNumber) match {
case Some(_) =>
Some(AccessPermissionGroup.Gunner)
case None =>
Some(AccessPermissionGroup.Passenger)
}
case None =>
CargoHold(seatNumber) match {
case Some(_) =>
Some(AccessPermissionGroup.Passenger) //TODO confirm this
case None =>
if (seatNumber >= trunk.Offset && seatNumber < trunk.Offset + trunk.TotalCapacity) {
Some(AccessPermissionGroup.Trunk)
} else {
None
}
}
}
}
} }
def Utilities: Map[Int, Utility] = utilities def Utilities: Map[Int, Utility] = utilities

View file

@ -2,6 +2,7 @@
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.ce.TelepadLike import net.psforever.objects.ce.TelepadLike
import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
@ -439,4 +440,49 @@ object Vehicles {
val turnAway = if (offset.x >= 0) -90f else 90f val turnAway = if (offset.x >= 0) -90f else 90f
(obj.Position + offset, (shuttleAngle + turnAway) % 360f) (obj.Position + offset, (shuttleAngle + turnAway) % 360f)
} }
/**
* Based on a mounting index, for a certain mount, to what mounting group does this seat belong?
* @param vehicle the vehicle
* @param seatNumber specific seat index
* @return the seat group designation
*/
def SeatPermissionGroup(vehicle: Vehicle, seatNumber: Int): Option[AccessPermissionGroup.Value] = {
SeatPermissionGroup(vehicle.Definition, seatNumber)
}
/**
* Based on a mounting index, for a certain mount, to what mounting group does this seat belong?
* @param definition global vehicle specification
* @param seatNumber specific seat index
* @return the seat group designation
*/
def SeatPermissionGroup(definition: VehicleDefinition, seatNumber: Int): Option[AccessPermissionGroup.Value] = {
if (seatNumber == 0) { //valid in almost all cases
Some(AccessPermissionGroup.Driver)
} else {
definition.Seats
.get(seatNumber)
.map { _ =>
definition.controlledWeapons()
.get(seatNumber)
.map { _ => AccessPermissionGroup.Gunner }
.getOrElse { AccessPermissionGroup.Passenger }
}
.orElse {
definition.Cargo
.get(seatNumber)
.map { _ => AccessPermissionGroup.Passenger }
.orElse {
val offset = definition.TrunkOffset
val size = definition.TrunkSize
if (seatNumber >= offset && seatNumber < offset + size.Width * size.Height) {
Some(AccessPermissionGroup.Trunk)
} else {
None
}
}
}
}
}
} }

View file

@ -1,8 +1,8 @@
// Copyright (c) 2023 PSForever // Copyright (c) 2023 PSForever
package net.psforever.objects.avatar.scoring package net.psforever.objects.avatar.scoring
final case class EquipmentStat(objectId: Int, shotsFired: Int, shotsLanded: Int, kills: Int) final case class EquipmentStat(objectId: Int, shotsFired: Int, shotsLanded: Int, kills: Int, assists: Int)
object EquipmentStat { object EquipmentStat {
def apply(objectId: Int): EquipmentStat = EquipmentStat(objectId, 0, 1, 0) def apply(objectId: Int): EquipmentStat = EquipmentStat(objectId, 0, 1, 0, 0)
} }

View file

@ -30,7 +30,7 @@ class ScoreCard() {
curr = ScoreCard.updateEquipmentStat(curr, e) curr = ScoreCard.updateEquipmentStat(curr, e)
case k: Kill => case k: Kill =>
curr = curr.copy(kills = k +: curr.kills) curr = curr.copy(kills = k +: curr.kills)
curr = ScoreCard.updateEquipmentStat(curr, EquipmentStat(k.info.interaction.cause.attribution, 0, 0, 1)) curr = ScoreCard.updateEquipmentStat(curr, EquipmentStat(k.info.interaction.cause.attribution, 0, 0, 1, 0))
ScoreCard.updateStatisticsFor(killStatistics, k.info.interaction.cause.attribution, k.victim.Faction) ScoreCard.updateStatisticsFor(killStatistics, k.info.interaction.cause.attribution, k.victim.Faction)
case a: Assist => case a: Assist =>
curr = curr.copy(assists = a +: curr.assists) curr = curr.copy(assists = a +: curr.assists)
@ -51,14 +51,15 @@ class ScoreCard() {
object ScoreCard { object ScoreCard {
private def updateEquipmentStat(curr: Life, entry: EquipmentStat): Life = { private def updateEquipmentStat(curr: Life, entry: EquipmentStat): Life = {
updateEquipmentStat(curr, entry, entry.objectId, entry.kills) updateEquipmentStat(curr, entry, entry.objectId, entry.kills, entry.assists)
} }
private def updateEquipmentStat( private def updateEquipmentStat(
curr: Life, curr: Life,
entry: EquipmentStat, entry: EquipmentStat,
objectId: Int, objectId: Int,
killCount: Int killCount: Int,
assists: Int
): Life = { ): Life = {
curr.equipmentStats.indexWhere { a => a.objectId == objectId } match { curr.equipmentStats.indexWhere { a => a.objectId == objectId } match {
case -1 => case -1 =>

View file

@ -5,13 +5,15 @@ import akka.actor.{Actor, Cancellable}
import net.psforever.objects.{Vehicle, Vehicles} import net.psforever.objects.{Vehicle, Vehicles}
import net.psforever.objects.equipment.JammableUnit import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.sourcing.VehicleSource
import net.psforever.objects.vital.base.DamageResolution import net.psforever.objects.vital.base.DamageResolution
import net.psforever.objects.vital.interaction.DamageResult import net.psforever.objects.vital.interaction.{Adversarial, DamageResult}
import net.psforever.objects.vital.resolution.ResolutionCalculations import net.psforever.objects.vital.resolution.ResolutionCalculations
import net.psforever.objects.zones.Zone
import net.psforever.objects.zones.exp.ToDatabase
import net.psforever.packet.game.DamageWithPositionMessage
import net.psforever.services.Service import net.psforever.services.Service
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.DamageWithPositionMessage
import net.psforever.types.Vector3 import net.psforever.types.Vector3
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -213,6 +215,18 @@ trait DamageableVehicle
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, obj.Definition.shieldUiAttribute, 0) VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, obj.Definition.shieldUiAttribute, 0)
) )
} }
//database entry
cause.adversarial.collect {
case Adversarial(attacker, victim: VehicleSource, implement) =>
ToDatabase.reportMachineDestruction(
attacker.CharId,
victim,
DamageableObject.HackedBy,
DamageableObject.MountedIn.nonEmpty,
implement,
obj.Zone.Number
)
}
//clean up //clean up
target.Actor ! Vehicle.Deconstruct(Some(1 minute)) target.Actor ! Vehicle.Deconstruct(Some(1 minute))
DamageableWeaponTurret.DestructionAwareness(obj, cause) DamageableWeaponTurret.DestructionAwareness(obj, cause)

View file

@ -298,7 +298,7 @@ class VehicleControl(vehicle: Vehicle)
obj.PassengerInSeat(user) match { obj.PassengerInSeat(user) match {
case Some(seatNumber) => case Some(seatNumber) =>
val vsrc = VehicleSource(vehicle) val vsrc = VehicleSource(vehicle)
user.LogActivity(VehicleMountActivity(vsrc, PlayerSource.inSeat(user, vehicle, vsrc), vehicle.Zone.Number)) user.LogActivity(VehicleMountActivity(vsrc, PlayerSource.inSeat(user, vsrc, seatNumber), vehicle.Zone.Number))
//if the driver mount, change ownership if that is permissible for this vehicle //if the driver mount, change ownership if that is permissible for this vehicle
if (seatNumber == 0 && !obj.OwnerName.contains(user.Name) && obj.Definition.CanBeOwned.nonEmpty) { if (seatNumber == 0 && !obj.OwnerName.contains(user.Name) && obj.Definition.CanBeOwned.nonEmpty) {
//whatever vehicle was previously owned //whatever vehicle was previously owned

View file

@ -0,0 +1,128 @@
// Copyright (c) 2022 PSForever
package net.psforever.objects.zones.exp
import scala.concurrent.ExecutionContext.Implicits.global
import net.psforever.objects.avatar.scoring.EquipmentStat
import net.psforever.objects.serverobject.hackable.Hackable.HackInfo
import net.psforever.objects.sourcing.VehicleSource
import net.psforever.persistence
import net.psforever.types.Vector3
import net.psforever.util.Database.ctx
import net.psforever.util.Database.ctx._
import scala.util.Success
object ToDatabase {
def reportKillBy(
killerId: Long,
victimId: Long,
victimExoSuitId: Int,
victimMounted: Int,
weaponId: Int,
zoneId: Int,
position: Vector3,
exp: Long
): Unit = {
ctx.run(query[persistence.Killactivity]
.insert(
_.victimId -> lift(victimId),
_.killerId -> lift(killerId),
_.victimExosuit -> lift(victimExoSuitId),
_.victimMounted -> lift(victimMounted),
_.weaponId -> lift(weaponId),
_.zoneId -> lift(zoneId),
_.px -> lift((position.x * 1000).toInt),
_.py -> lift((position.y * 1000).toInt),
_.pz -> lift((position.z * 1000).toInt),
_.exp -> lift(exp)
)
)
}
def reportToolDischarge(avatarId: Long, stats: EquipmentStat): Unit = {
val result = for {
res <- ctx.run(
query[persistence.Weaponstatsession]
.filter(_.avatarId == lift(avatarId))
.filter(_.weaponId == lift(stats.objectId))
.update(
_.shotsFired -> lift(stats.shotsFired),
_.shotsLanded -> lift(stats.shotsLanded)
)
)
} yield res
result.onComplete {
case Success(rowCount) if rowCount.longValue > 0 => ()
case _ =>
ctx.run(query[persistence.Weaponstatsession]
.insert(
_.avatarId -> lift(avatarId),
_.weaponId -> lift(stats.objectId),
_.shotsFired -> lift(stats.shotsFired),
_.shotsLanded -> lift(stats.shotsLanded),
_.kills -> lift(0),
_.assists -> lift(0),
_.sessionId -> lift(-1L)
)
)
}
}
def reportAssistKills(avatarId: Long, weaponId: Int, assists: Int): Unit = {
val result = for {
res <- ctx.run(
query[persistence.Weaponstatsession]
.filter(_.avatarId == lift(avatarId))
.filter(_.weaponId == lift(weaponId))
.update(
_.assists -> lift(assists)
)
)
} yield res
result.onComplete {
case Success(rowCount) if rowCount.longValue > 0 => ()
case _ =>
ctx.run(query[persistence.Weaponstatsession]
.insert(
_.avatarId -> lift(avatarId),
_.weaponId -> lift(weaponId),
_.assists -> lift(assists),
_.shotsFired -> lift(0),
_.shotsLanded -> lift(0),
_.kills -> lift(0),
_.sessionId -> lift(-1L)
)
)
}
}
def reportMachineDestruction(
avatarId: Long,
machine: VehicleSource,
hackState: Option[HackInfo],
isCargo: Boolean,
weaponId: Int,
zoneNumber: Int
): Unit = {
import net.psforever.util.Database.ctx
import net.psforever.util.Database.ctx._
val normalFaction = machine.Faction.id
val hackedFaction = hackState.map { _.player.Faction }.getOrElse(normalFaction)
val machinePosition = machine.Position
ctx.run(query[persistence.Machinedestroyedinstance]
.insert(
_.avatarId -> lift(avatarId),
_.weaponId -> lift(weaponId),
_.machineType -> lift(machine.Definition.ObjectId),
_.machineFaction -> lift(normalFaction),
_.hackedFaction -> lift(hackedFaction),
_.asCargo -> lift(isCargo),
_.zoneNum -> lift(zoneNumber),
_.px -> lift((machinePosition.x * 1000).toInt),
_.py -> lift((machinePosition.y * 1000).toInt),
_.pz -> lift((machinePosition.z * 1000).toInt)
)
)
}
}

View file

@ -12,6 +12,8 @@ case class Account(
inactive: Boolean = false, inactive: Boolean = false,
gm: Boolean = false, gm: Boolean = false,
lastFactionId: Int = 3, lastFactionId: Int = 3,
avatarLoggedIn: Long,
sessionId: Long,
token: Option[String], token: Option[String],
tokenCreated: Option[LocalDateTime] tokenCreated: Option[LocalDateTime]
) )

View file

@ -0,0 +1,16 @@
// Copyright (c) 2022 PSForever
package net.psforever.persistence
case class Killactivity(
index: Int,
killerId: Long,
victimId: Long,
victimExosuit: Int,
victimMounted: Int, //object type id * 10 + seat type
weaponId: Int,
zoneId: Int,
px: Int, //Position.x * 1000
py: Int, //Position.y * 1000
pz: Int, //Position.z * 1000
exp: Long
)

View file

@ -0,0 +1,19 @@
// Copyright (c) 2022 PSForever
package net.psforever.persistence
import org.joda.time.LocalDateTime
case class Machinedestroyedinstance(
index: Int,
avatarId: Long,
weaponId: Int,
machineType: Int,
machineFaction: Int,
hackedFaction: Int,
asCargo: Boolean,
zoneNum: Int,
px: Int, //Position.x * 1000
py: Int, //Position.y * 1000
pz: Int, //Position.z * 1000
timestamp: LocalDateTime = LocalDateTime.now()
)

View file

@ -0,0 +1,12 @@
// Copyright (c) 2022 PSForever
package net.psforever.persistence
case class Weaponstatsession(
avatarId: Long,
weaponId: Int,
shotsFired: Int,
shotsLanded: Int,
kills: Int,
assists: Int,
sessionId: Long
)