]> git.lizzy.rs Git - dragonblocks_alpha.git/blob - src/server/server_player.c
refactoring
[dragonblocks_alpha.git] / src / server / server_player.c
1 #include <dragonstd/map.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <pthread.h>
6 #include "day.h"
7 #include "entity.h"
8 #include "perlin.h"
9 #include "server/database.h"
10 #include "server/server_config.h"
11 #include "server/server_player.h"
12 #include "server/server_terrain.h"
13
14 static Map players;
15 static Map players_named;
16
17 // main thread
18 // called on server shutdown
19 static void player_drop(ServerPlayer *player)
20 {
21         pthread_rwlock_rdlock(&player->lock_peer);
22         pthread_t recv_thread = player->peer ? player->peer->recv_thread : 0;
23         pthread_rwlock_unlock(&player->lock_peer);
24
25         server_player_disconnect(player);
26         if (recv_thread)
27                 pthread_join(recv_thread, NULL);
28
29         refcount_drp(&player->rc); // map no longer has a reference to player
30 }
31
32 // any thread
33 // called when all refs have been dropped
34 static void player_delete(ServerPlayer *player)
35 {
36         refcount_dst(&player->rc);
37
38         pthread_rwlock_destroy(&player->lock_peer);
39
40         free(player->name);
41         pthread_rwlock_destroy(&player->lock_auth);
42
43         pthread_rwlock_destroy(&player->lock_pos);
44
45         free(player);
46 }
47
48 // recv thread
49 // called when auth was successful
50 static void player_spawn(ServerPlayer *player)
51 {
52         // lock_pos has already been wrlocked by caller
53         if (!database_load_player(player->name, &player->pos, &player->rot)) {
54                 player->pos = (v3f64) {0.0, server_terrain_spawn_height() + 0.5, 0.0};
55                 player->rot = (v3f32) {0.0f, 0.0f, 0.0f};
56                 database_create_player(player->name, player->pos, player->rot);
57         }
58
59         // since this is recv thread, we don't need lock_peer
60         dragonnet_peer_send_ToClientInfo(player->peer, &(ToClientInfo) {
61                 .seed = seed,
62                 .load_distance = server_config.load_distance,
63         });
64         dragonnet_peer_send_ToClientTimeOfDay(player->peer, &(ToClientTimeOfDay) {
65                 .time_of_day = get_time_of_day(),
66         });
67         dragonnet_peer_send_ToClientMovement(player->peer, &(ToClientMovement) {
68                 .flight = false,
69                 .collision = true,
70                 .speed = server_config.movement.speed_normal,
71                 .gravity = server_config.movement.gravity,
72                 .jump = server_config.movement.jump,
73         });
74         dragonnet_peer_send_ToClientEntityAdd(player->peer, &(ToClientEntityAdd) {
75                 .type = ENTITY_LOCALPLAYER,
76                 .data = {
77                         .id = player->id,
78                         .pos = player->pos,
79                         .rot = player->rot,
80                         .box = {{-0.3f, 0.0f, -0.3f}, {0.3f, 1.75f, 0.3f}},
81                         .eye = {0.0f, 1.75f, 0.0f},
82                         .nametag = NULL,
83                 },
84         });
85 }
86
87 // any thread
88 // called when adding, getting or removing a player from the map
89 static int cmp_player_id(const Refcount *player, const u64 *id)
90 {
91         return u64_cmp(&((ServerPlayer *) player->obj)->id, id);
92 }
93
94 // any thread
95 // called when adding, getting or removing a player from the players_named Map
96 static int cmp_player_name(const Refcount *player, const char *name)
97 {
98         // names of players in players_named Map don't change, no lock_auth needed
99         return strcmp(((ServerPlayer *) player->obj)->name, name);
100 }
101
102 // main thread
103 // called on server startup
104 void server_player_init()
105 {
106         map_ini(&players);
107         map_ini(&players_named);
108 }
109
110 // main thread
111 // called on server shutdown
112 void server_player_deinit()
113 {
114         // just forget about name -> player mapping
115         map_cnl(&players_named, &refcount_drp, NULL, NULL,          0);
116         // disconnect players and forget about them
117         map_cnl(&players,       &player_drop,  NULL, &refcount_obj, 0);
118 }
119
120 // accept thread
121 // called on new connection
122 void server_player_add(DragonnetPeer *peer)
123 {
124         ServerPlayer *player = malloc(sizeof *player);
125
126         // ID is allocated later in this function
127         player->id = 0;
128         refcount_ini(&player->rc, player, &player_delete);
129
130         player->peer = peer;
131         pthread_rwlock_init(&player->lock_peer, NULL);
132
133         player->auth = false;
134         // use address as name until auth is done
135         player->name = dragonnet_addr_str(peer->raddr);
136         pthread_rwlock_init(&player->lock_auth, NULL);
137
138         player->pos = (v3f64) {0.0f, 0.0f, 0.0f};
139         player->rot = (v3f32) {0.0f, 0.0f, 0.0f};
140         pthread_rwlock_init(&player->lock_pos, NULL);
141
142         printf("[access] connected %s\n", player->name);
143         peer->extra = refcount_grb(&player->rc);
144
145         // keep the search tree somewhat balanced by using random IDs
146         // duplicate IDs are very unlikely, but it doesn't hurt to check
147         // make sure to avoid 0 since it's not a valid ID
148         while (!player->id || !map_add(&players, &player->id, &player->rc, &cmp_player_id, &refcount_inc))
149                 player->id = random();
150
151         // player has been grabbed by Map and peer
152         refcount_drp(&player->rc);
153 }
154
155 // recv thread
156 // called on connection close
157 void server_player_remove(DragonnetPeer *peer)
158 {
159         ServerPlayer *player = peer->extra;
160         peer->extra = NULL; // technically not necessary, but just in case
161
162         // peer will be deleted - forget about it!
163         pthread_rwlock_wrlock(&player->lock_peer);
164         player->peer = NULL;
165         pthread_rwlock_unlock(&player->lock_peer);
166
167         // only (this) recv thread will modify the auth or name fields, no lock_auth needed
168         // map_del returns false if it was canceled
169         // (we don't want disconnect messages for every player on server shutdown)
170         if (map_del(&players, &player->id, &cmp_player_id, &refcount_drp, NULL, NULL))
171                 printf("[access] disconnected %s\n", player->name);
172         if (player->auth)
173                 map_del(&players_named, player->name, &cmp_player_name, &refcount_drp, NULL, NULL);
174
175         // peer no longer has a reference to player
176         refcount_drp(&player->rc);
177 }
178
179 // any thread
180 ServerPlayer *server_player_grab(u64 id)
181 {
182         // 0 is an invalid ID
183         return id ? map_get(&players, &id, &cmp_player_id, &refcount_grb) : NULL;
184 }
185
186 // any thread
187 ServerPlayer *server_player_grab_named(char *name)
188 {
189         // NULL is an invalid name
190         return name ? map_get(&players, name, &cmp_player_name, &refcount_grb) : NULL;
191 }
192
193 // recv thread
194 // called on recv auth packet
195 bool server_player_auth(ServerPlayer *player, char *name)
196 {
197         pthread_rwlock_wrlock(&player->lock_auth);
198         pthread_rwlock_wrlock(&player->lock_pos);
199
200         // temporary change name, save old name to either free or reset it if auth fails
201         char *old_name = player->name;
202         player->name = name;
203
204         bool success = map_add(&players_named, player->name, &player->rc, &cmp_player_name, &refcount_inc);
205
206         printf("[access] authentication %s: %s -> %s\n", success ? "success" : "failure", old_name, player->name);
207
208         // since this is recv thread, we don't need lock_peer
209         dragonnet_peer_send_ToClientAuth(player->peer, &(ToClientAuth) {
210                 .success = success,
211         });
212
213         if (success) {
214                 free(old_name);
215                 player->auth = true;
216                 // load player from database and send some initial info
217                 player_spawn(player);
218         } else {
219                 player->name = old_name;
220         }
221
222         pthread_rwlock_unlock(&player->lock_pos);
223         pthread_rwlock_unlock(&player->lock_auth);
224         return success;
225 }
226
227 // any thread
228 void server_player_disconnect(ServerPlayer *player)
229 {
230         pthread_rwlock_rdlock(&player->lock_peer);
231         // the recv thread will call server_player_remove when the connection was shut down
232         if (player->peer)
233                 dragonnet_peer_shutdown(player->peer);
234         pthread_rwlock_unlock(&player->lock_peer);
235 }
236
237 // any thread
238 void server_player_iterate(void *func, void *arg)
239 {
240         map_trv(&players_named, func, arg, &refcount_obj, TRAVERSION_INORDER);
241 }
242
243 /*
244 229779
245 373875
246 374193
247 110738
248 390402
249 357272
250 390480
251
252 (these are only the wholesome ones)
253 */