Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiplayer quickstart #107

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion OmsiExtensions.sln
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TriggersSample", "_OmsiHook
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VideoDemo", "_OmsiHookExamples\VideoDemo\VideoDemo.csproj", "{D94FF6D3-08AA-41CB-B9B9-F82E860E6E96}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClickablePlaneDemo", "_OmsiHookExamples\ClickablePlaneDemo\ClickablePlaneDemo.csproj", "{49428923-732C-4541-8C97-C6D9C004D726}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Multiplayer_Server", "_OmsiHookExamples\Multiplayer_Server\Multiplayer_Server.csproj", "{2D4DDEF6-2C5D-4348-8DCE-33201BFF046C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Multiplayer_Client", "_OmsiHookExamples\Multiplayer_Client\Multiplayer_Client.csproj", "{33EBE989-3BB8-4773-96E6-A7D68E5655CD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClickablePlaneDemo", "_OmsiHookExamples\ClickablePlaneDemo\ClickablePlaneDemo.csproj", "{49428923-732C-4541-8C97-C6D9C004D726}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -157,6 +161,30 @@ Global
{D94FF6D3-08AA-41CB-B9B9-F82E860E6E96}.ReleaseAndDocs|Any CPU.Build.0 = Release|x86
{D94FF6D3-08AA-41CB-B9B9-F82E860E6E96}.ReleaseAndDocs|x86.ActiveCfg = Release|x86
{D94FF6D3-08AA-41CB-B9B9-F82E860E6E96}.ReleaseAndDocs|x86.Build.0 = Release|x86
{2D4DDEF6-2C5D-4348-8DCE-33201BFF046C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D4DDEF6-2C5D-4348-8DCE-33201BFF046C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D4DDEF6-2C5D-4348-8DCE-33201BFF046C}.Debug|x86.ActiveCfg = Debug|Any CPU
{2D4DDEF6-2C5D-4348-8DCE-33201BFF046C}.Debug|x86.Build.0 = Debug|Any CPU
{2D4DDEF6-2C5D-4348-8DCE-33201BFF046C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D4DDEF6-2C5D-4348-8DCE-33201BFF046C}.Release|Any CPU.Build.0 = Release|Any CPU
{2D4DDEF6-2C5D-4348-8DCE-33201BFF046C}.Release|x86.ActiveCfg = Release|Any CPU
{2D4DDEF6-2C5D-4348-8DCE-33201BFF046C}.Release|x86.Build.0 = Release|Any CPU
{2D4DDEF6-2C5D-4348-8DCE-33201BFF046C}.ReleaseAndDocs|Any CPU.ActiveCfg = Release|Any CPU
{2D4DDEF6-2C5D-4348-8DCE-33201BFF046C}.ReleaseAndDocs|Any CPU.Build.0 = Release|Any CPU
{2D4DDEF6-2C5D-4348-8DCE-33201BFF046C}.ReleaseAndDocs|x86.ActiveCfg = Release|Any CPU
{2D4DDEF6-2C5D-4348-8DCE-33201BFF046C}.ReleaseAndDocs|x86.Build.0 = Release|Any CPU
{33EBE989-3BB8-4773-96E6-A7D68E5655CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{33EBE989-3BB8-4773-96E6-A7D68E5655CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{33EBE989-3BB8-4773-96E6-A7D68E5655CD}.Debug|x86.ActiveCfg = Debug|Any CPU
{33EBE989-3BB8-4773-96E6-A7D68E5655CD}.Debug|x86.Build.0 = Debug|Any CPU
{33EBE989-3BB8-4773-96E6-A7D68E5655CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{33EBE989-3BB8-4773-96E6-A7D68E5655CD}.Release|Any CPU.Build.0 = Release|Any CPU
{33EBE989-3BB8-4773-96E6-A7D68E5655CD}.Release|x86.ActiveCfg = Release|Any CPU
{33EBE989-3BB8-4773-96E6-A7D68E5655CD}.Release|x86.Build.0 = Release|Any CPU
{33EBE989-3BB8-4773-96E6-A7D68E5655CD}.ReleaseAndDocs|Any CPU.ActiveCfg = Release|Any CPU
{33EBE989-3BB8-4773-96E6-A7D68E5655CD}.ReleaseAndDocs|Any CPU.Build.0 = Release|Any CPU
{33EBE989-3BB8-4773-96E6-A7D68E5655CD}.ReleaseAndDocs|x86.ActiveCfg = Release|Any CPU
{33EBE989-3BB8-4773-96E6-A7D68E5655CD}.ReleaseAndDocs|x86.Build.0 = Release|Any CPU
{49428923-732C-4541-8C97-C6D9C004D726}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{49428923-732C-4541-8C97-C6D9C004D726}.Debug|Any CPU.Build.0 = Debug|Any CPU
{49428923-732C-4541-8C97-C6D9C004D726}.Debug|x86.ActiveCfg = Debug|Any CPU
Expand All @@ -178,6 +206,8 @@ Global
{47659503-9923-4E74-AD26-103C1F9FF2B0} = {3F0BF441-D76C-4E0D-A5A8-B20895438EA5}
{1DF326AE-4D10-4545-B36A-5622B76987EC} = {3F0BF441-D76C-4E0D-A5A8-B20895438EA5}
{D94FF6D3-08AA-41CB-B9B9-F82E860E6E96} = {3F0BF441-D76C-4E0D-A5A8-B20895438EA5}
{2D4DDEF6-2C5D-4348-8DCE-33201BFF046C} = {3F0BF441-D76C-4E0D-A5A8-B20895438EA5}
{33EBE989-3BB8-4773-96E6-A7D68E5655CD} = {3F0BF441-D76C-4E0D-A5A8-B20895438EA5}
{49428923-732C-4541-8C97-C6D9C004D726} = {3F0BF441-D76C-4E0D-A5A8-B20895438EA5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
Expand Down
7 changes: 7 additions & 0 deletions OmsiHook/OmsiStructs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,15 @@ public D3DVector(float x, float y, float z)
public static implicit operator Vector3(D3DVector v) => new(v.x, v.y, v.z);
public static implicit operator D3DVector(Vector3 v) => new() { x = v.X, y = v.Y, z = v.Z };
}

public struct D3DXVector2
{
public float x, y;

public override readonly string ToString() => $"[{x,8:F3}, {y,8:F3}]";

public static implicit operator Vector2(D3DXVector2 v) => new(v.x, v.y);
public static implicit operator D3DXVector2(Vector2 v) => new() { x = v.X, y = v.Y };
}

/// <summary>
Expand Down Expand Up @@ -107,6 +111,9 @@ public struct D3DXQuaternion
public float x, y, z, w;

public override readonly string ToString() => $"[{x,8:F3}, {y,8:F3}, {z,8:F3}, {w,8:F3}]";

public static implicit operator Quaternion(D3DXQuaternion v) => new (v.x, v.y, v.z, v.w);
public static implicit operator D3DXQuaternion(Quaternion v) => new() { x=v.X, y=v.Y, z=v.Z, w=v.W };
}

/// <summary>
Expand Down
24 changes: 20 additions & 4 deletions OmsiHook/WrappedOmsiClasses/OmsiMapObjInst.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,16 @@ public float Scale
get => Memory.ReadMemory<float>(Address + 0x60);
set => Memory.WriteMemory(Address + 0x60, value);
}
public D3DMatrix RelMatrix => Memory.ReadMemory<D3DMatrix>(Address + 0x64);
public D3DMatrix Used_RelVec => Memory.ReadMemory<D3DMatrix>(Address + 0x68);
public D3DMatrix RelMatrix
{
get => Memory.ReadMemory<D3DMatrix>(Memory.ReadMemory<int>(Address + 0x64));
set => Memory.WriteMemory(Memory.ReadMemory<int>(Address + 0x64), value);
}
public D3DVector Used_RelVec
{
get => Memory.ReadMemory<D3DVector>(Address + 0x68);
set => Memory.WriteMemory(Address + 0x68, value);
}
public int Kachel
{
get => Memory.ReadMemory<int>(Address + 0x74);
Expand All @@ -40,7 +48,15 @@ public D3DMatrix AbsPosition
get => Memory.ReadMemory<D3DMatrix>(Address + 0x78);
set => Memory.WriteMemory(Address + 0x78, value);
}
public D3DMatrix AbsPosition_Inv => Memory.ReadMemory<D3DMatrix>(Address + 0xb8);
public D3DMatrix AbsPosition_ThreadFree => Memory.ReadMemory<D3DMatrix>(Address + 0xf8);
public D3DMatrix AbsPosition_Inv
{
get => Memory.ReadMemory<D3DMatrix>(Address + 0xb8);
set => Memory.WriteMemory(Address + 0xb8, value);
}
public D3DMatrix AbsPosition_ThreadFree
{
get => Memory.ReadMemory<D3DMatrix>(Address + 0xf8);
set => Memory.WriteMemory(Address + 0xf8, value);
}
}
}
85 changes: 85 additions & 0 deletions _OmsiHookExamples/Multiplayer_Client/GameClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using OmsiHook;
using System.Numerics;
using System.Runtime.CompilerServices;

namespace OmsiHook.Examples.Multiplayer_Client
{
internal class GameClient
{
public Tuple<int, long, long> LastPing;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tuple<int, long, long> LastPing --> (int xxx, long yyy, long zzz) lastPing

private OmsiHook omsi;
private Dictionary<int, OmsiRoadVehicleInst> Vehicles = new Dictionary<int, OmsiRoadVehicleInst>();
private int frameCounter = 0;
public GameClient()
{
omsi = new OmsiHook();
omsi.AttachToOMSI().Wait();
var OMSIRM = omsi.RemoteMethods;
OMSIRM.OmsiSetCriticalSectionLock(omsi.Globals.ProgamManager.CS_MakeVehiclePtr).ContinueWith((_) =>
{
OMSIRM.MakeVehicle(@"Vehicles\GPM_MAN_LionsCity_M\MAN_A47.bus", __copyToMainList: true).ContinueWith((id) =>
{
Console.WriteLine($"Spawned Vehicle ID: {id.Result}");
Vehicles[0] = omsi.Globals.RoadVehicles.FList[1];
OMSIRM.OmsiReleaseCriticalSectionLock(omsi.Globals.ProgamManager.CS_MakeVehiclePtr).ContinueWith((_) => Console.WriteLine($"Unlock"));
});
});
}

public void UpdateVehicles(OMSIMPMessages.Vehicle_Position_Update update)
{
if (Vehicles.TryGetValue(update.ID, out var vehicle))
{
if (frameCounter % 20 == 0)
vehicle.Position = update.position;
vehicle.Rotation = update.rotation;
vehicle.Velocity = update.velocity;
vehicle.MyKachelPnt = update.tile;
vehicle.RelMatrix = update.relmatrix;
vehicle.Acc_Local = update.acclocal;

var posMat = Matrix4x4.CreateFromQuaternion(update.rotation);
posMat.Translation = update.position;
var absPosMat = Matrix4x4.Multiply(posMat, Matrix4x4.Identity/*update.relmatrix*/);
Matrix4x4.Invert(absPosMat, out var absPosMatInv);

vehicle.Pos_Mat = posMat;
vehicle.AbsPosition = absPosMat;
vehicle.AbsPosition_Inv = absPosMatInv;
vehicle.Used_RelVec = ((Matrix4x4)update.relmatrix).Translation;
vehicle.AI_Blinker_L = 1;
vehicle.AI_Blinker_R = 1;
vehicle.AI_var = 1;
frameCounter++;
}
else
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tidy this up a bit.

{

}
}

public void Tick(Telepathy.Client client)
{
if (omsi.Globals.PlayerVehicle.IsNull || !client.Connected)
return;

var vehicle = omsi.Globals.PlayerVehicle;
Console.WriteLine($"\x1b[8;0HP:{vehicle.Position}/{vehicle.MyKachelPnt}\x1b[9;0HR:{vehicle.Rotation}\x1b[10;0HV:{vehicle.Velocity}\x1b[11;0HB:{vehicle.Acc_Local} / {((Vehicles.ContainsKey(0)) ? (Vehicles[0].Acc_Local.ToString()) : "-")}");
byte[] buff = new byte[Unsafe.SizeOf<OMSIMPMessages.Player_Position_Update>() + 4];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To reduce GC pressure, allocate a single byte array and just reuse it. (Use AsSpan() to get a slice of the buffer)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also applies to the message parser class...

int out_pos = 0;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming conventions

FastBinaryWriter.Write(buff, ref out_pos, OMSIMPMessages.Messages.UPDATE_PLAYER_POSITION);
FastBinaryWriter.Write(buff, ref out_pos, new OMSIMPMessages.Player_Position_Update()
{
position = vehicle.Position,
tile = vehicle.MyKachelPnt,
rotation = vehicle.Rotation,
velocity = vehicle.Velocity,
relmatrix = vehicle.RelMatrix,
acclocal = vehicle.Acc_Local
//vehicle = vehicle.RoadVehicle.MyPath

}); ;
client.Send(buff);
}
}
}
49 changes: 49 additions & 0 deletions _OmsiHookExamples/Multiplayer_Client/MessageParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
namespace OmsiHook.Examples.Multiplayer_Client
{
internal class MessageParser
{
readonly static int MAJOR_VERSION = 1;
readonly static int MINOR_VERSION = 0;
public static void ParseMessage(byte[] message, Telepathy.Client client, GameClient gameClient)
{
message.AsSpan<byte>(0, message.Length);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use this lovely span you just made?

int parse_pos = 0;
switch ((OMSIMPMessages.Messages)FastBinaryReader.ReadI32(message, ref parse_pos))
{
case OMSIMPMessages.Messages.PING:
{
byte[] buff = new byte[8];
int out_pos = 0;
FastBinaryWriter.Write(buff, ref out_pos, OMSIMPMessages.Messages.PONG);
FastBinaryWriter.Write(buff, ref out_pos, FastBinaryReader.ReadI32(message, ref parse_pos));
client.Send(buff);
}
break;
case OMSIMPMessages.Messages.PONG:
{
if (gameClient.LastPing.Item1 == FastBinaryReader.ReadI32(message, ref parse_pos))
{
gameClient.LastPing = new Tuple<int, long, long>(gameClient.LastPing.Item1, gameClient.LastPing.Item2, DateTime.Now.Ticks);
Console.WriteLine($"Last Ping time: {((gameClient.LastPing.Item3 - gameClient.LastPing.Item2) / 10000):f3}ms");
}
}
break;
case OMSIMPMessages.Messages.REPLY_VERSION:
{
Console.WriteLine($"Server Version: {FastBinaryReader.ReadI32(message, ref parse_pos)}.{FastBinaryReader.ReadI32(message, ref parse_pos):D2}");
}
break;
case OMSIMPMessages.Messages.UPDATE_VEHICLE_POSITION:
{
gameClient.UpdateVehicles(FastBinaryReader.Read<OMSIMPMessages.Vehicle_Position_Update>(message, ref parse_pos));
}
break;
default:
{
Console.WriteLine($"Data received {message}");
break;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please be consistent with where you put your break statements.

}
}
}
}
}
23 changes: 23 additions & 0 deletions _OmsiHookExamples/Multiplayer_Client/Multiplayer_Client.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<StartupObject>OMSIClient</StartupObject>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="OmsiHook" Version="2.5.4" />
<PackageReference Include="Telepathy" Version="1.0.341" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\Multiplayer_Server\Lib\OMSIMPMessages.cs" Link="Lib\OMSIMPMessages.cs" />
<Compile Include="..\Multiplayer_Server\Lib\FastBinaryReader.cs" Link="Lib\FastBinaryReader.cs" />
<Compile Include="..\Multiplayer_Server\Lib\FastBinaryWriter.cs" Link="Lib\FastBinaryWriter.cs" />
</ItemGroup>

</Project>
48 changes: 48 additions & 0 deletions _OmsiHookExamples/Multiplayer_Client/OMSIClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using OmsiHook.Examples.Multiplayer_Client;
using Telepathy;

class OMSIClient
{
static void Main(string[] args)
{
Client client = new Client();
GameClient gameClient = new GameClient();
client.Connect("127.0.0.1", 1337);


while (true)
{
Telepathy.Message msg;
while (client.GetNextMessage(out msg))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realised that the version of Telepathy on NuGet is really out of date (from 2019)...
Latest build is here: https://github.com/MirrorNetworking/Telepathy/actions/runs/10029006044

Anyway, is this a blocking method? If so, why do you thread sleep at the end of this method?

{
switch (msg.eventType)
{
case EventType.Connected:
{
Console.WriteLine($"Client connected: {msg.connectionId}");
byte[] buff = new byte[4];
int out_pos = 0;
FastBinaryWriter.Write(buff, ref out_pos, OMSIMPMessages.Messages.REQUEST_VERSION);
client.Send(buff);
byte[] buff2 = new byte[8];
out_pos = 0;
gameClient.LastPing = new Tuple<int, long, long>(787, DateTime.Now.Ticks, 0);
FastBinaryWriter.Write(buff2, ref out_pos, OMSIMPMessages.Messages.PING);
FastBinaryWriter.Write(buff2, ref out_pos, 787);
client.Send(buff2);
}
break;
case EventType.Data:
MessageParser.ParseMessage(msg.data, client, gameClient);
break;
case EventType.Disconnected:
Console.WriteLine($"Client disconnected: {msg.connectionId}");
break;
}
}
gameClient.Tick(client);
System.Threading.Thread.Sleep(33);
}
client.Disconnect();
}
}
17 changes: 17 additions & 0 deletions _OmsiHookExamples/Multiplayer_Server/Client.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OmsiHook.Examples.Multiplayer_Server
{
internal class Client
{
public int ClientId { get; private set; }
public Client(int clientId)
{
ClientId = clientId;
}
}
}
Loading
Loading