mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-07-08 04:54:39 +00:00
Merge pull request #1357 from Resaec/TimeOfDayMessage
TimeOfDayMessage implementation
This commit is contained in:
commit
2f9a6966ae
6 changed files with 168 additions and 112 deletions
|
|
@ -98,6 +98,12 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
|
|||
case (CMT_CAPTUREBASE, _, contents) =>
|
||||
ops.commandCaptureBase(session, message, contents)
|
||||
|
||||
case (CMT_SETTIME, _, contents) =>
|
||||
ops.commandSetTime(session, contents)
|
||||
|
||||
case (CMT_SETTIMESPEED, _, contents) =>
|
||||
ops.commandSetTimeSpeed(session, contents)
|
||||
|
||||
case (CMT_GMBROADCAST | CMT_GMBROADCAST_NC | CMT_GMBROADCAST_VS | CMT_GMBROADCAST_TR, _, _) =>
|
||||
ops.commandSendToRecipient(session, message, comms)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ import net.psforever.actors.zone.ZoneActor
|
|||
import net.psforever.objects.LivePlayerList
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.objects.zones.{Zone, ZoneInfo}
|
||||
import net.psforever.packet.game.SetChatFilterMessage
|
||||
import net.psforever.packet.game.TimeOfDayMessage.GetTimeOfDayValue
|
||||
import net.psforever.packet.game.{SetChatFilterMessage, TimeOfDayMessage}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.chat.{DefaultChannel, OutfitChannel, SquadChannel}
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
import net.psforever.services.teamwork.{SquadResponse, SquadService, SquadServiceResponse}
|
||||
|
|
@ -1419,6 +1422,62 @@ class ChatOperations(
|
|||
)
|
||||
}
|
||||
|
||||
def commandSetTime(session: Session, contents: String): Unit = {
|
||||
val TimePattern = """([01]?\d|2[0-3]):([0-5]?\d)""".r
|
||||
|
||||
TimePattern.findFirstMatchIn(contents) match {
|
||||
case Some(m) =>
|
||||
val hh = m.group(1).toInt % 24
|
||||
val mm = m.group(2).toInt % 60
|
||||
val requestedTimeOfDay = GetTimeOfDayValue(hh, mm)
|
||||
val zone = session.zone
|
||||
|
||||
// update zone
|
||||
zone.SetTimeOfDay(requestedTimeOfDay)
|
||||
|
||||
// build update message
|
||||
val msg = TimeOfDayMessage(zone.GetTimeOfDay(), zone.GetTimeOfDaySpeed())
|
||||
|
||||
// update players in zone
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.id,
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, msg)
|
||||
)
|
||||
|
||||
sendResponse(ChatMsg(messageType = UNK_227, contents = f"@CMT_SETTIME_OK^$hh~^$mm%02d~"))
|
||||
case _ =>
|
||||
sendResponse(ChatMsg(messageType = UNK_229, contents = "@CMT_SETTIME_usage"))
|
||||
}
|
||||
}
|
||||
|
||||
def commandSetTimeSpeed(session: Session, contents: String): Unit = {
|
||||
val FloatPattern = """-?\d+(\.\d{1,2})?""".r
|
||||
|
||||
FloatPattern.findAllMatchIn(contents).toList match {
|
||||
case List(m) =>
|
||||
var timeSpeed = m.matched.toFloat
|
||||
if (timeSpeed < -1000.0f) timeSpeed = -1000.0f
|
||||
if (timeSpeed > 1000.0f) timeSpeed = 1000.0f
|
||||
val zone = session.zone
|
||||
|
||||
// update zone
|
||||
zone.SetTimeOfDaySpeed(timeSpeed)
|
||||
|
||||
// build update message
|
||||
val msg = TimeOfDayMessage(zone.GetTimeOfDay(), zone.GetTimeOfDaySpeed())
|
||||
|
||||
// update players in zone
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.id,
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, msg)
|
||||
)
|
||||
|
||||
sendResponse(ChatMsg(messageType = UNK_227, contents = s"@CMT_SETTIMESPEED_OK^$timeSpeed~"))
|
||||
case _ =>
|
||||
sendResponse(ChatMsg(messageType = UNK_229, contents = "@CMT_SETTIMESPEED_usage"))
|
||||
}
|
||||
}
|
||||
|
||||
override protected[session] def stop(): Unit = {
|
||||
silenceTimer.cancel()
|
||||
chatService ! ChatService.LeaveAllChannels(chatServiceAdapter)
|
||||
|
|
|
|||
|
|
@ -306,7 +306,10 @@ class ZoningOperations(
|
|||
continent.VehicleEvents ! Service.Join(continentId)
|
||||
continent.VehicleEvents ! Service.Join(factionChannel)
|
||||
if (sessionLogic.connectionState != 100) configZone(continent)
|
||||
sendResponse(TimeOfDayMessage(1135214592))
|
||||
|
||||
// set time of day
|
||||
sendResponse(TimeOfDayMessage(continent.GetTimeOfDay(), continent.GetTimeOfDaySpeed()))
|
||||
|
||||
//custom
|
||||
sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list
|
||||
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 0)) // disable festive backpacks
|
||||
|
|
@ -879,35 +882,35 @@ class ZoningOperations(
|
|||
|
||||
def handleTrainingZoneMessage(pkt: TrainingZoneMessage): Unit = {
|
||||
pkt.zone.guid match {
|
||||
case 11 | 12 | 13 =>
|
||||
case 11 | 12 | 13 =>
|
||||
player.avatar.deployables.UpdateMaxCounts(player.avatar.certifications)
|
||||
RequestSanctuaryZoneSpawn(player, player.Zone.Number)
|
||||
// leveraging SetZone for now because the VR zones have no "proper" spawn points configured yet
|
||||
case 17 =>
|
||||
case 17 =>
|
||||
if (player.Zone.id != "tzshtr") {
|
||||
context.self ! SessionActor.SetZone("tzshtr", vrShootingZoneSpawns.toArray.apply(rand.nextInt(vrShootingZoneSpawns.size)))
|
||||
}
|
||||
case 18 =>
|
||||
case 18 =>
|
||||
if (player.Zone.id != "tzshnc") {
|
||||
context.self ! SessionActor.SetZone("tzshnc", vrShootingZoneSpawns.toArray.apply(rand.nextInt(vrShootingZoneSpawns.size)))
|
||||
}
|
||||
case 19 =>
|
||||
case 19 =>
|
||||
if (player.Zone.id != "tzshvs") {
|
||||
context.self ! SessionActor.SetZone("tzshvs", vrShootingZoneSpawns.toArray.apply(rand.nextInt(vrShootingZoneSpawns.size)))
|
||||
}
|
||||
case 20 =>
|
||||
case 20 =>
|
||||
if (player.Zone.id != "tzdrtr") {
|
||||
context.self ! SessionActor.SetZone("tzdrtr", vrDrivingAreaSpawns.toArray.apply(rand.nextInt(vrDrivingAreaSpawns.size)))
|
||||
}
|
||||
case 21 =>
|
||||
case 21 =>
|
||||
if (player.Zone.id != "tzdrnc") {
|
||||
context.self ! SessionActor.SetZone("tzdrnc", vrDrivingAreaSpawns.toArray.apply(rand.nextInt(vrDrivingAreaSpawns.size)))
|
||||
}
|
||||
case 22 =>
|
||||
case 22 =>
|
||||
if (player.Zone.id != "tzdrvs") {
|
||||
context.self ! SessionActor.SetZone("tzdrvs", vrDrivingAreaSpawns.toArray.apply(rand.nextInt(vrDrivingAreaSpawns.size)))
|
||||
}
|
||||
case _ =>
|
||||
case _ =>
|
||||
log.warn(s"Received TrainingZoneMessage that requests unexpected zone number ${pkt.zone.guid}?")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,6 +206,13 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
|||
*/
|
||||
var benefitRecipient: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
|
||||
|
||||
/**
|
||||
* Holds the origin time of this zones
|
||||
* @see `TimeOfDayMessage`
|
||||
*/
|
||||
private var timeOfDayOrigin: Long = 0
|
||||
private var timeOfDaySpeed: Float = 10.0f
|
||||
|
||||
/**
|
||||
* When the zone has completed initializing, this will be the future.
|
||||
* @see `init(ActorContext)`
|
||||
|
|
@ -565,6 +572,42 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
|||
HotSpotTimeFunction
|
||||
}
|
||||
|
||||
def GetTimeOfDay(): Float = {
|
||||
val now = System.currentTimeMillis()
|
||||
val timeDiff = now - timeOfDayOrigin
|
||||
|
||||
// get seconds
|
||||
val diffSeconds = timeDiff / 1000
|
||||
|
||||
// apply speed factor
|
||||
val elapsedTime = diffSeconds * timeOfDaySpeed
|
||||
|
||||
// wrap time to 24h
|
||||
elapsedTime % 86400
|
||||
}
|
||||
|
||||
def SetTimeOfDay(requestedTime: Float) = {
|
||||
val now = System.currentTimeMillis()
|
||||
|
||||
val requestedTimeDiff = requestedTime / timeOfDaySpeed
|
||||
|
||||
timeOfDayOrigin = now - (requestedTimeDiff * 1000.0).toLong
|
||||
}
|
||||
|
||||
def GetTimeOfDaySpeed(): Float = {
|
||||
timeOfDaySpeed
|
||||
}
|
||||
|
||||
def SetTimeOfDaySpeed(requestedSpeed: Float) = {
|
||||
// store current time
|
||||
val timeOfDay = GetTimeOfDay()
|
||||
|
||||
timeOfDaySpeed = requestedSpeed
|
||||
|
||||
// restore old time with new speed considered
|
||||
SetTimeOfDay(timeOfDay)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide bulk correspondence on all map entities that can be composed into packet messages and reported to a client.
|
||||
* These messages are sent in this fashion at the time of joining the server:<br>
|
||||
|
|
@ -1542,6 +1585,8 @@ object Zone {
|
|||
zone.localEvents = context.actorOf(Props(classOf[LocalService], zone), s"$id-local-events")
|
||||
zone.vehicleEvents = context.actorOf(Props(classOf[VehicleService], zone), s"$id-vehicle-events")
|
||||
|
||||
zone.timeOfDayOrigin = System.currentTimeMillis()
|
||||
|
||||
BuildLocalObjects(zone)(context, guid)
|
||||
BuildSupportObjects(zone)
|
||||
MakeBuildings(zone)(context, guid)
|
||||
|
|
@ -1898,11 +1943,11 @@ object Zone {
|
|||
val allAffectedTargets = pssos.filter { target => testTargetsFromZone(source, target, radius) }
|
||||
//inform remaining targets that they have suffered damage
|
||||
allAffectedTargets
|
||||
.foreach { target =>
|
||||
.foreach { target =>
|
||||
if (zone.id.startsWith("tz") && source.Faction == target.Faction) {
|
||||
//do not perform friendly-fire in VR zones
|
||||
} else {
|
||||
target.Actor ! Vitality.Damage(createInteraction(source, target).calculate())
|
||||
target.Actor ! Vitality.Damage(createInteraction(source, target).calculate())
|
||||
}
|
||||
}
|
||||
allAffectedTargets
|
||||
|
|
|
|||
|
|
@ -22,16 +22,15 @@ import scodec.codecs._
|
|||
* <br>
|
||||
* If no time is set, the client starts counting from 10:00 at an initial rate of about one Auraxis minute every four or five real seconds.
|
||||
* Setting the current time to 1107296256 sets the current time to 00:00 with an indeterminate, but slow, rate.
|
||||
* Time is normally initialized somewhere within an interval between 1174405120 and 1207959296.
|
||||
* Time is normally initialized somewhere within an interval between 2:16 (0x460000, 1174405120) and 36:24 (0x47FFFF00, 1207959296).
|
||||
* Setting the current time extremely high (near the numerical maximum) can cause psychedelic rendering.
|
||||
* (Setting the time to 4294967040 exactly will reduce the rendering system to gibberish.)<br>
|
||||
* <br>
|
||||
* The interval from 1178164736 (~03:18) to 1203765248 (03:18) is about a full twenty-four hours.
|
||||
* That is a count of 25600512.
|
||||
* @param time Auraxis time
|
||||
* @param unk consistently 1092616192; does nothing?
|
||||
|
||||
* @param timeOfDay Auraxis time
|
||||
* @param timeSpeed consistently 10.0f
|
||||
*/
|
||||
final case class TimeOfDayMessage(time: Long, unk: Long = 1092616192L) extends PlanetSideGamePacket {
|
||||
final case class TimeOfDayMessage(timeOfDay: Float, timeSpeed: Float = 10.0f) extends PlanetSideGamePacket {
|
||||
type Packet = TimeOfDayMessage
|
||||
def opcode = GamePacketOpcode.TimeOfDayMessage
|
||||
def encode = TimeOfDayMessage.encode(this)
|
||||
|
|
@ -39,95 +38,20 @@ final case class TimeOfDayMessage(time: Long, unk: Long = 1092616192L) extends P
|
|||
|
||||
object TimeOfDayMessage extends Marshallable[TimeOfDayMessage] {
|
||||
implicit val codec: Codec[TimeOfDayMessage] = (
|
||||
("time" | uint32L) ::
|
||||
("unk" | uint32L)
|
||||
("timeOfDay" | floatL) ::
|
||||
("timeSpeed" | floatL)
|
||||
).as[TimeOfDayMessage]
|
||||
}
|
||||
|
||||
/*
|
||||
Time Testing Conducted in VS Sanctuary
|
||||
48 00 __ __ __ 00 00 20 41
|
||||
--------------------------
|
||||
+01 00 00
|
||||
--------------------------
|
||||
48 00 1B 00 47 00 00 20 41 //09:06
|
||||
48 00 1C 00 47 00 00 20 41 //09:07
|
||||
...
|
||||
48 00 59 00 47 00 00 20 41 //09:08 (+3D 00 00)
|
||||
--------------------------
|
||||
+10 00 00
|
||||
--------------------------
|
||||
48 00 00 00 47 00 00 20 41 //09:06 <--
|
||||
48 00 10 00 47 00 00 20 41 //09:06
|
||||
48 00 20 00 47 00 00 20 41 //09:07
|
||||
48 00 30 00 47 00 00 20 41 //09:07
|
||||
48 00 40 00 47 00 00 20 41 //09:07
|
||||
48 00 50 00 47 00 00 20 41 //09:07
|
||||
48 00 60 00 47 00 00 20 41 //09:08
|
||||
48 00 70 00 47 00 00 20 41 //09:08
|
||||
48 00 80 00 47 00 00 20 41 //09:08
|
||||
48 00 90 00 47 00 00 20 41 //09:08
|
||||
48 00 A0 00 47 00 00 20 41 //09:09
|
||||
48 00 B0 00 47 00 00 20 41 //09:09
|
||||
48 00 C0 00 47 00 00 20 41 //09:09
|
||||
48 00 D0 00 47 00 00 20 41 //09:09
|
||||
48 00 E0 00 47 00 00 20 41 //09:10
|
||||
48 00 F0 00 47 00 00 20 41 //09:10
|
||||
48 00 00 01 47 00 00 20 41 //09:10
|
||||
--------------------------
|
||||
+00 01 00
|
||||
--------------------------
|
||||
48 00 00 00 47 00 00 20 41 //09:06 <--
|
||||
48 00 00 01 47 00 00 20 41 //09:10
|
||||
48 00 00 02 47 00 00 20 41 //09:15
|
||||
48 00 00 03 47 00 00 20 41 //09:19
|
||||
48 00 00 04 47 00 00 20 41 //09:23
|
||||
48 00 00 05 47 00 00 20 41 //09:27
|
||||
48 00 00 06 47 00 00 20 41 //09:32
|
||||
48 00 00 07 47 00 00 20 41 //09:36
|
||||
48 00 00 08 47 00 00 20 41 //09:40
|
||||
48 00 00 09 47 00 00 20 41 //09:44
|
||||
48 00 00 0A 47 00 00 20 41 //09:49
|
||||
48 00 00 0B 47 00 00 20 41 //09:53
|
||||
48 00 00 0C 47 00 00 20 41 //09:57
|
||||
48 00 00 0D 47 00 00 20 41 //09:01
|
||||
48 00 00 0E 47 00 00 20 41 //10:06
|
||||
48 00 00 0F 47 00 00 20 41 //10:10
|
||||
48 00 00 10 47 00 00 20 41 //10:14
|
||||
--------------------------
|
||||
+00 10 00
|
||||
--------------------------
|
||||
48 00 00 00 46 00 00 20 41 //02:17 (-00:17)
|
||||
48 00 00 10 46 00 00 20 41 //02:34 (-00:17)
|
||||
48 00 00 20 46 00 00 20 41 //02:51 (-00:17)
|
||||
48 00 00 30 46 00 00 20 41 //03:08 (-00:17)
|
||||
48 00 00 40 46 00 00 20 41 //03:25 (-00:17)
|
||||
48 00 00 50 46 00 00 20 41 //03:42 (-00:17)
|
||||
48 00 00 60 46 00 00 20 41 //03:59 (-00:17)
|
||||
48 00 00 70 46 00 00 20 41 //04:16 (-00:17)
|
||||
48 00 00 80 46 00 00 20 41 //04:33 (-00:34)
|
||||
48 00 00 90 46 00 00 20 41 //05:07 (-00:34)
|
||||
48 00 00 A0 46 00 00 20 41 //05:41 (-00:35)
|
||||
48 00 00 B0 46 00 00 20 41 //06:16 (-00:34)
|
||||
48 00 00 C0 46 00 00 20 41 //06:50 (-00:34)
|
||||
48 00 00 D0 46 00 00 20 41 //07:24 (-00:34)
|
||||
48 00 00 E0 46 00 00 20 41 //07:58 (-00:34)
|
||||
48 00 00 F0 46 00 00 20 41 //08:32 (-00:34)
|
||||
48 00 00 00 47 00 00 20 41 //09:06 <--
|
||||
48 00 00 10 47 00 00 20 41 //10:14 (+01:08)
|
||||
48 00 00 20 47 00 00 20 41 //11:23 (+01:09)
|
||||
48 00 00 30 47 00 00 20 41 //12:31 (+01:08)
|
||||
48 00 00 40 47 00 00 20 41 //13:39 (+01:08)
|
||||
48 00 00 50 47 00 00 20 41 //14:47 (+01:08)
|
||||
48 00 00 60 47 00 00 20 41 //15:56 (+01:08)
|
||||
48 00 00 70 47 00 00 20 41 //17:04 (+01:08)
|
||||
48 00 00 80 47 00 00 20 41 //18:12 (+01:08)
|
||||
48 00 00 90 47 00 00 20 41 //20:29 (+02:16)
|
||||
48 00 00 A0 47 00 00 20 41 //22:45 (+02:16)
|
||||
48 00 00 B0 47 00 00 20 41 //01:02 (+02:17)
|
||||
48 00 00 C0 47 00 00 20 41 //03:18 (+02:16)
|
||||
48 00 00 D0 47 00 00 20 41 //05:35 (+02:17)
|
||||
48 00 00 E0 47 00 00 20 41 //07:51 (+02:16)
|
||||
48 00 00 F0 47 00 00 20 41 //10:08 (+02:17)
|
||||
48 00 00 00 48 00 00 20 41 //12:24 (+02:16)
|
||||
*/
|
||||
def GetTimeOfDayValue(hours: Int, minutes: Int): Float = {
|
||||
((hours * 3600 + minutes * 60) % 86400).toFloat
|
||||
}
|
||||
|
||||
def GetTimeOfDayValue(timeSpeed: Float = 10.0f): Float = {
|
||||
val now = System.currentTimeMillis()
|
||||
val scaled = now * timeSpeed
|
||||
val seconds = scaled / 1_000 // back to seconds
|
||||
val clamped = seconds % 86400 // clamp to 24h
|
||||
|
||||
clamped
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,18 +11,37 @@ class TimeOfDayMessageTest extends Specification {
|
|||
|
||||
"decode" in {
|
||||
PacketCoding.decodePacket(string).require match {
|
||||
case TimeOfDayMessage(time, unk) =>
|
||||
time mustEqual 1191182336
|
||||
unk mustEqual 1092616192
|
||||
case TimeOfDayMessage(timeOfDay, timeSpeed) =>
|
||||
timeOfDay mustEqual 32768.0f
|
||||
timeSpeed mustEqual 10.0f
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
val msg = TimeOfDayMessage(1191182336)
|
||||
val msg = TimeOfDayMessage(32768.0f)
|
||||
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string
|
||||
}
|
||||
|
||||
val string2 = hex"48 00 00 A0 47 00 00 20 41" //22:45
|
||||
|
||||
"decode2" in {
|
||||
PacketCoding.decodePacket(string2).require match {
|
||||
case TimeOfDayMessage(timeOfDay, timeSpeed) =>
|
||||
timeOfDay mustEqual 81920.0f
|
||||
timeSpeed mustEqual 10.0f
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode2" in {
|
||||
val msg = TimeOfDayMessage(81920.0f)
|
||||
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string2
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue