The online racing simulator
Searching in All forums
(779 results)
J_Valkonen
S3 licensed
I got it working.

If you want to do Drivers Briefing file here is how I did it.
First i made the file Briefing.lpr in the folder location:
C:\LFS\LFSLapper\bin\servers\includes\

Then i added line:
include( "./Briefing.lpr");

Line was added to file:
C:\LFS\LFSLapper\bin\servers\includes\addonsused.lpr

Then I made sure that my Server_OPEN.lpr in the folder location:
C:\LFS\LFSLapper\bin\servers\

had line
include( "./includes/addonsused.lpr");

After that
I wrote code and tested it.
I found out that, if you want to have multiple commands for different text flods, you have to write them in same file as I have done here.

Next I am going to explore the "SWITCH( $command )" if it would make possible to make individual "files with execute commands".

Here is my code
----------
# DriversBriefing

# THIS IS THE DRIVERSBRIEFING text
Sub line1 ()
cmdLFS("/msg" ); #This maybe clears all other written text before it
DelayedCommand( 1,line2 );
EndSub
Sub line2 ()
globalmsg("^7Welcome to OPEN Drift Event 1 ");
DelayedCommand( 4,line3 );
EndSub
Sub line3 ()
globalmsg("^7We now start the DRIVERS BRIEFING.");
DelayedCommand( 4,line4 );
EndSub
Sub line4 ()
globalmsg("^7Keep the game chat empty for competition leader messages.");
DelayedCommand( 4,line5 );
EndSub
Sub line5 ()
globalmsg("^7Do not write anything to the game chat during the QUALIFY.");
DelayedCommand( 4,line6 );
Sub line6 ()
globalmsg("^7Thank you!");
EndSub

# THIS CLOSES DRIVERSBRIEFING text flod
Sub CloseBriefMsg( $KeyFlags,$id )
closeGlobalButton("Brief_msg");
EndSub

# THIS IS THE COMMAND FOR START DRIVERSBRIEFING text flod
CatchEvent OnMSO( $userName, $text )
$ucaseText = ToLower($text);
IF( $ucaseText == "!brief" ) THEN
IF( UserIsAdmin($userName) == 1 ) THEN
DelayedCommand( 1, line1 ); # delay in secends, here 1, whats happens next
privMsg($userName, "^2Drivers Briefing starts ...");
ELSE
privMsg("Only for admin!");
ENDIF
ENDIF
IF( $ucaseText == "!huuto" ) THEN
IF( UserIsAdmin($userName) == 1 ) THEN
DelayedCommand( 1, huuto_line1 ); # delay in secends, here 1, whats happens next
privMsg($userName, "^2Sign up name call starts ...");
ELSE
privMsg("Only for admin!");
ENDIF
ENDIF
EndCatchEvent

# THIS IS THE SIGN UP NAME CALL text
##########NIMENHUUTO##########
Sub huuto_line1 ()
globalmsg("^4SIGN UP NAME CALL");
DelayedCommand( 4,huuto_line2 );
EndSub
Sub huuto_line2 ()
globalmsg("^7We now start the SIGN UP NAME CALL FOR THE EVENT.");
DelayedCommand( 4,huuto_line3 );
Sub huuto_line3 ()
globalmsg("^7ALL DRIVERS who want to PARTICIPATE in the competition TODAY,");
DelayedCommand( 4,huuto_line4 );
EndSub
Sub huuto_line4 ()
globalmsg("^7TYPE hi in the game chat now.");
EndSub

# THIS CLOSES text flod
Sub CloseHuutoMsg( $KeyFlags,$id )
closeGlobalButton("huuto_msg");
EndSub
Personal opinion
tsichles
S3 licensed
Quote from joaopaulopt :I believe LFS has enormous potential in the future Smile It just needs to keep...

LFS is Scawens software baby.
Idea and implementation that represents his life philosophy.

It begun in the strange waters of "will it work", next the satisfaction or applause oriented coding and finally the difficulty to keep up.

I am thankful for his commitment to proceed the development and personally consider LFS as a form of art.

As a result, this one (three) man show, excellence comes with some culprits

You Build It, You Run It, syndrome and this nice video by Modern Software Engineering:
https://www.youtube.com/watch?v=y_W3v-KMPz0&ab_channel=ModernSoftwareEngineering

And ... they have to earn their living some how.

Scaling up LFS, is almost equivalent to a complete "acquisition" by big players, where big money and manpower will try to replace art.
Open sourcing it, will make harder the, "I can live with it" potential.

So, lets enjoy the ride, and mr. Scawen play tennis, it prolongs life !
sinanju
S3 licensed
The most obvious issue is that if you are adding a lapper script, you need to 'Catch' any and all events.

So "Event onMSO.." should be "CatchEvent OnMSO...", and the end should be changed from "EndEvent" to "EndCatchEvent".

Not sure why there's a 'rivi1' sub, when the !brief command sends you to 'rivi2'.

As for the sub info (or lack thereof) within the brackets after the sub name, this is something I always struggle with, and even asked about ...

SUB event Q - https://www.lfs.net/forum/post/1693093#post1693093
SUB event A - https://www.lfs.net/forum/post/1693302#post1693302

The answer helped me a little, but not always, so I go through the following ...

# Possible subs to use:
# Sub sub1()
# Sub sub1($userName)
# Sub sub1($KeyFlags,$userName)
# Sub sub1($KeyFlags,$argv)
# Sub sub1($KeyFlags,$id)

If you're using a command to start things off, why do you need a distance to do first?

Another issue might be the directory and folder you're placing your briefing.lpr file. It should be in the standard \bin\default\includes directory, and should be added into the existing addonsused.lpr file.

Good luck.
Velocity Motorsports Events
lordferius
S3 licensed


RULES

Each round consists a total of up to 12 stage combos (ex: 6 stages x 2 car pools = 12 combinations)
From that total, only your best 5 results will count towards round points. Points are added per stage:
1st - 10pts
2nd - 9
...
10th - 1
ex: If your best results are 1st-2nd-2nd-5th-5th, your leaderboard points from this round will be 40pts

At the end of each round, your position on the leaderboard will determine how many championship points you earn:
1st 23pts
2nd 20
3rd 17
4rd 14
5th 12
6th 10
7th 8
8th 6
9th 4
10th 2

Warning: Admins will be on the lookout for any bad conduct. Any abuse will be met with severe punishment.

For Discord:
-Keep an eye on ⁠#champ-announcement for round details, points update and general info.
-Check #⁠champ-bot for update on stage times.
-If you want to report a bug or misconduct, use #champ-reports or alternative #⁠champ-bot-reports using in-game server !report command.

SYSTEM TUTORIAL

Last edited by lordferius, .
Wait for the results
Eduard.kbg
S3 licensed
Quote from SupPah :no front fog lights.. so any chance of the c6 becoming s6? Big grin...

I want to make a multi setup mod , like one setup its A6 , S line , Street Tuned , All-Road , Undercover and Police ...

About front fogs , I will put it , but it's not a priority for now 🥹😉
Scawen
Developer
F14 is on the auto updater now.

So with all the updates there is quite a process now, that takes you right through what is necessary.

For example, if you have not yet set an unlock code, and you start up LFS in version F (or earlier)...

-----
1) Message on entry screen "A new update is available". This flashes for a few seconds. There is a button to visit the forum and another to directly download in game.

2a) Visit forum, you will see the test patch thread.
OR
2b) Click direct download, you go straight to the download screen.
OR
2c) Ignore that and click "List of Hosts", you see the download screen.

3) Install patch manually or using the auto updater.

4) Start LFS. The "Unlock LFS" or "S3 licensed" button is flashing.

5) Click the button. There is a message that you need to update your unlock code and an "external link" icon button.

6) Click the external link, you go to your details page and the option "Set a new unlock code" is in RED text so you can't miss it.
-----

The worst issue remaining is for totally new players, they will install LFS and it already says "A new update is available" on the entry screen, but they cannot use the auto updater.

So that means it is a high priority to release the full version.
McLaud
S2 licensed
Quote from Kova. :Good evening, the answer is here ...

Thanks a lot! :-D With this I am lost, I don't understand this part:

'... and is set automatically and sent to you in an email when you click a link on your details page.'
WestlY
S3 licensed
ojee .. my email acwestly@gmail was hacked ... block from google .. and now ? .. no chance of change [email protected] to [email protected]
why cant I play ? [unlock code]
murtagh
S3 licensed
Nobody told me I had to get a new unlock code ... why didn't you alert us before messing with the passwords?!!!
Last edited by murtagh, .
Scawen
Developer
Good to hear you got it working.

That is interesting. Possible copy and paste issue with LFS when using remote desktop (and maybe other times too). Uhmm

If any Windows programmers want to check my code, please go ahead:


if (OpenClipboard(NULL))
{
HANDLE data = GetClipboardData(CF_UNICODETEXT);

if (data)
{
wchar_t *ch = (wchar_t *) GlobalLock(data);

if (ch)
{
[ ... some code in here to read the characters into a buffer ... ]

GlobalUnlock(data);

[ ... some code in here to add the characters as if typed ... ]
}
}

CloseClipboard();
}

There's more LFS-specific stuff in those [ ... ] but just in case someone spots anything wrong with this clipboard code.
klavesnice
S3 licensed
[quote="Kova.;2131121"]...[/quote
thank you i didnt know that thanks
Scawen
Developer
I understand the distortion is kind of strange, because it is linear. Although 'correct' in one sense it is sort of wrong in another, related to our perception.

Ideally there could be a distortion shader to make this affect more agreeable but this is not available in LFS.

There is a quite extreme setting you can use. It uses multiple renders to create the main scene.

In Options - View ... there is a setting "Multiple screen layout" and you can set up to 5 left screens and 5 right screens. This can approximate a cylindrical render, created from 11 vertical linear renders per frame.

I'd be interested to know if you get any improvement by using that.

You would adjust the view in that case by "Main screen FOV" and "Screen Angle" which should be a fairly small number.
cuni
S3 licensed
Nilex . . .

Quote from Nilex :'Twas a sad day when they patched up that fence. Those red/white barriers too, both such crowd-pleasers.

I still dream about creating a follow up video, showing practical application of the jump during a live race. Those were the times!

* * *

Anyway, my first LFS memory ...

Just so you know, I decided to revive this thread because its your last post on the forums, after a 3 year hiatus (2017)...

@admins - send email to Nilex on befalf of LFS community asking him to come back
pls?
he's probably just waiting for the update . . .
cuni
S3 licensed
Here I am again, beating on the dead horse due to an external reference :
"Microsoft is not on the business of making OS's, the OS is nothing but a data mining tool. I wonder how much money they make on the sale of user data vs total sales of Windows OS" ... "at this point you're paying to be spied on"


Guess " meat is back on the menu boys "
sinanju
S3 licensed
Top Times per vehicle ...


I've also included the 3 layouts I made for the circuit; Perimeter, Short and Long.
J_Valkonen
S3 licensed
Quote from Bass-Driver :Change $modinfo to $modinfo["class"]...

Made the changes.
I still need to exclude front-wheel drive cars. Is there a query for the drivetrain?
is it something like
$modinfo["drive"] = getmoddedcarinfo($userName);
... and then the same if-then logic can be applied in the same way.

## Allowed Drivetrains on the server
## Drivetrain: Rear Wheel Drive [RWD] or Four wheel drive AWD
## List of Drivetrains
## No_drive, //0: No_drive
## Rear_wheel_drive, //1: RWD
## Front_wheel_drive, //2: FWD
## Four_wheel_drive, //3: AWD


Code:
----
Event OnNewPlayerJoin( $userName )

## Allowed tyres on the server
## Tyre types: Super or Normal
## List of Tyre types
## Slick_R1, //0: R1
## Slick_R2, //1: R2
## Slick_R3, //2: R3
## Slick_R4, //3: R4
## Road_super, //4: Super
## Road_normal, //5: Normal
## Hybrid, //6: Hybrid
## Knobbly, //7: Knobbly

$OTFR = GetCurrentPlayerVar( "OldTyreFrontRight" ); # Vanha Rengas etu oikea
$OTRR = GetCurrentPlayerVar( "OldTyreRearRight" ); # Vanha Rengas taka oikea
$TFR = GetCurrentPlayerVar( "TyreFrontRight" ); # Rengas etu oikea
$TRR = GetCurrentPlayerVar( "TyreRearRight" ); # Rengas taka oikea
$FWAdj = GetCurrentPlayerVar( "FrontWheelsAdj" ); # Etu rengas säätö?
$RWAdj = GetCurrentPlayerVar( "RearWheelsAdj" ); # Taka rengas säätö?

IF (($OTRR != "TYRE_ROAD_SUPER" ) && ($OTFR != "TYRE_ROAD_SUPER" ) && ($TRR != "TYRE_ROAD_SUPER" ) && ($TFR != "TYRE_ROAD_SUPER" ) && ($OTRR != "TYRE_ROAD_NORMAL" ) && ($OTFR != "TYRE_ROAD_NORMAL" ) && ($TRR != "TYRE_ROAD_NORMAL" ) && ($TFR != "TYRE_ROAD_NORMAL" ))
THEN
cmdLFS( "/spec " . GetCurrentPlayerVar("UserName") );
openPrivButton( "tyrespec",50,35,100,15,5,15,32, "^7You have been moved to spectators because your ^1disallowed ^7tyres");
openPrivButton( "tyrechange",50,50,100,15,5,15,32, "^7Please change your tyres to ^3Normal ^7or ^3Super" );
# openPrivButton( "tyrechange",50,50,100,15,5,15,32, "^7Please change rear ^7and front ^7tyres to ^3Super" );
ENDIF

## Allowed cars on the server
## Vehicle class: saloon, touring car or GT
## List of correct names of vehicleclasses
## Object, //0: Object
## Touring_Car, //1: Touring car
## Saloon_Car, //2: Saloon car
## Buggy, //3: Buggy
## Formula, //4: Formula
## GT, //5: GT
## Kart, //6: Kart
## Bike, //7: Bike
## Van, //8: Van
## Truck, //9: Truck
## Formula_1, //10: Formula 1
## Formula_SAE //11: Formula SAE

$modinfo["class"] = getmoddedcarinfo($userName);
IF(($modinfo["class"] != "GT" ) && ($modinfo["class"] != "Saloon_Car") && ($modinfo["class"] != "Touring_Car"))
THEN
cmdLFS( "/spec " . GetCurrentPlayerVar("UserName") );
openPrivButton( "classspec",50,35,100,15,5,15,32, "^7You have been moved to spectators because your ^1car ^7does not meet the server ^1requirements.");
openPrivButton( "classchange",50,50,100,15,5,15,32, "^7Allowed vehicles ^7on the server are ^3Saloon, Touring Car, or GT" );
ENDIF
EndEvent
WestlY
S3 licensed
defender control online with cloud proces ... gpedit.msc and control
sinanju
S3 licensed
Made a video of my driving a grass cutter round the 'infields' layout ...

sinanju
S3 licensed
Couldn't resist fiddling ...

sinanju
S3 licensed
As per your suggestion, I've made 2 different versions of the 'Perimeter' layout that now have pit areas. One to the left just before Turn 1, and one to the right ...


If anyoune wants to modify the other 3 versions, then be my guest.

As each layout uses the maximum 3000 objects, you'll need to remove the bales round some of the bends to give you spare objects to use for pits.
LA1 Sinanju GoKart
sinanju
S3 licensed
Go-kart layout with 4 configurations ...



Heavily based on circuit layout on Saipan (co-ordinates: 15.266423936618589, 145.78913892726305).
WildGamerz
S3 licensed
Alright here the program.cs press S to show the buttons and h to hide them ofc and it not even that refined but it better than nothing Smile


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();
}
}
}
}
}


sinanju
S3 licensed
Further update to top times per vehicle ...

sinanju
S3 licensed
The %nl% part means put next lot of text on a new line.

On my server, I have the following as part of a table to see the top time for each car ...



The code for this could look like this ...

<?php 
openPrivButton
( "tt_car_note",$tt_left,$tt_top+142,68,4,3,$tt_time-12,16,"^3This table has to be partially laid out by hand ^8and was last updated: ^019 May 2025"%nl%^1If a driver beats the existing fastest time for a vehicle shown above, then table order may be wrong%nl%^7If a driver sets a time for a vehicle that is *NOT* on this list,"%nl%^2then I will have to manually update the table");
?>
Because I don't want a huge long line of text in my script, my actual code looks like ...
<?php 
openPrivButton
( "tt_car_note",$tt_left,$tt_top+142,68,4,3,$tt_time-12,16,"^3This table has to be partially laid out by hand ^8and was last updated: ^019 May 2025"
. "%nl%^1If a driver beats the existing fastest time for a vehicle shown above, then table order may be wrong"
. "%nl%^7If a driver sets a time for a vehicle that is *NOT* on this list,"
. "%nl%^2then I will have to manually update the table");
?>
To make it more readable in the script, I've put the new lines on seperate lines, and started each line with

. "%nl%

The full stop and quotation mark (. ") tells lapper that the current line should be added to the previous line.
FGED GREDG RDFGDR GSFDG