using System;
using System.Collections.Generic;
using System.Linq;
using InSimDotNet;
using InSimDotNet.Packets;
namespace MultiLineButton
{
public class MultiLineButton
{
private readonly InSim _inSim;
private readonly byte _clickId;
private readonly byte _ucid;
private readonly byte _width;
private readonly byte _height;
private readonly byte _top;
private readonly byte _left;
private readonly byte _rowHeight;
private readonly byte _maxCharsPerRow;
private readonly string _text;
private readonly bool _showScrollbar;
private readonly ButtonStyles _style;
private readonly List<string> _lines;
private int _scrollPosition;
private readonly byte _scrollbarWidth;
private readonly Dictionary<byte, ButtonInfo> _buttonInfo;
private struct ButtonInfo
{
public ButtonType Type { get; set; }
public string Text { get; set; }
}
private enum ButtonType
{
Background,
TextLine,
ScrollUp,
ScrollTrack,
ScrollDown
}
public MultiLineButton(
InSim inSim,
byte clickId,
byte ucid,
byte width,
byte height,
byte top,
byte left,
byte rowHeight,
byte maxCharsPerRow,
string text,
ButtonStyles style = ButtonStyles.ISB_DARK)
{
_inSim = inSim;
_clickId = clickId;
_ucid = ucid;
_width = width;
_height = height;
_top = top;
_left = left;
_rowHeight = rowHeight;
_maxCharsPerRow = maxCharsPerRow;
_text = text;
_style = style;
_scrollPosition = 0;
_scrollbarWidth = (byte)Math.Max(4, Math.Round(rowHeight * 0.75));
_buttonInfo = new Dictionary<byte, ButtonInfo>();
_lines = SplitTextIntoLines(text, maxCharsPerRow);
_showScrollbar = _lines.Count * rowHeight > height;
// Setup event handlers
_inSim.Bind<IS_BTC>((inSim, packet) => HandleButtonClick(packet));
}
public void Show()
{
DrawButtons();
LogButtonInfo();
}
public void Hide()
{
ClearButtons();
}
private void DrawButtons()
{
ClearButtons();
var textAreaWidth = (byte)(_showScrollbar ? _width - _scrollbarWidth : _width);
var maxVisibleRows = _height / _rowHeight;
// Background button
SendButton(
_clickId,
_top,
_left,
_width,
_height,
"",
_style | ButtonStyles.ISB_DARK,
ButtonType.Background
);
// Draw scrollbar if needed
if (_showScrollbar)
{
var canScrollUp = _scrollPosition > 0;
var canScrollDown = _scrollPosition + maxVisibleRows < _lines.Count;
var scrollbarLeft = (byte)(_left + textAreaWidth);
// Up arrow
if (canScrollUp)
{
SendButton(
(byte)(_clickId + 1),
_top,
scrollbarLeft,
_scrollbarWidth,
_rowHeight,
"▲",
_style | ButtonStyles.ISB_CLICK | ButtonStyles.ISB_DARK,
ButtonType.ScrollUp
);
}
// Scrollbar track
if (maxVisibleRows > 2)
{
var scrollbarHeight = (byte)(_height - (2 * _rowHeight));
var scrollbarThumbHeight = (byte)(scrollbarHeight * maxVisibleRows / Math.Max(1, _lines.Count));
var scrollbarPosition = (byte)(_top + _rowHeight +
(_scrollPosition * (scrollbarHeight - scrollbarThumbHeight) /
Math.Max(1, _lines.Count - maxVisibleRows)));
SendButton(
(byte)(_clickId + 2),
scrollbarPosition,
scrollbarLeft,
_scrollbarWidth,
scrollbarThumbHeight,
"",
_style | ButtonStyles.ISB_DARK,
ButtonType.ScrollTrack
);
}
// Down arrow
if (canScrollDown)
{
SendButton(
(byte)(_clickId + 3),
(byte)(_top + _height - _rowHeight),
scrollbarLeft,
_scrollbarWidth,
_rowHeight,
"▼",
_style | ButtonStyles.ISB_CLICK | ButtonStyles.ISB_DARK,
ButtonType.ScrollDown
);
}
}
// Draw text lines
byte currentId = (byte)(_clickId + 4);
for (var i = 0; i < maxVisibleRows && i + _scrollPosition < _lines.Count; i++)
{
var text = _lines[i + _scrollPosition];
// Add ellipsis if there's more text below
if (i == maxVisibleRows - 1 && i + _scrollPosition < _lines.Count - 1)
{
text += "...";
}
SendButton(
currentId++,
(byte)(_top + i * _rowHeight),
_left,
textAreaWidth,
_rowHeight,
text,
_style | ButtonStyles.ISB_C4,
ButtonType.TextLine
);
}
}
private void SendButton(
byte clickId,
byte top,
byte left,
byte width,
byte height,
string text,
ButtonStyles style,
ButtonType type)
{
_buttonInfo[clickId] = new ButtonInfo { Type = type, Text = text };
var button = new IS_BTN
{
ReqI = 1,
ClickID = clickId,
UCID = _ucid,
T = top,
L = left,
W = width,
H = height,
Text = text,
BStyle = style
};
_inSim.Send(button);
}
private void HandleButtonClick(IS_BTC packet)
{
if (!_buttonInfo.ContainsKey(packet.ClickID))
return;
var buttonInfo = _buttonInfo[packet.ClickID];
var maxVisibleRows = _height / _rowHeight;
switch (buttonInfo.Type)
{
case ButtonType.ScrollUp when _scrollPosition > 0:
_scrollPosition--;
DrawButtons();
break;
case ButtonType.ScrollDown when _scrollPosition + maxVisibleRows < _lines.Count:
_scrollPosition++;
DrawButtons();
break;
}
}
private void LogButtonInfo()
{
Console.WriteLine("\nButton Information:");
foreach (KeyValuePair<byte, ButtonInfo> button in _buttonInfo)
{
Console.WriteLine($"ClickID {button.Key}: Type={button.Value.Type}, Text={button.Value.Text}");
}
Console.WriteLine($"Total Buttons: {_buttonInfo.Count}");
}
private void ClearButtons()
{
foreach (var clickId in _buttonInfo.Keys.ToList())
{
var button = new IS_BFN
{
ReqI = 1,
UCID = _ucid,
ClickID = clickId,
SubT = ButtonFunction.BFN_DEL_BTN
};
_inSim.Send(button);
}
_buttonInfo.Clear();
}
private static List<string> SplitTextIntoLines(string text, int maxCharsPerLine)
{
var words = text.Split(' ');
var lines = new List<string>();
var currentLine = "";
foreach (var word in words)
{
if (currentLine.Length + word.Length + 1 <= maxCharsPerLine)
{
if (currentLine.Length > 0)
currentLine += " ";
currentLine += word;
}
else
{
if (currentLine.Length > 0)
lines.Add(currentLine);
currentLine = word;
}
}
if (currentLine.Length > 0)
lines.Add(currentLine);
return lines;
}
}
// Example usage
class Program
{
static void Main(string[] args)
{
Console.WriteLine("=== InSim Multi-line Button Test ===\n");
Console.WriteLine("This test demonstrates the InSim packet flow for multi-line buttons.\n");
var inSim = new InSim();
MultiLineButton multiLineButton = null;
try
{
inSim.Initialize(new InSimSettings
{
Host = "127.0.0.1",
Port = 29999,
Admin = "",
Prefix = '/',
Interval = 100,
Flags = InSimFlags.ISF_LOCAL
});
var testText = @"^7InSim Packets Explained:
1. ^7IS_BTN (Button) Packet:
- ^7ReqI: Request ID (1)
- ^7UCID: Connection ID
- ^7ClickID: Unique button ID
- ^7Inst: 0
- ^7BStyle: Button style flags
- ^7TypeIn: Max chars (0-240)
- ^7L,T: Position
- ^7W,H: Size
- ^7Text: Button text
2. ^7IS_BTC (Button Click):
- ^7ReqI: Copy of button ReqI
- ^7UCID: Connection that clicked
- ^7ClickID: Clicked button ID
- ^7Inst: 0
- ^7CFlags: Click flags
3. ^7IS_BFN (Button Function):
- ^7ReqI: Request ID
- ^7SubT: Delete/clear/etc
- ^7UCID: Target connection
- ^7ClickID: Target button";
multiLineButton = new MultiLineButton(
inSim: inSim,
clickId: 1,
ucid: 0,
width: 40,
height: 30,
top: 20,
left: 20,
rowHeight: 5,
maxCharsPerRow: 20,
text: testText,
style: ButtonStyles.ISB_C1
);
multiLineButton.Show();
Console.WriteLine("\nProgram is running. Press 'H' to hide the button, 'S' to show it again, or 'Q' to quit.");
while (true)
{
var key = Console.ReadKey(true);
switch (char.ToUpper(key.KeyChar))
{
case 'H':
multiLineButton.Hide();
Console.WriteLine("Button hidden");
break;
case 'S':
multiLineButton.Show();
Console.WriteLine("Button shown");
break;
case 'Q':
Console.WriteLine("Exiting...");
return;
}
}
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
finally
{
if (multiLineButton != null)
{
multiLineButton.Hide();
}
if (inSim != null)
{
inSim.Disconnect();
inSim.Dispose();
}
}
}
}
}
using System;
using InSimDotNet;
using InSimDotNet.Packets;
using InSimDotNet.Helpers;
class Program
{
static void Main(string[] args)
{
InSim insim = new InSim();
// Bind the MSO packet handler
insim.Bind<IS_MSO>(MessageMSO);
insim.Bind<IR_HOS>(HostList);
// Initialize InSim relay
insim.Initialize(new InSimSettings {
IsRelayHost = true,
});
// Send host select packet
insim.Send(new IR_HLR { ReqI = 1 });
insim.Send(new IR_SEL {
HName = "Hostname", // Host to select
Admin = "Passwprd", // Optional admin pass
Spec = String.Empty // Optional spectator pass
});
insim.Send(new IS_TINY { ReqI = 1, SubT = TinyType.TINY_NCN,});
insim.Send(new IS_MTC {UCID= 255, ReqI = 3, Msg = "This is Testing"});
Console.WriteLine("Connected to relay. Press any key to exit.");
Console.ReadKey();
}
static void HostList(InSim insim, IR_HOS hos) {
// Loop through each host connected to the server and print out some details
foreach (HInfo info in hos.Info) {
Console.WriteLine(
"{0} ({1} / {2})",
StringHelper.StripColors(info.HName),
info.Track,
info.NumConns);
}
}
// MSO message handler
static void MessageMSO(InSim insim, IS_MSO packet)
{
Console.WriteLine($"[{packet.UCID}] {packet.Msg}");
}
}
using System;
using InSimDotNet;
using InSimDotNet.Packets;
class Program
{
static void Main(string[] args)
{
InSim insim = new InSim();
// Bind the MSO packet handler
insim.Bind<IS_MSO>(MessageMSO);
// Initialize InSim relay
insim.Initialize(new InSimSettings {
IsRelayHost = true,
Admin = "",
Host = "isrelay.lfs.net",
Port = 47474
});
// Send host select packet
insim.Send(new IR_HLR { ReqI = 1 });
insim.Send(new IR_SEL { HName = "Hostname" });
insim.Send(new IS_MTC{UCID = 255, Msg = "This is Testing"});
Console.WriteLine("Connected to relay. Press any key to exit.");
Console.ReadKey();
}
// MSO message handler
static void MessageMSO(InSim insim, IS_MSO packet)
{
Console.WriteLine($"[{packet.UCID}] {packet.Msg}");
}
}
using System;
using InSimDotNet;
using InSimDotNet.Packets;
using InSimDotNet.Helpers;
class Program {
static void Main() {
InSim insim = new InSim();
// Bind handler for HOS (host list) packet.
insim.Bind<IR_HOS>(HostList);
// Initialize connection to InSim Relay
insim.Initialize(new InSimSettings {
Host = "isrelay.lfs.net", // Relay host
Port = 47474, // Default relay port
IsRelayHost = true,
Admin = "", // Not needed for relay
IName = "HostLister" // Name of this client
});
// Request host list.
insim.Send(new IR_HLR { ReqI = 1 });
Console.WriteLine("Requesting host list from InSim Relay...");
Console.ReadLine();
}
static void HostList(InSim insim, IR_HOS hos) {
if (hos.NumHosts == 0) {
Console.WriteLine("No hosts found.");
return;
}
Console.WriteLine("Found {0} host(s):", hos.NumHosts);
foreach (HInfo info in hos.Info) {
Console.WriteLine(
"{0} ({1} / {2} connections)",
StringHelper.StripColors(info.HName),
info.Track,
info.NumConns);
}
}
}
using System;
using System.IO;
using InSimDotNet;
using InSimDotNet.Packets;
using InSimDotNet.Helpers;
namespace LFSInSimRelay
{
class Program
{
// File to log messages to
private static readonly string LogFile = "mso_log.txt";
static void Main(string[] args)
{
Console.WriteLine("Starting LFS InSim Relay...");
InSim insim = new InSim();
// Bind event handlers
insim.Bind<IS_NCN>(NewConnection);
insim.Bind<IS_MSO>(MessageOut); // Add MSO event handler
// Initialize InSim relay
insim.Initialize(new InSimSettings {
IsRelayHost = true
});
Console.WriteLine("InSim relay initialized.");
Console.WriteLine("Selecting host:");
// Select a host
insim.Send(new IR_SEL {
HName = "", // Host to select
Admin = "", // Optional admin pass
Spec = String.Empty // Optional spectator pass
});
Console.WriteLine("Requesting current connection list...");
// Request connection list
insim.Send(new IS_TINY {
ReqI = 1,
SubT = TinyType.TINY_NCN,
});
Console.WriteLine("Monitoring for new connections and MSO messages.");
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
// Clean up
insim.Disconnect();
}
static void NewConnection(InSim insim, IS_NCN ncn)
{
// Output name of each player connected to the host
Console.WriteLine("Player connected: {0}", StringHelper.StripColors(ncn.PName));
}
static void MessageOut(InSim insim, IS_MSO mso)
{
// Strip color codes from message
string cleanMessage = StringHelper.StripColors(mso.Msg);
// Format the message with user info when available
string logMessage;
if (mso.UCID > 0)
{
// Message from a user
logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [USER:{mso.UCID}] {cleanMessage}";
}
else
{
// System message
logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [SYSTEM] {cleanMessage}";
}
// Output to console
Console.WriteLine(logMessage);
// Log to file
try
{
File.AppendAllText(LogFile, logMessage + Environment.NewLine);
}
catch (Exception ex)
{
Console.WriteLine($"Error writing to log file: {ex.Message}");
}
}
}
}