Anton Tarasenko
2 years ago
7 changed files with 325 additions and 2 deletions
@ -0,0 +1,53 @@
|
||||
/** |
||||
* Author: dkanus |
||||
* Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore |
||||
* License: GPL |
||||
* Copyright 2023 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class ACommandNotify extends Command; |
||||
|
||||
protected function BuildData(CommandDataBuilder builder) { |
||||
builder.Name(P("notify")); |
||||
builder.Group(P("core")); |
||||
builder.Summary(P("Notifies players with provided message.")); |
||||
builder.ParamText(P("message")); |
||||
builder.OptionalParams(); |
||||
builder.ParamNumber(P("duration")); |
||||
builder.Describe(P("Notify to players message with distinct header and body.")); |
||||
builder.RequireTarget(); |
||||
|
||||
builder.Option(P("title")); |
||||
builder.Describe(P("Specify the optional title of the notification.")); |
||||
builder.ParamText(P("title")); |
||||
} |
||||
|
||||
protected function ExecutedFor(EPlayer target, CallData arguments, EPlayer instigator) { |
||||
local Text title, message, plainTitle, plainMessage; |
||||
|
||||
plainMessage = arguments.parameters.GetText(P("message")); |
||||
if (arguments.options.HasKey(P("title"))) { |
||||
plainTitle = arguments.options.GetTextBy(P("/title/title")); |
||||
} |
||||
title = _.text.FromFormatted(plainTitle); |
||||
message = _.text.FromFormatted(plainMessage); |
||||
target.Notify(title, message, arguments.parameters.GetFloat(P("duration"))); |
||||
_.memory.Free4(title, message, plainTitle, plainMessage); |
||||
} |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,219 @@
|
||||
/** |
||||
* Author: dkanus |
||||
* Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore |
||||
* License: GPL |
||||
* Copyright 2023 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class PlayerNotificationQueue extends AcediaObject |
||||
config(AcediaSystem); |
||||
|
||||
/// Manages queue of notifications that should be displayed for a certain player. |
||||
|
||||
/// Describes a single notification: title (optional, can be `none`) + message body and timeout |
||||
struct Notification { |
||||
var Text title; |
||||
var Text body; |
||||
var float time; |
||||
}; |
||||
var private array<Notification> notificationQueue; |
||||
/// Reference to the `PlayerController` for the player that owns this queue |
||||
var private NativeActorRef playerControllerRef; |
||||
/// Timer until next notification can be displayed |
||||
/// |
||||
/// `none` if queue currently doesn't display any notifications |
||||
var private Timer nextNotificationTimer; |
||||
|
||||
/// Maximum time that a notification is allowed to be displayed on the player's screen |
||||
var private const config float maximumNotifyTime; |
||||
|
||||
var private const int CODEPOINT_NEWLINE; |
||||
|
||||
protected function Finalizer() { |
||||
local int i; |
||||
|
||||
for (i = 0; i < notificationQueue.length; i += 1) { |
||||
_.memory.Free(notificationQueue[i].title); |
||||
_.memory.Free(notificationQueue[i].body); |
||||
} |
||||
notificationQueue.length = 0; |
||||
_.memory.Free(nextNotificationTimer); |
||||
_.memory.Free(playerControllerRef); |
||||
nextNotificationTimer = none; |
||||
playerControllerRef = none; |
||||
} |
||||
|
||||
/// Set owner `PlayerController` for this queue |
||||
public final /*native*/ function SetupController(NativeActorRef newPlayerControllerRef) { |
||||
local PlayerController oldController, newController; |
||||
|
||||
if (playerControllerRef != none) { |
||||
oldController = PlayerController(playerControllerRef.Get()); |
||||
} |
||||
if (newPlayerControllerRef != none) { |
||||
newController = PlayerController(newPlayerControllerRef.Get()); |
||||
} |
||||
if (oldController != newController) { |
||||
InterruptScheduling(); |
||||
_.memory.Free(playerControllerRef); |
||||
if (newPlayerControllerRef != none) { |
||||
newPlayerControllerRef.NewRef(); |
||||
playerControllerRef = newPlayerControllerRef; |
||||
SetupNextNotification(none); |
||||
} else { |
||||
playerControllerRef = none; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Add new notification to the queue |
||||
public final function AddNotification(BaseText title, BaseText body, float time) { |
||||
local Notification newNotification; |
||||
|
||||
if (body == none) { |
||||
return; |
||||
} |
||||
if (title != none) { |
||||
newNotification.title = title.Copy(); |
||||
} |
||||
newNotification.body = body.Copy(); |
||||
notificationQueue[notificationQueue.length] = newNotification; |
||||
if (!IsUpdateScheduled()) { |
||||
SetupNextNotification(none); |
||||
} |
||||
} |
||||
|
||||
// Sets up [`SetupNextNotification()`] to be called after [`timeUntilNextUpdate`] |
||||
private function ScheduleUpdate(float timeUntilNextUpdate) { |
||||
if (nextNotificationTimer == none) { |
||||
nextNotificationTimer = __level().time.StartRealTimer(timeUntilNextUpdate, false); |
||||
nextNotificationTimer.OnElapsed(nextNotificationTimer).connect = SetupNextNotification; |
||||
} else { |
||||
nextNotificationTimer.SetInterval(timeUntilNextUpdate); |
||||
nextNotificationTimer.Start(); |
||||
} |
||||
} |
||||
|
||||
private function bool IsUpdateScheduled() { |
||||
return (nextNotificationTimer != none); |
||||
} |
||||
|
||||
// Prevents scheduled [`SetupNextNotification()`] call from going off |
||||
private function InterruptScheduling() { |
||||
_.memory.Free(nextNotificationTimer); |
||||
nextNotificationTimer = none; |
||||
} |
||||
|
||||
// Properly translates [`contents`] into a colored [`string`] before setting it up as |
||||
// a [`PlayerController`]'s progress message at the given line index. |
||||
private function SetupProgressLine( |
||||
int lineIndex, |
||||
BaseText contents, |
||||
PlayerController playerController |
||||
) { |
||||
local string contentsAsString; |
||||
local BaseText.Formatting startingFormatting; |
||||
|
||||
if (contents == none) return; |
||||
if (playerController == none) return; |
||||
|
||||
// Drop first colored tag, since we'll set first color through `playerController.progressColor` |
||||
contentsAsString = Mid(contents.ToColoredString(,, _.color.white), 4); |
||||
startingFormatting = contents.GetFormatting(0); |
||||
if (startingFormatting.isColored) { |
||||
playerController.SetProgressMessage(lineIndex, contentsAsString, startingFormatting.color); |
||||
} |
||||
else { |
||||
playerController.SetProgressMessage(lineIndex, contentsAsString, _.color.white); |
||||
} |
||||
} |
||||
|
||||
// Prints [`notification`] on given lines, respecting line breaks inside it as much as possible |
||||
// (creates up to [`maxLines`], replacing the rest of line breaks with whitespaces) |
||||
private function PrintNotifcationAt( |
||||
PlayerController playerController, |
||||
BaseText notification, |
||||
int startingLine, |
||||
int maxLines |
||||
) { |
||||
local int i, j; |
||||
local MutableText lastLine; |
||||
local array<BaseText> lines; |
||||
|
||||
if (notification == none) return; |
||||
if (startingLine < 0) return; |
||||
if (startingLine > 3) return; |
||||
|
||||
lines = notification.SplitByCharacter(_.text.CharacterFromCodePoint(CODEPOINT_NEWLINE),, true); |
||||
for (i = 0; i < lines.length; i += 1) { |
||||
if (i + 1 < maxLines) { |
||||
SetupProgressLine(i + startingLine, lines[i], playerController); |
||||
} else { |
||||
lastLine = lines[i].MutableCopy(); |
||||
for (j = i + 1; j < lines.length; j += 1) { |
||||
lastLine.Append(P(" ")); |
||||
lastLine.Append(lines[j]); |
||||
} |
||||
SetupProgressLine(startingLine + maxLines - 1, lastLine, playerController); |
||||
_.memory.Free(lastLine); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
private function SetupNextNotification(Timer callerInstance) { |
||||
local int titleShift; |
||||
local MutableText upperCaseTitle; |
||||
local Notification nextNotification; |
||||
local PlayerController playerController; |
||||
|
||||
// Get appropriate [`PlayerController`] and next notification |
||||
playerController = PlayerController(playerControllerRef.Get()); |
||||
if (playerController == none) { |
||||
_.memory.Free(playerControllerRef); |
||||
playerControllerRef = none; |
||||
} |
||||
if (notificationQueue.length <= 0 || playerController == none) { |
||||
InterruptScheduling(); |
||||
return; |
||||
} |
||||
nextNotification = notificationQueue[0]; |
||||
notificationQueue.Remove(0, 1); |
||||
nextNotification.time = FMin(nextNotification.time, maximumNotifyTime); |
||||
if (nextNotification.time <= 0) { |
||||
nextNotification.time = 10.0; |
||||
} |
||||
|
||||
// And print |
||||
playerController.ClearProgressMessages(); |
||||
playerController.SetProgressTime(nextNotification.time); |
||||
if (nextNotification.title != none) { |
||||
upperCaseTitle = nextNotification.title.UpperMutableCopy(); |
||||
upperCaseTitle.ChangeDefaultColor(_.color.TextHeader); |
||||
PrintNotifcationAt(playerController, upperCaseTitle, 0, 1); |
||||
titleShift = 1; |
||||
_.memory.Free(upperCaseTitle); |
||||
} |
||||
PrintNotifcationAt(playerController, nextNotification.body, titleShift, 4 - titleShift); |
||||
ScheduleUpdate(nextNotification.time); |
||||
_.memory.Free2(nextNotification.title, nextNotification.body); |
||||
} |
||||
|
||||
defaultproperties { |
||||
CODEPOINT_NEWLINE = 10 |
||||
maximumNotifyTime = 20.0 |
||||
} |
Loading…
Reference in new issue