Browse Source

Merge branch 'enchance_notify_channels' into develop

pull/12/head
Anton Tarasenko 2 years ago
parent
commit
10b673ccec
  1. 12
      sources/BaseAPI/API/Commands/BuiltInCommands/ACommandNotify.uc
  2. 4
      sources/BaseAPI/API/Commands/Voting/Voting.uc
  3. 28
      sources/Players/EPlayer.uc
  4. 107
      sources/Players/PlayerNotificationQueue.uc

12
sources/BaseAPI/API/Commands/BuiltInCommands/ACommandNotify.uc

@ -34,6 +34,12 @@ protected function BuildData(CommandDataBuilder builder) {
builder.Option(P("title")); builder.Option(P("title"));
builder.Describe(P("Specify the optional title of the notification.")); builder.Describe(P("Specify the optional title of the notification."));
builder.ParamText(P("title")); builder.ParamText(P("title"));
builder.Option(P("channel"));
builder.Describe(P("Specify the optional channel. A channel is a grouping mechanism used to"
@ "control the display of related notifications. Only last message from the same channel is"
@ "stored in queue."));
builder.ParamText(P("channel_name"));
} }
protected function ExecutedFor(EPlayer target, CallData arguments, EPlayer instigator) { protected function ExecutedFor(EPlayer target, CallData arguments, EPlayer instigator) {
@ -45,7 +51,11 @@ protected function ExecutedFor(EPlayer target, CallData arguments, EPlayer insti
} }
title = _.text.FromFormatted(plainTitle); title = _.text.FromFormatted(plainTitle);
message = _.text.FromFormatted(plainMessage); message = _.text.FromFormatted(plainMessage);
target.Notify(title, message, arguments.parameters.GetFloat(P("duration"))); target.Notify(
title,
message,
arguments.parameters.GetFloat(P("duration")),
arguments.options.GetTextBy(P("/channel/channel_name")));
_.memory.Free4(title, message, plainTitle, plainMessage); _.memory.Free4(title, message, plainTitle, plainMessage);
} }

4
sources/BaseAPI/API/Commands/Voting/Voting.uc

@ -141,7 +141,7 @@ public final function Start(BaseText votingConfigName) {
voters = UpdateVoters(); voters = UpdateVoters();
howToVoteHint = MakeHowToVoteHint(); howToVoteHint = MakeHowToVoteHint();
for (i = 0; i < voters.length; i += 1) { for (i = 0; i < voters.length; i += 1) {
voters[i].Notify(F(votingStartedLine), howToVoteHint); voters[i].Notify(F(votingStartedLine), howToVoteHint,, P("voting"));
voters[i].BorrowConsole().WriteLine(F(votingStartedLine)); voters[i].BorrowConsole().WriteLine(F(votingStartedLine));
voters[i].BorrowConsole().WriteLine(howToVoteHint); voters[i].BorrowConsole().WriteLine(howToVoteHint);
} }
@ -420,7 +420,7 @@ private final function AnnounceOutcome(BaseText outcomeMessage) {
for (i = 0; i < currentPlayers.length; i += 1) { for (i = 0; i < currentPlayers.length; i += 1) {
currentPlayers[i].BorrowConsole().WriteLine(outcomeMessage); currentPlayers[i].BorrowConsole().WriteLine(outcomeMessage);
currentPlayers[i].BorrowConsole().WriteLine(summaryLine); currentPlayers[i].BorrowConsole().WriteLine(summaryLine);
currentPlayers[i].Notify(outcomeMessage, summaryLine); currentPlayers[i].Notify(outcomeMessage, summaryLine,, P("voting"));
} }
_.memory.FreeMany(currentPlayers); _.memory.FreeMany(currentPlayers);
_.memory.Free(summaryLine); _.memory.Free(summaryLine);

28
sources/Players/EPlayer.uc

@ -504,12 +504,28 @@ public final function /* borrow */ ConsoleWriter BorrowConsole()
return consoleInstance.ForPlayer(self); return consoleInstance.ForPlayer(self);
} }
/// Notifies player about something with a text message. /// Sends a text message to notify the player about a particular event or situation, displayed
/// as a notification.
/// ///
/// Header is allowed to be `none`, but it is recommended to set it to an actual value. /// While the header parameter is optional, it is recommended to provide a meaningful header
/// Duration is more of a suggestion and might be clamped to some reasonable value that depends on /// to give the player context about the notification. The duration parameter suggests the time
/// implementation/server settings. /// that the message should be displayed for, but the actual duration may be limited by the
public final function Notify(BaseText header, BaseText body, optional float duration) { /// implementation or server settings.
///
/// The [`channel`] parameter (case-sensitive) allows you to group related notifications together by
/// assigning them to a specific channel.
/// Each channel can only have one message in the queue at any given time.
/// For instance, when conducting a vote, the old message about the vote's start can be quickly
/// replaced with a newer message about the vote's end.
/// Notifications without a channel `none` can have any number of messages queued up.
///
/// Acedia uses "voting" channel for voting.
public final function Notify(
BaseText header,
BaseText body,
optional float duration,
optional BaseText channel
) {
local HashTable sessionData; local HashTable sessionData;
local PlayerNotificationQueue messageQueue; local PlayerNotificationQueue messageQueue;
@ -524,7 +540,7 @@ public final function Notify(BaseText header, BaseText body, optional float dura
sessionData.SetItem(P("MessageQueue"), messageQueue); sessionData.SetItem(P("MessageQueue"), messageQueue);
} }
messageQueue.SetupController(controller); messageQueue.SetupController(controller);
messageQueue.AddNotification(header, body, duration); messageQueue.AddNotification(header, body, duration, channel);
_.memory.Free2(sessionData, messageQueue); _.memory.Free2(sessionData, messageQueue);
} }

107
sources/Players/PlayerNotificationQueue.uc

@ -22,15 +22,32 @@
class PlayerNotificationQueue extends AcediaObject class PlayerNotificationQueue extends AcediaObject
config(AcediaSystem); config(AcediaSystem);
/// Manages queue of notifications that should be displayed for a certain player. //! Manages queue of notifications that should be displayed for a certain player.
//!
//! Pushes messages one-by-one, once its their time to be displayed and making sure that only up to
//! one message per channel is ever in queue.
//!
//! A channel is a way to group related notifications together.
//! Its purpose is to control the display of notifications in a more organized and efficient manner.
//! Each channel can only have one message in the queue at a time.
//! This ensures that only the most relevant or up-to-date message is displayed to the player.
/// Describes a single notification: title (optional, can be `none`) + message body and timeout /// Describes a single notification: title (optional, can be `none`) + message body, channel
/// and timeout.
struct Notification { struct Notification {
var Text title; var Text title;
var Text body; var Text body;
var float duration; var float duration;
var Text channel;
}; };
var private array<Notification> notificationQueue; var private array<Notification> notificationQueue;
/// We need this variable to keep track of which channel the currently displayed message belongs to,
/// so that we can ensure that only one message is displayed at a time for each channel.
/// This variable allows us to easily check if a new message belongs to the same channel as
/// the currently displayed message, and if so, replace the currently displayed message with
/// the new one.
var private Text currentChannel;
/// Reference to the `PlayerController` for the player that owns this queue /// Reference to the `PlayerController` for the player that owns this queue
var private NativeActorRef playerControllerRef; var private NativeActorRef playerControllerRef;
/// Timer until next notification can be displayed /// Timer until next notification can be displayed
@ -80,8 +97,29 @@ public final /*native*/ function SetupController(NativeActorRef newPlayerControl
} }
} }
/// Add new notification to the queue /// Sends a text message to notify the player about a particular event or situation, displayed
public final function AddNotification(BaseText title, BaseText body, float duration) { /// as a notification.
///
/// While the header parameter is optional, it is recommended to provide a meaningful header
/// to give the player context about the notification. The duration parameter suggests the time
/// that the message should be displayed for, but the actual duration may be limited by the
/// implementation or server settings.
///
/// The [`channel`] parameter (case-sensitive) allows you to group related notifications together by
/// assigning them to a specific channel.
/// Each channel can only have one message in the queue at any given time.
/// For instance, when conducting a vote, the old message about the vote's start can be quickly
/// replaced with a newer message about the vote's end.
/// Notifications without a channel `none` can have any number of messages queued up.
///
/// Acedia uses "voting" channel for voting.
public final function AddNotification(
BaseText title,
BaseText body,
float duration,
BaseText channel
) {
local int i, newNotificationIndex;
local Notification newNotification; local Notification newNotification;
if (body == none) { if (body == none) {
@ -90,9 +128,30 @@ public final function AddNotification(BaseText title, BaseText body, float durat
if (title != none) { if (title != none) {
newNotification.title = title.Copy(); newNotification.title = title.Copy();
} }
if (channel != none) {
newNotification.channel = channel.Copy();
}
newNotification.body = body.Copy(); newNotification.body = body.Copy();
newNotification.duration = duration; newNotification.duration = duration;
notificationQueue[notificationQueue.length] = newNotification; newNotificationIndex = notificationQueue.length;
// Make sure there is only one message per channel by replacing the old one
if (currentChannel != none && currentChannel.Compare(channel)) {
SetupNotification(/*take*/ newNotification);
return;
}
if (channel != none) {
for (i = 0; i < notificationQueue.length; i += 1) {
if (channel.Compare(notificationQueue[i].channel)) {
_.memory.Free3(
notificationQueue[i].title,
notificationQueue[i].body,
notificationQueue[i].channel);
newNotificationIndex = i;
break;
}
}
}
notificationQueue[newNotificationIndex] = newNotification;
if (!IsUpdateScheduled()) { if (!IsUpdateScheduled()) {
SetupNextNotification(none); SetupNextNotification(none);
} }
@ -177,9 +236,20 @@ private function PrintNotifcationAt(
} }
private function SetupNextNotification(Timer callerInstance) { private function SetupNextNotification(Timer callerInstance) {
local Notification nextNotification;
if (notificationQueue.length <= 0) {
InterruptScheduling();
return;
}
nextNotification = notificationQueue[0];
notificationQueue.Remove(0, 1);
SetupNotification(/*take*/ nextNotification);
}
private function SetupNotification(/*take*/ Notification notification) {
local int titleShift; local int titleShift;
local MutableText upperCaseTitle; local MutableText upperCaseTitle;
local Notification nextNotification;
local PlayerController playerController; local PlayerController playerController;
// Get appropriate [`PlayerController`] and next notification // Get appropriate [`PlayerController`] and next notification
@ -187,31 +257,30 @@ private function SetupNextNotification(Timer callerInstance) {
if (playerController == none) { if (playerController == none) {
_.memory.Free(playerControllerRef); _.memory.Free(playerControllerRef);
playerControllerRef = none; playerControllerRef = none;
}
if (notificationQueue.length <= 0 || playerController == none) {
InterruptScheduling(); InterruptScheduling();
return; return;
} }
nextNotification = notificationQueue[0]; _.memory.Free(currentChannel);
notificationQueue.Remove(0, 1); currentChannel = notification.channel;
nextNotification.duration = FMin(nextNotification.duration, maximumNotifyTime); notification.duration = FMin(notification.duration, maximumNotifyTime);
if (nextNotification.duration <= 0) { if (notification.duration <= 0) {
nextNotification.duration = 10.0; notification.duration = 10.0;
} }
// And print // And print
playerController.ClearProgressMessages(); playerController.ClearProgressMessages();
playerController.SetProgressTime(nextNotification.duration); playerController.SetProgressTime(notification.duration);
if (nextNotification.title != none) { if (notification.title != none) {
upperCaseTitle = nextNotification.title.UpperMutableCopy(); upperCaseTitle = notification.title.UpperMutableCopy();
upperCaseTitle.ChangeDefaultColor(_.color.TextHeader); upperCaseTitle.ChangeDefaultColor(_.color.TextHeader);
PrintNotifcationAt(playerController, upperCaseTitle, 0, 1); PrintNotifcationAt(playerController, upperCaseTitle, 0, 1);
titleShift = 1; titleShift = 1;
_.memory.Free(upperCaseTitle); _.memory.Free(upperCaseTitle);
} }
PrintNotifcationAt(playerController, nextNotification.body, titleShift, 4 - titleShift); PrintNotifcationAt(playerController, notification.body, titleShift, 4 - titleShift);
ScheduleUpdate(nextNotification.duration); ScheduleUpdate(notification.duration);
_.memory.Free2(nextNotification.title, nextNotification.body); // We moved channel's reference into `currentChannel`
_.memory.Free2(notification.title, notification.body);
} }
defaultproperties { defaultproperties {

Loading…
Cancel
Save