Merge remote-tracking branch 'upstream/main' into lifesteal

This commit is contained in:
DasIschBims 2023-08-14 19:00:19 +02:00
commit 3b844211ee
No known key found for this signature in database
GPG Key ID: A01C65C4183A6610
18 changed files with 889 additions and 267 deletions

View File

@ -1,4 +1,5 @@
using System.Numerics;
using CommunityServerAPI.BattleBitAPI.Server;
namespace BattleBitAPI.Common
{

View File

@ -0,0 +1,18 @@
using System;
namespace BattleBitAPI.Common
{
public class PlayerJoiningArguments
{
public PlayerStats Stats;
public Team Team;
public Squads Squad;
public void Write(BattleBitAPI.Common.Serialization.Stream ser)
{
this.Stats.Write(ser);
ser.Write((byte)this.Team);
ser.Write((byte)this.Squad);
}
}
}

View File

@ -0,0 +1,7 @@
namespace BattleBitAPI.Common
{
public enum LeaningSide
{
Left, None, Right
}
}

View File

@ -0,0 +1,12 @@
namespace BattleBitAPI.Common
{
public enum LoadoutIndex : byte
{
Primary = 0,
Secondary = 1,
FirstAid = 2,
LightGadget = 3,
HeavyGadget = 4,
Throwable = 5,
}
}

View File

@ -19,8 +19,8 @@
PlayerConnected = 50,
PlayerDisconnected = 51,
OnPlayerTypedMessage = 52,
OnPlayerKilledAnotherPlayer = 53,
GetPlayerStats = 54,
OnAPlayerDownedAnotherPlayer = 53,
OnPlayerJoining = 54,
SavePlayerStats = 55,
OnPlayerAskingToChangeRole = 56,
OnPlayerChangedRole = 57,
@ -34,5 +34,9 @@
NotifyNewMapRotation = 65,
NotifyNewGamemodeRotation = 66,
NotifyNewRoundState = 67,
OnPlayerAskingToChangeTeam = 68,
GameTick = 69,
OnPlayerGivenUp = 70,
OnPlayerRevivedAnother = 71,
}
}

View File

@ -1,148 +0,0 @@
using BattleBitAPI.Common;
using BattleBitAPI.Networking;
using BattleBitAPI.Server;
using System.Net;
using System.Numerics;
namespace BattleBitAPI
{
public class Player<TPlayer> where TPlayer : Player<TPlayer>
{
public ulong SteamID { get; internal set; }
public string Name { get; internal set; }
public IPAddress IP { get; internal set; }
public GameServer<TPlayer> GameServer { get; internal set; }
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();
public virtual void OnCreated()
{
}
public virtual async Task OnConnected()
{
}
public virtual async Task OnSpawned()
{
}
public virtual async Task OnDied()
{
}
public virtual async Task OnDisconnected()
{
}
public void Kick(string reason = "")
{
this.GameServer.Kick(this, reason);
}
public void Kill()
{
this.GameServer.Kill(this);
}
public void ChangeTeam()
{
this.GameServer.ChangeTeam(this);
}
public void KickFromSquad()
{
this.GameServer.KickFromSquad(this);
}
public void DisbandTheSquad()
{
this.GameServer.DisbandPlayerCurrentSquad(this);
}
public void PromoteToSquadLeader()
{
this.GameServer.PromoteSquadLeader(this);
}
public void WarnPlayer(string msg)
{
this.GameServer.WarnPlayer(this, msg);
}
public void Message(string msg)
{
this.GameServer.MessageToPlayer(this, msg);
}
public void SetNewRole(GameRole role)
{
this.GameServer.SetRoleTo(this, role);
}
public void Teleport(Vector3 target)
{
}
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 void SetHP(float newHP)
{
GameServer.SetHP(this, newHP);
}
public void GiveDamage(float damage)
{
GameServer.GiveDamage(this, damage);
}
public void Heal(float hp)
{
GameServer.Heal(this, hp);
}
public void SetRunningSpeedMultiplier(float value)
{
GameServer.SetRunningSpeedMultiplier(this, value);
}
public void SetReceiveDamageMultiplier(float value)
{
GameServer.SetReceiveDamageMultiplier(this, value);
}
public void SetGiveDamageMultiplier(float value)
{
GameServer.SetGiveDamageMultiplier(this, value);
}
public void SetJumpMultiplier(float value)
{
GameServer.SetJumpMultiplier(this, value);
}
public void SetFallDamageMultiplier(float value)
{
GameServer.SetFallDamageMultiplier(this, value);
}
public void SetPrimaryWeapon(WeaponItem item, int extraMagazines,bool clear=false)
{
GameServer.SetPrimaryWeapon(this, item, extraMagazines, clear);
}
public void SetSecondaryWeapon(WeaponItem item, int extraMagazines, bool clear = false)
{
GameServer.SetSecondaryWeapon(this, item, extraMagazines, clear);
}
public void SetFirstAidGadget(string item, int extra, bool clear = false)
{
GameServer.SetFirstAid(this, item, extra, clear);
}
public void SetLightGadget(string item, int extra, bool clear = false)
{
GameServer.SetLightGadget(this, item, extra, clear);
}
public void SetHeavyGadget(string item, int extra, bool clear = false)
{
GameServer.SetHeavyGadget(this, item, extra, clear);
}
public void SetThrowable(string item, int extra, bool clear = false)
{
GameServer.SetThrowable(this, item, extra, clear);
}
public override string ToString()
{
return this.Name + " (" + this.SteamID + ")";
}
}
}

View File

@ -25,9 +25,9 @@ namespace BattleBitAPI.Server
public string Map => mInternal.Map;
public MapSize MapSize => mInternal.MapSize;
public MapDayNight DayNight => mInternal.DayNight;
public int CurrentPlayers => mInternal.CurrentPlayers;
public int InQueuePlayers => mInternal.InQueuePlayers;
public int MaxPlayers => mInternal.MaxPlayers;
public int CurrentPlayerCount => mInternal.CurrentPlayerCount;
public int InQueuePlayerCount => mInternal.InQueuePlayerCount;
public int MaxPlayerCount => mInternal.MaxPlayerCount;
public string LoadingScreenText => mInternal.LoadingScreenText;
public string ServerRulesText => mInternal.ServerRulesText;
public ServerSettings<TPlayer> ServerSettings => mInternal.ServerSettings;
@ -264,15 +264,18 @@ namespace BattleBitAPI.Server
{
return true;
}
public virtual async Task<PlayerStats> OnGetPlayerStats(ulong steamID, PlayerStats officialStats)
public virtual async Task OnPlayerJoiningToServer(ulong steamID, PlayerJoiningArguments args)
{
return officialStats;
}
public virtual async Task OnSavePlayerStats(ulong steamID, PlayerStats stats)
{
}
public virtual async Task<bool> OnPlayerRequestingToChangeRole(TPlayer player, GameRole role)
public virtual async Task<bool> OnPlayerRequestingToChangeRole(TPlayer player, GameRole requestedRole)
{
return true;
}
public virtual async Task<bool> OnPlayerRequestingToChangeTeam(TPlayer player, Team requestedTeam)
{
return true;
}
@ -304,7 +307,15 @@ namespace BattleBitAPI.Server
{
}
public virtual async Task OnAPlayerKilledAnotherPlayer(OnPlayerKillArguments<TPlayer> args)
public virtual async Task OnPlayerGivenUp(TPlayer player)
{
}
public virtual async Task OnAPlayerDownedAnotherPlayer(OnPlayerKillArguments<TPlayer> args)
{
}
public virtual async Task OnAPlayerRevivedAnotherPlayer(TPlayer from,TPlayer to)
{
}
@ -417,6 +428,17 @@ namespace BattleBitAPI.Server
{
ChangeTeam(player.SteamID);
}
public void ChangeTeam(ulong steamID, Team team)
{
if (team == Team.TeamA)
ExecuteCommand("changeteam " + steamID + " a");
else if (team == Team.TeamB)
ExecuteCommand("changeteam " + steamID + " b");
}
public void ChangeTeam(Player<TPlayer> player, Team team)
{
ChangeTeam(player.SteamID, team);
}
public void KickFromSquad(ulong steamID)
{
ExecuteCommand("squadkick " + steamID);
@ -425,13 +447,21 @@ namespace BattleBitAPI.Server
{
KickFromSquad(player.SteamID);
}
public void DisbandPlayerSSquad(ulong steamID)
public void JoinSquad(ulong steamID, Squads targetSquad)
{
ExecuteCommand("setsquad " + steamID + " " + ((int)targetSquad));
}
public void JoinSquad(Player<TPlayer> player, Squads targetSquad)
{
JoinSquad(player.SteamID, targetSquad);
}
public void DisbandPlayerSquad(ulong steamID)
{
ExecuteCommand("squaddisband " + steamID);
}
public void DisbandPlayerCurrentSquad(Player<TPlayer> player)
{
DisbandPlayerSSquad(player.SteamID);
DisbandPlayerSquad(player.SteamID);
}
public void PromoteSquadLeader(ulong steamID)
{
@ -457,6 +487,14 @@ namespace BattleBitAPI.Server
{
MessageToPlayer(player.SteamID, msg);
}
public void MessageToPlayer(ulong steamID, string msg, float fadeOutTime)
{
ExecuteCommand("msgf " + steamID + " " + fadeOutTime + " " + msg);
}
public void MessageToPlayer(Player<TPlayer> player, string msg, float fadeOutTime)
{
MessageToPlayer(player.SteamID, msg, fadeOutTime);
}
public void SetRoleTo(ulong steamID, GameRole role)
{
ExecuteCommand("setrole " + steamID + " " + role);
@ -664,7 +702,7 @@ namespace BattleBitAPI.Server
}
public void SetThrowable(Player<TPlayer> player, string tool, int extra, bool clear = false)
{
SetThrowable(player.SteamID, tool, extra,clear);
SetThrowable(player.SteamID, tool, extra, clear);
}
// ---- Closing ----
@ -727,9 +765,9 @@ namespace BattleBitAPI.Server
public string Map;
public MapSize MapSize;
public MapDayNight DayNight;
public int CurrentPlayers;
public int InQueuePlayers;
public int MaxPlayers;
public int CurrentPlayerCount;
public int InQueuePlayerCount;
public int MaxPlayerCount;
public string LoadingScreenText;
public string ServerRulesText;
public ServerSettings<TPlayer> ServerSettings;
@ -814,9 +852,9 @@ namespace BattleBitAPI.Server
this.Map = map;
this.MapSize = mapSize;
this.DayNight = dayNight;
this.CurrentPlayers = currentPlayers;
this.InQueuePlayers = inQueuePlayers;
this.MaxPlayers = maxPlayers;
this.CurrentPlayerCount = currentPlayers;
this.InQueuePlayerCount = inQueuePlayers;
this.MaxPlayerCount = maxPlayers;
this.LoadingScreenText = loadingScreenText;
this.ServerRulesText = serverRulesText;
@ -870,13 +908,14 @@ namespace BattleBitAPI.Server
{
public float DamageMultiplier = 1.0f;
public bool BleedingEnabled = true;
public bool StamineEnabled = false;
public bool StaminaEnabled = 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 float CaptureFlagSpeedMultiplier = 1f;
public byte MedicLimitPerSquad = 8;
public byte EngineerLimitPerSquad = 8;
@ -887,13 +926,15 @@ namespace BattleBitAPI.Server
{
ser.Write(this.DamageMultiplier);
ser.Write(this.BleedingEnabled);
ser.Write(this.StamineEnabled);
ser.Write(this.StaminaEnabled);
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.CaptureFlagSpeedMultiplier);
ser.Write(this.MedicLimitPerSquad);
ser.Write(this.EngineerLimitPerSquad);
ser.Write(this.SupportLimitPerSquad);
@ -903,13 +944,14 @@ namespace BattleBitAPI.Server
{
this.DamageMultiplier = ser.ReadFloat();
this.BleedingEnabled = ser.ReadBool();
this.StamineEnabled = ser.ReadBool();
this.StaminaEnabled = 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.CaptureFlagSpeedMultiplier = ser.ReadFloat();
this.MedicLimitPerSquad = ser.ReadInt8();
this.EngineerLimitPerSquad = ser.ReadInt8();
@ -920,7 +962,7 @@ namespace BattleBitAPI.Server
{
this.DamageMultiplier = 1.0f;
this.BleedingEnabled = true;
this.StamineEnabled = false;
this.StaminaEnabled = false;
this.FriendlyFireEnabled = false;
this.HideMapVotes = true;
this.OnlyWinnerTeamCanVote = false;

View File

@ -1,4 +1,7 @@
namespace BattleBitAPI.Server
using BattleBitAPI.Common;
using CommunityServerAPI.BattleBitAPI.Server;
namespace BattleBitAPI.Server
{
public class GamemodeRotation<TPlayer> where TPlayer : Player<TPlayer>
{
@ -34,6 +37,27 @@
mResources.IsDirtyGamemodeRotation = true;
return true;
}
public void SetRotation(params string[] gamemodes)
{
lock (mResources._GamemodeRotation)
{
mResources._GamemodeRotation.Clear();
foreach (var item in gamemodes)
mResources._GamemodeRotation.Add(item);
}
mResources.IsDirtyGamemodeRotation = true;
}
public void ClearRotation()
{
lock (mResources._GamemodeRotation)
{
if (mResources._GamemodeRotation.Count == 0)
return;
mResources._GamemodeRotation.Clear();
}
mResources.IsDirtyGamemodeRotation = true;
}
public void Reset()
{

View File

@ -1,4 +1,6 @@
namespace BattleBitAPI.Server
using CommunityServerAPI.BattleBitAPI.Server;
namespace BattleBitAPI.Server
{
public class MapRotation<TPlayer> where TPlayer : Player<TPlayer>
{
@ -40,6 +42,27 @@
mResources.IsDirtyMapRotation = true;
return true;
}
public void SetRotation(params string[] maps)
{
lock (mResources._MapRotation)
{
mResources._MapRotation.Clear();
foreach (var item in maps)
mResources._MapRotation.Add(item);
}
mResources.IsDirtyMapRotation = true;
}
public void ClearRotation()
{
lock (mResources._MapRotation)
{
if (mResources._MapRotation.Count == 0)
return;
mResources._MapRotation.Clear();
}
mResources.IsDirtyMapRotation = true;
}
public void Reset()
{

View File

@ -0,0 +1,28 @@
namespace BattleBitAPI.Server
{
public class PlayerModifications<TPlayer> where TPlayer : Player<TPlayer>
{
private Player<TPlayer>.Internal @internal;
public PlayerModifications(Player<TPlayer>.Internal @internal)
{
this.@internal = @internal;
}
public float RunningSpeedMultiplier { get; set; }
public float ReceiveDamageMultiplier { get; set; }
public float GiveDamageMultiplier { get; set; }
public float JumpHeightMultiplier { get; set; }
public float FallDamageMultiplier { get; set; }
public float ReloadSpeedMultiplier { get; set; }
public bool CanUseNightVision { get; set; }
public bool HasCollision { get; set; }
public float DownTimeGiveUpTime { get; set; }
public bool AirStrafe { get; set; }
public bool CanSpawn { get; set; }
public bool CanSpectate { get; set; }
public bool IsTextChatMuted { get; set; }
public bool IsVoiceChatMuted { get; set; }
public float RespawnTime { get; set; }
public bool CanRespawn { get; set; }
}
}

View File

@ -1,4 +1,5 @@
using BattleBitAPI.Common;
using CommunityServerAPI.BattleBitAPI.Server;
namespace BattleBitAPI.Server
{

View File

@ -1,4 +1,6 @@
namespace BattleBitAPI.Server
using CommunityServerAPI.BattleBitAPI.Server;
namespace BattleBitAPI.Server
{
public class ServerSettings<TPlayer> where TPlayer : Player<TPlayer>
{
@ -28,10 +30,10 @@
}
public bool StamineEnabled
{
get => mResources._RoomSettings.StamineEnabled;
get => mResources._RoomSettings.StaminaEnabled;
set
{
mResources._RoomSettings.StamineEnabled = value;
mResources._RoomSettings.StaminaEnabled = value;
mResources.IsDirtyRoomSettings = true;
}
}

View File

@ -0,0 +1,364 @@
using BattleBitAPI.Common;
using BattleBitAPI.Server;
using System.Net;
using System.Numerics;
namespace BattleBitAPI
{
public class Player<TPlayer> where TPlayer : Player<TPlayer>
{
private Internal mInternal;
// ---- Variables ----
public ulong SteamID => mInternal.SteamID;
public string Name => mInternal.Name;
public IPAddress IP => mInternal.IP;
public GameServer<TPlayer> GameServer => mInternal.GameServer;
public GameRole Role
{
get => mInternal.Role;
set
{
if (value == mInternal.Role)
return;
SetNewRole(value);
}
}
public Team Team
{
get => mInternal.Team;
set
{
if (mInternal.Team != value)
ChangeTeam(value);
}
}
public Squads Squad
{
get => mInternal.Squad;
set
{
if (value == mInternal.Squad)
return;
if (value == Squads.NoSquad)
KickFromSquad();
else
JoinSquad(value);
}
}
public bool InSquad => mInternal.Squad != Squads.NoSquad;
public int PingMs => mInternal.PingMs;
public float HP => mInternal.HP;
public bool IsAlive => mInternal.HP >= 0f;
public bool IsUp => mInternal.HP > 0f;
public bool IsDown => mInternal.HP == 0f;
public bool IsDead => mInternal.HP == -1f;
public Vector3 Position
{
get => mInternal.Position;
set => Teleport(value);
}
public PlayerStand StandingState => mInternal.Standing;
public LeaningSide LeaningState => mInternal.Leaning;
public LoadoutIndex CurrentLoadoutIndex => mInternal.CurrentLoadoutIndex;
public bool InVehicle => mInternal.InVehicle;
public bool IsBleeding => mInternal.IsBleeding;
public PlayerLoadout CurrentLoadout => mInternal.CurrentLoadout;
public PlayerWearings CurrentWearings => mInternal.CurrentWearings;
public PlayerModifications<TPlayer> Modifications => mInternal.Modifications;
// ---- Events ----
public virtual void OnCreated()
{
}
public virtual async Task OnConnected()
{
}
public virtual async Task OnSpawned()
{
}
public virtual async Task OnDowned()
{
}
public virtual async Task OnGivenUp()
{
}
public virtual async Task OnRevivedByAnotherPlayer()
{
}
public virtual async Task OnRevivedAnotherPlayer()
{
}
public virtual async Task OnDied()
{
}
public virtual async Task OnChangedTeam()
{
}
public virtual async Task OnChangedRole(GameRole newRole)
{
}
public virtual async Task OnJoinedSquad(Squads newSquad)
{
}
public virtual async Task OnLeftSquad(Squads oldSquad)
{
}
public virtual async Task OnDisconnected()
{
}
// ---- Functions ----
public void Kick(string reason = "")
{
GameServer.Kick(this, reason);
}
public void Kill()
{
GameServer.Kill(this);
}
public void ChangeTeam()
{
GameServer.ChangeTeam(this);
}
public void ChangeTeam(Team team)
{
GameServer.ChangeTeam(this, team);
}
public void KickFromSquad()
{
GameServer.KickFromSquad(this);
}
public void JoinSquad(Squads targetSquad)
{
GameServer.JoinSquad(this, targetSquad);
}
public void DisbandTheSquad()
{
GameServer.DisbandPlayerCurrentSquad(this);
}
public void PromoteToSquadLeader()
{
GameServer.PromoteSquadLeader(this);
}
public void WarnPlayer(string msg)
{
GameServer.WarnPlayer(this, msg);
}
public void Message(string msg)
{
GameServer.MessageToPlayer(this, msg);
}
public void Message(string msg, float fadeoutTime)
{
GameServer.MessageToPlayer(this, msg, fadeoutTime);
}
public void SetNewRole(GameRole role)
{
GameServer.SetRoleTo(this, role);
}
public void Teleport(Vector3 target)
{
}
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 void SetHP(float newHP)
{
GameServer.SetHP(this, newHP);
}
public void GiveDamage(float damage)
{
GameServer.GiveDamage(this, damage);
}
public void Heal(float hp)
{
GameServer.Heal(this, hp);
}
public void SetRunningSpeedMultiplier(float value)
{
GameServer.SetRunningSpeedMultiplier(this, value);
}
public void SetReceiveDamageMultiplier(float value)
{
GameServer.SetReceiveDamageMultiplier(this, value);
}
public void SetGiveDamageMultiplier(float value)
{
GameServer.SetGiveDamageMultiplier(this, value);
}
public void SetJumpMultiplier(float value)
{
GameServer.SetJumpMultiplier(this, value);
}
public void SetFallDamageMultiplier(float value)
{
GameServer.SetFallDamageMultiplier(this, value);
}
public void SetPrimaryWeapon(WeaponItem item, int extraMagazines, bool clear = false)
{
GameServer.SetPrimaryWeapon(this, item, extraMagazines, clear);
}
public void SetSecondaryWeapon(WeaponItem item, int extraMagazines, bool clear = false)
{
GameServer.SetSecondaryWeapon(this, item, extraMagazines, clear);
}
public void SetFirstAidGadget(string item, int extra, bool clear = false)
{
GameServer.SetFirstAid(this, item, extra, clear);
}
public void SetLightGadget(string item, int extra, bool clear = false)
{
GameServer.SetLightGadget(this, item, extra, clear);
}
public void SetHeavyGadget(string item, int extra, bool clear = false)
{
GameServer.SetHeavyGadget(this, item, extra, clear);
}
public void SetThrowable(string item, int extra, bool clear = false)
{
GameServer.SetThrowable(this, item, extra, clear);
}
// ---- Static ----
public static TPlayer CreateInstance<TPlayer>(Player<TPlayer>.Internal @internal) where TPlayer : Player<TPlayer>
{
TPlayer player = (TPlayer)Activator.CreateInstance(typeof(TPlayer));
player.mInternal = @internal;
return player;
}
// ---- Overrides ----
public override string ToString()
{
return Name + " (" + SteamID + ")";
}
// ---- Internal ----
public class Internal
{
public ulong SteamID;
public string Name;
public IPAddress IP;
public GameServer<TPlayer> GameServer;
public GameRole Role;
public Team Team;
public Squads Squad;
public int PingMs = 999;
public bool IsAlive;
public float HP;
public Vector3 Position;
public PlayerStand Standing;
public LeaningSide Leaning;
public LoadoutIndex CurrentLoadoutIndex;
public bool InVehicle;
public bool IsBleeding;
public PlayerLoadout CurrentLoadout;
public PlayerWearings CurrentWearings;
public mPlayerModifications _Modifications;
public PlayerModifications<TPlayer> Modifications;
public Internal()
{
this.Modifications = new PlayerModifications<TPlayer>(this);
this._Modifications = new mPlayerModifications();
}
public void OnDie()
{
IsAlive = false;
HP = -1f;
Position = default;
Standing = PlayerStand.Standing;
Leaning = LeaningSide.None;
CurrentLoadoutIndex = LoadoutIndex.Primary;
InVehicle = false;
IsBleeding = false;
CurrentLoadout = new PlayerLoadout();
CurrentWearings = new PlayerWearings();
}
}
public class mPlayerModifications
{
public float RunningSpeedMultiplier = 1f;
public float ReceiveDamageMultiplier = 1f;
public float GiveDamageMultiplier = 1f;
public float JumpHeightMultiplier = 1f;
public float FallDamageMultiplier = 1f;
public float ReloadSpeedMultiplier = 1f;
public bool CanUseNightVision = true;
public bool HasCollision = false;
public float DownTimeGiveUpTime = 60f;
public bool AirStrafe = true;
public bool CanSpawn = true;
public bool CanSpectate = true;
public bool IsTextChatMuted = false;
public bool IsVoiceChatMuted = false;
public float RespawnTime = 10f;
public bool CanRespawn = true;
public bool IsDirtyFlag = false;
public void Write(BattleBitAPI.Common.Serialization.Stream ser)
{
ser.Write(this.RunningSpeedMultiplier);
ser.Write(this.ReceiveDamageMultiplier);
ser.Write(this.GiveDamageMultiplier);
ser.Write(this.JumpHeightMultiplier);
ser.Write(this.FallDamageMultiplier);
ser.Write(this.ReloadSpeedMultiplier);
ser.Write(this.CanUseNightVision);
ser.Write(this.HasCollision);
ser.Write(this.DownTimeGiveUpTime);
ser.Write(this.AirStrafe);
ser.Write(this.CanSpawn);
ser.Write(this.CanSpectate);
ser.Write(this.IsTextChatMuted);
ser.Write(this.IsVoiceChatMuted);
ser.Write(this.RespawnTime);
ser.Write(this.CanRespawn);
}
public void Read(BattleBitAPI.Common.Serialization.Stream ser)
{
this.RunningSpeedMultiplier = ser.ReadFloat();
if (this.RunningSpeedMultiplier <= 0f)
this.RunningSpeedMultiplier = 0.01f;
this.ReceiveDamageMultiplier = ser.ReadFloat();
this.GiveDamageMultiplier = ser.ReadFloat();
this.JumpHeightMultiplier = ser.ReadFloat();
this.FallDamageMultiplier = ser.ReadFloat();
this.ReloadSpeedMultiplier = ser.ReadFloat();
this.CanUseNightVision = ser.ReadBool();
this.HasCollision = ser.ReadBool();
this.DownTimeGiveUpTime = ser.ReadFloat();
this.AirStrafe = ser.ReadBool();
this.CanSpawn = ser.ReadBool();
this.CanSpectate = ser.ReadBool();
this.IsTextChatMuted = ser.ReadBool();
this.IsVoiceChatMuted = ser.ReadBool();
this.RespawnTime = ser.ReadFloat();
this.CanRespawn = ser.ReadBool();
}
}
}
}

View File

@ -1,12 +1,15 @@
using System.Net;
using System.Diagnostics;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Numerics;
using System.Xml;
using BattleBitAPI.Common;
using BattleBitAPI.Common.Extentions;
using BattleBitAPI.Common.Serialization;
using BattleBitAPI.Networking;
using CommunityServerAPI.BattleBitAPI;
using CommunityServerAPI.BattleBitAPI.Server;
namespace BattleBitAPI.Server
{
@ -59,6 +62,24 @@ namespace BattleBitAPI.Server
/// </remarks>
public Func<GameServer<TPlayer>, Task> OnGameServerDisconnected { get; set; }
/// <summary>
/// Fired when a new instance of game server created.
/// </summary>
///
/// <remarks>
/// GameServer: Game server that has been just created.<br/>
/// </remarks>
public Func<GameServer<TPlayer>, Task> OnCreatingGameServerInstance { get; set; }
/// <summary>
/// Fired when a new instance of player instance created.
/// </summary>
///
/// <remarks>
/// TPlayer: The player instance that was created<br/>
/// </remarks>
public Func<TPlayer, Task> OnCreatingPlayerInstance { get; set; }
// --- Private ---
private TcpListener mSocket;
private Dictionary<ulong, (TGameServer server, GameServer<TPlayer>.Internal resources)> mActiveConnections;
@ -91,7 +112,7 @@ namespace BattleBitAPI.Server
}
public void Start(int port)
{
Start(IPAddress.Loopback, port);
Start(IPAddress.Any, port);
}
// --- Stopping ---
@ -166,7 +187,7 @@ namespace BattleBitAPI.Server
gamePort = readStream.ReadUInt16();
}
//Read is Port protected
//Read is server protected
bool isPasswordProtected;
{
readStream.Reset();
@ -325,7 +346,7 @@ namespace BattleBitAPI.Server
}
var hash = ((ulong)gamePort << 32) | (ulong)ip.ToUInt();
server = this.mInstanceDatabase.GetServerInstance(hash, out resources);
server = this.mInstanceDatabase.GetServerInstance(hash, out bool isNew, out resources);
resources.Set(
this.mExecutePackage,
client,
@ -498,23 +519,37 @@ namespace BattleBitAPI.Server
wearings.Read(readStream);
}
TPlayer player = mInstanceDatabase.GetPlayerInstance(steamid);
player.SteamID = steamid;
player.Name = username;
player.IP = new IPAddress(ipHash);
player.GameServer = (GameServer<TPlayer>)server;
player.Team = team;
player.Squad = squad;
player.Role = role;
player.IsAlive = isAlive;
player.CurrentLoadout = loadout;
player.CurrentWearings = wearings;
TPlayer player = mInstanceDatabase.GetPlayerInstance(steamid, out bool isNewClient, out var playerInternal);
playerInternal.SteamID = steamid;
playerInternal.Name = username;
playerInternal.IP = new IPAddress(ipHash);
playerInternal.GameServer = (GameServer<TPlayer>)server;
playerInternal.Team = team;
playerInternal.Squad = squad;
playerInternal.Role = role;
playerInternal.IsAlive = isAlive;
playerInternal.CurrentLoadout = loadout;
playerInternal.CurrentWearings = wearings;
if (isNewClient)
{
if (this.OnCreatingPlayerInstance != null)
this.OnCreatingPlayerInstance(player);
}
resources.AddPlayer(player);
}
//Send accepted notification.
networkStream.WriteByte((byte)NetworkCommuncation.Accepted);
if (isNew)
{
if (this.OnCreatingGameServerInstance != null)
this.OnCreatingGameServerInstance(server);
}
}
}
}
@ -575,9 +610,9 @@ namespace BattleBitAPI.Server
client.SendBufferSize = Const.MaxNetworkPackageSize;
//Join to main server loop.
await mHandleGameServer(server);
await mHandleGameServer(server, resources);
}
private async Task mHandleGameServer(TGameServer server)
private async Task mHandleGameServer(TGameServer server, GameServer<TPlayer>.Internal @internal)
{
bool isTicking = false;
@ -632,19 +667,25 @@ namespace BattleBitAPI.Server
Squads squad = (Squads)stream.ReadInt8();
GameRole role = (GameRole)stream.ReadInt8();
TPlayer player = mInstanceDatabase.GetPlayerInstance(steamID);
player.SteamID = steamID;
player.Name = username;
player.IP = new IPAddress(ip);
player.GameServer = (GameServer<TPlayer>)server;
TPlayer player = mInstanceDatabase.GetPlayerInstance(steamID, out bool isNewClient, out var playerInternal);
playerInternal.SteamID = steamID;
playerInternal.Name = username;
playerInternal.IP = new IPAddress(ip);
playerInternal.GameServer = (GameServer<TPlayer>)server;
player.Team = team;
player.Squad = squad;
player.Role = role;
playerInternal.Team = team;
playerInternal.Squad = squad;
playerInternal.Role = role;
if (isNewClient)
{
if (this.OnCreatingPlayerInstance != null)
this.OnCreatingPlayerInstance(player);
}
resources.AddPlayer(player);
server.OnPlayerConnected(player);
player.OnConnected();
server.OnPlayerConnected(player);
}
}
break;
@ -661,8 +702,17 @@ namespace BattleBitAPI.Server
if (exist)
{
server.OnPlayerDisconnected((TPlayer)player);
var @internal = mInstanceDatabase.GetPlayerInternals(steamID);
if (@internal.HP > -1f)
{
@internal.OnDie();
player.OnDied();
server.OnPlayerDied((TPlayer)player);
}
player.OnDisconnected();
server.OnPlayerDisconnected((TPlayer)player);
}
}
break;
@ -700,7 +750,7 @@ namespace BattleBitAPI.Server
}
break;
}
case NetworkCommuncation.OnPlayerKilledAnotherPlayer:
case NetworkCommuncation.OnAPlayerDownedAnotherPlayer:
{
if (stream.CanRead(8 + 12 + 8 + 12 + 2 + 1 + 1))
{
@ -730,7 +780,8 @@ namespace BattleBitAPI.Server
KillerTool = tool,
};
server.OnAPlayerKilledAnotherPlayer(args);
victimClient.OnDowned();
server.OnAPlayerDownedAnotherPlayer(args);
}
}
}
@ -738,7 +789,7 @@ namespace BattleBitAPI.Server
break;
}
case NetworkCommuncation.GetPlayerStats:
case NetworkCommuncation.OnPlayerJoining:
{
if (stream.CanRead(8 + 2))
{
@ -746,14 +797,22 @@ namespace BattleBitAPI.Server
var stats = new PlayerStats();
stats.Read(stream);
async Task mHandle()
{
stats = await server.OnGetPlayerStats(steamID, stats);
var args = new PlayerJoiningArguments()
{
Stats = stats,
Squad = Squads.NoSquad,
Team = Team.None
};
await server.OnPlayerJoiningToServer(steamID, args);
using (var response = Common.Serialization.Stream.Get())
{
response.Write((byte)NetworkCommuncation.SendPlayerStats);
response.Write(steamID);
stats.Write(response);
args.Write(response);
server.WriteToSocket(response);
}
}
@ -804,7 +863,10 @@ namespace BattleBitAPI.Server
if (resources.TryGetPlayer(steamID, out var client))
{
client.Role = role;
var @internal = mInstanceDatabase.GetPlayerInternals(steamID);
@internal.Role = role;
client.OnChangedRole(role);
server.OnPlayerChangedRole((TPlayer)client, role);
}
}
@ -819,7 +881,10 @@ namespace BattleBitAPI.Server
if (resources.TryGetPlayer(steamID, out var client))
{
client.Squad = squad;
var @internal = mInstanceDatabase.GetPlayerInternals(steamID);
@internal.Squad = squad;
client.OnJoinedSquad(squad);
server.OnPlayerJoinedSquad((TPlayer)client, squad);
}
}
@ -833,14 +898,21 @@ namespace BattleBitAPI.Server
if (resources.TryGetPlayer(steamID, out var client))
{
var @internal = mInstanceDatabase.GetPlayerInternals(steamID);
var oldSquad = client.Squad;
var oldRole = client.Role;
client.Squad = Squads.NoSquad;
client.Role = GameRole.Assault;
@internal.Squad = Squads.NoSquad;
@internal.Role = GameRole.Assault;
client.OnLeftSquad(oldSquad);
server.OnPlayerLeftSquad((TPlayer)client, oldSquad);
if (oldRole != GameRole.Assault)
{
client.OnChangedRole(GameRole.Assault);
server.OnPlayerChangedRole((TPlayer)client, GameRole.Assault);
}
}
}
break;
@ -854,7 +926,11 @@ namespace BattleBitAPI.Server
if (resources.TryGetPlayer(steamID, out var client))
{
client.Team = team;
var @internal = mInstanceDatabase.GetPlayerInternals(steamID);
@internal.Team = team;
client.OnChangedTeam();
server.OnPlayerChangeTeam((TPlayer)client, team);
}
}
@ -915,18 +991,20 @@ namespace BattleBitAPI.Server
{
if (stream.CanRead(8 + 2))
{
ulong reporter = stream.ReadUInt64();
if (resources.TryGetPlayer(reporter, out var client))
ulong steamID = stream.ReadUInt64();
if (resources.TryGetPlayer(steamID, out var client))
{
var @internal = mInstanceDatabase.GetPlayerInternals(steamID);
var loadout = new PlayerLoadout();
loadout.Read(stream);
client.CurrentLoadout = loadout;
@internal.CurrentLoadout = loadout;
var wearings = new PlayerWearings();
wearings.Read(stream);
client.CurrentWearings = wearings;
@internal.CurrentWearings = wearings;
client.IsAlive = true;
@internal.IsAlive = true;
client.OnSpawned();
server.OnPlayerSpawned((TPlayer)client);
@ -938,12 +1016,11 @@ namespace BattleBitAPI.Server
{
if (stream.CanRead(8))
{
ulong reporter = stream.ReadUInt64();
if (resources.TryGetPlayer(reporter, out var client))
ulong steamid = stream.ReadUInt64();
if (resources.TryGetPlayer(steamid, out var client))
{
client.CurrentLoadout = new PlayerLoadout();
client.CurrentWearings = new PlayerWearings();
client.IsAlive = false;
var @internal = mInstanceDatabase.GetPlayerInternals(steamid);
@internal.OnDie();
client.OnDied();
server.OnPlayerDied((TPlayer)client);
@ -1007,6 +1084,106 @@ namespace BattleBitAPI.Server
}
break;
}
case NetworkCommuncation.OnPlayerAskingToChangeTeam:
{
if (stream.CanRead(8 + 1))
{
ulong steamID = stream.ReadUInt64();
Team team = (Team)stream.ReadInt8();
if (resources.TryGetPlayer(steamID, out var client))
{
async Task mHandle()
{
bool accepted = await server.OnPlayerRequestingToChangeTeam((TPlayer)client, team);
if (accepted)
server.ChangeTeam(steamID, team);
}
mHandle();
}
}
break;
}
case NetworkCommuncation.GameTick:
{
if (stream.CanRead(4 + 4 + 4))
{
float decompressX = stream.ReadFloat();
float decompressY = stream.ReadFloat();
float decompressZ = stream.ReadFloat();
int playerCount = stream.ReadInt8();
while (playerCount > 0)
{
playerCount--;
ulong steamID = stream.ReadUInt64();
//TODO, can compressed further later.
ushort com_posX = stream.ReadUInt16();
ushort com_posY = stream.ReadUInt16();
ushort com_posZ = stream.ReadUInt16();
byte com_healt = stream.ReadInt8();
PlayerStand standing = (PlayerStand)stream.ReadInt8();
LeaningSide side = (LeaningSide)stream.ReadInt8();
LoadoutIndex loadoutIndex = (LoadoutIndex)stream.ReadInt8();
bool inSeat = stream.ReadBool();
bool isBleeding = stream.ReadBool();
ushort ping = stream.ReadUInt16();
var @internal = mInstanceDatabase.GetPlayerInternals(steamID);
if (@internal.IsAlive)
{
@internal.Position = new Vector3()
{
X = com_posX * decompressX,
Y = com_posY * decompressY,
Z = com_posZ * decompressZ,
};
@internal.HP = (com_healt * 0.5f) - 1f;
@internal.Standing = standing;
@internal.Leaning = side;
@internal.CurrentLoadoutIndex = loadoutIndex;
@internal.InVehicle = inSeat;
@internal.IsBleeding = isBleeding;
@internal.PingMs = ping;
}
}
}
break;
}
case NetworkCommuncation.OnPlayerGivenUp:
{
if (stream.CanRead(8))
{
ulong steamID = stream.ReadUInt64();
if (resources.TryGetPlayer(steamID, out var client))
{
client.OnGivenUp();
server.OnPlayerGivenUp((TPlayer)client);
}
}
break;
}
case NetworkCommuncation.OnPlayerRevivedAnother:
{
if (stream.CanRead(8 + 8))
{
ulong from = stream.ReadUInt64();
ulong to = stream.ReadUInt64();
if (resources.TryGetPlayer(to, out var toClient))
{
toClient.OnRevivedByAnotherPlayer();
if (resources.TryGetPlayer(from, out var fromClient))
{
fromClient.OnRevivedAnotherPlayer();
server.OnAPlayerRevivedAnotherPlayer((TPlayer)fromClient, (TPlayer)toClient);
}
}
}
break;
}
}
}
@ -1056,44 +1233,57 @@ namespace BattleBitAPI.Server
private class mInstances<TPlayer, TGameServer> where TPlayer : Player<TPlayer> where TGameServer : GameServer<TPlayer>
{
private Dictionary<ulong, (TGameServer, GameServer<TPlayer>.Internal)> mGameServerInstances;
private Dictionary<ulong, TPlayer> mPlayerInstances;
private Dictionary<ulong, (TPlayer, Player<TPlayer>.Internal)> mPlayerInstances;
public mInstances()
{
this.mGameServerInstances = new Dictionary<ulong, (TGameServer, GameServer<TPlayer>.Internal)>(64);
this.mPlayerInstances = new Dictionary<ulong, TPlayer>(1024 * 16);
this.mPlayerInstances = new Dictionary<ulong, (TPlayer, Player<TPlayer>.Internal)>(1024 * 16);
}
public TGameServer GetServerInstance(ulong hash, out GameServer<TPlayer>.Internal @internal)
public TGameServer GetServerInstance(ulong hash, out bool isNew, out GameServer<TPlayer>.Internal @internal)
{
lock (mGameServerInstances)
{
if (mGameServerInstances.TryGetValue(hash, out var data))
{
@internal = data.Item2;
isNew = false;
return data.Item1;
}
@internal = new GameServer<TPlayer>.Internal();
TGameServer gameServer = GameServer<TPlayer>.CreateInstance<TGameServer>(@internal);
isNew = true;
mGameServerInstances.Add(hash, (gameServer, @internal));
return gameServer;
}
}
public TPlayer GetPlayerInstance(ulong steamID)
public TPlayer GetPlayerInstance(ulong steamID, out bool isNew, out Player<TPlayer>.Internal @internal)
{
lock (this.mPlayerInstances)
{
if (this.mPlayerInstances.TryGetValue(steamID, out var player))
return player;
{
isNew = false;
@internal = player.Item2;
return player.Item1;
}
player = Activator.CreateInstance<TPlayer>();
player.OnCreated();
mPlayerInstances.Add(steamID, player);
return player;
@internal = new Player<TPlayer>.Internal();
var pplayer = Player<TPlayer>.CreateInstance(@internal);
isNew = true;
mPlayerInstances.Add(steamID, (pplayer, @internal));
return pplayer;
}
}
public Player<TPlayer>.Internal GetPlayerInternals(ulong steamID)
{
lock (mPlayerInstances)
return mPlayerInstances[steamID].Item2;
}
}
}
}

View File

@ -15,6 +15,8 @@ class Program
Thread.Sleep(-1);
}
}
class MyPlayer : Player<MyPlayer>
{
@ -49,7 +51,19 @@ class MyGameServer : GameServer<MyPlayer>
public override async Task OnGameStateChanged(GameState oldState, GameState newState)
{
await Console.Out.WriteLineAsync("State changed to -> " + newState);
await Console.Out.WriteLineAsync("Giveup: " + player);
}
public override async Task OnPlayerDied(MyPlayer player)
{
await Console.Out.WriteLineAsync("Died: " + player);
}
public override async Task OnAPlayerRevivedAnotherPlayer(MyPlayer from, MyPlayer to)
{
await Console.Out.WriteLineAsync(from + " revived " + to);
}
public override async Task OnPlayerDisconnected(MyPlayer player)
{
await Console.Out.WriteLineAsync("Disconnected: " + player);
}
public override async Task OnTick()

33
README-zhCN.md Normal file
View File

@ -0,0 +1,33 @@
# BattleBit Remastered 服务器 API
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
Language [English](/README.md) | 中文 | [한국어](/README_koKR.md)
这个 repo 是 BBR像素战地的服务端 API
## 如何开始
### 前置需求
- 拥有 BBR 服务端的开服权限,且满足开服条件。
- 可以写基于 [.NET 6.0](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) 的 C# 代码。
- 可以在生产环境中部署此代码。
### 如何制作功能
制作对应的功能可以 [查看维基(制作中)](https://github.com/MrOkiDoki/BattleBit-Community-Server-API/wiki).
使用这个 API 将在运行本程序后开启一个`ServerListener` 的监听进程,传递你*自己定义*的`Player` 和 `GameServer` 类型覆盖原本游戏自身的`Player` 和 `GameServer`类型。在这些类型中添加任何你想要的功能,以此来定制属于你自己的游戏玩法。
如果想给你的服务端添加功能,可以直接把覆盖的功能写在 `Program.cs``MyPlayer``MyGameServer`中,当然也可以按照框架规范进行其他的功能纂写。
### 编译
可以直接使用 [`dotnet build`](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-build) 的命令进行编译,或者在你的 IDE 中自定义编译.
### 连接服务端
当你将此项目的功能写完并进行了编译后,需要将此 API 服务进行部署。此 API 服务可以部署在本地网络、本机网络或者广域网中。我们强烈建议以「最低延迟」为基准进行部署,以保证 `ServerListener` 可以同时监听 *多个* 游戏服务端。
你可以在游戏服务端的启动配置中对 API 的地址和端口进行设定。

View File

@ -1,7 +1,9 @@
# BattleBit Remastered Community Server API
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
Language English | [中文](/README-zhCN.md) | [한국어](/README_koKR.md)
This repository provides an API that can be used to handle events on your community server(s) and manipulate them.
## Getting started
@ -24,38 +26,8 @@ The easiest way to get started with all of this, is to use `Program.cs` and add
This project can either be built by using [`dotnet build`](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-build) on the command-line or by using the run / build options inside your preferred IDE.
### Connecting to the gameserver
### Connecting to the gameserver(s)
After writing and compiling this project. You will want to host it somewhere. This could be on the same server that the gameserver runs on, or somewhere completely different. We do recommend to keep the latency to the gameserver minimal for smoother and faster communication. The same `ServerListener` can be used for *multiple* gameservers at the same time. You can specify the API server (address & port) in the launch options of the gameserver.
After writing and compiling this project. You will want to host it somewhere. This could be on the same server that the gameservers run on, or somewhere completely different. We do recommend to keep the latency to the gameserver minimal for smoother and faster communication. The same `ServerListener` can be used for *multiple* gameservers at the same time. You can specify the API server (address & port) in the launch options of the gameserver.
Chinese Version
# BBR服务器API
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
这个是BBR像素战地的服务端API
## 如何开始
### 系统需求
- 拥有 BBR 服务端的开服权限,且满足开服条件。
- 可以写基于 [.NET 6.0](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) 的 C# 代码.
- 生产环境中可以部署此代码.
### 如何制作功能
查看维基 [此页面](https://github.com/MrOkiDoki/BattleBit-Community-Server-API/wiki).
使用这个 API 将开启一个`ServerListener` 的监听进程,来了解你的服务端中正在发生什么。
如果想给你的服务端添加功能,可以直接把功能写在 `Program.cs` 中,当然也可以按照框架规范进行其他的功能纂写。
### 编译
可以直接使用 [`dotnet build`](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-build) 的命令进行编译,或者在你的 VSC 等 IDE 中自定义编译.
### 连接服务端
当你将此项目的功能写完并进行了编译后,需要将此 API 进行部署。此服务可以部署在本地网络、本机网络或者广域网中。我们强烈建议以「最低延迟」为基准进行部署,以保证 `ServerListener` 可以同时监听*多个*游戏服务端。
你可以在游戏服务端的启动配置文件中对 API 的地址和端口进行设定。
The gameserver connects to the API with the launch argument `"-apiendpoint=<IP>:<port>"`, where `<port>` is the port that the listener listens on and the `<IP>` is the IP of the API server.

35
README_koKR.md Normal file
View File

@ -0,0 +1,35 @@
# BattleBit Remastered Community Server API
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
Language [English](/README.md) | [中文](/README-zhCN.md) | 한국어
이 레포지토리는 BattleBit Remastered 커뮤니티 서버에서 이벤트를 처리하고 조작하는 데 사용할 수 있는 API를 제공합니다.
## Getting started
### Prerequisites
- BattleBit Remastered 커뮤니티 서버를 개설할 수 있는 권한을 보유하고 BattleBit Remastered 에서 요구하는 조건을 충족해야 합니다.
- [.NET 6.0](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)을 이용해 C# 코드를 작성하고 컴파일할 수 있어야 합니다.
- (프로덕션 한정) 이 API를 호스팅할 장소가 필요합니다.
### Writing
문서와 예제는 [wiki](https://github.com/MrOkiDoki/BattleBit-Community-Server-API/wiki)에서 확인할 수 있습니다.
이 API를 사용하는 방법은 `Player``GameServer`*자체* 서브클래스의 유형을 전달하는 `ServerListener` 인스턴스를 생성하고 이를 시작하는 것입니다.
이러한 서브클래스에서는 `Player``GameServer`에 이미 존재하는 메서드를 오버라이드하여 자신만의 메서드, 필드 및 프로퍼티를 추가할 수 있습니다
가장 쉬운 방법은 `Program.cs`에 직접 코드를 추가/작성한 후 프로젝트를 빌드하는 것입니다.
### Build
이 프로젝트는 CMD에서 [`dotnet build`](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-build)를 사용하거나 선호하는 IDE에서 Run / Build 옵션을 이용하여 빌드할 수 있습니다.
### Connecting to the gameserver
API를 작성하고 컴파일한 후, 다른 곳에서 호스팅하고 싶을 수도 있습니다. 게임 서버가 실행되는 서버와 동일한 서버일 수 있으며 완전히 다른 서버일 수도 있습니다. 보다 원활하고 빠른 통신을 위해 게임 서버와의 지연 시간을 최소화하는 것이 좋습니다. 동일한 `ServerListener`를 동시에 *여러* 게임 서버에 사용할 수 있습니다. 게임 서버의 실행 옵션에서 API 서버(주소와 포트)를 지정할 수 있습니다.
게임 서버는 실행 인수 `-apiendpoint=<IP>:<PORT>`를 사용하여 API에 연결합니다. 여기서 <PORT>는 리스터가 수신하는 포트이고, IP는 API 서버의 IP입니다.