1 #include <dragonstd/map.h>
9 #include "server/database.h"
10 #include "server/server_config.h"
11 #include "server/server_player.h"
12 #include "server/server_terrain.h"
19 static Map players_named;
21 static void send_entity_add(ServerPlayer *player, ServerPlayer *entity)
23 dragonnet_peer_send_ToClientEntityAdd(player->peer, &(ToClientEntityAdd) {
24 .type = player == entity ? ENTITY_LOCALPLAYER : ENTITY_PLAYER,
29 .nametag = entity->name,
34 static void send_entity_remove(ServerPlayer *client, ServerPlayer *entity)
36 dragonnet_peer_send_ToClientEntityRemove(client->peer, &(ToClientEntityRemove) {
41 static void send_entity_update_pos_rot(ServerPlayer *client, ServerPlayer *entity)
44 dragonnet_peer_send_ToClientEntityUpdatePosRot(client->peer, &(ToClientEntityUpdatePosRot) {
51 static void send_entity_add_existing(ServerPlayer *entity, ServerPlayer *client)
53 if (client != entity) {
54 pthread_rwlock_rdlock(&entity->lock_pos);
55 send_entity_add(client, entity);
56 pthread_rwlock_unlock(&entity->lock_pos);
60 static void send_player_inventory(ServerPlayer *client, ServerPlayer *player)
62 ToClientPlayerInventory pkt;
64 item_stack_serialize(&player->inventory.left, &pkt.left);
65 item_stack_serialize(&player->inventory.right, &pkt.right);
66 dragonnet_peer_send_ToClientPlayerInventory(client->peer, &pkt);
69 static void send_player_inventory_existing(ServerPlayer *player, ServerPlayer *client)
71 if (client != player) {
72 pthread_mutex_lock(&player->mtx_inv);
73 send_player_inventory(client, player);
74 pthread_mutex_unlock(&player->mtx_inv);
79 // called on server shutdown
80 static void player_drop(ServerPlayer *player)
82 pthread_rwlock_rdlock(&player->lock_peer);
83 pthread_t recv_thread = player->peer ? player->peer->recv_thread : 0;
84 pthread_rwlock_unlock(&player->lock_peer);
86 server_player_disconnect(player);
88 pthread_join(recv_thread, NULL);
90 refcount_drp(&player->rc); // map no longer has a reference to player
94 // called when all refs have been dropped
95 static void player_delete(ServerPlayer *player)
97 refcount_dst(&player->rc);
99 pthread_rwlock_destroy(&player->lock_peer);
102 pthread_rwlock_destroy(&player->lock_auth);
104 pthread_rwlock_destroy(&player->lock_pos);
106 item_stack_destroy(&player->inventory.left);
107 item_stack_destroy(&player->inventory.right);
108 pthread_mutex_destroy(&player->mtx_inv);
114 // called when auth was successful
115 static void player_spawn(ServerPlayer *player)
117 // lock_pos has already been wrlocked by caller
118 if (!database_load_player(player->name, &player->pos, &player->rot)) {
119 player->pos = (v3f64) {0.0, server_terrain_spawn_height() + 0.5, 0.0};
120 player->rot = (v3f32) {0.0f, 0.0f, 0.0f};
121 database_create_player(player->name, player->pos, player->rot);
124 item_stack_set(&player->inventory.left, ITEM_NONE + rand() % (COUNT_ITEM - ITEM_NONE), 1, (Blob) {0, NULL});
125 item_stack_set(&player->inventory.right, ITEM_NONE + rand() % (COUNT_ITEM - ITEM_NONE), 1, (Blob) {0, NULL});
127 // since this is recv thread, we don't need lock_peer
128 dragonnet_peer_send_ToClientInfo(player->peer, &(ToClientInfo) {
130 .load_distance = server_config.load_distance,
132 dragonnet_peer_send_ToClientTimeOfDay(player->peer, &(ToClientTimeOfDay) {
133 .time_of_day = get_time_of_day(),
135 dragonnet_peer_send_ToClientMovement(player->peer, &(ToClientMovement) {
138 .speed = server_config.movement.speed_normal,
139 .gravity = server_config.movement.gravity,
140 .jump = server_config.movement.jump,
143 server_player_iterate(&send_entity_add, player);
144 server_player_iterate(&send_entity_add_existing, player);
146 server_player_iterate(&send_player_inventory, player);
147 server_player_iterate(&send_player_inventory_existing, player);
151 // called when adding, getting or removing a player from the map
152 static int cmp_player_id(const Refcount *player, const u64 *id)
154 return u64_cmp(&((ServerPlayer *) player->obj)->id, id);
158 // called when adding, getting or removing a player from the players_named Map
159 static int cmp_player_name(const Refcount *player, const char *name)
161 // names of players in players_named Map don't change, no lock_auth needed
162 return strcmp(((ServerPlayer *) player->obj)->name, name);
166 // called on server startup
167 void server_player_init()
170 map_ini(&players_named);
174 // called on server shutdown
175 void server_player_deinit()
177 // just forget about name -> player mapping
178 map_cnl(&players_named, &refcount_drp, NULL, NULL, 0);
179 // disconnect players and forget about them
180 map_cnl(&players, &player_drop, NULL, &refcount_obj, 0);
184 // called on new connection
185 void server_player_add(DragonnetPeer *peer)
187 ServerPlayer *player = malloc(sizeof *player);
189 // ID is allocated later in this function
191 refcount_ini(&player->rc, player, &player_delete);
194 pthread_rwlock_init(&player->lock_peer, NULL);
196 player->auth = false;
197 // use address as name until auth is done
198 player->name = strdup(peer->address);
199 pthread_rwlock_init(&player->lock_auth, NULL);
201 player->pos = (v3f64) {0.0f, 0.0f, 0.0f};
202 player->rot = (v3f32) {0.0f, 0.0f, 0.0f};
203 pthread_rwlock_init(&player->lock_pos, NULL);
205 item_stack_initialize(&player->inventory.left);
206 item_stack_initialize(&player->inventory.right);
207 pthread_mutex_init(&player->mtx_inv, NULL);
209 printf("[access] connected %s\n", player->name);
210 peer->extra = refcount_grb(&player->rc);
212 // keep the search tree somewhat balanced by using random IDs
213 // duplicate IDs are very unlikely, but it doesn't hurt to check
214 // make sure to avoid 0 since it's not a valid ID
215 while (!player->id || !map_add(&players, &player->id, &player->rc, &cmp_player_id, &refcount_inc))
216 player->id = random();
218 // player has been grabbed by Map and peer
219 refcount_drp(&player->rc);
223 // called on connection close
224 void server_player_remove(DragonnetPeer *peer)
226 ServerPlayer *player = peer->extra;
227 peer->extra = NULL; // technically not necessary, but just in case
229 // peer will be deleted - forget about it!
230 pthread_rwlock_wrlock(&player->lock_peer);
232 pthread_rwlock_unlock(&player->lock_peer);
234 // only (this) recv thread will modify the auth or name fields, no lock_auth needed
235 // map_del returns false if it was canceled
236 // (we don't want disconnect messages for every player on server shutdown)
237 if (map_del(&players, &player->id, &cmp_player_id, &refcount_drp, NULL, NULL))
238 printf("[access] disconnected %s\n", player->name);
240 if (player->auth && map_del(&players_named, player->name, &cmp_player_name, &refcount_drp, NULL, NULL)) {
241 pthread_rwlock_rdlock(&player->lock_pos);
242 server_player_iterate(&send_entity_remove, player);
243 pthread_rwlock_unlock(&player->lock_pos);
246 // peer no longer has a reference to player
247 refcount_drp(&player->rc);
251 ServerPlayer *server_player_grab(u64 id)
253 // 0 is an invalid ID
254 return id ? map_get(&players, &id, &cmp_player_id, &refcount_grb) : NULL;
258 ServerPlayer *server_player_grab_named(char *name)
260 // NULL is an invalid name
261 return name ? map_get(&players, name, &cmp_player_name, &refcount_grb) : NULL;
265 // called on recv auth packet
266 bool server_player_auth(ServerPlayer *player, char *name)
268 pthread_rwlock_wrlock(&player->lock_auth);
269 pthread_rwlock_wrlock(&player->lock_pos);
271 // temporary change name, save old name to either free or reset it if auth fails
272 char *old_name = player->name;
275 bool success = map_add(&players_named, player->name, &player->rc, &cmp_player_name, &refcount_inc);
277 printf("[access] authentication %s: %s -> %s\n", success ? "success" : "failure", old_name, player->name);
279 // since this is recv thread, we don't need lock_peer
280 dragonnet_peer_send_ToClientAuth(player->peer, &(ToClientAuth) {
287 // load player from database and send some initial info
288 player_spawn(player);
290 player->name = old_name;
293 pthread_rwlock_unlock(&player->lock_pos);
294 pthread_rwlock_unlock(&player->lock_auth);
299 void server_player_move(ServerPlayer *player, v3f64 pos, v3f32 rot)
301 pthread_rwlock_wrlock(&player->lock_pos);
302 // this is recv thread, no lock_auth needed
303 database_update_player_pos_rot(player->name, player->pos = pos, player->rot = rot);
304 server_player_iterate(&send_entity_update_pos_rot, player);
305 pthread_rwlock_unlock(&player->lock_pos);
309 void server_player_disconnect(ServerPlayer *player)
311 pthread_rwlock_rdlock(&player->lock_peer);
312 // the recv thread will call server_player_remove when the connection was shut down
314 dragonnet_peer_shutdown(player->peer);
315 pthread_rwlock_unlock(&player->lock_peer);
319 void server_player_iterate(void *func, void *arg)
321 map_trv(&players_named, func, arg, &refcount_obj, TRAVERSION_INORDER);
324 void server_player_inventory_changed(ServerPlayer *player)
326 server_player_iterate(&send_player_inventory, player);
338 (these are only the wholesome ones)