mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
QoL changes; event chat messages for exp when in debt; different calculations for sep; timestamps for progress system start and clear; hopefully proper cleanup for progress system
This commit is contained in:
parent
e9dbd5f259
commit
d3392ecab2
|
|
@ -19,4 +19,77 @@ BEGIN
|
|||
END;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
/* New */
|
||||
ALTER TABLE "progressiondebt"
|
||||
ADD COLUMN IF NOT EXISTS "max_experience" INT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS "enroll_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
ADD COLUMN IF NOT EXISTS "clear_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
/*
|
||||
Upon indoctrinating a player into the progression system,
|
||||
update the peak experience for the battle rank for future reference
|
||||
and record when the player asked for this enhanced rank promotion.
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION fn_progressiondebt_updateEnrollment()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
DECLARE avatarId Int;
|
||||
DECLARE oldExp Int;
|
||||
DECLARE newExp Int;
|
||||
BEGIN
|
||||
avatarId := NEW.avatar_id;
|
||||
newExp := NEW.experience;
|
||||
oldExp := OLD.experience;
|
||||
BEGIN
|
||||
IF (oldExp = 0 AND newExp > 0) THEN
|
||||
UPDATE progressiondebt
|
||||
SET experience = newExp, max_experience = newExp, enroll_time = CURRENT_TIMESTAMP
|
||||
WHERE avatar_id = avatarId;
|
||||
END IF;
|
||||
END;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER psf_progressiondebt_updateEnrollment
|
||||
BEFORE UPDATE
|
||||
ON progressiondebt
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE fn_progressiondebt_updateEnrollment();
|
||||
|
||||
/*
|
||||
Upon unlisting a player from the progression system,
|
||||
update the time when the player has completed his tensure.
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION fn_progressiondebt_updateClearTime()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
DECLARE avatarId Int;
|
||||
DECLARE oldExp Int;
|
||||
DECLARE newExp Int;
|
||||
DECLARE newMaxExp Int;
|
||||
BEGIN
|
||||
avatarId := NEW.avatar_id;
|
||||
newExp := NEW.experience;
|
||||
oldExp := OLD.experience;
|
||||
newMaxExp := NEW.max_experience;
|
||||
BEGIN
|
||||
IF (oldExp > newExp AND newExp = 0) THEN
|
||||
UPDATE progressiondebt
|
||||
SET experience = 0, max_experience = newMaxExp, clear_time = CURRENT_TIMESTAMP
|
||||
WHERE avatar_id = avatarId;
|
||||
END IF;
|
||||
END;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER psf_progressiondebt_updateClearTime
|
||||
BEFORE UPDATE
|
||||
ON progressiondebt
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE fn_progressiondebt_updateClearTime();
|
||||
|
|
|
|||
|
|
@ -273,16 +273,14 @@ game {
|
|||
#
|
||||
# name - label by which this event is organized
|
||||
# base - whole number value
|
||||
# shots-multiplier - whether use count matters for this event
|
||||
# - when set to 0.0 (default), it does not
|
||||
# shots-limit - upper limit of use count
|
||||
# - cap the count here, if higher
|
||||
# shots-min - lower limit of use count
|
||||
# - minimum amount of shots required before applying multiplier
|
||||
# shots-max - upper limit of use count
|
||||
# - cap the count here, if higher
|
||||
# shots-cutoff - if the use count exceeds this number, the event no longer applies
|
||||
# - a hard limit that should zero the contribution reward
|
||||
# - the *-cutoff should probably apply before *-limit, maybe
|
||||
# shots-nat-log - when set, may the use count to a natural logarithmic curve
|
||||
# - actually the exponent on the use count before the logarithm
|
||||
# - similar to shots-limit, but the curve plateaus quickly
|
||||
# shots-multiplier - whether use count matters for this event
|
||||
# - when set to 0.0 (default), it does not
|
||||
# amount-multiplier - whether active amount matters for this event
|
||||
# - when set to 0.0 (default), it does not
|
||||
events = [
|
||||
|
|
@ -290,26 +288,26 @@ game {
|
|||
name = "support-heal"
|
||||
base = 10
|
||||
shots-multiplier = 5.0
|
||||
shots-limit = 100
|
||||
shots-max = 100
|
||||
amount-multiplier = 2.0
|
||||
}
|
||||
{
|
||||
name = "support-repair"
|
||||
base = 10
|
||||
shots-multiplier = 5.0
|
||||
shots-limit = 100
|
||||
shots-max = 100
|
||||
}
|
||||
{
|
||||
name = "support-repair-terminal"
|
||||
base = 10
|
||||
shots-multiplier = 5.0
|
||||
shots-limit = 100
|
||||
shots-max = 100
|
||||
}
|
||||
{
|
||||
name = "support-repair-turret"
|
||||
base = 10
|
||||
shots-multiplier = 5.0
|
||||
shots-limit = 100
|
||||
shots-max = 100
|
||||
}
|
||||
{
|
||||
name = "mounted-kill"
|
||||
|
|
@ -332,27 +330,23 @@ game {
|
|||
name = "ams-resupply"
|
||||
base = 15
|
||||
shots-multiplier = 1.0
|
||||
shots-nat-log = 5.0
|
||||
}
|
||||
{
|
||||
name = "lodestar-repair"
|
||||
base = 10
|
||||
shots-multiplier = 1.0
|
||||
shots-nat-log = 5.0
|
||||
shots-limit = 100
|
||||
shots-max = 100
|
||||
amount-multiplier = 1.0
|
||||
}
|
||||
{
|
||||
name = "lodestar-rearm"
|
||||
base = 10
|
||||
shots-multiplier = 1.0
|
||||
shots-nat-log = 5.0
|
||||
}
|
||||
{
|
||||
name = "revival"
|
||||
base = 0
|
||||
shots-multiplier = 15.0
|
||||
shots-nat-log = 5.0
|
||||
shots-cutoff = 10
|
||||
}
|
||||
]
|
||||
|
|
@ -376,6 +370,7 @@ game {
|
|||
# The maximum command experience that can be earned in a facility capture based on squad size
|
||||
maximum-per-squad-size = [990, 1980, 3466, 4950, 6436, 7920, 9406, 10890, 12376, 13860]
|
||||
# When the cep has to be capped for squad size, add a small value to the capped value
|
||||
# This is that value
|
||||
# -1 reuses the cep before being capped
|
||||
squad-size-limit-overflow = -1
|
||||
# When the cep has to be capped for squad size, calculate a small amount to add to the capped value
|
||||
|
|
@ -399,6 +394,8 @@ game {
|
|||
# How much direct combat contributes to paying back promotion debt.
|
||||
# Typically, it does not contribute.
|
||||
battle-experience-points-modifier = 0f
|
||||
support-experience-points-modifier = 2f
|
||||
capture-experience-points-modifier = 1f
|
||||
# Don't forget to pay back that debt.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,16 @@ import net.psforever.util.Database._
|
|||
import net.psforever.util.{Config, Database, DefinitionUtil}
|
||||
|
||||
object AvatarActor {
|
||||
private val basicLoginCertifications: Set[Certification] = Set(
|
||||
Certification.StandardExoSuit,
|
||||
Certification.AgileExoSuit,
|
||||
Certification.ReinforcedExoSuit,
|
||||
Certification.StandardAssault,
|
||||
Certification.MediumAssault,
|
||||
Certification.ATV,
|
||||
Certification.Harasser
|
||||
)
|
||||
|
||||
def apply(sessionActor: ActorRef[SessionActor.Command]): Behavior[Command] =
|
||||
Behaviors
|
||||
.supervise[Command] {
|
||||
|
|
@ -336,7 +346,7 @@ object AvatarActor {
|
|||
case "Kit" =>
|
||||
container.Slot(objectIndex).Equipment =
|
||||
Kit(DefinitionUtil.idToDefinition(objectId).asInstanceOf[KitDefinition])
|
||||
case "Telepad" | "BoomerTrigger" => ;
|
||||
case "Telepad" | "BoomerTrigger" => ()
|
||||
//special types of equipment that are not actually loaded
|
||||
case name =>
|
||||
log.error(s"failing to add unknown equipment to a container - $name")
|
||||
|
|
@ -368,10 +378,10 @@ object AvatarActor {
|
|||
cooldownDurations.get(DefinitionUtil.fromString(name)) match {
|
||||
case Some(duration) if now.compareTo(cooldown.plusMillis(duration.toMillis.toInt)) == -1 =>
|
||||
cooldowns.put(name, cooldown)
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
} catch {
|
||||
case _: Exception => ;
|
||||
case _: Exception => ()
|
||||
}
|
||||
case _ =>
|
||||
log.warn(s"ignoring invalid cooldown string: '$value'")
|
||||
|
|
@ -534,9 +544,9 @@ object AvatarActor {
|
|||
otherAvatar.headOption match {
|
||||
case Some(a) =>
|
||||
func(a.id, a.name, a.factionId)
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
None //satisfy the orElse
|
||||
|
|
@ -857,23 +867,30 @@ object AvatarActor {
|
|||
case Success(debt) if debt.nonEmpty =>
|
||||
out.completeWith(Future(debt.head.experience))
|
||||
case _ =>
|
||||
ctx.run(
|
||||
query[persistence.Progressiondebt]
|
||||
.filter(_.avatarId == lift(avatarId))
|
||||
.update(_.experience -> lift(0L))
|
||||
)
|
||||
out.completeWith(Future(0L))
|
||||
}
|
||||
out.future
|
||||
}
|
||||
|
||||
def saveExperienceDebt(avatarId: Long, exp: Long): Future[Int] = {
|
||||
def saveExperienceDebt(avatarId: Long, exp: Long, max: Long): Future[Int] = {
|
||||
import ctx._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
val out: Promise[Int] = Promise()
|
||||
val result = ctx.run(query[persistence.Progressiondebt].filter(_.avatarId == lift(avatarId)))
|
||||
result.onComplete {
|
||||
case Success(debt) if debt.nonEmpty =>
|
||||
ctx.run(
|
||||
query[persistence.Progressiondebt]
|
||||
.filter(_.avatarId == lift(avatarId))
|
||||
.update(_.experience -> lift(exp))
|
||||
val result = ctx.run(
|
||||
query[persistence.Progressiondebt]
|
||||
.filter(_.avatarId == lift(avatarId))
|
||||
.update(
|
||||
_.experience -> lift(exp),
|
||||
_.maxExperience -> lift(max)
|
||||
)
|
||||
)
|
||||
result.onComplete {
|
||||
case Success(debt) if debt.toInt > 0 =>
|
||||
out.completeWith(Future(1))
|
||||
case _ =>
|
||||
out.completeWith(Future(0))
|
||||
|
|
@ -1127,15 +1144,7 @@ class AvatarActor(
|
|||
val inits = for {
|
||||
_ <- ctx.run(
|
||||
liftQuery(
|
||||
List(
|
||||
persistence.Certification(Certification.StandardExoSuit.value, avatarId),
|
||||
persistence.Certification(Certification.AgileExoSuit.value, avatarId),
|
||||
persistence.Certification(Certification.ReinforcedExoSuit.value, avatarId),
|
||||
persistence.Certification(Certification.StandardAssault.value, avatarId),
|
||||
persistence.Certification(Certification.MediumAssault.value, avatarId),
|
||||
persistence.Certification(Certification.ATV.value, avatarId),
|
||||
persistence.Certification(Certification.Harasser.value, avatarId)
|
||||
)
|
||||
basicLoginCertifications.map { cert => persistence.Certification(cert.value, avatarId) }.toList
|
||||
).foreach(c => query[persistence.Certification].insertValue(c))
|
||||
)
|
||||
_ <- ctx.run(
|
||||
|
|
@ -1212,135 +1221,11 @@ class AvatarActor(
|
|||
Behaviors.same
|
||||
|
||||
case LearnCertification(terminalGuid, certification) =>
|
||||
import ctx._
|
||||
|
||||
if (avatar.certifications.contains(certification)) {
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Learn, success = false)
|
||||
)
|
||||
} else {
|
||||
val replace = certification.replaces.intersect(avatar.certifications)
|
||||
Future
|
||||
.sequence(replace.map(cert => {
|
||||
ctx
|
||||
.run(
|
||||
query[persistence.Certification]
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.filter(_.id == lift(cert.value))
|
||||
.delete
|
||||
)
|
||||
.map(_ => cert)
|
||||
}))
|
||||
.onComplete {
|
||||
case Failure(exception) =>
|
||||
log.error(exception)("db failure")
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = false)
|
||||
)
|
||||
case Success(_replace) =>
|
||||
_replace.foreach { cert =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
PlanetsideAttributeMessage(session.get.player.GUID, 25, cert.value)
|
||||
)
|
||||
}
|
||||
ctx
|
||||
.run(
|
||||
query[persistence.Certification]
|
||||
.insert(_.id -> lift(certification.value), _.avatarId -> lift(avatar.id))
|
||||
)
|
||||
.onComplete {
|
||||
case Failure(exception) =>
|
||||
log.error(exception)("db failure")
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = false)
|
||||
)
|
||||
|
||||
case Success(_) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
PlanetsideAttributeMessage(session.get.player.GUID, 24, certification.value)
|
||||
)
|
||||
replaceAvatar(
|
||||
avatar.copy(certifications = avatar.certifications.diff(replace) + certification)
|
||||
)
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = true)
|
||||
)
|
||||
sessionActor ! SessionActor.CharSaved
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
performCertificationAction(terminalGuid, certification, learnCertificationInTheFuture, TransactionType.Buy)
|
||||
Behaviors.same
|
||||
|
||||
case SellCertification(terminalGuid, certification) =>
|
||||
import ctx._
|
||||
|
||||
if (!avatar.certifications.contains(certification)) {
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Learn, success = false)
|
||||
)
|
||||
} else {
|
||||
var requiredByCert: Set[Certification] = Set(certification)
|
||||
var removeThese: Set[Certification] = Set(certification)
|
||||
val allCerts: Set[Certification] = Certification.values.toSet
|
||||
do {
|
||||
removeThese = allCerts.filter { testingCert =>
|
||||
testingCert.requires.intersect(removeThese).nonEmpty
|
||||
}
|
||||
requiredByCert = requiredByCert ++ removeThese
|
||||
} while (removeThese.nonEmpty)
|
||||
|
||||
Future
|
||||
.sequence(
|
||||
avatar.certifications
|
||||
.intersect(requiredByCert)
|
||||
.map(cert => {
|
||||
ctx
|
||||
.run(
|
||||
query[persistence.Certification]
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.filter(_.id == lift(cert.value))
|
||||
.delete
|
||||
)
|
||||
.map(_ => cert)
|
||||
})
|
||||
)
|
||||
.onComplete {
|
||||
case Failure(exception) =>
|
||||
log.error(exception)("db failure")
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = false)
|
||||
)
|
||||
case Success(certs) =>
|
||||
val player = session.get.player
|
||||
replaceAvatar(avatar.copy(certifications = avatar.certifications.diff(certs)))
|
||||
certs.foreach { cert =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
PlanetsideAttributeMessage(player.GUID, 25, cert.value)
|
||||
)
|
||||
}
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = true)
|
||||
)
|
||||
sessionActor ! SessionActor.CharSaved
|
||||
//wearing invalid armor?
|
||||
if (
|
||||
if (certification == Certification.ReinforcedExoSuit) player.ExoSuit == ExoSuitType.Reinforced
|
||||
else if (certification == Certification.InfiltrationSuit) player.ExoSuit == ExoSuitType.Infiltration
|
||||
else if (player.ExoSuit == ExoSuitType.MAX) {
|
||||
lazy val subtype =
|
||||
InfantryLoadout.DetermineSubtypeA(ExoSuitType.MAX, player.Slot(slot = 0).Equipment)
|
||||
if (certification == Certification.UniMAX) true
|
||||
else if (certification == Certification.AAMAX) subtype == 1
|
||||
else if (certification == Certification.AIMAX) subtype == 2
|
||||
else if (certification == Certification.AVMAX) subtype == 3
|
||||
else false
|
||||
} else false
|
||||
) {
|
||||
player.Actor ! PlayerControl.SetExoSuit(ExoSuitType.Standard, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
performCertificationAction(terminalGuid, certification, sellCertificationInTheFuture, TransactionType.Sell)
|
||||
Behaviors.same
|
||||
|
||||
case SetCertifications(certifications) =>
|
||||
|
|
@ -1394,84 +1279,19 @@ class AvatarActor(
|
|||
implant.definition.implantType.value
|
||||
)
|
||||
)
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
deinitializeImplants()
|
||||
Behaviors.same
|
||||
|
||||
case LearnImplant(terminalGuid, definition) =>
|
||||
// TODO there used to be a terminal check here, do we really need it?
|
||||
val index = avatar.implants.zipWithIndex.collectFirst {
|
||||
case (Some(implant), _index) if implant.definition.implantType == definition.implantType => _index
|
||||
case (None, _index) if _index < avatar.br.implantSlots => _index
|
||||
}
|
||||
index match {
|
||||
case Some(_index) =>
|
||||
import ctx._
|
||||
ctx
|
||||
.run(query[persistence.Implant].insert(_.name -> lift(definition.Name), _.avatarId -> lift(avatar.id)))
|
||||
.onComplete {
|
||||
case Success(_) =>
|
||||
replaceAvatar(avatar.copy(implants = avatar.implants.updated(_index, Some(Implant(definition)))))
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
AvatarImplantMessage(
|
||||
session.get.player.GUID,
|
||||
ImplantAction.Add,
|
||||
_index,
|
||||
definition.implantType.value
|
||||
)
|
||||
)
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Learn, success = true)
|
||||
)
|
||||
context.self ! ResetImplants()
|
||||
sessionActor ! SessionActor.CharSaved
|
||||
case Failure(exception) => log.error(exception)("db failure")
|
||||
}
|
||||
|
||||
case None =>
|
||||
log.warn("attempted to learn implant but could not find slot")
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Learn, success = false)
|
||||
)
|
||||
}
|
||||
buyImplantAction(terminalGuid, definition)
|
||||
Behaviors.same
|
||||
|
||||
case SellImplant(terminalGuid, definition) =>
|
||||
// TODO there used to be a terminal check here, do we really need it?
|
||||
val index = avatar.implants.zipWithIndex.collectFirst {
|
||||
case (Some(implant), _index) if implant.definition.implantType == definition.implantType => _index
|
||||
}
|
||||
index match {
|
||||
case Some(_index) =>
|
||||
import ctx._
|
||||
ctx
|
||||
.run(
|
||||
query[persistence.Implant]
|
||||
.filter(_.name == lift(definition.Name))
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.delete
|
||||
)
|
||||
.onComplete {
|
||||
case Success(_) =>
|
||||
replaceAvatar(avatar.copy(implants = avatar.implants.updated(_index, None)))
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
AvatarImplantMessage(session.get.player.GUID, ImplantAction.Remove, _index, 0)
|
||||
)
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = true)
|
||||
)
|
||||
context.self ! ResetImplants()
|
||||
sessionActor ! SessionActor.CharSaved
|
||||
case Failure(exception) => log.error(exception)("db failure")
|
||||
}
|
||||
|
||||
case None =>
|
||||
log.warn("attempted to sell implant but could not find slot")
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = false)
|
||||
)
|
||||
}
|
||||
sellImplantAction(terminalGuid, definition)
|
||||
Behaviors.same
|
||||
|
||||
case SaveLoadout(player, loadoutType, label, number) =>
|
||||
|
|
@ -1597,7 +1417,7 @@ class AvatarActor(
|
|||
case _ => true
|
||||
}
|
||||
)
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
if (updateTheTimes) {
|
||||
|
|
@ -1790,36 +1610,27 @@ class AvatarActor(
|
|||
Behaviors.same
|
||||
|
||||
case AwardBep(bep, ExperienceType.Support) =>
|
||||
val gain = bep - experienceDebt
|
||||
if (gain > 0L) {
|
||||
awardSupportExperience(gain, previousDelay = 0L)
|
||||
} else {
|
||||
experienceDebt = experienceDebt - bep
|
||||
}
|
||||
awardProgressionOrExperience(
|
||||
setSupportAction,
|
||||
bep,
|
||||
Config.app.game.promotion.supportExperiencePointsModifier
|
||||
)
|
||||
Behaviors.same
|
||||
|
||||
case AwardBep(bep, modifier) =>
|
||||
val mod = Config.app.game.promotion.battleExperiencePointsModifier
|
||||
if (experienceDebt == 0L) {
|
||||
setBep(avatar.bep + bep, modifier)
|
||||
} else if (mod > 0f) {
|
||||
val modifiedBep = (bep.toFloat * Config.app.game.promotion.battleExperiencePointsModifier).toLong
|
||||
val gain = modifiedBep - experienceDebt
|
||||
if (gain > 0L) {
|
||||
setBep(avatar.bep + gain, modifier)
|
||||
} else {
|
||||
experienceDebt = experienceDebt - modifiedBep
|
||||
}
|
||||
}
|
||||
awardProgressionOrExperience(
|
||||
setBepAction(modifier),
|
||||
avatar.bep + bep,
|
||||
Config.app.game.promotion.battleExperiencePointsModifier
|
||||
)
|
||||
Behaviors.same
|
||||
|
||||
case AwardFacilityCaptureBep(bep) =>
|
||||
val gain = bep - experienceDebt
|
||||
if (gain > 0L) {
|
||||
setBep(gain, ExperienceType.Normal)
|
||||
} else {
|
||||
experienceDebt = experienceDebt - bep
|
||||
}
|
||||
awardProgressionOrExperience(
|
||||
setBepAction(ExperienceType.Normal),
|
||||
avatar.bep + bep,
|
||||
Config.app.game.promotion.captureExperiencePointsModifier
|
||||
)
|
||||
Behaviors.same
|
||||
|
||||
case SupportExperienceDeposit(bep, delayBy) =>
|
||||
|
|
@ -1837,25 +1648,36 @@ class AvatarActor(
|
|||
val newBr = BattleRank.withExperience(bep).value
|
||||
if (Config.app.game.promotion.active && oldBr == 1 && newBr > 1 && newBr < Config.app.game.promotion.maxBattleRank + 1) {
|
||||
experienceDebt = bep
|
||||
if (avatar.cep > 0) {
|
||||
setCep(0L)
|
||||
}
|
||||
AvatarActor.saveExperienceDebt(avatar.id, bep, bep)
|
||||
true
|
||||
} else if (experienceDebt > 0 && newBr == 2) {
|
||||
experienceDebt = 0
|
||||
AvatarActor.saveExperienceDebt(avatar.id, exp = 0, bep)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
setBep(bep, ExperienceType.Normal)
|
||||
if (avatar.cep > 0) {
|
||||
setCep(0L)
|
||||
}
|
||||
restoreBasicCerts()
|
||||
removeAllImplants()
|
||||
sessionActor ! SessionActor.CharSaved
|
||||
sessionActor ! SessionActor.SendResponse(ChatMsg(ChatMessageType.UNK_229, "@AckSuccessSetBattleRank"))
|
||||
} else if (experienceDebt > 0) {
|
||||
sessionActor ! SessionActor.SendResponse(ChatMsg(ChatMessageType.CMT_QUIT, s"You already must earn back $experienceDebt."))
|
||||
} else {
|
||||
sessionActor ! SessionActor.SendResponse(ChatMsg(ChatMessageType.CMT_QUIT, "You may not suffer this debt."))
|
||||
}
|
||||
Behaviors.same
|
||||
|
||||
case AwardCep(cep) =>
|
||||
if (experienceDebt > 0L) {
|
||||
setCep(avatar.cep + cep)
|
||||
} else {
|
||||
sessionActor ! SessionActor.SendResponse(ExperienceAddedMessage(0))
|
||||
}
|
||||
Behaviors.same
|
||||
|
||||
|
|
@ -1973,7 +1795,7 @@ class AvatarActor(
|
|||
case RemoveShortcut(slot) =>
|
||||
import ctx._
|
||||
avatar.shortcuts.lift(slot).flatten match {
|
||||
case None => ;
|
||||
case None => ()
|
||||
case Some(_) =>
|
||||
ctx.run(
|
||||
query[persistence.Shortcut]
|
||||
|
|
@ -1996,7 +1818,7 @@ class AvatarActor(
|
|||
AvatarActor.saveAvatarData(avatar)
|
||||
saveLockerFunc()
|
||||
AvatarActor.updateToolDischargeFor(avatar)
|
||||
AvatarActor.saveExperienceDebt(avatar.id, experienceDebt)
|
||||
AvatarActor.saveExperienceDebt(avatar.id, experienceDebt, avatar.bep)
|
||||
AvatarActor.avatarNoLongerLoggedIn(account.get.id)
|
||||
Behaviors.same
|
||||
}
|
||||
|
|
@ -2072,7 +1894,7 @@ class AvatarActor(
|
|||
// }
|
||||
// }.foreach { c =>
|
||||
// shortcutList.indexWhere { _.isEmpty } match {
|
||||
// case -1 => ;
|
||||
// case -1 => ()
|
||||
// case index =>
|
||||
// shortcutList.update(index, Some(AvatarShortcut(2, c.name)))
|
||||
// }
|
||||
|
|
@ -2180,7 +2002,7 @@ class AvatarActor(
|
|||
avatar.implants.zipWithIndex.foreach {
|
||||
case (Some(_), slot) =>
|
||||
sessionActor ! SessionActor.SendResponse(AvatarImplantMessage(guid, ImplantAction.OutOfStamina, slot, 0))
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
sessionActor ! SessionActor.SendResponse(PlanetsideAttributeMessage(guid, 2, totalStamina))
|
||||
|
|
@ -2223,7 +2045,7 @@ class AvatarActor(
|
|||
sessionActor ! SessionActor.SendResponse(
|
||||
AvatarImplantMessage(player.GUID, ImplantAction.OutOfStamina, slot, 1)
|
||||
)
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
sessionActor ! SessionActor.SendResponse(PlanetsideAttributeMessage(player.GUID, 2, totalStamina))
|
||||
|
|
@ -2268,7 +2090,7 @@ class AvatarActor(
|
|||
AvatarAction.SendResponse(Service.defaultPlayerGUID, ActionProgressMessage(slot + 6, 0))
|
||||
)
|
||||
|
||||
case (None, _) => ;
|
||||
case (None, _) => ()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2324,7 +2146,7 @@ class AvatarActor(
|
|||
avatar.name,
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, ActionProgressMessage(index + 6, 0))
|
||||
)
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2388,7 +2210,7 @@ class AvatarActor(
|
|||
tool.GUID = PlanetSideGUID(gen.getAndIncrement)
|
||||
case Some(item: Equipment) =>
|
||||
item.GUID = PlanetSideGUID(gen.getAndIncrement)
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
)
|
||||
player.GUID = PlanetSideGUID(gen.getAndIncrement)
|
||||
|
|
@ -2426,7 +2248,7 @@ class AvatarActor(
|
|||
item.Invalidate()
|
||||
case Some(item: Equipment) =>
|
||||
item.Invalidate()
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
)
|
||||
player.Invalidate()
|
||||
|
|
@ -2696,7 +2518,7 @@ class AvatarActor(
|
|||
subtype
|
||||
)
|
||||
)
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2884,7 +2706,7 @@ class AvatarActor(
|
|||
session match {
|
||||
case Some(sess) if sess.player != null =>
|
||||
sess.player.avatar = copyAvatar
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2903,7 +2725,7 @@ class AvatarActor(
|
|||
case MemberAction.RemoveFriend => getAvatarForFunc(name, formatForOtherFunc(memberActionRemoveFriend))
|
||||
case MemberAction.AddIgnoredPlayer => getAvatarForFunc(name, memberActionAddIgnored)
|
||||
case MemberAction.RemoveIgnoredPlayer => getAvatarForFunc(name, formatForOtherFunc(memberActionRemoveIgnored))
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2947,7 +2769,7 @@ class AvatarActor(
|
|||
def memberActionAddFriend(charId: Long, name: String, faction: Int): Unit = {
|
||||
val people = avatar.people
|
||||
people.friend.find { _.name.equals(name) } match {
|
||||
case Some(_) => ;
|
||||
case Some(_) => ()
|
||||
case None =>
|
||||
import ctx._
|
||||
ctx.run(
|
||||
|
|
@ -2983,7 +2805,7 @@ class AvatarActor(
|
|||
replaceAvatar(
|
||||
avatar.copy(people = people.copy(friend = people.friend.filterNot { _.charId == charId }))
|
||||
)
|
||||
case None => ;
|
||||
case None => ()
|
||||
}
|
||||
ctx.run(
|
||||
query[persistence.Friend]
|
||||
|
|
@ -3042,7 +2864,7 @@ class AvatarActor(
|
|||
def memberActionAddIgnored(charId: Long, name: String, faction: Int): Unit = {
|
||||
val people = avatar.people
|
||||
people.ignored.find { _.name.equals(name) } match {
|
||||
case Some(_) => ;
|
||||
case Some(_) => ()
|
||||
case None =>
|
||||
import ctx._
|
||||
ctx.run(
|
||||
|
|
@ -3078,7 +2900,7 @@ class AvatarActor(
|
|||
replaceAvatar(
|
||||
avatar.copy(people = people.copy(ignored = people.ignored.filterNot { _.charId == charId }))
|
||||
)
|
||||
case None => ;
|
||||
case None => ()
|
||||
}
|
||||
ctx.run(
|
||||
query[persistence.Ignored]
|
||||
|
|
@ -3163,12 +2985,7 @@ class AvatarActor(
|
|||
}
|
||||
|
||||
def awardSupportExperience(bep: Long, previousDelay: Long): Unit = {
|
||||
setBep(avatar.bep + bep, ExperienceType.Support) //todo simplify support testing
|
||||
// supportExperiencePool = supportExperiencePool + bep
|
||||
// avatar.scorecard.rate(bep)
|
||||
// if (supportExperienceTimer.isCancelled) {
|
||||
// resetSupportExperienceTimer(previousBep = 0, previousDelay = 0)
|
||||
// }
|
||||
setBep(avatar.bep + bep, ExperienceType.Support)
|
||||
}
|
||||
|
||||
def actuallyAwardSupportExperience(bep: Long, delayBy: Long): Unit = {
|
||||
|
|
@ -3183,6 +3000,34 @@ class AvatarActor(
|
|||
}
|
||||
}
|
||||
|
||||
private def awardProgressionOrExperience(
|
||||
awardAction: Long => Unit,
|
||||
experience: Long,
|
||||
modifier: Float
|
||||
): Unit = {
|
||||
if (experienceDebt == 0L) {
|
||||
awardAction(experience)
|
||||
} else if (modifier > 0f) {
|
||||
val modifiedBep = (experience.toFloat * modifier).toLong
|
||||
val gain = modifiedBep - experienceDebt
|
||||
if (gain > 0L) {
|
||||
experienceDebt = 0L
|
||||
awardAction(experience)
|
||||
} else {
|
||||
experienceDebt = experienceDebt - modifiedBep
|
||||
sessionActor ! SessionActor.SendResponse(ExperienceAddedMessage())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def setBepAction(modifier: ExperienceType)(value: Long): Unit = {
|
||||
setBep(value, modifier)
|
||||
}
|
||||
|
||||
private def setSupportAction(value: Long): Unit = {
|
||||
awardSupportExperience(value, previousDelay = 0L)
|
||||
}
|
||||
|
||||
def updateKills(killStat: Kill): Unit = {
|
||||
val exp = killStat.experienceEarned
|
||||
val (modifiedExp, msg) = updateExperienceAndType(killStat.experienceEarned)
|
||||
|
|
@ -3431,6 +3276,245 @@ class AvatarActor(
|
|||
output.future
|
||||
}
|
||||
|
||||
def performCertificationAction(
|
||||
terminalGuid: PlanetSideGUID,
|
||||
certification: Certification,
|
||||
action: Certification => Future[Boolean],
|
||||
transaction: TransactionType.Value
|
||||
): Unit = {
|
||||
action(certification).onComplete {
|
||||
case Success(true) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, transaction, success = true)
|
||||
)
|
||||
sessionActor ! SessionActor.CharSaved
|
||||
case _ =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, transaction, success = false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def learnCertificationInTheFuture(certification: Certification): Future[Boolean] = {
|
||||
import ctx._
|
||||
val out: Promise[Boolean] = Promise()
|
||||
val replace = certification.replaces.intersect(avatar.certifications)
|
||||
Future
|
||||
.sequence(replace.map(cert => {
|
||||
ctx
|
||||
.run(
|
||||
query[persistence.Certification]
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.filter(_.id == lift(cert.value))
|
||||
.delete
|
||||
)
|
||||
.map(_ => cert)
|
||||
}))
|
||||
.onComplete {
|
||||
case Failure(exception) =>
|
||||
log.error(exception)("db failure")
|
||||
case Success(_replace) =>
|
||||
_replace.foreach { cert =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
PlanetsideAttributeMessage(session.get.player.GUID, 25, cert.value)
|
||||
)
|
||||
}
|
||||
ctx
|
||||
.run(
|
||||
query[persistence.Certification]
|
||||
.insert(_.id -> lift(certification.value), _.avatarId -> lift(avatar.id))
|
||||
)
|
||||
.onComplete {
|
||||
case Failure(exception) =>
|
||||
log.error(exception)("db failure")
|
||||
out.completeWith(Future(false))
|
||||
case Success(_) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
PlanetsideAttributeMessage(session.get.player.GUID, 24, certification.value)
|
||||
)
|
||||
replaceAvatar(
|
||||
avatar.copy(certifications = avatar.certifications.diff(replace) + certification)
|
||||
)
|
||||
out.completeWith(Future(true))
|
||||
}
|
||||
}
|
||||
out.future
|
||||
}
|
||||
|
||||
def sellCertificationInTheFuture(certification: Certification): Future[Boolean] = {
|
||||
import ctx._
|
||||
val out: Promise[Boolean] = Promise()
|
||||
var requiredByCert: Set[Certification] = Set(certification)
|
||||
var removeThese: Set[Certification] = Set(certification)
|
||||
val allCerts: Set[Certification] = Certification.values.toSet
|
||||
do {
|
||||
removeThese = allCerts.filter { testingCert =>
|
||||
testingCert.requires.intersect(removeThese).nonEmpty
|
||||
}
|
||||
requiredByCert = requiredByCert ++ removeThese
|
||||
} while (removeThese.nonEmpty)
|
||||
|
||||
Future
|
||||
.sequence(
|
||||
avatar.certifications
|
||||
.intersect(requiredByCert)
|
||||
.map(cert => {
|
||||
ctx
|
||||
.run(
|
||||
query[persistence.Certification]
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.filter(_.id == lift(cert.value))
|
||||
.delete
|
||||
)
|
||||
.map(_ => cert)
|
||||
})
|
||||
)
|
||||
.onComplete {
|
||||
case Failure(exception) =>
|
||||
log.error(exception)("db failure")
|
||||
out.complete(Success(false))
|
||||
case Success(certs) if certs.isEmpty =>
|
||||
out.complete(Success(false))
|
||||
case Success(certs) =>
|
||||
val player = session.get.player
|
||||
replaceAvatar(avatar.copy(certifications = avatar.certifications.diff(certs)))
|
||||
certs.foreach { cert =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
PlanetsideAttributeMessage(player.GUID, 25, cert.value)
|
||||
)
|
||||
}
|
||||
//wearing invalid armor?
|
||||
if (
|
||||
if (certification == Certification.ReinforcedExoSuit) player.ExoSuit == ExoSuitType.Reinforced
|
||||
else if (certification == Certification.InfiltrationSuit) player.ExoSuit == ExoSuitType.Infiltration
|
||||
else if (player.ExoSuit == ExoSuitType.MAX) {
|
||||
lazy val subtype =
|
||||
InfantryLoadout.DetermineSubtypeA(ExoSuitType.MAX, player.Slot(slot = 0).Equipment)
|
||||
if (certification == Certification.UniMAX) true
|
||||
else if (certification == Certification.AAMAX) subtype == 1
|
||||
else if (certification == Certification.AIMAX) subtype == 2
|
||||
else if (certification == Certification.AVMAX) subtype == 3
|
||||
else false
|
||||
} else false
|
||||
) {
|
||||
player.Actor ! PlayerControl.SetExoSuit(ExoSuitType.Standard, 0)
|
||||
}
|
||||
out.complete(Success(true))
|
||||
}
|
||||
out.future
|
||||
}
|
||||
|
||||
def restoreBasicCerts(): Unit = {
|
||||
val certs = avatar.certifications
|
||||
certs.diff(AvatarActor.basicLoginCertifications).foreach { sellCertificationInTheFuture }
|
||||
AvatarActor.basicLoginCertifications.diff(certs).foreach { learnCertificationInTheFuture }
|
||||
}
|
||||
|
||||
def buyImplantAction(
|
||||
terminalGuid: PlanetSideGUID,
|
||||
definition: ImplantDefinition
|
||||
): Unit = {
|
||||
buyImplantInTheFuture(definition).onComplete {
|
||||
case Success(true) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Buy, success = true)
|
||||
)
|
||||
resetAnImplant(definition.implantType)
|
||||
sessionActor ! SessionActor.CharSaved
|
||||
case _ =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Buy, success = false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def buyImplantInTheFuture(definition: ImplantDefinition): Future[Boolean] = {
|
||||
val out: Promise[Boolean] = Promise()
|
||||
avatar.implants.zipWithIndex.collectFirst {
|
||||
case (Some(implant), _) if implant.definition.implantType == definition.implantType => None
|
||||
case (None, index) if index < avatar.br.implantSlots => Some(index)
|
||||
}.flatten match {
|
||||
case Some(index) =>
|
||||
import ctx._
|
||||
ctx
|
||||
.run(query[persistence.Implant].insert(_.name -> lift(definition.Name), _.avatarId -> lift(avatar.id)))
|
||||
.onComplete {
|
||||
case Success(_) =>
|
||||
replaceAvatar(avatar.copy(implants = avatar.implants.updated(index, Some(Implant(definition)))))
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
AvatarImplantMessage(
|
||||
session.get.player.GUID,
|
||||
ImplantAction.Add,
|
||||
index,
|
||||
definition.implantType.value
|
||||
)
|
||||
)
|
||||
out.completeWith(Future(true))
|
||||
case Failure(exception) =>
|
||||
log.error(exception)("db failure")
|
||||
out.completeWith(Future(false))
|
||||
}
|
||||
case None =>
|
||||
log.warn("attempted to learn implant but could not find slot")
|
||||
out.completeWith(Future(false))
|
||||
}
|
||||
out.future
|
||||
}
|
||||
|
||||
def sellImplantAction(
|
||||
terminalGuid: PlanetSideGUID,
|
||||
definition: ImplantDefinition
|
||||
): Unit = {
|
||||
sellImplantInTheFuture(definition).onComplete {
|
||||
case Success(true) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = true)
|
||||
)
|
||||
sessionActor ! SessionActor.CharSaved
|
||||
case _ =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def sellImplantInTheFuture(definition: ImplantDefinition): Future[Boolean] = {
|
||||
val out: Promise[Boolean] = Promise()
|
||||
avatar.implants.zipWithIndex.collectFirst {
|
||||
case (Some(implant), index) if implant.definition.implantType == definition.implantType => index
|
||||
} match {
|
||||
case Some(index) =>
|
||||
import ctx._
|
||||
ctx
|
||||
.run(
|
||||
query[persistence.Implant]
|
||||
.filter(_.name == lift(definition.Name))
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.delete
|
||||
)
|
||||
.onComplete {
|
||||
case Success(_) =>
|
||||
replaceAvatar(avatar.copy(implants = avatar.implants.updated(index, None)))
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
AvatarImplantMessage(session.get.player.GUID, ImplantAction.Remove, index, 0)
|
||||
)
|
||||
out.completeWith(Future(true))
|
||||
case Failure(exception) =>
|
||||
log.error(exception)("db failure")
|
||||
out.completeWith(Future(false))
|
||||
}
|
||||
case None =>
|
||||
log.warn("attempted to sell implant but could not find slot")
|
||||
out.completeWith(Future(false))
|
||||
}
|
||||
out.future
|
||||
}
|
||||
|
||||
def removeAllImplants(): Unit = {
|
||||
avatar.implants.collect { case Some(imp) => imp.definition }.foreach { sellImplantInTheFuture }
|
||||
context.self ! ResetImplants()
|
||||
}
|
||||
|
||||
def resetSupportExperienceTimer(previousBep: Long, previousDelay: Long): Unit = {
|
||||
val bep: Long = if (supportExperiencePool < 10L) {
|
||||
supportExperiencePool
|
||||
|
|
|
|||
|
|
@ -732,10 +732,18 @@ class ChatActor(
|
|||
}
|
||||
|
||||
case (CMT_SETBATTLERANK, _, contents) if gmCommandAllowed =>
|
||||
setBattleRank(message, contents, session, AvatarActor.SetBep)
|
||||
if (!setBattleRank(contents, session, AvatarActor.SetBep)) {
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
message.copy(messageType = UNK_229, contents = "@CMT_SETBATTLERANK_usage")
|
||||
)
|
||||
}
|
||||
|
||||
case (CMT_SETCOMMANDRANK, _, contents) if gmCommandAllowed =>
|
||||
setCommandRank(message, contents, session)
|
||||
if (!setCommandRank(contents, session)) {
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
message.copy(messageType = UNK_229, contents = "@CMT_SETCOMMANDRANK_usage")
|
||||
)
|
||||
}
|
||||
|
||||
case (CMT_ADDBATTLEEXPERIENCE, _, contents) if gmCommandAllowed =>
|
||||
contents.toIntOption match {
|
||||
|
|
@ -1124,7 +1132,7 @@ class ChatActor(
|
|||
true
|
||||
|
||||
} else if (contents.startsWith("!list")) {
|
||||
val zone = contents.split(" ").lift(1) match {
|
||||
val zone = dropFirstWord(contents).split(" ").headOption match {
|
||||
case None =>
|
||||
Some(session.zone)
|
||||
case Some(id) =>
|
||||
|
|
@ -1170,8 +1178,8 @@ class ChatActor(
|
|||
true
|
||||
|
||||
} else if (contents.startsWith("!ntu") && gmCommandAllowed) {
|
||||
val buffer = contents.toLowerCase.split("\\s+")
|
||||
val (facility, customNtuValue) = (buffer.lift(1), buffer.lift(2)) match {
|
||||
val buffer = dropFirstWord(contents).toLowerCase.split("\\s+")
|
||||
val (facility, customNtuValue) = (buffer.headOption, buffer.lift(1)) match {
|
||||
case (Some(x), Some(y)) if y.toIntOption.nonEmpty => (Some(x), Some(y.toInt))
|
||||
case (Some(x), None) if x.toIntOption.nonEmpty => (None, Some(x.toInt))
|
||||
case _ => (None, None)
|
||||
|
|
@ -1202,8 +1210,8 @@ class ChatActor(
|
|||
true
|
||||
|
||||
} else if (contents.startsWith("!zonerotate") && gmCommandAllowed) {
|
||||
val buffer = contents.toLowerCase.split("\\s+")
|
||||
cluster ! InterstellarClusterService.CavernRotation(buffer.lift(1) match {
|
||||
val buffer = dropFirstWord(contents).toLowerCase.split("\\s+")
|
||||
cluster ! InterstellarClusterService.CavernRotation(buffer.headOption match {
|
||||
case Some("-list") | Some("-l") =>
|
||||
CavernRotationService.ReportRotationOrder(sessionActor.toClassic)
|
||||
case _ =>
|
||||
|
|
@ -1224,8 +1232,8 @@ class ChatActor(
|
|||
|
||||
} else if (contents.startsWith("!macro")) {
|
||||
val avatar = session.avatar
|
||||
val args = contents.split(" ").filter(_ != "")
|
||||
(args.lift(1), args.lift(2)) match {
|
||||
val args = dropFirstWord(contents).split(" ").filter(_ != "")
|
||||
(args.headOption, args.lift(1)) match {
|
||||
case (Some(cmd), other) =>
|
||||
cmd.toLowerCase() match {
|
||||
case "medkit" =>
|
||||
|
|
@ -1275,9 +1283,10 @@ class ChatActor(
|
|||
}
|
||||
} else if (contents.startsWith("!progress")) {
|
||||
if (!session.account.gm && BattleRank.withExperience(session.avatar.bep).value < Config.app.game.promotion.maxBattleRank + 1) {
|
||||
setBattleRank(message, contents, session, AvatarActor.Progress)
|
||||
setBattleRank(dropFirstWord(contents), session, AvatarActor.Progress)
|
||||
true
|
||||
} else {
|
||||
setBattleRank(contents="1", session, AvatarActor.Progress)
|
||||
false
|
||||
}
|
||||
} else {
|
||||
|
|
@ -1288,12 +1297,19 @@ class ChatActor(
|
|||
}
|
||||
}
|
||||
|
||||
private def dropFirstWord(str: String): String = {
|
||||
val noExtraSpaces = str.replaceAll("\\s+", " ").toLowerCase.trim
|
||||
noExtraSpaces.indexOf({ char: String => char.equals(" ") }) match {
|
||||
case -1 => ""
|
||||
case beforeFirstBlank => noExtraSpaces.drop(beforeFirstBlank + 1)
|
||||
}
|
||||
}
|
||||
|
||||
def setBattleRank(
|
||||
message: ChatMsg,
|
||||
contents: String,
|
||||
session: Session,
|
||||
msgFunc: Long => AvatarActor.Command
|
||||
): Unit = {
|
||||
): Boolean = {
|
||||
val buffer = contents.toLowerCase.split("\\s+")
|
||||
val (target, rank) = (buffer.lift(0), buffer.lift(1)) match {
|
||||
case (Some(target), Some(rank)) if target == session.avatar.name =>
|
||||
|
|
@ -1301,6 +1317,8 @@ class ChatActor(
|
|||
case Some(rank) => (None, BattleRank.withValueOpt(rank))
|
||||
case None => (None, None)
|
||||
}
|
||||
case (Some("-h"), _) | (Some("-help"), _) =>
|
||||
(None, Some(BattleRank.BR1))
|
||||
case (Some(_), Some(_)) =>
|
||||
// picking other targets is not supported for now
|
||||
(None, None)
|
||||
|
|
@ -1314,14 +1332,16 @@ class ChatActor(
|
|||
(target, rank) match {
|
||||
case (_, Some(rank)) if rank.value <= Config.app.game.maxBattleRank =>
|
||||
avatarActor ! msgFunc(rank.experience)
|
||||
true
|
||||
case _ =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
message.copy(messageType = UNK_229, contents = "@CMT_SETBATTLERANK_usage")
|
||||
)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
def setCommandRank(message: ChatMsg, contents: String, session: Session): Unit = {
|
||||
def setCommandRank(
|
||||
contents: String,
|
||||
session: Session
|
||||
): Boolean = {
|
||||
val buffer = contents.toLowerCase.split("\\s+")
|
||||
val (target, rank) = (buffer.lift(0), buffer.lift(1)) match {
|
||||
case (Some(target), Some(rank)) if target == session.avatar.name =>
|
||||
|
|
@ -1342,11 +1362,9 @@ class ChatActor(
|
|||
(target, rank) match {
|
||||
case (_, Some(rank)) =>
|
||||
avatarActor ! AvatarActor.SetCep(rank.experience)
|
||||
//sessionActor ! SessionActor.SendResponse(message.copy(contents = "@AckSuccessSetCommandRank"))
|
||||
true
|
||||
case _ =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
message.copy(messageType = UNK_229, contents = "@CMT_SETCOMMANDRANK_usage")
|
||||
)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ object ZoningOperations {
|
|||
"If you consider yourself as a veteran soldier, despite looking so green, please read this.\n" ++
|
||||
"You only have this opportunity while you are battle rank 1." ++
|
||||
"\n\n" ++
|
||||
"The normal method of rank advancement comes from progress on the battlefield - fighting enemies, helping allies, and capturing facilities. " ++
|
||||
"The normal method of rank advancement comes from the battlefield - fighting enemies, helping allies, and capturing facilities. " ++
|
||||
"\n\n" ++
|
||||
s"You may, however, rapidly promote yourself to at most battle rank ${Config.app.game.promotion.maxBattleRank}. " ++
|
||||
"You have access to all of the normal benefits, certification points, implants, etc., of your chosen rank. " ++
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ object FacilityHackParticipation {
|
|||
if (dataSum != 0) {
|
||||
math.max(0.15f, math.min(2f, dataSum / dataCount.toFloat))
|
||||
} else {
|
||||
1f //can't do anything; multiplier should not affect values
|
||||
1f
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,14 +47,15 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
|||
import scala.concurrent.Promise
|
||||
import scala.util.Success
|
||||
val requestLayers: Promise[ZoneHotSpotProjector.ExposedHeat] = Promise[ZoneHotSpotProjector.ExposedHeat]()
|
||||
val request = updateHotSpotInfoOnly()
|
||||
requestLayers.completeWith(request)
|
||||
request.onComplete {
|
||||
case Success(ZoneHotSpotProjector.ExposedHeat(_, _, activity)) =>
|
||||
hotSpotLayersOverTime = timeSensitiveFilterAndAppend(hotSpotLayersOverTime, activity, System.currentTimeMillis() - 900000L)
|
||||
case _ =>
|
||||
requestLayers.completeWith(Future(ZoneHotSpotProjector.ExposedHeat(Vector3.Zero, 0, Nil)))
|
||||
}
|
||||
// val request = updateHotSpotInfoOnly()
|
||||
// requestLayers.completeWith(request)
|
||||
// request.onComplete {
|
||||
// case Success(ZoneHotSpotProjector.ExposedHeat(_, _, activity)) =>
|
||||
// hotSpotLayersOverTime = timeSensitiveFilterAndAppend(hotSpotLayersOverTime, activity, System.currentTimeMillis() - 900000L)
|
||||
// case _ =>
|
||||
// requestLayers.completeWith(Future(ZoneHotSpotProjector.ExposedHeat(building.Position.xy, building.Definition.SOIRadius, Nil)))
|
||||
// }
|
||||
requestLayers.completeWith(Future(ZoneHotSpotProjector.ExposedHeat(building.Position.xy, building.Definition.SOIRadius, Nil)))
|
||||
requestLayers.future
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +141,7 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
|||
*/
|
||||
val finalMap = mutable.HashMap[Vector3, Map[PlanetSideEmpire.Value, Seq[Long]]]()
|
||||
.addAll(
|
||||
hotSpotLayersOverTime.take(1).flatMap { entry =>
|
||||
hotSpotLayersOverTime.flatMap { entry =>
|
||||
entry.map { f => (f.DisplayLocation, Map.empty[PlanetSideEmpire.Value, Seq[Long]]) }
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -215,18 +215,14 @@ object Support {
|
|||
.find(evt => event.equals(evt.name))
|
||||
.map { event =>
|
||||
val shots = weaponStat.shots
|
||||
val shotsMax = event.shotsMax
|
||||
val shotsMultiplier = event.shotsMultiplier
|
||||
if (shotsMultiplier > 0f && shots < event.shotsCutoff) {
|
||||
val modifiedShotsReward: Float = {
|
||||
val partialShots = math.min(event.shotsLimit, shots).toFloat
|
||||
shotsMultiplier * (if (event.shotsNatLog > 0f) {
|
||||
math.log(math.pow(partialShots, event.shotsNatLog) + 2d).toFloat
|
||||
} else {
|
||||
partialShots
|
||||
})
|
||||
}
|
||||
val modifiedAmountReward: Float = event.amountMultiplier * weaponStat.amount.toFloat
|
||||
event.base + modifiedShotsReward + modifiedAmountReward
|
||||
val modifiedShotsReward: Float =
|
||||
shotsMultiplier * math.log(math.min(shotsMax, shots).toDouble + 2d).toFloat
|
||||
val modifiedAmountReward: Float =
|
||||
event.amountMultiplier * weaponStat.amount.toFloat
|
||||
event.base.toFloat + modifiedShotsReward + modifiedAmountReward
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
|
||||
import scodec.Codec
|
||||
import scodec.bits.BitVector
|
||||
import scodec.{Attempt, Codec}
|
||||
import scodec.codecs._
|
||||
|
||||
/**
|
||||
|
|
@ -12,21 +13,33 @@ import scodec.codecs._
|
|||
* It merely generates the message:<br>
|
||||
* `"You have been awarded x experience points."`<br>
|
||||
* ... where `x` is the number of experience points that have been promised.
|
||||
* If the `Boolean` parameter is `true`, `x` will be equal to the number provided followed by the word "Command."
|
||||
* If the `Boolean` parameter is `false`, `x` will be represented as an obvious blank space character.
|
||||
* (Yes, it prints to the events chat like that.)
|
||||
* @param exp the number of (Command) experience points earned
|
||||
* @param unk defaults to `true` for effect;
|
||||
* if `false`, the number of experience points in the message will be blanked
|
||||
* @param cmd if `true`, the message will be tailored for "Command" experience;
|
||||
* if `false`, the number of experience points and the "Command" flair will be blanked
|
||||
*/
|
||||
final case class ExperienceAddedMessage(exp: Int, unk: Boolean = true) extends PlanetSideGamePacket {
|
||||
final case class ExperienceAddedMessage(exp: Int, cmd: Boolean) extends PlanetSideGamePacket {
|
||||
type Packet = ExperienceAddedMessage
|
||||
def opcode = GamePacketOpcode.ExperienceAddedMessage
|
||||
def encode = ExperienceAddedMessage.encode(this)
|
||||
def opcode: GamePacketOpcode.Value = GamePacketOpcode.ExperienceAddedMessage
|
||||
def encode: Attempt[BitVector] = ExperienceAddedMessage.encode(this)
|
||||
}
|
||||
|
||||
object ExperienceAddedMessage extends Marshallable[ExperienceAddedMessage] {
|
||||
/**
|
||||
* Produce a packet whose message to the event chat is
|
||||
* "You have been awarded experience points."
|
||||
* @return `ExperienceAddedMessage` packet
|
||||
*/
|
||||
def apply(): ExperienceAddedMessage = ExperienceAddedMessage(0, cmd = false)
|
||||
|
||||
/**
|
||||
* Produce a packet whose message to the event chat is
|
||||
* "You have been awarded 'exp' Command experience points."
|
||||
* @param exp the number of Command experience points earned
|
||||
* @return `ExperienceAddedMessage` packet
|
||||
*/
|
||||
def apply(exp: Int): ExperienceAddedMessage = ExperienceAddedMessage(exp, cmd = true)
|
||||
|
||||
implicit val codec: Codec[ExperienceAddedMessage] = (
|
||||
("exp" | uintL(15)) :: ("unk" | bool)
|
||||
("exp" | uintL(bits = 15)) :: ("unk" | bool)
|
||||
).as[ExperienceAddedMessage]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
package net.psforever.persistence
|
||||
|
||||
import org.joda.time.LocalDateTime
|
||||
|
||||
case class Progressiondebt(
|
||||
avatarId:Long,
|
||||
experience: Long
|
||||
experience: Long,
|
||||
maxExperience: Long = -1,
|
||||
enrollTime: LocalDateTime = LocalDateTime.now(),
|
||||
clearTime: LocalDateTime = LocalDateTime.now()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -272,25 +272,26 @@ case class SupportExperiencePoints(
|
|||
case class SupportExperienceEvent(
|
||||
name: String,
|
||||
base: Long,
|
||||
shotsMultiplier: Float = 0f,
|
||||
shotsNatLog: Double = 0f,
|
||||
shotsLimit: Int = 50,
|
||||
shotsMax: Int = 50,
|
||||
shotsCutoff: Int = 50,
|
||||
shotsMultiplier: Float = 0f,
|
||||
amountMultiplier: Float = 0f
|
||||
)
|
||||
|
||||
case class CommandExperiencePoints(
|
||||
rate: Float,
|
||||
lluCarrierModifier: Float,
|
||||
lluSlayerCreditDuration: Duration,
|
||||
lluSlayerCredit: Long,
|
||||
maximumPerSquadSize: Seq[Int],
|
||||
squadSizeLimitOverflow: Int,
|
||||
squadSizeLimitOverflowMultiplier: Float
|
||||
rate: Float,
|
||||
lluCarrierModifier: Float,
|
||||
lluSlayerCreditDuration: Duration,
|
||||
lluSlayerCredit: Long,
|
||||
maximumPerSquadSize: Seq[Int],
|
||||
squadSizeLimitOverflow: Int,
|
||||
squadSizeLimitOverflowMultiplier: Float
|
||||
)
|
||||
|
||||
case class PromotionSystem(
|
||||
active: Boolean,
|
||||
maxBattleRank: Int,
|
||||
battleExperiencePointsModifier: Float
|
||||
battleExperiencePointsModifier: Float,
|
||||
supportExperiencePointsModifier: Float,
|
||||
captureExperiencePointsModifier: Float
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue