Support for Math Blaster Ambassador Funzones (#5)

Added ambassador support (for Math Blaster).
Tons of config options too.

* Moved ambassador room controlling to seperate class.
* More changes to appease the Pull Request.
This commit is contained in:
Hipposgrumm 2024-12-30 02:54:53 -07:00 committed by GitHub
parent c06cfb4d17
commit 158ac4ee21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 244 additions and 152 deletions

View File

@ -0,0 +1,27 @@
using sodoffmmo.Attributes;
using sodoffmmo.Core;
using sodoffmmo.Data;
namespace sodoffmmo.CommandHandlers;
[ExtensionCommandHandler("SCE")]
class CounterEventHandler : CommandHandler {
public override Task Handle(Client client, NetworkObject receivedObject) { // {"a":13,"c":1,"p":{"c":"SCE","p":{"NAME":"COUNT"},"r":-1}}
if (client.Room is SpecialRoom room) {
string name = receivedObject.Get<NetworkObject>("p").Get<string>("NAME");
if (name == "COUNT" || name == "COUNT2" || name == "COUNT3") {
int index = name switch {
"COUNT" => 0,
"COUNT2" => 1,
"COUNT3" => 2
};
room.ambassadorGauges[index] = Math.Min(100, room.ambassadorGauges[index]+(1/Configuration.ServerConfiguration.AmbassadorGaugePlayers));
room.Send(Utils.VlNetworkPacket(room.GetRoomVars(), client.Room.Id));
} else {
Console.WriteLine($"Invalid attempt to increment room var {name} in {room.Name}.");
}
}
return Task.CompletedTask;
}
}

View File

@ -27,7 +27,7 @@ class SetUserVariablesHandler : CommandHandler {
client.PlayerData.InitFromNetworkData(suvData); client.PlayerData.InitFromNetworkData(suvData);
UpdatePlayersInRoom(); UpdatePlayersInRoom();
SendSUVToPlayerInRoom(); SendSUVToPlayerInRoom();
client.Room.SendAllAlerts(client); if (client.Room is SpecialRoom room) room.SendAllAlerts(client);
} else { } else {
UpdateVars(); UpdateVars();
} }

View File

@ -33,6 +33,11 @@ internal sealed class ServerConfiguration {
public int EventTimer { get; set; } = 30; public int EventTimer { get; set; } = 30;
public int FirstEventTimer { get; set; } = 10; public int FirstEventTimer { get; set; } = 10;
public Dictionary<string, string[][]> RoomAlerts { get; set; } = new(); public Dictionary<string, string[][]> RoomAlerts { get; set; } = new();
public string[] AmbassadorRooms { get; set; } = Array.Empty<string>();
public int AmbassadorGaugeStart { get; set; } = 75;
public float AmbassadorGaugeDecayRate { get; set; } = 60;
public bool AmbassadorGaugeDecayOnlyWhenInRoom { get; set; } = true;
public float AmbassadorGaugePlayers { get; set; } = 0.5f;
public int RacingMaxPlayers { get; set; } = 6; public int RacingMaxPlayers { get; set; } = 6;
public int RacingMinPlayers { get; set; } = 2; public int RacingMinPlayers { get; set; } = 2;
public int RacingMainLobbyTimer { get; set; } = 15; public int RacingMainLobbyTimer { get; set; } = 15;

View File

@ -111,7 +111,7 @@ public class Room {
roomInfo.Add(false); // is password protected roomInfo.Add(false); // is password protected
roomInfo.Add((short)clients.Count); // player count roomInfo.Add((short)clients.Count); // player count
roomInfo.Add((short)4096); // max player count roomInfo.Add((short)4096); // max player count
roomInfo.Add(RoomVariables); // variables roomInfo.Add(GetRoomVars()); // variables (plus added data)
roomInfo.Add((short)0); // spectator count roomInfo.Add((short)0); // spectator count
roomInfo.Add((short)0); // max spectator count roomInfo.Add((short)0); // max spectator count
@ -143,7 +143,7 @@ public class Room {
r1.Add(false); r1.Add(false);
r1.Add((short)clients.Count); // player count r1.Add((short)clients.Count); // player count
r1.Add((short)4096); // max player count r1.Add((short)4096); // max player count
r1.Add(new NetworkArray()); r1.Add(GetRoomVars());
r1.Add((short)0); r1.Add((short)0);
r1.Add((short)0); r1.Add((short)0);
@ -154,141 +154,5 @@ public class Room {
return NetworkObject.WrapObject(0, 15, obj).Serialize(); return NetworkObject.WrapObject(0, 15, obj).Serialize();
} }
internal virtual NetworkArray GetRoomVars() { return RoomVariables; }
private int alertId = -1;
private Random random = new Random();
List<AlertInfo> alerts = new();
public void AddAlert(AlertInfo alert) {
alerts.Add(alert);
ResetAlertTimer(alert);
}
public void SendAllAlerts(Client client) {
foreach (AlertInfo alert in alerts) {
if (alert.IsRunning()) StartAlert(alert, client);
}
}
private void StartAlert(AlertInfo alert, Client? specificClient = null) {
if (specificClient != null) return; // Disables joining ongoing alerts.
NetworkArray NewRoomVariables = new();
NewRoomVariables.Add(NetworkArray.VlElement(REDALERT_START, alertId++, isPersistent: true));
NewRoomVariables.Add(NetworkArray.VlElement(REDALERT_TYPE, alert.type, isPersistent: true));
double duration = (alert.endTime - DateTime.Now).TotalSeconds;
NewRoomVariables.Add(NetworkArray.VlElement(REDALERT_LENGTH, alert.type == "1" ? alert.redAlertDuration : duration, isPersistent: true));
if (alert.type == "1") {
NewRoomVariables.Add(NetworkArray.VlElement(REDALERT_TIMEOUT, duration, isPersistent: true));
} else if (alert.type == "3") {
alert.songId = random.Next(0, alert.songs);
NewRoomVariables.Add(NetworkArray.VlElement(REDALERT_SONG, (double)alert.songId, isPersistent: true));
}
NetworkPacket packet = Utils.VlNetworkPacket(NewRoomVariables, Id);
if (specificClient is null) {
RoomVariables = NewRoomVariables;
Send(packet);
Console.WriteLine("Started event " +alert + " in room " + Name);
} else {
specificClient.Send(packet);
Console.WriteLine("Added " + specificClient.PlayerData.DiplayName + " to event " + alert + " with " + duration + " seconds remaining");
}
}
void ResetAlertTimer(AlertInfo alert) {
System.Timers.Timer? timer = alert.timer;
if (timer != null) {
timer.Stop();
timer.Close();
}
DateTime startTime = DateTime.Now.AddMilliseconds(random.Next(alert.minTime * 1000, alert.maxTime * 1000));
DateTime endTime = startTime.AddSeconds(alert.duration);
for (int i = 0; i < alerts.IndexOf(alert); i++) {
// Prevent overlap between two events.
if (alerts[i].Overlaps(endTime)) {
startTime = alerts[i].endTime.AddSeconds(5);
endTime = startTime.AddSeconds(alert.duration);
}
}
timer = new System.Timers.Timer((startTime - DateTime.Now).TotalMilliseconds);
timer.AutoReset = false;
timer.Enabled = true;
timer.Elapsed += (sender, e) => StartAlert(alert);
timer.Elapsed += (sender, e) => ResetAlertTimer(alert);
alert.timer = timer;
Console.WriteLine("Event " + alert + " in " + Name + " scheduled for " + startTime.ToString("MM/dd/yyyy HH:mm:ss tt") + " (in " + (startTime - DateTime.Now).TotalSeconds + " seconds)");
alert.startTime = startTime;
alert.endTime = endTime;
}
private const string REDALERT_START = "RA_S";
private const string REDALERT_TYPE = "RA_A";
private const string REDALERT_LENGTH = "RA_L";
private const string REDALERT_TIMEOUT = "RA_T";
private const string REDALERT_SONG = "RA_SO";
public class AlertInfo {
public readonly string type;
public readonly double duration;
public readonly int minTime;
public readonly int maxTime;
public readonly int redAlertDuration;
public readonly int songs;
public int songId;
public DateTime startTime {
get {
return newStartTime;
}
set {
oldStartTime = newStartTime;
newStartTime = value;
}
}
public DateTime endTime {
get {
return newEndTime;
}
set {
oldEndTime = newEndTime;
newEndTime = value;
}
}
private DateTime newStartTime;
private DateTime newEndTime;
private DateTime oldStartTime;
private DateTime oldEndTime;
public System.Timers.Timer? timer = null;
public AlertInfo(string type, double duration = 20.0, int minTime = 30, int maxTime = 240, int redAlertDuration = 60, int songs = 16) {
this.type = type;
this.duration = duration;
this.minTime = minTime;
this.maxTime = maxTime;
this.redAlertDuration = redAlertDuration;
this.songs = songs;
}
public bool Overlaps(DateTime time) {
return (time >= oldStartTime && time <= oldEndTime);
}
public bool IsRunning() {
return Overlaps(DateTime.Now);
}
public override string ToString() {
return type switch {
"1" => "RedAlert",
"2" => "DiscoAlert",
"3" => "DanceOff",
_ => type
};
}
}
} }

191
src/Core/SpecialRoom.cs Normal file
View File

@ -0,0 +1,191 @@
using sodoffmmo.Data;
namespace sodoffmmo.Core;
public class SpecialRoom : Room {
public double[] ambassadorGauges = new double[3]; // There is always a maximum of 3.
System.Timers.Timer? ambassadorTimer;
public static void CreateRooms() {
foreach (var room in Configuration.ServerConfiguration.RoomAlerts) {
foreach (var alert in room.Value) {
AlertInfo alertInfo = new AlertInfo(
alert[0], // type
float.Parse(alert[1], System.Globalization.CultureInfo.InvariantCulture.NumberFormat), // duration
Int32.Parse(alert[2]), Int32.Parse(alert[3]), // start min - max for random start time
Int32.Parse(alert[4]), Int32.Parse(alert[5]) // extra parameters for specific alarm types
);
Console.WriteLine($"Setup alert {alertInfo} for {room.Key}");
(rooms.GetValueOrDefault(room.Key) as SpecialRoom ?? new SpecialRoom(room.Key)).AddAlert(alertInfo);
}
}
foreach (var room in Configuration.ServerConfiguration.AmbassadorRooms) {
Console.WriteLine($"Setup Ambassador for {room}");
(rooms.GetValueOrDefault(room) as SpecialRoom ?? new SpecialRoom(room)).InitAmbassador();
}
}
public SpecialRoom(string name) : base(name) {}
public void InitAmbassador() {
for (int i=0;i<3;i++) ambassadorGauges[i] = Configuration.ServerConfiguration.AmbassadorGaugeStart;
ambassadorTimer = new(Configuration.ServerConfiguration.AmbassadorGaugeDecayRate * 1000) {
AutoReset = true,
Enabled = true
};
ambassadorTimer.Elapsed += (sender, e) => {
if (!Configuration.ServerConfiguration.AmbassadorGaugeDecayOnlyWhenInRoom || ClientsCount > 0) {
for (int i=0;i<3;i++) ambassadorGauges[i] = Math.Max(0, ambassadorGauges[i]-1);
Send(Utils.VlNetworkPacket(GetRoomVars(), Id));
}
};
}
internal override NetworkArray GetRoomVars() {
NetworkArray vars = new();
vars.Add(NetworkArray.VlElement("COUNT", (int)Math.Round(ambassadorGauges[0]), isPersistent: true));
vars.Add(NetworkArray.VlElement("COUNT2", (int)Math.Round(ambassadorGauges[1]), isPersistent: true));
vars.Add(NetworkArray.VlElement("COUNT3", (int)Math.Round(ambassadorGauges[2]), isPersistent: true));
for (int i=0;i<RoomVariables.Length;i++) vars.Add(RoomVariables[i]);
return vars;
}
private int alertId = -1;
private Random random = new();
List<AlertInfo> alerts = new();
public void AddAlert(AlertInfo alert) {
alerts.Add(alert);
ResetAlertTimer(alert);
}
public void SendAllAlerts(Client client) {
return; // Disables joining ongoing alerts (since it doesn't work properly).
foreach (AlertInfo alert in alerts) {
if (alert.IsRunning()) StartAlert(alert, client);
}
}
private void StartAlert(AlertInfo alert, Client? specificClient = null) {
NetworkArray NewRoomVariables = new();
NewRoomVariables.Add(NetworkArray.VlElement(REDALERT_START, alertId++, isPersistent: true));
NewRoomVariables.Add(NetworkArray.VlElement(REDALERT_TYPE, alert.type, isPersistent: true));
double duration = (alert.endTime - DateTime.Now).TotalSeconds;
NewRoomVariables.Add(NetworkArray.VlElement(REDALERT_LENGTH, alert.type == "1" ? alert.redAlertDuration : duration, isPersistent: true));
if (alert.type == "1") {
NewRoomVariables.Add(NetworkArray.VlElement(REDALERT_TIMEOUT, duration, isPersistent: true));
} else if (alert.type == "3") {
alert.songId = random.Next(0, alert.songs);
NewRoomVariables.Add(NetworkArray.VlElement(REDALERT_SONG, (double)alert.songId, isPersistent: true));
}
NetworkPacket packet = Utils.VlNetworkPacket(NewRoomVariables, Id);
if (specificClient is null) {
RoomVariables = NewRoomVariables;
Send(packet);
RoomVariables = new();
Console.WriteLine("Started event " +alert + " in room " + Name);
} else {
specificClient.Send(packet);
Console.WriteLine("Added " + specificClient.PlayerData.DiplayName + " to event " + alert + " with " + duration + " seconds remaining");
}
}
void ResetAlertTimer(AlertInfo alert) {
System.Timers.Timer? timer = alert.timer;
if (timer != null) {
timer.Stop();
timer.Close();
}
DateTime startTime = DateTime.Now.AddMilliseconds(random.Next(alert.minTime * 1000, alert.maxTime * 1000));
DateTime endTime = startTime.AddSeconds(alert.duration);
for (int i = 0; i < alerts.IndexOf(alert); i++) {
// Prevent overlap between two events.
if (alerts[i].Overlaps(endTime)) {
startTime = alerts[i].endTime.AddSeconds(5);
endTime = startTime.AddSeconds(alert.duration);
}
}
timer = new System.Timers.Timer((startTime - DateTime.Now).TotalMilliseconds);
timer.AutoReset = false;
timer.Enabled = true;
timer.Elapsed += (sender, e) => StartAlert(alert);
timer.Elapsed += (sender, e) => ResetAlertTimer(alert);
alert.timer = timer;
Console.WriteLine("Event " + alert + " in " + Name + " scheduled for " + startTime.ToString("MM/dd/yyyy HH:mm:ss tt") + " (in " + (startTime - DateTime.Now).TotalSeconds + " seconds)");
alert.startTime = startTime;
alert.endTime = endTime;
}
private const string REDALERT_START = "RA_S";
private const string REDALERT_TYPE = "RA_A";
private const string REDALERT_LENGTH = "RA_L";
private const string REDALERT_TIMEOUT = "RA_T";
private const string REDALERT_SONG = "RA_SO";
public class AlertInfo {
public readonly string type;
public readonly double duration;
public readonly int minTime;
public readonly int maxTime;
public readonly int redAlertDuration;
public readonly int songs;
public int songId;
public DateTime startTime {
get {
return newStartTime;
}
set {
oldStartTime = newStartTime;
newStartTime = value;
}
}
public DateTime endTime {
get {
return newEndTime;
}
set {
oldEndTime = newEndTime;
newEndTime = value;
}
}
private DateTime newStartTime;
private DateTime newEndTime;
private DateTime oldStartTime;
private DateTime oldEndTime;
public System.Timers.Timer? timer = null;
public AlertInfo(string type, double duration = 20.0, int minTime = 30, int maxTime = 240, int redAlertDuration = 60, int songs = 16) {
this.type = type;
this.duration = duration;
this.minTime = minTime;
this.maxTime = maxTime;
this.redAlertDuration = redAlertDuration;
this.songs = songs;
}
public bool Overlaps(DateTime time) {
return (time >= oldStartTime && time <= oldEndTime);
}
public bool IsRunning() {
return Overlaps(DateTime.Now);
}
public override string ToString() {
return type switch {
"1" => "RedAlert",
"2" => "DiscoAlert",
"3" => "DanceOff",
_ => type
};
}
}
}

View File

@ -29,17 +29,7 @@ public class Server {
listener.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 0); listener.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 0);
listener.Bind(new IPEndPoint(ipAddress, port)); listener.Bind(new IPEndPoint(ipAddress, port));
foreach (var room in Configuration.ServerConfiguration.RoomAlerts) { SpecialRoom.CreateRooms();
foreach (var alert in room.Value) {
Console.WriteLine($"Setup alert \"{alert[0]}\" for {room.Key}");
Room.GetOrAdd(room.Key).AddAlert(new Room.AlertInfo(
alert[0], // type
float.Parse(alert[1], System.Globalization.CultureInfo.InvariantCulture.NumberFormat), // duration
Int32.Parse(alert[2]), Int32.Parse(alert[3]), // start min - max for random start time
Int32.Parse(alert[4]), Int32.Parse(alert[5]) // extra parameters for specific alarm types
));
}
}
await Listen(listener); await Listen(listener);
} }

View File

@ -32,6 +32,21 @@
"JunkYardEMD": [ ["1", 20.0, 240, 300, 60, 0] ], "JunkYardEMD": [ ["1", 20.0, 240, 300, 60, 0] ],
}, },
"// AmbassadorRooms": "Rooms with ambassadors (MB funzones).",
"AmbassadorRooms": ["Spaceport"],
"// AmbassadorGaugeStart": "The starting value for all ambassador gauges (MB funzones).",
"AmbassadorGaugeStart": 75,
"// AmbassadorGaugeDecayRate": "Time in seconds before ambassador gauges decrease (MB funzones).",
"AmbassadorGaugeDecayRate": 60,
"// AmbassadorGaugeDecayOnlyWhenInRoom": "Only decrease ambassador gauges when there is at least one player in the room (MB funzones).",
"AmbassadorGaugeDecayOnlyWhenInRoom": true,
"// AmbassadorGaugePlayers": "Denominator for filling the ambassador gauges (MB funzones).",
"AmbassadorGaugePlayers": 0.5,
"// RacingMaxPlayers": "maximum players allowed in Thunder Run Racing (no more than 6)", "// RacingMaxPlayers": "maximum players allowed in Thunder Run Racing (no more than 6)",
"RacingMaxPlayers": 6, "RacingMaxPlayers": 6,