use chumsky::IterParser as _;
use chumsky::Parser;
use chumsky::prelude::{any, choice, end, just, one_of};
use chumsky::text::{digits, newline, whitespace};
use sl_types::utils::{i64_parser, u64_parser, unsigned_f32_parser, usize_parser};
use crate::take_until;
#[derive(Debug, Clone, PartialEq)]
pub enum SystemMessage {
SavedSnapshot {
filename: std::path::PathBuf,
},
FailedToSaveSnapshotDueToMissingDestinationFolder {
folder: std::path::PathBuf,
},
FailedToSaveSnapshotDueToDiskSpace {
folder: std::path::PathBuf,
required_disk_space: bytesize::ByteSize,
free_disk_space: bytesize::ByteSize,
},
DrawDistanceSet {
distance: sl_types::map::Distance,
},
HomePositionSet,
LandDivided,
FailedToJoinLandDueToRegionBoundary,
OfferedCallingCard {
recipient_avatar_name: String,
},
AttachmentSavedMessage,
YouPaidForObject {
seller: sl_types::key::OwnerKey,
amount: sl_types::money::LindenAmount,
object_name: String,
},
YouPaidToCreateGroup {
payment_recipient: sl_types::key::AgentKey,
amount: sl_types::money::LindenAmount,
},
YouPaidToJoinGroup {
joined_group: sl_types::key::GroupKey,
join_fee: sl_types::money::LindenAmount,
},
YouPaidForLand {
previous_land_owner: sl_types::key::OwnerKey,
amount: sl_types::money::LindenAmount,
},
FailedToPay {
payment_recipient: sl_types::key::OwnerKey,
amount: sl_types::money::LindenAmount,
},
ObjectGrantedPermissionToTakeMoney {
object_name: String,
owner_name: String,
object_region: Option<sl_types::map::RegionName>,
object_location: Option<sl_types::map::RegionCoordinates>,
},
SentPayment {
recipient_key: sl_types::key::OwnerKey,
amount: sl_types::money::LindenAmount,
message: Option<String>,
},
ReceivedPayment {
sender_key: sl_types::key::OwnerKey,
amount: sl_types::money::LindenAmount,
message: Option<String>,
},
AddedToGroup,
LeftGroup {
group_name: String,
},
UnableToInviteUserDueToMissingGroupMembership,
UnableToInviteUserToGroupDueToDifferingLimitedEstate,
UnableToLoadNotecard,
UnableToLoadGesture {
gesture_name: String,
},
NowPlaying {
song_name: String,
},
TeleportCompleted {
origin: sl_types::map::UnconstrainedLocation,
},
RegionRestart,
ObjectGaveObject {
giving_object_name: String,
giving_object_location: sl_types::map::UnconstrainedLocation,
giving_object_owner: sl_types::key::OwnerKey,
given_object_name: String,
},
ObjectGaveFolder {
giving_object_key: sl_types::key::ObjectKey,
giving_object_name: String,
giving_object_owner: sl_types::key::OwnerKey,
giving_object_location: sl_types::map::Location,
giving_object_link_label: String,
folder_name: String,
},
AvatarGaveObject {
is_group_member: bool,
giving_avatar_name: String,
given_object_name: String,
},
DeclinedGivenObject {
object_name: String,
giver_location: sl_types::map::UnconstrainedLocation,
giver_name: String,
},
SelectResidentsToShareWith,
ItemsSuccessfullyShared,
ModifiedSearchQuery {
query: String,
},
SimulatorVersion {
previous_region_simulator_version: String,
current_region_simulator_version: String,
},
RenamedAvatar {
old_name: String,
new_name: String,
},
DoubleClickTeleport {
enabled: bool,
},
AlwaysRun {
enabled: bool,
},
AddedAsEstateManager,
CreatingBridge,
BridgeCreated,
BridgeCreationInProgress,
BridgeFailedToAttach,
BridgeFailedToAttachDueToBridgeAttachmentPointInUse,
BridgeNotCreated,
BridgeDetached,
BridgeObjectNotFoundCantProceedWithCreation,
FailedToPlaceObjectAtSpecifiedLocation,
ScriptCountChanged {
previous_script_count: u32,
current_script_count: u32,
change: i32,
},
MultiPersonChatMessageStillBeingProcessed,
ChatMessageToNoLongerExistingImSessionStillBeingProcessed,
ConferenceChatMessageStillBeingProcessed {
avatar_name: String,
},
GroupChatMessageStillBeingProcessed {
group_name: String,
},
AvatarDeclinedVoice {
avatar_name: String,
},
AvatarUnavailableForVoice {
avatar_name: String,
},
AudioFromDomainWillAlwaysBePlayed {
domain: String,
},
ObjectNotForSale,
CanNotCreateRequestedInventory,
LinkFailedDueToPieceDistance {
link_failed_pieces: Option<usize>,
total_selected_pieces: Option<usize>,
},
RezObjectFailedDueToFullParcel {
object_name: String,
parcel_name: String,
attempted_rez_location: sl_types::map::RegionCoordinates,
region_name: sl_types::map::RegionName,
},
CreateObjectFailedDueToFullRegion,
YourObjectHasBeenReturned {
object_name: String,
parcel_name: String,
location: sl_types::map::UnconstrainedLocation,
auto_return: bool,
},
PermissionToCreateObjectDenied,
PermissionToRezObjectDenied {
object_name: String,
parcel_name: String,
attempted_rez_location: sl_types::map::RegionCoordinates,
region_name: sl_types::map::RegionName,
},
PermissionToRepositionDenied,
PermissionToRotateDenied,
PermissionToRescaleDenied,
PermissionToUnlinkDeniedDueToMissingParcelBuildPermissions,
PermissionToViewScriptDenied,
PermissionToViewNotecardDenied,
PermissionToChangeShapeDenied,
PermissionToEnterParcelDenied,
PermissionToEnterParcelDeniedDueToBan,
EjectedAvatar,
EjectedFromParcel,
EjectedFromParcelBecauseNoLongerAllowed,
BannedFromParcelTemporarily {
ban_duration: time::Duration,
},
BannedFromParcelIndefinitely,
OnlyGroupMembersCanVisitThisArea,
UnableToTeleportDueToRlv,
UnableToOpenTextureDueToRlv,
UnsupportedSlurl,
BlockedUntrustedBrowserSlurl,
GridStatusErrorInvalidMessageFormat,
ScriptInfoObjectInvalidOrOutOfRange,
ScriptInfo {
name: String,
running_scripts: usize,
total_scripts: usize,
allowed_memory_size_limit: bytesize::ByteSize,
cpu_time_consumed: time::Duration,
},
ExtendedScriptInfo {
object_key: sl_types::key::ObjectKey,
description: Option<String>,
root_prim: sl_types::key::ObjectKey,
prim_count: usize,
land_impact: usize,
inventory_items: usize,
velocity: sl_types::lsl::Vector,
position: sl_types::map::RegionCoordinates,
position_distance: sl_types::map::Distance,
rotation: sl_types::lsl::Rotation,
rotation_vector_degrees: sl_types::lsl::Vector,
angular_velocity: sl_types::lsl::Vector,
creator: sl_types::key::AgentKey,
owner: Option<sl_types::key::OwnerKey>,
previous_owner: Option<sl_types::key::OwnerKey>,
rezzed_by: sl_types::key::AgentKey,
group: Option<sl_types::key::GroupKey>,
creation_time: Option<time::OffsetDateTime>,
rez_time: Option<time::OffsetDateTime>,
pathfinding_type: sl_types::pathfinding::PathfindingType,
attachment_point: Option<sl_types::attachment::AttachmentPoint>,
temporarily_attached: bool,
inspecting_avatar_position: sl_types::map::RegionCoordinates,
},
DiceRollCommandUsageInstructions,
DiceRollResult {
roll_number: usize,
dice_faces: usize,
roll_result: usize,
},
DiceRollResultSum {
roll_count: usize,
dice_faces: usize,
result_sum: usize,
},
TextureInfoForObject {
object_name: String,
},
TextureInfoForFace {
face_number: usize,
texture_width: u16,
texture_height: u16,
texture_type: String,
},
FirestormMessage {
message_type: String,
message: String,
},
GridStatusEvent {
title: String,
scheduled: bool,
body: String,
incident_url: String,
},
SystemMessageWithLink {
message: String,
link: String,
},
FirestormHolidayWishes {
message: String,
},
PhishingWarning {
message: String,
},
TestMessageOfTheDay,
EarlyFirestormStartupMessage {
message: String,
},
OtherSystemMessage {
message: String,
},
}
#[must_use]
pub fn snapshot_saved_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Snapshot saved: ")
.ignore_then(
any()
.repeated()
.collect::<String>()
.map(std::path::PathBuf::from),
)
.map(|filename| SystemMessage::SavedSnapshot { filename })
.or(just("Failed to save snapshot to ").ignore_then(
take_until!(just(": Directory does not exist.").ignored()).map(|(folder, ())| {
SystemMessage::FailedToSaveSnapshotDueToMissingDestinationFolder {
folder: std::path::PathBuf::from(folder),
}
}),
))
.or(just("Failed to save snapshot to ").ignore_then(
take_until!(just(": Disk is full. ").ignored())
.map(|(folder, ())| std::path::PathBuf::from(folder))
.then(u64_parser())
.then_ignore(just("KB is required but only "))
.then(u64_parser())
.then_ignore(just("KB is free."))
.map(|((folder, required), free)| {
let required_disk_space = bytesize::ByteSize::kib(required);
let free_disk_space = bytesize::ByteSize::kib(free);
SystemMessage::FailedToSaveSnapshotDueToDiskSpace {
folder,
required_disk_space,
free_disk_space,
}
}),
))
}
#[must_use]
pub fn draw_distance_set_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Draw Distance set to ")
.ignore_then(sl_types::map::distance_parser())
.then_ignore(just('.'))
.map(|distance| SystemMessage::DrawDistanceSet { distance })
.labelled("draw distance set")
}
#[must_use]
pub fn home_position_set_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Home position set.")
.to(SystemMessage::HomePositionSet)
.labelled("home position set")
}
#[must_use]
pub fn land_divided_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Land has been divided.")
.to(SystemMessage::LandDivided)
.labelled("land has been divided")
}
#[must_use]
pub fn failed_to_join_land_due_to_region_boundary_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Selected land is not all in the same region.")
.ignore_then(newline())
.ignore_then(just(" Try selecting a smaller piece of land."))
.to(SystemMessage::FailedToJoinLandDueToRegionBoundary)
.labelled("selected labe is not all in the same region")
}
#[must_use]
pub fn offered_calling_card_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("You have offered a calling card to ")
.ignore_then(take_until!(just('.')))
.map(
|(recipient_avatar_name, _)| SystemMessage::OfferedCallingCard {
recipient_avatar_name,
},
)
.labelled("offered calling card")
}
#[must_use]
pub fn attachment_saved_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Attachment has been saved.")
.to(SystemMessage::AttachmentSavedMessage)
.labelled("attachment has been saved")
}
#[must_use]
pub fn you_paid_for_object_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("You paid ")
.ignore_then(sl_types::key::app_agent_or_group_uri_as_owner_key_parser())
.then_ignore(whitespace())
.then(sl_types::money::linden_amount_parser())
.then_ignore(just(" for "))
.then(take_until!(just(".").ignore_then(end())))
.map(
|((seller, amount), (object_name, ()))| SystemMessage::YouPaidForObject {
seller,
amount,
object_name,
},
)
.labelled("you paid for object")
}
#[must_use]
pub fn sent_payment_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("You paid ")
.ignore_then(
sl_types::key::app_agent_or_group_uri_as_owner_key_parser()
.then_ignore(whitespace())
.then(sl_types::money::linden_amount_parser())
.then(
just(": ")
.ignore_then(any().repeated().collect::<String>())
.ignore_then(take_until!(newline().or(end())).map(|(n, ())| Some(n)))
.or(just(".").map(|_| None)),
)
.map(
|((recipient_key, amount), message)| SystemMessage::SentPayment {
recipient_key,
amount,
message,
},
),
)
.labelled("you paid avatar")
}
#[must_use]
pub fn received_payment_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
sl_types::key::app_agent_or_group_uri_as_owner_key_parser()
.then_ignore(just(" paid you "))
.then(sl_types::money::linden_amount_parser())
.then(
just(": ")
.ignore_then(any().repeated().collect::<String>())
.ignore_then(take_until!(newline().or(end())).map(|(n, ())| Some(n)))
.or(just(".").map(|_| None)),
)
.map(
|((sender_key, amount), message)| SystemMessage::ReceivedPayment {
sender_key,
amount,
message,
},
)
.labelled("received payment")
}
#[must_use]
pub fn you_paid_to_create_a_group_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("You paid ")
.ignore_then(sl_types::key::app_agent_uri_as_agent_key_parser())
.then_ignore(whitespace())
.then(sl_types::money::linden_amount_parser())
.then_ignore(just(" to create a group."))
.map(
|(payment_recipient, amount)| SystemMessage::YouPaidToCreateGroup {
payment_recipient,
amount,
},
)
.labelled("you paid to create a group")
}
#[must_use]
pub fn you_paid_to_join_group_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("You paid ")
.ignore_then(sl_types::key::app_group_uri_as_group_key_parser())
.then_ignore(whitespace())
.then(sl_types::money::linden_amount_parser())
.then_ignore(just(" to join a group."))
.map(
|(joined_group, join_fee)| SystemMessage::YouPaidToJoinGroup {
joined_group,
join_fee,
},
)
.labelled("you paid to join a group")
}
#[must_use]
pub fn you_paid_for_land_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("You paid ")
.ignore_then(sl_types::key::app_agent_or_group_uri_as_owner_key_parser())
.then_ignore(whitespace())
.then(sl_types::money::linden_amount_parser())
.then_ignore(just(" for a parcel of land."))
.map(
|(previous_land_owner, amount)| SystemMessage::YouPaidForLand {
previous_land_owner,
amount,
},
)
.labelled("you paid for a parcel of land")
}
#[must_use]
pub fn failed_to_pay_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("You failed to pay ")
.ignore_then(sl_types::key::app_agent_or_group_uri_as_owner_key_parser())
.then_ignore(whitespace())
.then(sl_types::money::linden_amount_parser())
.then_ignore(just('.'))
.map(|(payment_recipient, amount)| SystemMessage::FailedToPay {
payment_recipient,
amount,
})
.labelled("you failed to pay")
}
#[must_use]
pub fn object_granted_permission_to_take_money_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just('\'')
.ignore_then(take_until!(just("', an object owned by '")))
.then(take_until!(just("', located in ")))
.then(
just("(unknown region) at ")
.to(None)
.or(take_until!(just(" at ")).try_map(|(vc, _), span| {
Ok(Some(sl_types::map::RegionName::try_new(vc).map_err(
|err| {
chumsky::error::Rich::custom(
span,
format!("Error creating region name: {err:?}"),
)
},
)?))
})),
)
.then(
just("(unknown position)")
.to(None)
.or(sl_types::utils::f32_parser()
.then_ignore(just(", "))
.then(sl_types::utils::f32_parser())
.then_ignore(just(','))
.then(sl_types::utils::f32_parser())
.map(|((x, y), z)| Some(sl_types::map::RegionCoordinates::new(x, y, z)))),
)
.then_ignore(just(
", has been granted permission to: Take Linden dollars (L$) from you.",
))
.map(
|((((object_name, _), (owner_name, _)), object_region), object_location)| {
SystemMessage::ObjectGrantedPermissionToTakeMoney {
object_name,
owner_name,
object_region,
object_location,
}
},
)
.labelled("object granted permission to take money")
}
#[must_use]
pub fn group_membership_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("You have been added to the group.")
.to(SystemMessage::AddedToGroup)
.or(just("You have left the group '")
.ignore_then(take_until!(just("'.")))
.map(|(group_name, _)| SystemMessage::LeftGroup { group_name }))
.labelled("you have been added to the group")
}
#[must_use]
pub fn unable_to_invite_user_due_to_missing_group_membership_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Unable to invite user because you are not in that group.")
.to(SystemMessage::UnableToInviteUserDueToMissingGroupMembership)
.labelled("unable to invite user because you are not in that group")
}
#[must_use]
pub fn unable_to_invite_user_due_to_differing_limited_estate_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Unable to invite users because at least one user is in a different")
.ignore_then(newline())
.ignore_then(just(" limited estate than the group.").then_ignore(whitespace().then(end())))
.to(SystemMessage::UnableToInviteUserToGroupDueToDifferingLimitedEstate)
.labelled("unable to invite users because of differing limited estate")
}
#[must_use]
pub fn unable_to_load_notecard_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Unable to load the notecard.")
.then_ignore(newline())
.then_ignore(whitespace())
.then(just("Please try again."))
.to(SystemMessage::UnableToLoadNotecard)
.labelled("unable to load notecard")
}
#[must_use]
pub fn unable_to_load_gesture_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Unable to load gesture ")
.ignore_then(take_until!(just('.').then(end())))
.map(|(gesture_name, _)| SystemMessage::UnableToLoadGesture { gesture_name })
.labelled("unable to load gesture")
}
#[must_use]
pub fn teleport_completed_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Teleport completed from http://maps.secondlife.com/secondlife/")
.ignore_then(sl_types::map::url_unconstrained_location_parser())
.try_map(|origin, _span: chumsky::span::SimpleSpan| {
Ok(SystemMessage::TeleportCompleted { origin })
})
.labelled("teleport completed")
}
#[must_use]
pub fn now_playing_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Now playing: ")
.ignore_then(any().repeated().collect::<String>())
.try_map(|song_name, _span: chumsky::span::SimpleSpan| {
Ok(SystemMessage::NowPlaying { song_name })
})
.labelled("now playing")
}
#[must_use]
pub fn region_restart_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("The region you are in now is about to restart. If you stay in this region you will be logged out.")
.try_map(|_, _span: chumsky::span::SimpleSpan| {
Ok(SystemMessage::RegionRestart)
})
.labelled("region about to restart")
}
#[must_use]
pub fn object_gave_object_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
take_until!(just(" owned by "))
.then(sl_types::key::app_agent_or_group_uri_as_owner_key_parser())
.then_ignore(
whitespace()
.or_not()
.ignore_then(just("gave you ").then(just("<nolink>'").or_not())),
)
.then(take_until!(
just("</nolink>'")
.or_not()
.then(whitespace())
.then(just("( http://slurl.com/secondlife/"))
))
.then(sl_types::map::url_unconstrained_location_parser())
.then_ignore(just(" ).").ignore_then(end()))
.map(
|(
(((giving_object_name, _), giving_object_owner), (given_object_name, _)),
giving_object_location,
)| {
SystemMessage::ObjectGaveObject {
giving_object_name,
giving_object_owner,
given_object_name,
giving_object_location,
}
},
)
.labelled("object gave you object")
}
#[must_use]
pub fn object_gave_folder_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("An object named [")
.ignore_then(sl_types::viewer_uri::viewer_app_objectim_uri_parser())
.then_ignore(whitespace())
.then(take_until!(just("] gave you this folder: '")))
.then(take_until!(just('\'').ignore_then(end())))
.try_map(
|((app_objectim_uri, (giving_object_link_label, _)), (folder_name, ())), span| {
match app_objectim_uri {
sl_types::viewer_uri::ViewerUri::ObjectInstantMessage {
object_key,
object_name,
owner,
location,
} => Ok(SystemMessage::ObjectGaveFolder {
giving_object_key: object_key,
giving_object_name: object_name,
giving_object_owner: owner,
giving_object_location: location,
giving_object_link_label,
folder_name,
}),
_ => Err(chumsky::error::Rich::custom(
span,
"Unexpected type of viewer URI in object gave folder message parser",
)),
}
},
)
.labelled("object gave you folder")
}
#[must_use]
pub fn avatar_gave_object_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("A group member named ")
.or_not()
.then(take_until!(just(" gave you ")))
.then(take_until!(just(".").ignore_then(end())))
.try_map(
|((group_member, (giving_avatar_name, _)), (given_object_name, ())),
_span: chumsky::span::SimpleSpan| {
Ok(SystemMessage::AvatarGaveObject {
is_group_member: group_member.is_some(),
giving_avatar_name,
given_object_name,
})
},
)
.labelled("group member gave you object")
}
#[must_use]
pub fn declined_given_object_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("You decline '")
.ignore_then(take_until!(
just("' ( http://slurl.com/secondlife/").ignored()
))
.then(sl_types::map::url_unconstrained_location_parser())
.then_ignore(just(" ) from "))
.then(
any()
.repeated()
.collect::<String>()
.map(|s| s.strip_suffix(".").map(|s| s.to_string()).unwrap_or(s)),
)
.map(|(((object_name, ()), giver_location), giver_name)| {
SystemMessage::DeclinedGivenObject {
object_name,
giver_location,
giver_name,
}
})
.labelled("you decline given object")
}
#[must_use]
pub fn select_residents_to_share_with_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Select residents to share with.")
.to(SystemMessage::SelectResidentsToShareWith)
.labelled("select residents to share with")
}
#[must_use]
pub fn items_successfully_shared_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Items successfully shared.")
.to(SystemMessage::ItemsSuccessfullyShared)
.labelled("items successfully shared")
}
#[must_use]
pub fn modified_search_query_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Your search query was modified and the words that were too short were removed.")
.ignore_then(whitespace())
.ignore_then(just("Searched for:"))
.ignore_then(whitespace())
.ignore_then(any().repeated().collect::<String>())
.try_map(|query, _span: chumsky::span::SimpleSpan| {
Ok(SystemMessage::ModifiedSearchQuery { query })
})
.labelled("your search query was modified")
}
#[must_use]
pub fn simulator_version_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("The region you have entered is running a different simulator version.")
.ignore_then(whitespace())
.ignore_then(just("Current simulator:"))
.ignore_then(whitespace())
.ignore_then(take_until!(just("\n")))
.then_ignore(whitespace())
.then_ignore(just("Previous simulator:"))
.then_ignore(whitespace())
.then(any().repeated().collect::<String>())
.try_map(
|((current_region_simulator_version, _), previous_region_simulator_version),
_span: chumsky::span::SimpleSpan| {
Ok(SystemMessage::SimulatorVersion {
previous_region_simulator_version,
current_region_simulator_version,
})
},
)
.labelled("region running different simulator version")
}
#[must_use]
pub fn renamed_avatar_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
take_until!(just(" is now known as"))
.then_ignore(whitespace())
.then(take_until!(just(".").ignore_then(end())))
.try_map(
|((old_name, _), (new_name, ())), _span: chumsky::span::SimpleSpan| {
Ok(SystemMessage::RenamedAvatar { old_name, new_name })
},
)
.labelled("avatar is now known as")
}
#[must_use]
pub fn doubleclick_teleport_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("DoubleClick Teleport enabled.")
.to(SystemMessage::DoubleClickTeleport { enabled: true })
.or(just("DoubleClick Teleport disabled.")
.to(SystemMessage::DoubleClickTeleport { enabled: false }))
.labelled("double click teleport enabled/disabled")
}
#[must_use]
pub fn always_run_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Always Run enabled.")
.to(SystemMessage::AlwaysRun { enabled: true })
.or(just("Always Run disabled.").to(SystemMessage::AlwaysRun { enabled: false }))
.labelled("always run enabled/disabled")
}
#[must_use]
pub fn added_as_estate_manager_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("You have been added as an estate manager.")
.to(SystemMessage::AddedAsEstateManager)
.labelled("you have been added as estate manager")
}
#[must_use]
pub fn bridge_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
choice([
just("Creating the bridge. This might take a moment, please wait.").to(SystemMessage::CreatingBridge).boxed(),
just("Creating the bridge. This might take a few moments, please wait").to(SystemMessage::CreatingBridge).boxed(),
just("Bridge created.").to(SystemMessage::BridgeCreated).boxed(),
just("Bridge creation in process, cannot start another. Please wait a few minutes before trying again.").to(SystemMessage::BridgeCreationInProgress).boxed(),
just("Bridge object not found. Can't proceed with creation, exiting.").to(SystemMessage::BridgeObjectNotFoundCantProceedWithCreation).boxed(),
just("Bridge failed to attach. This is not the current bridge version. Please use the Firestorm 'Avatar/Avatar Health/Recreate Bridge' menu option to recreate the bridge.").to(SystemMessage::BridgeFailedToAttach).boxed(),
just("Bridge failed to attach. Something else was using the bridge attachment point. Please try to recreate the bridge.").to(SystemMessage::BridgeFailedToAttachDueToBridgeAttachmentPointInUse).boxed(),
just("Bridge failed to attach. Something else was using the bridge attachment point. Please use the Firestorm 'Avatar/Avatar Health/Recreate Bridge' menu option to recreate the bridge.").to(SystemMessage::BridgeFailedToAttachDueToBridgeAttachmentPointInUse).boxed(),
just("Bridge not created. The bridge couldn't be found in inventory. Please use the Firestorm 'Avatar/Avatar Health/Recreate Bridge' menu option to recreate the bridge.").to(SystemMessage::BridgeNotCreated).boxed(),
just("Bridge detached.").to(SystemMessage::BridgeDetached).labelled("bridge message").boxed(),
])
}
#[must_use]
pub fn failed_to_place_object_at_specified_location_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Failed to place object at specified location. Please try again.")
.to(SystemMessage::FailedToPlaceObjectAtSpecifiedLocation)
.labelled("failed to place object at specified location")
}
#[must_use]
pub fn region_script_count_change_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Total scripts in region ")
.ignore_then(just("jumped from ").or(just("dropped from ")))
.ignore_then(
digits(10).collect::<String>()
.then_ignore(just(" to "))
.then(digits(10).collect::<String>())
.then_ignore(just(" ("))
.then(one_of("+-"))
.then(digits(10).collect::<String>())
.then_ignore(just(")."))
.try_map(
|(((previous_script_count, current_script_count), sign), diff): (
((String, String), char),
String,
),
span: chumsky::span::SimpleSpan| {
let previous_span = span;
let previous_script_count =
previous_script_count.parse().map_err(|err| {
chumsky::error::Rich::custom(previous_span, format!(
"Could not parse previous script count ({previous_script_count}) as u32: {err:?}"
))
})?;
let current_span = span;
let current_script_count = current_script_count.parse().map_err(|err| {
chumsky::error::Rich::custom(current_span, format!(
"Could not parse current script count ({current_script_count}) as u32: {err:?}"
))
})?;
let diff_span = span;
let diff: i32 = diff.parse().map_err(|err| {
chumsky::error::Rich::custom(diff_span, format!(
"Could not parse changed script count ({diff}) as i32: {err:?}"
))
})?;
let change = match sign {
'+' => diff,
'-' => {
#[expect(clippy::arithmetic_side_effects, reason = "this just switches the sign of a positive value to a negative one but only the opposite switch is panic-prone at i32::MIN")]
-diff
}
c => {
return Err(chumsky::error::Rich::custom(span, format!("Unexpected sign character for script change: {c}")))
}
};
Ok(SystemMessage::ScriptCountChanged {
previous_script_count,
current_script_count,
change,
})
},
),
)
.labelled("region script count changed")
}
#[must_use]
pub fn chat_message_still_being_processed_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("The message sent to ")
.ignore_then(
choice([
just("Multi-person chat is still being processed.").to(SystemMessage::MultiPersonChatMessageStillBeingProcessed).boxed(),
just("(IM Session Doesn't Exist) is still being processed.").to(SystemMessage::ChatMessageToNoLongerExistingImSessionStillBeingProcessed).boxed(),
just("Conference with ").ignore_then(take_until!(just(" is still being processed.")).map(|(avatar_name, _)| SystemMessage::ConferenceChatMessageStillBeingProcessed { avatar_name })).boxed(),
take_until!(just(" is still being processed.").ignored())
.map(|(group_name, ())| {
SystemMessage::GroupChatMessageStillBeingProcessed {
group_name,
}
}).boxed(),
])
)
.then_ignore(newline())
.then_ignore(whitespace())
.then_ignore(just("If the message does not appear in the next few minutes, it may have been dropped by the server."))
.labelled("chat message still being processed")
}
#[must_use]
pub fn avatar_declined_voice_call_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
take_until!(just(
"has declined your call. You will now be reconnected to Nearby Voice Chat.",
))
.map(|(avatar_name, _)| SystemMessage::AvatarDeclinedVoice { avatar_name })
.labelled("avatar declined voice call")
}
#[must_use]
pub fn avatar_unavailable_for_voice_call_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
take_until!(just(
"is not available to take your call. You will now be reconnected to Nearby Voice Chat.",
))
.map(|(avatar_name, _)| SystemMessage::AvatarUnavailableForVoice { avatar_name })
.labelled("avatar unavailable for voice call")
}
#[must_use]
pub fn audio_from_domain_will_always_be_played_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Audio from the domain ")
.ignore_then(
take_until!(just(" will always be played."))
.map(|(domain, _)| SystemMessage::AudioFromDomainWillAlwaysBePlayed { domain }),
)
.labelled("audio from domain will always be played")
}
#[must_use]
pub fn object_not_for_sale_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("This object is not for sale.")
.to(SystemMessage::ObjectNotForSale)
.labelled("object not for sale")
}
#[must_use]
pub fn can_not_create_requested_inventory_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Cannot create requested inventory.")
.to(SystemMessage::CanNotCreateRequestedInventory)
.labelled("cannot created requested inventory")
}
#[must_use]
pub fn link_failed_due_to_piece_distance_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Link failed -- Unable to link any pieces - pieces are too far apart.")
.to(SystemMessage::LinkFailedDueToPieceDistance {
link_failed_pieces: None,
total_selected_pieces: None,
})
.or(just("Link failed -- Unable to link ").ignore_then(
usize_parser()
.then_ignore(just(" of the "))
.then(usize_parser())
.then_ignore(just(" selected pieces - pieces are too far apart."))
.map(|(link_failed_pieces, total_selected_pieces)| {
SystemMessage::LinkFailedDueToPieceDistance {
link_failed_pieces: Some(link_failed_pieces),
total_selected_pieces: Some(total_selected_pieces),
}
}),
))
.labelled("link failed due to distance")
}
#[must_use]
pub fn rezzing_object_failed_due_to_full_parcel_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Can't rez object '").ignore_then(
take_until!(just("' at ").ignored())
.then(sl_types::map::region_coordinates_parser())
.then_ignore(just(" on parcel '"))
.then(
take_until!(just("' in region ").ignored())
)
.then(
take_until!(just(" because the parcel is too full.").ignored())
.try_map(|(region_name, ()), span| {
sl_types::map::RegionName::try_new(®ion_name).map_err(|err| {
chumsky::error::Rich::custom(span, format!(
"Could not turn parsed region name ({region_name}) into RegionName: {err:?}"
))
})
}),
)
.map(
|((((object_name, ()), attempted_rez_location), (parcel_name, ())), region_name)| {
SystemMessage::RezObjectFailedDueToFullParcel {
object_name,
attempted_rez_location,
parcel_name,
region_name,
}
},
),
).labelled("rezzing failed due to full parcel")
}
#[must_use]
pub fn create_object_failed_due_to_full_region_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Unable to create requested object. The region is full.")
.to(SystemMessage::CreateObjectFailedDueToFullRegion)
.labelled("create object failed due to full region")
}
#[must_use]
pub fn your_object_has_been_returned_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Your object '")
.ignore_then(take_until!(just(
"' has been returned to your inventory Lost and Found folder from parcel '",
)))
.then(take_until!(just("' at ")))
.then(sl_types::map::region_name_parser())
.then_ignore(whitespace())
.then(sl_types::utils::i16_parser())
.then_ignore(just(", "))
.then(sl_types::utils::i16_parser())
.then(
just(" due to parcel auto return.")
.to(true)
.or(just('.').to(false)),
)
.map(
|((((((object_name, _), (parcel_name, _)), region_name), x), y), auto_return)| {
SystemMessage::YourObjectHasBeenReturned {
object_name,
parcel_name,
location: sl_types::map::UnconstrainedLocation::new(region_name, x, y, 0),
auto_return,
}
},
)
.labelled("your object has been returned")
}
#[must_use]
pub fn permission_to_create_object_denied_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("You cannot create objects here. The owner of this land does not allow it. Use the land tool to see land ownership.").to(SystemMessage::PermissionToCreateObjectDenied).labelled("permission to create object denied")
}
#[must_use]
pub fn permission_to_rez_object_denied_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Can't rez object '")
.ignore_then(
take_until!(just("' at ").ignored())
.then(sl_types::map::region_coordinates_parser())
.then_ignore(just(" on parcel '"))
.then(take_until!(just("' in region ").ignored()))
.then(take_until!(just(" because the owner of this land does not allow it. Use the land tool to see land ownership.").ignored()).try_map(|(region_name, ()), span| {
sl_types::map::RegionName::try_new(®ion_name).map_err(|err| chumsky::error::Rich::custom(span, format!("Could not turn parsed region name ({region_name}) into RegionName: {err:?}")))
}))
.map(|((((object_name, ()), attempted_rez_location), (parcel_name, ())), region_name)| {
SystemMessage::PermissionToRezObjectDenied {
object_name,
attempted_rez_location,
parcel_name,
region_name,
}
})
)
.labelled("permission to rez object denied")
}
#[must_use]
pub fn permission_to_reposition_denied_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Can't reposition -- permission denied")
.to(SystemMessage::PermissionToRepositionDenied)
.labelled("permission to reposition denied")
}
#[must_use]
pub fn permission_to_rotate_denied_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Can't rotate -- permission denied")
.to(SystemMessage::PermissionToRotateDenied)
.labelled("permission to rotate denied")
}
#[must_use]
pub fn permission_to_rescale_denied_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Can't rescale -- permission denied")
.to(SystemMessage::PermissionToRescaleDenied)
.labelled("permission to rescale denied")
}
#[must_use]
pub fn permission_to_unlink_denied_due_to_missing_parcel_build_permissions_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Failed to unlink because you do not have permissions to build on all parcels")
.to(SystemMessage::PermissionToUnlinkDeniedDueToMissingParcelBuildPermissions)
.labelled("permission to unlink denied due to parcel build permissions")
}
#[must_use]
pub fn permission_to_view_script_denied_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Insufficient permissions to view the script.")
.to(SystemMessage::PermissionToViewScriptDenied)
.labelled("permission to view script denied")
}
#[must_use]
pub fn permission_to_view_notecard_denied_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("You do not have permission to view this notecard.")
.to(SystemMessage::PermissionToViewNotecardDenied)
.labelled("permission to view notecard denied")
}
#[must_use]
pub fn permission_to_change_shape_denied_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("You are not allowed to change this shape.")
.to(SystemMessage::PermissionToChangeShapeDenied)
.labelled("permission to change shape denied")
}
#[must_use]
pub fn permission_to_enter_parcel_denied_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Cannot enter parcel, you are not on the access list.")
.to(SystemMessage::PermissionToEnterParcelDenied)
.labelled("permission to enter parcel denied")
}
#[must_use]
pub fn permission_to_enter_parcel_denied_due_to_ban_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Cannot enter parcel, you have been banned.")
.to(SystemMessage::PermissionToEnterParcelDeniedDueToBan)
.labelled("permission to enter parcel denied due to ban")
}
#[must_use]
pub fn avatar_ejected_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Avatar ejected.")
.to(SystemMessage::EjectedAvatar)
.labelled("avatar ejected")
}
#[must_use]
pub fn ejected_from_parcel_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("You have been ejected from this land.")
.to(SystemMessage::EjectedFromParcel)
.or(
just("You are no longer allowed here and have been ejected.")
.to(SystemMessage::EjectedFromParcelBecauseNoLongerAllowed),
)
.labelled("ejected from parcel")
}
#[must_use]
pub fn banned_from_parcel_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("You have been banned ")
.ignore_then(
just("indefinitely")
.to(SystemMessage::BannedFromParcelIndefinitely)
.or(just("for ")
.ignore_then(i64_parser().then_ignore(just(" minutes")))
.map(|d| SystemMessage::BannedFromParcelTemporarily {
ban_duration: time::Duration::minutes(d),
})),
)
.labelled("you have been banned")
}
#[must_use]
pub fn only_group_members_can_visit_this_area_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Only members of a certain group can visit this area.")
.to(SystemMessage::OnlyGroupMembersCanVisitThisArea)
.labelled("only group members allowed here")
}
#[must_use]
pub fn unable_to_teleport_due_to_rlv_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Unable to initiate teleport due to RLV restrictions")
.to(SystemMessage::UnableToTeleportDueToRlv)
.labelled("unable to teleport due to RLV")
}
#[must_use]
pub fn unable_to_open_texture_due_to_rlv_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Unable to open texture due to RLV restrictions")
.to(SystemMessage::UnableToOpenTextureDueToRlv)
.labelled("unable to open texture due to RLV")
}
#[must_use]
pub fn unsupported_slurl_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("The SLurl you clicked on is not supported.")
.to(SystemMessage::UnsupportedSlurl)
.labelled("unsupported slurl")
}
#[must_use]
pub fn blocked_untrusted_browser_slurl_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("A SLurl was received from an untrusted browser and has been blocked for your security.")
.to(SystemMessage::BlockedUntrustedBrowserSlurl)
.labelled("blocked untrusted browser")
}
#[must_use]
pub fn grid_status_error_invalid_message_format_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("SL Grid Status error: Invalid message format. Try again later.")
.to(SystemMessage::GridStatusErrorInvalidMessageFormat)
.labelled("sl grid status invalid message format")
}
#[must_use]
pub fn script_info_object_invalid_or_out_of_range_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Script info: Object to check is invalid or out of range.")
.to(SystemMessage::ScriptInfoObjectInvalidOrOutOfRange)
.labelled("script info object invalid or out of range")
}
#[must_use]
pub fn script_info_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Script info: '")
.ignore_then(
take_until!(just("': [").ignored())
.then(usize_parser())
.then_ignore(just('/'))
.then(usize_parser())
.then_ignore(just("] running scripts, "))
.then(u64_parser().map(bytesize::ByteSize::kb))
.then_ignore(just(" KB allowed memory size limit, "))
.then(unsigned_f32_parser().map(|ms| time::Duration::seconds_f32(ms / 1000f32)))
.then_ignore(just(" ms of CPU time consumed."))
.map(
|(
((((name, ()), running_scripts), total_scripts), allowed_memory_size_limit),
cpu_time_consumed,
)| {
SystemMessage::ScriptInfo {
name,
running_scripts,
total_scripts,
allowed_memory_size_limit,
cpu_time_consumed,
}
},
),
)
.labelled("script info")
}
#[must_use]
pub fn extended_script_info_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Object ID: ")
.ignore_then(sl_types::key::object_key_parser())
.then_ignore(newline())
.then_ignore(just(" Description:"))
.then_ignore(just(" ").or_not())
.then(just("(No Description)").then_ignore(newline()).to(None).or(
take_until!(newline().ignored()).map(|(vc, ())| Some(vc)),
))
.then_ignore(just(" Root prim: "))
.then(sl_types::key::object_key_parser())
.then_ignore(newline())
.then_ignore(just(" Prim count: "))
.then(sl_types::utils::usize_parser())
.then_ignore(newline())
.then_ignore(just(" Land impact: "))
.then(sl_types::utils::usize_parser())
.then_ignore(newline())
.then_ignore(just(" Inventory items: "))
.then(sl_types::utils::usize_parser())
.then_ignore(newline())
.then_ignore(just(" Velocity: "))
.then(sl_types::lsl::vector_parser())
.then_ignore(newline())
.then_ignore(just(" Position: "))
.then(sl_types::lsl::vector_parser().map(sl_types::map::RegionCoordinates::from))
.then_ignore(whitespace())
.then(sl_types::map::distance_parser().delimited_by(just('('), just(')')))
.then_ignore(newline())
.then_ignore(just(" Rotation: "))
.then(sl_types::lsl::rotation_parser())
.then_ignore(whitespace())
.then(sl_types::lsl::vector_parser().delimited_by(just('('), just(')')))
.then_ignore(newline())
.then_ignore(just(" Angular velocity: "))
.then(sl_types::lsl::vector_parser())
.then_ignore(whitespace())
.then_ignore(just("(radians per second)"))
.then_ignore(newline())
.then_ignore(just(" Creator: "))
.then(sl_types::key::app_agent_uri_as_agent_key_parser())
.then_ignore(newline())
.then_ignore(just(" Owner: "))
.then(just("Group Owned").to(None).or(sl_types::key::app_agent_or_group_uri_as_owner_key_parser().map(Some)))
.then_ignore(newline())
.then_ignore(just(" Previous owner: "))
.then(
sl_types::key::app_agent_or_group_uri_as_owner_key_parser()
.map(Some)
.or(just("---").to(None)),
)
.then_ignore(newline())
.then_ignore(just(" Rezzed by: "))
.then(sl_types::key::agent_key_parser())
.then_ignore(newline())
.then_ignore(just(" Group: "))
.then(
sl_types::key::app_group_uri_as_group_key_parser()
.map(Some)
.or(just("---").to(None)),
)
.then_ignore(newline())
.then_ignore(just(" Creation time:"))
.then_ignore(just(' ').or_not())
.then(crate::utils::offset_datetime_parser().or_not())
.then_ignore(newline())
.then_ignore(just(" Rez time:"))
.then_ignore(just(' ').or_not())
.then(crate::utils::offset_datetime_parser().or_not())
.then_ignore(newline())
.then_ignore(just(" Pathfinding type: "))
.then(sl_types::pathfinding::int_as_pathfinding_type_parser())
.then_ignore(newline())
.then_ignore(just(" Attachment point: "))
.then(
sl_types::attachment::attachment_point_parser()
.map(Some)
.or(just("---").to(None)),
)
.then_ignore(newline())
.then_ignore(just(" Temporarily attached: "))
.then(just("Yes").to(true).or(just("No").to(false)))
.then_ignore(newline())
.then_ignore(just(" Your current position: "))
.then(sl_types::lsl::vector_parser().map(sl_types::map::RegionCoordinates::from))
.map(
|((((((((((((((((((((((
object_key,
description),
root_prim),
prim_count),
land_impact),
inventory_items),
velocity),
position),
position_distance),
rotation),
rotation_vector_degrees),
angular_velocity),
creator),
owner),
previous_owner),
rezzed_by),
group),
creation_time),
rez_time),
pathfinding_type),
attachment_point),
temporarily_attached),
inspecting_avatar_position,
)| {
SystemMessage::ExtendedScriptInfo {
object_key,
description,
root_prim,
prim_count,
land_impact,
inventory_items,
velocity,
position,
position_distance,
rotation,
rotation_vector_degrees,
angular_velocity,
creator,
owner,
previous_owner,
rezzed_by,
group,
creation_time,
rez_time,
pathfinding_type,
attachment_point,
temporarily_attached,
inspecting_avatar_position,
}
},
).labelled("extended script info")
}
#[must_use]
pub fn dice_roll_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
choice([
just("You must provide positive values for dice (max 100) and faces (max 1000).")
.to(SystemMessage::DiceRollCommandUsageInstructions)
.boxed(),
just('#')
.ignore_then(usize_parser())
.then_ignore(whitespace())
.then_ignore(just("1d"))
.then(usize_parser())
.then_ignore(just(":"))
.then_ignore(whitespace())
.then(usize_parser())
.then_ignore(just('.'))
.map(
|((roll_number, dice_faces), roll_result)| SystemMessage::DiceRollResult {
roll_number,
dice_faces,
roll_result,
},
)
.boxed(),
just("Total result for ")
.ignore_then(usize_parser())
.then_ignore(just('d'))
.then(usize_parser())
.then_ignore(just(':'))
.then_ignore(whitespace())
.then(usize_parser())
.then_ignore(just('.'))
.map(
|((roll_count, dice_faces), result_sum)| SystemMessage::DiceRollResultSum {
roll_count,
dice_faces,
result_sum,
},
)
.boxed(),
])
.labelled("dice roll")
}
#[must_use]
pub fn texture_info_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
choice([
just("Texture info for: ")
.ignore_then(
take_until!(newline().or(end()))
.map(|(object_name, ())| SystemMessage::TextureInfoForObject { object_name }),
)
.boxed(),
sl_types::utils::u16_parser()
.then_ignore(just('x'))
.then(sl_types::utils::u16_parser())
.then_ignore(whitespace())
.then(just("opaque").or(just("alpha")))
.then_ignore(just(" on face "))
.then(usize_parser())
.map(
|(((texture_width, texture_height), texture_type), face_number)| {
SystemMessage::TextureInfoForFace {
face_number,
texture_width,
texture_height,
texture_type: texture_type.to_owned(),
}
},
)
.boxed(),
])
.labelled("texture info")
}
#[must_use]
pub fn firestorm_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("Firestorm ")
.ignore_then(
take_until!(just("!").ignored())
.map(|(message_type, ())| message_type)
.then(any().repeated().collect::<String>())
.map(|(message_type, message)| SystemMessage::FirestormMessage {
message_type,
message,
}),
)
.labelled("firestorm message")
}
#[must_use]
pub fn grid_status_event_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
just("[ ")
.ignore_then(
take_until!(just(" ] "))
.map(|(vc, _)| vc)
.then(
just("THIS IS A SCHEDULED EVENT ")
.or_not()
.map(|s| s.is_some()),
)
.then(
take_until!(just(" [ https://status.secondlifegrid.net/incidents/").ignored())
.map(|(vc, ())| vc),
)
.then(take_until!(just(' ').ignored()).map(|(vc, ())| vc))
.then_ignore(just("]"))
.map(
|(((title, scheduled), body), url_fragment)| SystemMessage::GridStatusEvent {
title,
scheduled,
body,
incident_url: format!(
"https://status.secondlifegird.net/incidents/{url_fragment}"
),
},
),
)
.labelled("grid status event")
}
#[must_use]
pub fn system_message_parser<'src>()
-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
{
choice([
snapshot_saved_message_parser().boxed(),
attachment_saved_message_parser().boxed(),
draw_distance_set_message_parser().boxed(),
home_position_set_message_parser().boxed(),
land_divided_message_parser().boxed(),
failed_to_join_land_due_to_region_boundary_message_parser().boxed(),
offered_calling_card_message_parser().boxed(),
you_paid_for_object_message_parser().boxed(),
you_paid_to_create_a_group_message_parser().boxed(),
you_paid_to_join_group_message_parser().boxed(),
you_paid_for_land_message_parser().boxed(),
failed_to_pay_message_parser().boxed(),
object_granted_permission_to_take_money_parser().boxed(),
sent_payment_message_parser().boxed(),
received_payment_message_parser().boxed(),
group_membership_message_parser().boxed(),
unable_to_invite_user_due_to_missing_group_membership_message_parser().boxed(),
unable_to_invite_user_due_to_differing_limited_estate_message_parser().boxed(),
unable_to_load_notecard_message_parser().boxed(),
unable_to_load_gesture_message_parser().boxed(),
teleport_completed_message_parser().boxed(),
now_playing_message_parser().boxed(),
region_restart_message_parser().boxed(),
object_gave_object_message_parser().boxed(),
object_gave_folder_message_parser().boxed(),
declined_given_object_message_parser().boxed(),
select_residents_to_share_with_message_parser().boxed(),
items_successfully_shared_message_parser().boxed(),
modified_search_query_message_parser().boxed(),
avatar_gave_object_message_parser().boxed(),
simulator_version_message_parser().boxed(),
renamed_avatar_message_parser().boxed(),
doubleclick_teleport_message_parser().boxed(),
always_run_message_parser().boxed(),
added_as_estate_manager_message_parser().boxed(),
bridge_message_parser().boxed(),
failed_to_place_object_at_specified_location_message_parser().boxed(),
region_script_count_change_message_parser().boxed(),
chat_message_still_being_processed_message_parser().boxed(),
avatar_declined_voice_call_message_parser().boxed(),
avatar_unavailable_for_voice_call_message_parser().boxed(),
audio_from_domain_will_always_be_played_message_parser().boxed(),
object_not_for_sale_message_parser().boxed(),
can_not_create_requested_inventory_message_parser().boxed(),
link_failed_due_to_piece_distance_message_parser().boxed(),
rezzing_object_failed_due_to_full_parcel_message_parser().boxed(),
create_object_failed_due_to_full_region_message_parser().boxed(),
your_object_has_been_returned_message_parser().boxed(),
permission_to_create_object_denied_message_parser().boxed(),
permission_to_rez_object_denied_message_parser().boxed(),
permission_to_reposition_denied_message_parser().boxed(),
permission_to_rotate_denied_message_parser().boxed(),
permission_to_rescale_denied_message_parser().boxed(),
permission_to_unlink_denied_due_to_missing_parcel_build_permissions_message_parser()
.boxed(),
permission_to_view_script_denied_message_parser().boxed(),
permission_to_view_notecard_denied_message_parser().boxed(),
permission_to_change_shape_denied_message_parser().boxed(),
permission_to_enter_parcel_denied_message_parser().boxed(),
permission_to_enter_parcel_denied_due_to_ban_message_parser().boxed(),
avatar_ejected_message_parser().boxed(),
ejected_from_parcel_message_parser().boxed(),
banned_from_parcel_message_parser().boxed(),
only_group_members_can_visit_this_area_message_parser().boxed(),
unable_to_teleport_due_to_rlv_message_parser().boxed(),
unable_to_open_texture_due_to_rlv_message_parser().boxed(),
unsupported_slurl_message_parser().boxed(),
blocked_untrusted_browser_slurl_message_parser().boxed(),
grid_status_error_invalid_message_format_message_parser().boxed(),
script_info_object_invalid_or_out_of_range_message_parser().boxed(),
script_info_message_parser().boxed(),
extended_script_info_message_parser().boxed(),
dice_roll_message_parser().boxed(),
texture_info_message_parser().boxed(),
firestorm_message_parser().boxed(),
grid_status_event_message_parser().boxed(),
take_until!(just("https").or(just("http")).or(just("Http")))
.then(take_until!(newline().or(end())).map(|(vc, ())| vc))
.map(
|((message, scheme), rest_of_url)| SystemMessage::SystemMessageWithLink {
message,
link: format!("{scheme}://{rest_of_url}"),
},
)
.boxed(),
take_until!(just("www."))
.then(take_until!(newline().or(end())).map(|(vc, ())| vc))
.map(
|((message, subdomain), rest_of_url)| SystemMessage::SystemMessageWithLink {
message,
link: format!("{subdomain}{rest_of_url}"),
},
)
.boxed(),
any()
.repeated()
.collect::<String>()
.map(|message| {
if message.contains("Firestorm") && (message.contains("holiday") || message.contains("Happy New Year")) {
SystemMessage::FirestormHolidayWishes { message }
} else if message.contains("phishing") {
SystemMessage::PhishingWarning { message }
} else if message == "This is a test version of Firestorm. If this were an actual release version, a real message of the day would be here. This is only a test." {
SystemMessage::TestMessageOfTheDay
} else if (message.ends_with("...") && (message.starts_with("Loading") || message.starts_with("Initializing") || message.starts_with("Downloading") || message.starts_with("Verifying") || message.starts_with("Loading") || message.starts_with("Connecting") || message.starts_with("Decoding") || message.starts_with("Waiting"))) || message == "Welcome to Advertisement-Free Firestorm" || message.starts_with("Logging in") {
SystemMessage::EarlyFirestormStartupMessage { message }
} else if message.contains("wiki.phoenixviewer.com/firestorm_classes") {
SystemMessage::FirestormMessage {
message_type: "Classes".to_string(),
message,
}
} else if message.contains("BETA TESTERS") || message.contains("Beta Testers") {
SystemMessage::FirestormMessage {
message_type: "Beta Test".to_string(),
message,
}
} else {
SystemMessage::OtherSystemMessage { message }
}
})
.boxed(),
])
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_teleport_completed() -> Result<(), Box<dyn std::error::Error>> {
assert_eq!(
Ok(SystemMessage::TeleportCompleted {
origin: sl_types::map::UnconstrainedLocation {
region_name: sl_types::map::RegionName::try_new("Fudo")?,
x: 30,
y: 169,
z: 912
}
}),
teleport_completed_message_parser()
.parse(
"Teleport completed from http://maps.secondlife.com/secondlife/Fudo/30/169/912"
)
.into_result()
);
Ok(())
}
#[test]
fn test_teleport_completed_extra_short() -> Result<(), Box<dyn std::error::Error>> {
assert_eq!(
Ok(SystemMessage::TeleportCompleted {
origin: sl_types::map::UnconstrainedLocation {
region_name: sl_types::map::RegionName::try_new("AA")?,
x: 78,
y: 83,
z: 26
}
}),
teleport_completed_message_parser()
.parse("Teleport completed from http://maps.secondlife.com/secondlife/AA/78/83/26")
.into_result()
);
Ok(())
}
#[test]
fn test_cant_rez_object() -> Result<(), Box<dyn std::error::Error>> {
assert_eq!(
Ok(SystemMessage::PermissionToRezObjectDenied {
object_name: "Foo2".to_string(),
attempted_rez_location: sl_types::map::RegionCoordinates::new(63.0486, 45.2515, 1501.08),
parcel_name: "The Foo Bar".to_string(),
region_name: sl_types::map::RegionName::try_new("Fudo")?,
}),
permission_to_rez_object_denied_message_parser()
.parse("Can't rez object 'Foo2' at { 63.0486, 45.2515, 1501.08 } on parcel 'The Foo Bar' in region Fudo because the owner of this land does not allow it. Use the land tool to see land ownership.").into_result()
);
Ok(())
}
}