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"
15 static Map players_named;
17 static void send_entity_add(ServerPlayer *player, ServerPlayer *entity)
19 dragonnet_peer_send_ToClientEntityAdd(player->peer, &(ToClientEntityAdd) {
20 .type = player == entity ? ENTITY_LOCALPLAYER : ENTITY_PLAYER,
25 .nametag = entity->name,
30 static void send_entity_remove(ServerPlayer *client, ServerPlayer *entity)
32 dragonnet_peer_send_ToClientEntityRemove(client->peer, &(ToClientEntityRemove) {
37 static void send_entity_update_pos_rot(ServerPlayer *client, ServerPlayer *entity)
40 dragonnet_peer_send_ToClientEntityUpdatePosRot(client->peer, &(ToClientEntityUpdatePosRot) {
47 static void send_entity_add_existing(ServerPlayer *entity, ServerPlayer *client)
49 if (client != entity) {
50 pthread_rwlock_rdlock(&entity->lock_pos);
51 send_entity_add(client, entity);
52 pthread_rwlock_unlock(&entity->lock_pos);
56 static void send_player_inventory(ServerPlayer *client, ServerPlayer *player)
58 ToClientPlayerInventory pkt;
60 item_stack_serialize(&player->inventory.left, &pkt.left);
61 item_stack_serialize(&player->inventory.right, &pkt.right);
62 dragonnet_peer_send_ToClientPlayerInventory(client->peer, &pkt);
65 static void send_player_inventory_existing(ServerPlayer *player, ServerPlayer *client)
67 if (client != player) {
68 pthread_rwlock_rdlock(&player->lock_inv);
69 send_player_inventory(client, player);
70 pthread_rwlock_unlock(&player->lock_inv);
75 // called on server shutdown
76 static void player_drop(ServerPlayer *player)
78 pthread_rwlock_rdlock(&player->lock_peer);
79 pthread_t recv_thread = player->peer ? player->peer->recv_thread : 0;
80 pthread_rwlock_unlock(&player->lock_peer);
82 server_player_disconnect(player);
84 pthread_join(recv_thread, NULL);
86 refcount_drp(&player->rc); // map no longer has a reference to player
90 // called when all refs have been dropped
91 static void player_delete(ServerPlayer *player)
93 refcount_dst(&player->rc);
95 pthread_rwlock_destroy(&player->lock_peer);
98 pthread_rwlock_destroy(&player->lock_auth);
100 pthread_rwlock_destroy(&player->lock_pos);
102 item_stack_destroy(&player->inventory.left);
103 item_stack_destroy(&player->inventory.right);
104 pthread_rwlock_destroy(&player->lock_inv);
110 // called when auth was successful
111 static void player_spawn(ServerPlayer *player)
113 // lock_pos has already been wrlocked by caller
114 if (!database_load_player(player->name, &player->pos, &player->rot)) {
115 player->pos = (v3f64) {0.0, server_terrain_spawn_height() + 0.5, 0.0};
116 player->rot = (v3f32) {0.0f, 0.0f, 0.0f};
117 database_create_player(player->name, player->pos, player->rot);
120 item_stack_set(&player->inventory.left, ITEM_NONE + rand() % (ITEM_AXE - ITEM_NONE + 1), 1, (Blob) {0, NULL});
121 item_stack_set(&player->inventory.right, ITEM_NONE + rand() % (ITEM_AXE - ITEM_NONE + 1), 1, (Blob) {0, NULL});
123 // since this is recv thread, we don't need lock_peer
124 dragonnet_peer_send_ToClientInfo(player->peer, &(ToClientInfo) {
126 .load_distance = server_config.load_distance,
128 dragonnet_peer_send_ToClientTimeOfDay(player->peer, &(ToClientTimeOfDay) {
129 .time_of_day = get_time_of_day(),
131 dragonnet_peer_send_ToClientMovement(player->peer, &(ToClientMovement) {
134 .speed = server_config.movement.speed_normal,
135 .gravity = server_config.movement.gravity,
136 .jump = server_config.movement.jump,
139 server_player_iterate(&send_entity_add, player);
140 server_player_iterate(&send_entity_add_existing, player);
142 server_player_iterate(&send_player_inventory, player);
143 server_player_iterate(&send_player_inventory_existing, player);
147 // called when adding, getting or removing a player from the map
148 static int cmp_player_id(const Refcount *player, const u64 *id)
150 return u64_cmp(&((ServerPlayer *) player->obj)->id, id);
154 // called when adding, getting or removing a player from the players_named Map
155 static int cmp_player_name(const Refcount *player, const char *name)
157 // names of players in players_named Map don't change, no lock_auth needed
158 return strcmp(((ServerPlayer *) player->obj)->name, name);
162 // called on server startup
163 void server_player_init()
166 map_ini(&players_named);
170 // called on server shutdown
171 void server_player_deinit()
173 // just forget about name -> player mapping
174 map_cnl(&players_named, &refcount_drp, NULL, NULL, 0);
175 // disconnect players and forget about them
176 map_cnl(&players, &player_drop, NULL, &refcount_obj, 0);
180 // called on new connection
181 void server_player_add(DragonnetPeer *peer)
183 ServerPlayer *player = malloc(sizeof *player);
185 // ID is allocated later in this function
187 refcount_ini(&player->rc, player, &player_delete);
190 pthread_rwlock_init(&player->lock_peer, NULL);
192 player->auth = false;
193 // use address as name until auth is done
194 player->name = strdup(peer->address);
195 pthread_rwlock_init(&player->lock_auth, NULL);
197 player->pos = (v3f64) {0.0f, 0.0f, 0.0f};
198 player->rot = (v3f32) {0.0f, 0.0f, 0.0f};
199 pthread_rwlock_init(&player->lock_pos, NULL);
201 item_stack_initialize(&player->inventory.left);
202 item_stack_initialize(&player->inventory.right);
203 pthread_rwlock_init(&player->lock_inv, NULL);
205 printf("[access] connected %s\n", player->name);
206 peer->extra = refcount_grb(&player->rc);
208 // keep the search tree somewhat balanced by using random IDs
209 // duplicate IDs are very unlikely, but it doesn't hurt to check
210 // make sure to avoid 0 since it's not a valid ID
211 while (!player->id || !map_add(&players, &player->id, &player->rc, &cmp_player_id, &refcount_inc))
212 player->id = random();
214 // player has been grabbed by Map and peer
215 refcount_drp(&player->rc);
219 // called on connection close
220 void server_player_remove(DragonnetPeer *peer)
222 ServerPlayer *player = peer->extra;
223 peer->extra = NULL; // technically not necessary, but just in case
225 // peer will be deleted - forget about it!
226 pthread_rwlock_wrlock(&player->lock_peer);
228 pthread_rwlock_unlock(&player->lock_peer);
230 // only (this) recv thread will modify the auth or name fields, no lock_auth needed
231 // map_del returns false if it was canceled
232 // (we don't want disconnect messages for every player on server shutdown)
233 if (map_del(&players, &player->id, &cmp_player_id, &refcount_drp, NULL, NULL))
234 printf("[access] disconnected %s\n", player->name);
236 if (player->auth && map_del(&players_named, player->name, &cmp_player_name, &refcount_drp, NULL, NULL)) {
237 pthread_rwlock_rdlock(&player->lock_pos);
238 server_player_iterate(&send_entity_remove, player);
239 pthread_rwlock_unlock(&player->lock_pos);
242 // peer no longer has a reference to player
243 refcount_drp(&player->rc);
247 ServerPlayer *server_player_grab(u64 id)
249 // 0 is an invalid ID
250 return id ? map_get(&players, &id, &cmp_player_id, &refcount_grb) : NULL;
254 ServerPlayer *server_player_grab_named(char *name)
256 // NULL is an invalid name
257 return name ? map_get(&players, name, &cmp_player_name, &refcount_grb) : NULL;
261 // called on recv auth packet
262 bool server_player_auth(ServerPlayer *player, char *name)
264 pthread_rwlock_wrlock(&player->lock_auth);
265 pthread_rwlock_wrlock(&player->lock_pos);
267 // temporary change name, save old name to either free or reset it if auth fails
268 char *old_name = player->name;
271 bool success = map_add(&players_named, player->name, &player->rc, &cmp_player_name, &refcount_inc);
273 printf("[access] authentication %s: %s -> %s\n", success ? "success" : "failure", old_name, player->name);
275 // since this is recv thread, we don't need lock_peer
276 dragonnet_peer_send_ToClientAuth(player->peer, &(ToClientAuth) {
283 // load player from database and send some initial info
284 player_spawn(player);
286 player->name = old_name;
289 pthread_rwlock_unlock(&player->lock_pos);
290 pthread_rwlock_unlock(&player->lock_auth);
295 void server_player_move(ServerPlayer *player, v3f64 pos, v3f32 rot)
297 pthread_rwlock_wrlock(&player->lock_pos);
298 // this is recv thread, no lock_auth needed
299 database_update_player_pos_rot(player->name, player->pos = pos, player->rot = rot);
300 server_player_iterate(&send_entity_update_pos_rot, player);
301 pthread_rwlock_unlock(&player->lock_pos);
305 void server_player_disconnect(ServerPlayer *player)
307 pthread_rwlock_rdlock(&player->lock_peer);
308 // the recv thread will call server_player_remove when the connection was shut down
310 dragonnet_peer_shutdown(player->peer);
311 pthread_rwlock_unlock(&player->lock_peer);
315 void server_player_iterate(void *func, void *arg)
317 map_trv(&players_named, func, arg, &refcount_obj, TRAVERSION_INORDER);
329 (these are only the wholesome ones)