mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-07-09 13:34:40 +00:00
TimeOfDayMessage implementation
corrected packet decoding added helper functions added zone logic to store and access local time updated zoning logic to use local zone time added chat commands to change zone local time and time speed
This commit is contained in:
parent
575f5fd7fc
commit
42c66bb4ca
6 changed files with 143 additions and 112 deletions
|
|
@ -97,6 +97,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,8 @@ 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.chat.{DefaultChannel, OutfitChannel, SquadChannel}
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
import net.psforever.services.teamwork.{SquadResponse, SquadService, SquadServiceResponse}
|
||||
|
|
@ -1419,6 +1420,46 @@ 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 msg = TimeOfDayMessage(requestedTimeOfDay)
|
||||
|
||||
// update zone
|
||||
session.zone.SetTimeOfDay(requestedTimeOfDay)
|
||||
|
||||
sendResponse(msg)
|
||||
sendResponse(ChatMsg(messageType = UNK_227, contents = s"@CMT_SETTIME_OK^$hh~^$mm~"))
|
||||
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 msg = TimeOfDayMessage(GetTimeOfDayValue(), timeSpeed)
|
||||
|
||||
// update zone
|
||||
session.zone.SetTimeOfDaySpeed(timeSpeed)
|
||||
|
||||
sendResponse(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
|
||||
|
|
@ -890,35 +893,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,35 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
|||
HotSpotTimeFunction
|
||||
}
|
||||
|
||||
def GetTimeOfDay(): Float = {
|
||||
// get time since origin
|
||||
val timeDiff = System.currentTimeMillis() - timeOfDayOrigin
|
||||
// get seconds - limit to 24h
|
||||
val diffSeconds = timeDiff / 1000 % 86400
|
||||
|
||||
timeDiff.toFloat
|
||||
}
|
||||
|
||||
def SetTimeOfDay(requestedTime: Float) = {
|
||||
// get current time
|
||||
val now = System.currentTimeMillis()
|
||||
// get requested time in millis
|
||||
val requestMillis = requestedTime * 1000
|
||||
|
||||
// substract requestedMillis from now to get new origin
|
||||
val newOrigin = now - requestMillis
|
||||
|
||||
timeOfDayOrigin = newOrigin.toInt
|
||||
}
|
||||
|
||||
def GetTimeOfDaySpeed(): Float = {
|
||||
timeOfDaySpeed
|
||||
}
|
||||
|
||||
def SetTimeOfDaySpeed(requestedSpeed: Float) = {
|
||||
timeOfDaySpeed = requestedSpeed
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +1578,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)
|
||||
|
|
@ -1871,11 +1909,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