Map rotation & gamemode rotation & room settings implementation

This commit is contained in:
MrOkiDoki 2023-08-01 22:11:52 +03:00
parent 6200ad380a
commit ec935486de
29 changed files with 642 additions and 64 deletions

View File

@ -0,0 +1,69 @@
namespace BattleBitAPI.Common
{
public class Map : IEquatable<string>, IEquatable<Map>
{
public string Name { get; private set; }
public Map(string name)
{
Name = name;
}
public override string ToString()
{
return this.Name;
}
public bool Equals(string other)
{
if (other == null)
return false;
return this.Name.Equals(other);
}
public bool Equals(Map other)
{
if (other == null)
return false;
return this.Name.Equals(other.Name);
}
public static bool operator ==(string left, Map right)
{
bool leftNull = object.ReferenceEquals(left, null);
bool rightNull = object.ReferenceEquals(right, null);
if (leftNull && rightNull)
return true;
if (leftNull || rightNull)
return false;
return right.Name.Equals(left);
}
public static bool operator !=(string left, Map right)
{
bool leftNull = object.ReferenceEquals(left, null);
bool rightNull = object.ReferenceEquals(right, null);
if (leftNull && rightNull)
return true;
if (leftNull || rightNull)
return false;
return right.Name.Equals(left);
}
public static bool operator ==(Map right, string left)
{
bool leftNull = object.ReferenceEquals(left, null);
bool rightNull = object.ReferenceEquals(right, null);
if (leftNull && rightNull)
return true;
if (leftNull || rightNull)
return false;
return right.Name.Equals(left);
}
public static bool operator !=(Map right, string left)
{
bool leftNull = object.ReferenceEquals(left, null);
bool rightNull = object.ReferenceEquals(right, null);
if (leftNull && rightNull)
return true;
if (leftNull || rightNull)
return false;
return right.Name.Equals(left);
}
}
}

View File

@ -0,0 +1,23 @@
namespace BattleBitAPI.Common
{
public enum ReportReason
{
Cheating_WallHack = 0,
Cheating_Aimbot = 1,
Cheating_Speedhack = 2,
Racism_Discrimination_Text = 3,
Racism_Discrimination_Voice = 4,
Spamming_Text = 5,
Spamming_Voice = 6,
Bad_Language_Text = 7,
Bad_Language_Voice = 8,
Griefing = 9,
Exploiting = 10,
OtherToxicBehaviour = 11,
UserProfileNamePicture = 12,
}
}

View File

@ -10,6 +10,7 @@
ExecuteCommand = 10,
SendPlayerStats = 11,
SpawnPlayer = 12,
SetNewRoomSettings = 13,
PlayerConnected = 50,
PlayerDisconnected = 51,
@ -23,5 +24,10 @@
OnPlayerLeftSquad = 59,
OnPlayerChangedTeam = 60,
OnPlayerRequestingToSpawn = 61,
OnPlayerReport = 62,
OnPlayerSpawn = 63,
OnPlayerDie = 64,
NotifyNewMapRotation = 65,
NotifyNewGamemodeRotation = 66,
}
}

View File

@ -12,8 +12,19 @@ namespace BattleBitAPI
public GameRole Role { get; internal set; }
public Team Team { get; internal set; }
public Squads Squad { get; internal set; }
public bool IsAlive { get; internal set; }
public PlayerLoadout CurrentLoadout { get; internal set; } = new PlayerLoadout();
public PlayerWearings CurrentWearings { get; internal set; } = new PlayerWearings();
internal virtual void OnInitialized()
internal virtual async Task OnInitialized()
{
}
internal virtual async Task OnSpawned()
{
}
internal virtual async Task OnDied()
{
}
@ -58,6 +69,10 @@ namespace BattleBitAPI
{
}
public void SpawnPlayer(PlayerLoadout loadout, PlayerWearings wearings, Vector3 position, Vector3 lookDirection, PlayerStand stand, float spawnProtection)
{
GameServer.SpawnPlayer(this, loadout, wearings, position, lookDirection, stand, spawnProtection);
}
public override string ToString()
{

View File

@ -1,5 +1,7 @@
using System.Net;
using System.Net.Sockets;
using System.Numerics;
using System.Text;
using BattleBitAPI.Common;
using BattleBitAPI.Common.Extentions;
using BattleBitAPI.Networking;
@ -30,6 +32,9 @@ namespace BattleBitAPI.Server
public int MaxPlayers { get; private set; }
public string LoadingScreenText { get; private set; }
public string ServerRulesText { get; private set; }
public ServerSettings Settings { get; private set; }
public MapRotation MapRotation { get; private set; }
public GamemodeRotation GamemodeRotation { get; private set; }
/// <summary>
/// Reason why connection was terminated.
@ -47,6 +52,7 @@ namespace BattleBitAPI.Server
private long mLastPackageSent;
private bool mIsDisposed;
private mInternalResources mInternal;
private StringBuilder mBuilder;
// ---- Constrction ----
public GameServer(TcpClient socket, mInternalResources resources, Func<GameServer, mInternalResources, Common.Serialization.Stream, Task> func, IPAddress iP, int port, bool isPasswordProtected, string serverName, string gamemode, string map, MapSize mapSize, MapDayNight dayNight, int currentPlayers, int inQueuePlayers, int maxPlayers, string loadingScreenText, string serverRulesText)
@ -55,6 +61,7 @@ namespace BattleBitAPI.Server
this.Socket = socket;
this.mInternal = resources;
this.mExecutionFunc = func;
this.mBuilder = new StringBuilder(1024);
this.GameIP = iP;
this.GamePort = port;
@ -94,7 +101,10 @@ namespace BattleBitAPI.Server
this.mLastPackageReceived = Extentions.TickCount;
this.mLastPackageSent = Extentions.TickCount;
this.ServerHash = (ulong)(port << 32) | iP.ToUInt();
this.ServerHash = ((ulong)port << 32) | (ulong)iP.ToUInt();
this.Settings = new ServerSettings(resources);
this.MapRotation = new MapRotation(resources);
this.GamemodeRotation = new GamemodeRotation(resources);
}
// ---- Tick ----
@ -105,6 +115,47 @@ namespace BattleBitAPI.Server
if (this.mIsDisposed)
return;
if (this.mInternal.IsDirtySettings)
{
this.mInternal.IsDirtySettings = false;
//Send new settings
using (var pck = Common.Serialization.Stream.Get())
{
pck.Write((byte)NetworkCommuncation.SetNewRoomSettings);
this.mInternal.Settings.Write(pck);
WriteToSocket(pck);
}
}
if (this.mInternal.MapRotationDirty)
{
this.mInternal.MapRotationDirty = false;
this.mBuilder.Clear();
this.mBuilder.Append("setmaprotation ");
lock (this.mInternal.MapRotation)
foreach (var map in this.mInternal.MapRotation)
{
this.mBuilder.Append(map);
this.mBuilder.Append(',');
}
this.ExecuteCommand(this.mBuilder.ToString());
}
if (this.mInternal.GamemodeRotationDirty)
{
this.mInternal.GamemodeRotationDirty = false;
this.mBuilder.Clear();
this.mBuilder.Append("setgamemoderotation ");
lock (this.mInternal.GamemodeRotation)
foreach (var gamemode in this.mInternal.GamemodeRotation)
{
this.mBuilder.Append(gamemode);
this.mBuilder.Append(',');
}
this.ExecuteCommand(this.mBuilder.ToString());
}
try
{
//Are we still connected on socket level?
@ -351,6 +402,34 @@ namespace BattleBitAPI.Server
{
SetRoleTo(player.SteamID, role);
}
public void SpawnPlayer(ulong steamID, PlayerLoadout loadout, PlayerWearings wearings, Vector3 position, Vector3 lookDirection, PlayerStand stand, float spawnProtection)
{
var request = new PlayerSpawnRequest()
{
Loadout = loadout,
Wearings = wearings,
RequestedPoint = PlayerSpawningPosition.Null,
SpawnPosition = position,
LookDirection = lookDirection,
SpawnStand = stand,
SpawnProtection = spawnProtection
};
//Respond back.
using (var response = Common.Serialization.Stream.Get())
{
response.Write((byte)NetworkCommuncation.SpawnPlayer);
response.Write(steamID);
request.Write(response);
response.Write((ushort)0);
WriteToSocket(response);
}
}
public void SpawnPlayer(Player player, PlayerLoadout loadout, PlayerWearings wearings, Vector3 position, Vector3 lookDirection, PlayerStand stand, float spawnProtection)
{
SpawnPlayer(player.SteamID, loadout, wearings, position, lookDirection, stand, spawnProtection);
}
// ---- Closing ----
private void mClose(string reason)
@ -401,6 +480,15 @@ namespace BattleBitAPI.Server
{
public Dictionary<ulong, Player> Players = new Dictionary<ulong, Player>(254);
public mRoomSettings Settings = new mRoomSettings();
public bool IsDirtySettings;
public HashSet<string> MapRotation = new HashSet<string>(8);
public bool MapRotationDirty = false;
public HashSet<string> GamemodeRotation = new HashSet<string>(8);
public bool GamemodeRotationDirty = false;
public void AddPlayer<TPlayer>(TPlayer player) where TPlayer : Player
{
lock (Players)
@ -420,5 +508,56 @@ namespace BattleBitAPI.Server
return Players.TryGetValue(steamID, out result);
}
}
public class mRoomSettings
{
public float DamageMultiplier = 1.0f;
public bool BleedingEnabled = true;
public bool StamineEnabled = false;
public bool FriendlyFireEnabled = false;
public bool HideMapVotes = true;
public bool OnlyWinnerTeamCanVote = false;
public bool HitMarkersEnabled = true;
public bool PointLogEnabled = true;
public bool SpectatorEnabled = true;
public byte MedicLimitPerSquad = 8;
public byte EngineerLimitPerSquad = 8;
public byte SupportLimitPerSquad = 8;
public byte ReconLimitPerSquad = 8;
public void Write(Common.Serialization.Stream ser)
{
ser.Write(this.DamageMultiplier);
ser.Write(this.BleedingEnabled);
ser.Write(this.StamineEnabled);
ser.Write(this.FriendlyFireEnabled);
ser.Write(this.HideMapVotes);
ser.Write(this.OnlyWinnerTeamCanVote);
ser.Write(this.HitMarkersEnabled);
ser.Write(this.PointLogEnabled);
ser.Write(this.SpectatorEnabled);
ser.Write(this.MedicLimitPerSquad);
ser.Write(this.EngineerLimitPerSquad);
ser.Write(this.SupportLimitPerSquad);
ser.Write(this.ReconLimitPerSquad);
}
public void Read(Common.Serialization.Stream ser)
{
this.DamageMultiplier = ser.ReadFloat();
this.BleedingEnabled = ser.ReadBool();
this.StamineEnabled = ser.ReadBool();
this.FriendlyFireEnabled = ser.ReadBool();
this.HideMapVotes = ser.ReadBool();
this.OnlyWinnerTeamCanVote = ser.ReadBool();
this.HitMarkersEnabled = ser.ReadBool();
this.PointLogEnabled = ser.ReadBool();
this.SpectatorEnabled = ser.ReadBool();
this.MedicLimitPerSquad = ser.ReadInt8();
this.EngineerLimitPerSquad = ser.ReadInt8();
this.SupportLimitPerSquad = ser.ReadInt8();
this.ReconLimitPerSquad = ser.ReadInt8();
}
}
}
}

View File

@ -0,0 +1,38 @@
namespace BattleBitAPI.Server
{
public class GamemodeRotation
{
private GameServer.mInternalResources mResources;
public GamemodeRotation(GameServer.mInternalResources resources)
{
mResources = resources;
}
public IEnumerable<string> GetGamemodeRotation()
{
lock (mResources.GamemodeRotation)
return new List<string>(mResources.GamemodeRotation);
}
public bool InRotation(string gamemode)
{
lock (mResources.GamemodeRotation)
return mResources.GamemodeRotation.Contains(gamemode);
}
public bool RemoveFromRotation(string gamemode)
{
lock (mResources.GamemodeRotation)
if (!mResources.GamemodeRotation.Remove(gamemode))
return false;
mResources.GamemodeRotationDirty = true;
return true;
}
public bool AddToRotation(string gamemode)
{
lock (mResources.GamemodeRotation)
if (!mResources.GamemodeRotation.Add(gamemode))
return false;
mResources.GamemodeRotationDirty = true;
return true;
}
}
}

View File

@ -0,0 +1,44 @@
namespace BattleBitAPI.Server
{
public class MapRotation
{
private GameServer.mInternalResources mResources;
public MapRotation(GameServer.mInternalResources resources)
{
mResources = resources;
}
public IEnumerable<string> GetMapRotation()
{
lock (mResources.MapRotation)
return new List<string>(mResources.MapRotation);
}
public bool InRotation(string map)
{
map = map.ToUpperInvariant();
lock (mResources.MapRotation)
return mResources.MapRotation.Contains(map);
}
public bool RemoveFromRotation(string map)
{
map = map.ToUpperInvariant();
lock (mResources.MapRotation)
if (!mResources.MapRotation.Remove(map))
return false;
mResources.MapRotationDirty = true;
return true;
}
public bool AddToRotation(string map)
{
map = map.ToUpperInvariant();
lock (mResources.MapRotation)
if (!mResources.MapRotation.Add(map))
return false;
mResources.MapRotationDirty = true;
return true;
}
}
}

View File

@ -0,0 +1,84 @@
namespace BattleBitAPI.Server
{
public class ServerSettings
{
private GameServer.mInternalResources mResources;
public ServerSettings(GameServer.mInternalResources resources)
{
mResources = resources;
}
public float DamageMultiplier
{
get => mResources.Settings.DamageMultiplier;
set
{
mResources.Settings.DamageMultiplier = value;
mResources.IsDirtySettings = true;
}
}
public bool BleedingEnabled
{
get => mResources.Settings.BleedingEnabled;
set
{
mResources.Settings.BleedingEnabled = value;
mResources.IsDirtySettings = true;
}
}
public bool StamineEnabled
{
get => mResources.Settings.StamineEnabled;
set
{
mResources.Settings.StamineEnabled = value;
mResources.IsDirtySettings = true;
}
}
public bool FriendlyFireEnabled
{
get => mResources.Settings.FriendlyFireEnabled;
set
{
mResources.Settings.FriendlyFireEnabled = value;
mResources.IsDirtySettings = true;
}
}
public bool OnlyWinnerTeamCanVote
{
get => mResources.Settings.OnlyWinnerTeamCanVote;
set
{
mResources.Settings.OnlyWinnerTeamCanVote = value;
mResources.IsDirtySettings = true;
}
}
public bool HitMarkersEnabled
{
get => mResources.Settings.HitMarkersEnabled;
set
{
mResources.Settings.HitMarkersEnabled = value;
mResources.IsDirtySettings = true;
}
}
public bool PointLogEnabled
{
get => mResources.Settings.PointLogEnabled;
set
{
mResources.Settings.PointLogEnabled = value;
mResources.IsDirtySettings = true;
}
}
public bool SpectatorEnabled
{
get => mResources.Settings.SpectatorEnabled;
set
{
mResources.Settings.SpectatorEnabled = value;
mResources.IsDirtySettings = true;
}
}
}
}

View File

@ -17,6 +17,11 @@ namespace BattleBitAPI.Server
public int ListeningPort { get; private set; }
// --- Events ---
/// <summary>
/// Fired when game server is ticking (~100hz)<br/>
/// </summary>
public Func<GameServer, Task> OnGameServerTick { get; set; }
/// <summary>
/// Fired when an attempt made to connect to the server.<br/>
/// Default, any connection attempt will be accepted
@ -194,6 +199,36 @@ namespace BattleBitAPI.Server
/// </value>
public Func<TPlayer, PlayerSpawnRequest, Task<PlayerSpawnRequest>> OnPlayerSpawning { get; set; }
/// <summary>
/// Fired when a player is spawns
/// </summary>
///
/// <remarks>
/// TPlayer - The player<br/>
/// </remarks>
public Func<TPlayer, Task> OnPlayerSpawned { get; set; }
/// <summary>
/// Fired when a player dies
/// </summary>
///
/// <remarks>
/// TPlayer - The player<br/>
/// </remarks>
public Func<TPlayer, Task> OnPlayerDied { get; set; }
/// <summary>
/// Fired when a player reports another player.
/// </summary>
///
/// <remarks>
/// TPlayer - The reporter player<br/>
/// TPlayer - The reported player<br/>
/// ReportReason - The reason of report<br/>
/// String - Additional detail<br/>
/// </remarks>
public Func<TPlayer, TPlayer, ReportReason, string, Task> OnPlayerReported { get; set; }
// --- Private ---
private TcpListener mSocket;
private Dictionary<ulong, GameServer> mActiveConnections;
@ -460,6 +495,47 @@ namespace BattleBitAPI.Server
resources = new GameServer.mInternalResources();
server = new GameServer(client, resources, mExecutePackage, ip, gamePort, isPasswordProtected, serverName, gameMode, gamemap, size, dayNight, currentPlayers, queuePlayers, maxPlayers, loadingScreenText, serverRulesText);
//Room settings
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 4, source.Token))
throw new Exception("Unable to read the room size");
int roomSize = (int)readStream.ReadUInt32();
readStream.Reset();
if (!await networkStream.TryRead(readStream, roomSize, source.Token))
throw new Exception("Unable to read the room");
resources.Settings.Read(readStream);
}
//Map&gamemode rotation
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 4, source.Token))
throw new Exception("Unable to read the map&gamemode rotation size");
int rotationSize = (int)readStream.ReadUInt32();
readStream.Reset();
if (!await networkStream.TryRead(readStream, rotationSize, source.Token))
throw new Exception("Unable to read the map&gamemode");
uint count = readStream.ReadUInt32();
while (count > 0)
{
count--;
if (readStream.TryReadString(out var item))
resources.MapRotation.Add(item.ToUpperInvariant());
}
count = readStream.ReadUInt32();
while (count > 0)
{
count--;
if (readStream.TryReadString(out var item))
resources.GamemodeRotation.Add(item);
}
}
//Client Count
int clientCount = 0;
{
@ -530,16 +606,45 @@ namespace BattleBitAPI.Server
role = (GameRole)readStream.ReadInt8();
}
var loadout = new PlayerLoadout();
var wearings = new PlayerWearings();
//IsAlive
bool isAlive;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 1, source.Token))
throw new Exception("Unable to read the isAlive");
isAlive = readStream.ReadBool();
}
//Loadout + Wearings
if (isAlive)
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 4, source.Token))
throw new Exception("Unable to read the LoadoutSize");
int loadoutSize = (int)readStream.ReadUInt32();
readStream.Reset();
if (!await networkStream.TryRead(readStream, loadoutSize, source.Token))
throw new Exception("Unable to read the Loadout + Wearings");
loadout.Read(readStream);
wearings.Read(readStream);
}
TPlayer player = Activator.CreateInstance<TPlayer>();
player.SteamID = steamid;
player.Name = username;
player.GameServer = server;
player.Team = team;
player.Squad = squad;
player.Role = role;
player.IsAlive = isAlive;
player.CurrentLoadout = loadout;
player.CurrentWearings = wearings;
player.OnInitialized();
await player.OnInitialized();
resources.AddPlayer(player);
}
@ -618,8 +723,10 @@ namespace BattleBitAPI.Server
{
while (server.IsConnected)
{
if (OnGameServerTick != null)
await OnGameServerTick(server);
await server.Tick();
await Task.Delay(1);
await Task.Delay(10);
}
if (OnGameServerDisconnected != null && !server.ReconnectFlag)
@ -658,7 +765,7 @@ namespace BattleBitAPI.Server
player.Squad = squad;
player.Role = role;
player.OnInitialized();
await player.OnInitialized();
resources.AddPlayer(player);
if (OnPlayerConnected != null)
@ -858,6 +965,7 @@ namespace BattleBitAPI.Server
var request = new PlayerSpawnRequest();
request.Read(stream);
ushort vehicleID = stream.ReadUInt16();
if (resources.TryGetPlayer(steamID, out var client))
if (this.OnPlayerSpawning != null)
@ -869,11 +977,112 @@ namespace BattleBitAPI.Server
response.Write((byte)NetworkCommuncation.SpawnPlayer);
response.Write(steamID);
request.Write(response);
response.Write(vehicleID);
server.WriteToSocket(response);
}
}
break;
}
case NetworkCommuncation.OnPlayerReport:
{
if (stream.CanRead(8 + 8 + 1 + 2))
{
ulong reporter = stream.ReadUInt64();
ulong reported = stream.ReadUInt64();
ReportReason reason = (ReportReason)stream.ReadInt8();
stream.TryReadString(out var additionalInfo);
if (resources.TryGetPlayer(reporter, out var reporterClient))
{
if (resources.TryGetPlayer(reported, out var reportedClient))
{
if (OnPlayerReported != null)
await OnPlayerReported.Invoke((TPlayer)reporterClient, (TPlayer)reportedClient, reason, additionalInfo);
}
}
}
break;
}
case NetworkCommuncation.OnPlayerSpawn:
{
if (stream.CanRead(8 + 2))
{
ulong reporter = stream.ReadUInt64();
if (resources.TryGetPlayer(reporter, out var client))
{
var loadout = new PlayerLoadout();
loadout.Read(stream);
client.CurrentLoadout = loadout;
var wearings = new PlayerWearings();
wearings.Read(stream);
client.CurrentWearings = wearings;
client.IsAlive = true;
await client.OnSpawned();
if (OnPlayerSpawned != null)
await OnPlayerSpawned.Invoke((TPlayer)client);
}
}
break;
}
case NetworkCommuncation.OnPlayerDie:
{
if (stream.CanRead(8))
{
ulong reporter = stream.ReadUInt64();
if (resources.TryGetPlayer(reporter, out var client))
{
client.CurrentLoadout = new PlayerLoadout();
client.CurrentWearings = new PlayerWearings();
client.IsAlive = false;
await client.OnDied();
if (OnPlayerDied != null)
await OnPlayerDied.Invoke((TPlayer)client);
}
}
break;
}
case NetworkCommuncation.NotifyNewMapRotation:
{
if (stream.CanRead(4))
{
uint count = stream.ReadUInt32();
lock (resources.MapRotation)
{
resources.MapRotation.Clear();
while (count > 0)
{
count--;
if (stream.TryReadString(out var map))
resources.MapRotation.Add(map.ToUpperInvariant());
}
}
}
break;
}
case NetworkCommuncation.NotifyNewGamemodeRotation:
{
if (stream.CanRead(4))
{
uint count = stream.ReadUInt32();
lock (resources.GamemodeRotation)
{
resources.GamemodeRotation.Clear();
while (count > 0)
{
count--;
if (stream.TryReadString(out var map))
resources.GamemodeRotation.Add(map);
}
}
}
break;
}
}
}

View File

@ -6,73 +6,24 @@ using System.Numerics;
class Program
{
static DiskStorage playerStats;
static void Main(string[] args)
{
playerStats = new DiskStorage("Players\\");
var listener = new ServerListener<MyPlayer>();
listener.OnGetPlayerStats += OnGetPlayerStats;
listener.OnPlayerSpawning += OnPlayerSpawning;
listener.OnGameServerTick += OnGameServerTick;
listener.Start(29294);//Port
Thread.Sleep(-1);
}
private static async Task<PlayerSpawnRequest> OnPlayerSpawning(MyPlayer player, PlayerSpawnRequest request)
private static async Task OnGameServerTick(GameServer server)
{
if (request.Loadout.PrimaryWeapon.Tool == Weapons.M4A1)
{
//Don't allow M4A1
request.Loadout.PrimaryWeapon.Tool = null;
}
else if (request.Loadout.PrimaryWeapon.Tool.WeaponType == WeaponType.SniperRifle)
{
//Force 6x if weapon is sniper.
request.Loadout.PrimaryWeapon.MainSight = Attachments._6xScope;
}
//Override pistol with deagle
request.Loadout.SecondaryWeapon.Tool = Weapons.DesertEagle;
//Force everyone to use RPG
request.Loadout.LightGadget = Gadgets.Rpg7HeatExplosive;
//Don't allow C4s
if (request.Loadout.HeavyGadget == Gadgets.C4)
request.Loadout.HeavyGadget = null;
//Spawn player 2 meter above than the original position.
request.SpawnPosition.Y += 2f;
//Remove spawn protection
request.SpawnProtection = 0f;
//Remove chest armor
request.Wearings.Chest = null;
//Give extra 10 more magazines on primary
request.Loadout.PrimaryExtraMagazines += 10;
//Give extra 5 more throwables
request.Loadout.ThrowableExtra += 5;
return request;
}
private async static Task<PlayerStats> OnGetPlayerStats(ulong steamID, PlayerStats officialStats)
{
officialStats.Progress.Rank = 200;
return officialStats;
//server.Settings.SpectatorEnabled = !server.Settings.SpectatorEnabled;
//server.MapRotation.AddToRotation("DustyDew");
//server.MapRotation.AddToRotation("District");
//server.GamemodeRotation.AddToRotation("CONQ");
//server.ForceEndGame();
}
}
class MyPlayer : Player
{
public int Cash;
public bool InJail = false;
}

View File

@ -1 +1 @@
037b534b95ed15aafcef5ecdb4d5a0fad75c5328
d1f232126136336fb7f4efeeb67c0c5982544fe4