forked from rwmt/Multiplayer
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPlayerManager.cs
More file actions
202 lines (158 loc) · 7.1 KB
/
PlayerManager.cs
File metadata and controls
202 lines (158 loc) · 7.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
namespace Multiplayer.Common
{
public class PlayerManager
{
private MultiplayerServer server;
const long ThrottleMillis = 1000;
private Dictionary<object, long> lastConnection = new();
private Stopwatch clock = Stopwatch.StartNew();
public List<ServerPlayer> Players { get; } = new();
public IEnumerable<ServerPlayer> JoinedPlayers => Players.Where(p => p.HasJoined);
public IEnumerable<ServerPlayer> PlayingPlayers => Players.Where(p => p.IsPlaying);
public PlayerManager(MultiplayerServer server)
{
this.server = server;
}
public void SendLatencies()
{
var writer = new ByteWriter();
writer.WriteEnum(PlayerListAction.Latencies);
writer.WriteInt32(JoinedPlayers.Count());
foreach (var player in JoinedPlayers)
player.WriteLatencyUpdate(writer);
server.SendToPlaying(Packets.Server_PlayerList, writer.ToArray());
}
// id can be an IPAddress or CSteamID
public MpDisconnectReason? OnPreConnect(object id)
{
if (server.FullyStarted is false)
return MpDisconnectReason.ServerStarting;
if (id is IPAddress addr && IPAddress.IsLoopback(addr))
return null;
if (server.settings.maxPlayers > 0 &&
Players.Count(p => !p.IsArbiter) >= server.settings.maxPlayers)
return MpDisconnectReason.ServerFull;
if (lastConnection.TryGetValue(id, out var last) && clock.ElapsedMilliseconds - last < ThrottleMillis)
return MpDisconnectReason.Throttled;
lastConnection[id] = clock.ElapsedMilliseconds;
return null;
}
private int nextPlayerId;
public ServerPlayer OnConnected(ConnectionBase conn)
{
if (conn.serverPlayer != null)
ServerLog.Error($"Connection {conn} already has a server player");
conn.serverPlayer = new ServerPlayer(nextPlayerId++, conn);
Players.Add(conn.serverPlayer);
ServerLog.Log($"New connection: {conn}");
return conn.serverPlayer;
}
public void SetDisconnected(ConnectionBase conn, MpDisconnectReason reason)
{
if (conn.State == ConnectionStateEnum.Disconnected) return;
conn.StateObj?.OnDisconnect();
ServerPlayer player = conn.serverPlayer;
Players.Remove(player);
if (player.hasJoined)
{
// Handle unexpected disconnections by sending PlayerCount command
if (reason == MpDisconnectReason.ClientLeft || reason == MpDisconnectReason.NetFailed)
{
// Send PlayerCount command to remove player from their last known map
if (player.currentMap != -1)
{
byte[] playerCountData = ByteWriter.GetBytes(player.currentMap, -1); // previousMap: player's map, newMap: -1 (disconnected)
server.commands.Send(CommandType.PlayerCount, ScheduledCommand.NoFaction, ScheduledCommand.Global, playerCountData);
}
}
// todo check player.IsPlaying?
// todo FactionId might throw when called for not fully initialized players
// if (Players.All(p => p.FactionId != player.FactionId))
// {
// byte[] data = ByteWriter.GetBytes(player.FactionId);
// server.commands.Send(CommandType.FactionOffline, ScheduledCommand.NoFaction, ScheduledCommand.Global, data);
// }
server.SendNotification("MpPlayerDisconnected", conn.username);
server.SendChat($"{conn.username} has left.");
server.SendToPlaying(Packets.Server_PlayerList, new object[] { PlayerListAction.Remove, player.id });
player.ResetTimeVotes();
}
conn.ChangeState(ConnectionStateEnum.Disconnected);
ServerLog.Log($"Disconnected ({reason}): {conn}");
}
public void OnDesync(ServerPlayer player, int tick, int diffAt)
{
player.UpdateStatus(PlayerStatus.Desynced);
server.HostPlayer.SendPacket(Packets.Server_Traces, new object[] { TracesPacket.Request, tick, diffAt, player.id });
player.ResetTimeVotes();
if (server.settings.pauseOnDesync)
server.commands.PauseAll();
if (server.settings.autoJoinPoint.HasFlag(AutoJoinPointFlags.Desync))
server.worldData.TryStartJoinPointCreation(true);
}
public static ColorRGB[] PlayerColors =
{
new(0,125,255),
new(255,0,0),
new(0,255,45),
new(255,0,150),
new(80,250,250),
new(200,255,75),
new(100,0,75)
};
public static Dictionary<string, ColorRGB> givenColors = new();
public void OnJoin(ServerPlayer player)
{
player.hasJoined = true;
player.FactionId = player.id == 0 || !server.settings.multifaction ?
server.worldData.hostFactionId :
server.worldData.spectatorFactionId;
server.SendNotification("MpPlayerConnected", player.Username);
server.SendChat($"{player.Username} has joined.");
if (!player.IsArbiter)
{
if (!givenColors.TryGetValue(player.Username, out ColorRGB color))
givenColors[player.Username] = color = PlayerColors[givenColors.Count % PlayerColors.Length];
player.color = color;
}
var writer = new ByteWriter();
writer.WriteEnum(PlayerListAction.Add);
writer.WriteRaw(player.SerializePlayerInfo());
server.SendToPlaying(Packets.Server_PlayerList, writer.ToArray());
}
public void SendInitDataCommand(ServerPlayer player)
{
server.commands.Send(
CommandType.InitPlayerData,
ScheduledCommand.NoFaction, ScheduledCommand.Global,
ByteWriter.GetBytes(player.id, server.commands.CanUseDevMode(player))
);
}
public void OnServerStop()
{
foreach (var player in Players)
player.conn.Close(MpDisconnectReason.ServerClosed);
Players.Clear();
}
public void MakeHost(ServerPlayer host)
{
OnJoin(host);
host.conn.ChangeState(ConnectionStateEnum.ServerPlaying);
host.SendPlayerList();
SendInitDataCommand(host);
host.UpdateStatus(PlayerStatus.Playing);
}
public ServerPlayer? GetPlayer(string username)
{
return Players.Find(player => player.Username == username);
}
public ServerPlayer? GetPlayer(int id)
{
return Players.Find(player => player.id == id);
}
}
}