diff --git a/minetest.conf.example b/minetest.conf.example index 49fc95ba2..295725876 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -245,6 +245,21 @@ # Files that are not present would be fetched the usual way #remote_media = +# Physics stuff +#movement_acceleration_default = 2 +#movement_acceleration_air = 0.5 +#movement_acceleration_fast = 4 +#movement_speed_walk = 4 +#movement_speed_crouch = 1.35 +#movement_speed_fast = 20 +#movement_speed_climb = 2 +#movement_speed_jump = 6.5 +#movement_speed_descend = 6 +#movement_liquid_fluidity = 1 +#movement_liquid_fluidity_smooth = 0.5 +#movement_liquid_sink = 10 +#movement_gravity = 9.81 + # Mapgen stuff #mg_name = v6 #water_level = 1 diff --git a/src/client.cpp b/src/client.cpp index 9969ef538..415f07311 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -1514,6 +1514,26 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id) } } } + else if(command == TOCLIENT_MOVEMENT) + { + std::string datastring((char*)&data[2], datasize-2); + std::istringstream is(datastring, std::ios_base::binary); + Player *player = m_env.getLocalPlayer(); + assert(player != NULL); + + player->movement_acceleration_default = readF1000(is) * BS; + player->movement_acceleration_air = readF1000(is) * BS; + player->movement_acceleration_fast = readF1000(is) * BS; + player->movement_speed_walk = readF1000(is) * BS; + player->movement_speed_crouch = readF1000(is) * BS; + player->movement_speed_fast = readF1000(is) * BS; + player->movement_speed_climb = readF1000(is) * BS; + player->movement_speed_jump = readF1000(is) * BS; + player->movement_liquid_fluidity = readF1000(is) * BS; + player->movement_liquid_fluidity_smooth = readF1000(is) * BS; + player->movement_liquid_sink = readF1000(is) * BS; + player->movement_gravity = readF1000(is) * BS; + } else if(command == TOCLIENT_HP) { std::string datastring((char*)&data[2], datasize-2); diff --git a/src/clientserver.h b/src/clientserver.h index 52b9dc7b0..7fb3e83d2 100644 --- a/src/clientserver.h +++ b/src/clientserver.h @@ -364,6 +364,23 @@ enum ToClientCommand u16 len u8[len] formname */ + + TOCLIENT_MOVEMENT = 0x45, + /* + u16 command + f1000 movement_acceleration_default + f1000 movement_acceleration_air + f1000 movement_acceleration_fast + f1000 movement_speed_walk + f1000 movement_speed_crouch + f1000 movement_speed_fast + f1000 movement_speed_climb + f1000 movement_speed_jump + f1000 movement_liquid_fluidity + f1000 movement_liquid_fluidity_smooth + f1000 movement_liquid_sink + f1000 movement_gravity + */ }; enum ToServerCommand diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index a164d0693..1c673f76c 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -172,6 +172,20 @@ void set_default_settings(Settings *settings) settings->setDefault("congestion_control_max_rate", "400"); settings->setDefault("congestion_control_min_rate", "10"); settings->setDefault("remote_media", ""); + + // physics stuff + settings->setDefault("movement_acceleration_default", "2"); + settings->setDefault("movement_acceleration_air", "0.5"); + settings->setDefault("movement_acceleration_fast", "8"); + settings->setDefault("movement_speed_walk", "4"); + settings->setDefault("movement_speed_crouch", "1.35"); + settings->setDefault("movement_speed_fast", "20"); + settings->setDefault("movement_speed_climb", "2"); + settings->setDefault("movement_speed_jump", "6.5"); + settings->setDefault("movement_liquid_fluidity", "1"); + settings->setDefault("movement_liquid_fluidity_smooth", "0.5"); + settings->setDefault("movement_liquid_sink", "10"); + settings->setDefault("movement_gravity", "9.81"); //mapgen related things settings->setDefault("mg_name", "v6"); diff --git a/src/environment.cpp b/src/environment.cpp index 51255b918..ebf5e9a63 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -2065,20 +2065,37 @@ void ClientEnvironment::step(float dtime) { // Gravity v3f speed = lplayer->getSpeed(); - if(lplayer->swimming_up == false) - speed.Y -= 9.81 * BS * dtime_part * 2; + if(lplayer->in_liquid == false) + speed.Y -= lplayer->movement_gravity * dtime_part * 2; - // Water resistance - if(lplayer->in_water_stable || lplayer->in_water) + // Liquid floating / sinking + if(lplayer->in_liquid && !lplayer->swimming_vertical) + speed.Y -= lplayer->movement_liquid_sink * dtime_part * 2; + + // Liquid resistance + if(lplayer->in_liquid_stable || lplayer->in_liquid) { - f32 max_down = 2.0*BS; - if(speed.Y < -max_down) speed.Y = -max_down; + // How much the node's viscosity blocks movement, ranges between 0 and 1 + // Should match the scale at which viscosity increase affects other liquid attributes + const f32 viscosity_factor = 0.3; - f32 max = 2.5*BS; - if(speed.getLength() > max) - { - speed = speed / speed.getLength() * max; - } + v3f d_wanted = -speed / lplayer->movement_liquid_fluidity; + f32 dl = d_wanted.getLength(); + if(dl > lplayer->movement_liquid_fluidity_smooth) + dl = lplayer->movement_liquid_fluidity_smooth; + dl *= (lplayer->liquid_viscosity * viscosity_factor) + (1 - viscosity_factor); + + v3f d = d_wanted.normalize() * dl; + speed += d; + +#if 0 // old code + if(speed.X > lplayer->movement_liquid_fluidity + lplayer->movement_liquid_fluidity_smooth) speed.X -= lplayer->movement_liquid_fluidity_smooth; + if(speed.X < -lplayer->movement_liquid_fluidity - lplayer->movement_liquid_fluidity_smooth) speed.X += lplayer->movement_liquid_fluidity_smooth; + if(speed.Y > lplayer->movement_liquid_fluidity + lplayer->movement_liquid_fluidity_smooth) speed.Y -= lplayer->movement_liquid_fluidity_smooth; + if(speed.Y < -lplayer->movement_liquid_fluidity - lplayer->movement_liquid_fluidity_smooth) speed.Y += lplayer->movement_liquid_fluidity_smooth; + if(speed.Z > lplayer->movement_liquid_fluidity + lplayer->movement_liquid_fluidity_smooth) speed.Z -= lplayer->movement_liquid_fluidity_smooth; + if(speed.Z < -lplayer->movement_liquid_fluidity - lplayer->movement_liquid_fluidity_smooth) speed.Z += lplayer->movement_liquid_fluidity_smooth; +#endif } lplayer->setSpeed(speed); diff --git a/src/localplayer.cpp b/src/localplayer.cpp index 2d0d77140..8b6d7e2f6 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -90,37 +90,39 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d, */ /* - Check if player is in water (the oscillating value) + Check if player is in liquid (the oscillating value) */ try{ - // If in water, the threshold of coming out is at higher y - if(in_water) + // If in liquid, the threshold of coming out is at higher y + if(in_liquid) { v3s16 pp = floatToInt(position + v3f(0,BS*0.1,0), BS); - in_water = nodemgr->get(map.getNode(pp).getContent()).isLiquid(); + in_liquid = nodemgr->get(map.getNode(pp).getContent()).isLiquid(); + liquid_viscosity = nodemgr->get(map.getNode(pp).getContent()).liquid_viscosity; } - // If not in water, the threshold of going in is at lower y + // If not in liquid, the threshold of going in is at lower y else { v3s16 pp = floatToInt(position + v3f(0,BS*0.5,0), BS); - in_water = nodemgr->get(map.getNode(pp).getContent()).isLiquid(); + in_liquid = nodemgr->get(map.getNode(pp).getContent()).isLiquid(); + liquid_viscosity = nodemgr->get(map.getNode(pp).getContent()).liquid_viscosity; } } catch(InvalidPositionException &e) { - in_water = false; + in_liquid = false; } /* - Check if player is in water (the stable value) + Check if player is in liquid (the stable value) */ try{ v3s16 pp = floatToInt(position + v3f(0,0,0), BS); - in_water_stable = nodemgr->get(map.getNode(pp).getContent()).isLiquid(); + in_liquid_stable = nodemgr->get(map.getNode(pp).getContent()).isLiquid(); } catch(InvalidPositionException &e) { - in_water_stable = false; + in_liquid_stable = false; } /* @@ -159,7 +161,7 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d, If sneaking, keep in range from the last walked node and don't fall off from it */ - if(control.sneak && m_sneak_node_exists && !(fly_allowed && g_settings->getBool("free_move"))) + if(control.sneak && m_sneak_node_exists && !(fly_allowed && g_settings->getBool("free_move")) && !in_liquid) { f32 maxd = 0.5*BS + sneak_max; v3f lwn_f = intToFloat(m_sneak_node, BS); @@ -315,7 +317,7 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d, } if(bouncy_jump && control.jump){ - m_speed.Y += 6.5*BS; + m_speed.Y += movement_speed_jump*BS; touching_ground = false; MtEvent *e = new SimpleTriggerEvent("PlayerJump"); m_gamedef->event()->put(e); @@ -348,7 +350,7 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d, */ const ContentFeatures &f = nodemgr->get(map.getNodeNoEx(getStandingNodePos())); // Determine if jumping is possible - m_can_jump = touching_ground; + m_can_jump = touching_ground && !in_liquid; if(itemgroup_get(f.groups, "disable_jump")) m_can_jump = false; } @@ -361,12 +363,8 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d) void LocalPlayer::applyControl(float dtime) { // Clear stuff - swimming_up = false; + swimming_vertical = false; - // Random constants - f32 walk_acceleration = 4.0 * BS; - f32 walkspeed_max = 4.0 * BS; - setPitch(control.pitch); setYaw(control.yaw); @@ -380,22 +378,17 @@ void LocalPlayer::applyControl(float dtime) v3f move_direction = v3f(0,0,1); move_direction.rotateXZBy(getYaw()); - v3f speed = v3f(0,0,0); + v3f speedH = v3f(0,0,0); // Horizontal (X, Z) + v3f speedV = v3f(0,0,0); // Vertical (Y) bool fly_allowed = m_gamedef->checkLocalPrivilege("fly"); bool fast_allowed = m_gamedef->checkLocalPrivilege("fast"); bool free_move = fly_allowed && g_settings->getBool("free_move"); bool fast_move = fast_allowed && g_settings->getBool("fast_move"); + bool fast_or_aux1_descends = (fast_move && control.aux1) || (fast_move && g_settings->getBool("aux1_descends")); bool continuous_forward = g_settings->getBool("continuous_forward"); - if(free_move || is_climbing) - { - v3f speed = getSpeed(); - speed.Y = 0; - setSpeed(speed); - } - // Whether superspeed mode is used or not bool superspeed = false; @@ -415,18 +408,21 @@ void LocalPlayer::applyControl(float dtime) if(free_move) { // In free movement mode, aux1 descends - v3f speed = getSpeed(); if(fast_move) - speed.Y = -20*BS; + speedV.Y = -movement_speed_fast; else - speed.Y = -walkspeed_max; - setSpeed(speed); + speedV.Y = -movement_speed_walk; + } + else if(in_liquid || in_liquid_stable) + { + // Always use fast when aux1_descends & fast_move are enabled in liquid, since the aux1 button would mean both turbo and "swim down" causing a conflict + speedV.Y = -movement_speed_fast; + swimming_vertical = true; } else if(is_climbing) { - v3f speed = getSpeed(); - speed.Y = -3*BS; - setSpeed(speed); + // Always use fast when aux1_descends & fast_move are enabled when climbing, since the aux1 button would mean both turbo and "descend" causing a conflict + speedV.Y = -movement_speed_fast; } else { @@ -456,66 +452,69 @@ void LocalPlayer::applyControl(float dtime) if(free_move) { // In free movement mode, sneak descends - v3f speed = getSpeed(); - if(fast_move && (control.aux1 || - g_settings->getBool("always_fly_fast"))) - speed.Y = -20*BS; + if(fast_move && (control.aux1 || g_settings->getBool("always_fly_fast"))) + speedV.Y = -movement_speed_fast; else - speed.Y = -walkspeed_max; - setSpeed(speed); + speedV.Y = -movement_speed_walk; + } + else if(in_liquid || in_liquid_stable) + { + if(fast_or_aux1_descends) + // Always use fast when aux1_descends & fast_move are enabled in liquid, since the aux1 button would mean both turbo and "swim down" causing a conflict + speedV.Y = -movement_speed_fast; + else + speedV.Y = -movement_speed_walk; + swimming_vertical = true; } else if(is_climbing) { - v3f speed = getSpeed(); - speed.Y = -3*BS; - setSpeed(speed); + if(fast_or_aux1_descends) + // Always use fast when aux1_descends & fast_move are enabled when climbing, since the aux1 button would mean both turbo and "descend" causing a conflict + speedV.Y = -movement_speed_fast; + else + speedV.Y = -movement_speed_climb; } } } if(continuous_forward) - speed += move_direction; + speedH += move_direction; if(control.up) { if(continuous_forward) superspeed = true; else - speed += move_direction; + speedH += move_direction; } if(control.down) { - speed -= move_direction; + speedH -= move_direction; } if(control.left) { - speed += move_direction.crossProduct(v3f(0,1,0)); + speedH += move_direction.crossProduct(v3f(0,1,0)); } if(control.right) { - speed += move_direction.crossProduct(v3f(0,-1,0)); + speedH += move_direction.crossProduct(v3f(0,-1,0)); } if(control.jump) { if(free_move) - { - v3f speed = getSpeed(); - - if(g_settings->getBool("aux1_descends") || - g_settings->getBool("always_fly_fast")) + { + if(g_settings->getBool("aux1_descends") || g_settings->getBool("always_fly_fast")) { if(fast_move) - speed.Y = 20*BS; + speedV.Y = movement_speed_fast; else - speed.Y = walkspeed_max; + speedV.Y = movement_speed_walk; } else { if(fast_move && control.aux1) - speed.Y = 20*BS; + speedV.Y = movement_speed_fast; else - speed.Y = walkspeed_max; + speedV.Y = movement_speed_walk; } - - setSpeed(speed); } else if(m_can_jump) { @@ -524,49 +523,66 @@ void LocalPlayer::applyControl(float dtime) raising the height at which the jump speed is kept at its starting value */ - v3f speed = getSpeed(); - if(speed.Y >= -0.5*BS) + v3f speedJ = getSpeed(); + if(speedJ.Y >= -0.5 * BS) { - speed.Y = 6.5*BS; - setSpeed(speed); + speedJ.Y = movement_speed_jump; + setSpeed(speedJ); MtEvent *e = new SimpleTriggerEvent("PlayerJump"); m_gamedef->event()->put(e); } } - // Use the oscillating value for getting out of water - // (so that the player doesn't fly on the surface) - else if(in_water) + else if(in_liquid) { - v3f speed = getSpeed(); - speed.Y = 1.5*BS; - setSpeed(speed); - swimming_up = true; + if(fast_or_aux1_descends) + // Always use fast when aux1_descends & fast_move are enabled in liquid, since the aux1 button would mean both turbo and "swim down" causing a conflict + speedV.Y = movement_speed_fast; + else + speedV.Y = movement_speed_walk; + swimming_vertical = true; } else if(is_climbing) { - v3f speed = getSpeed(); - speed.Y = 3*BS; - setSpeed(speed); + if(fast_or_aux1_descends) + // Always use fast when aux1_descends & fast_move are enabled when climbing, since the aux1 button would mean both turbo and "descend" causing a conflict + speedV.Y = movement_speed_fast; + else + speedV.Y = movement_speed_climb; } } // The speed of the player (Y is ignored) - if(superspeed) - speed = speed.normalize() * walkspeed_max * 5.0; - else if(control.sneak && !free_move) - speed = speed.normalize() * walkspeed_max / 3.0; + if(superspeed || (is_climbing && fast_or_aux1_descends) || ((in_liquid || in_liquid_stable) && fast_or_aux1_descends)) + speedH = speedH.normalize() * movement_speed_fast; + else if(control.sneak && !free_move && !in_liquid && !in_liquid_stable) + speedH = speedH.normalize() * movement_speed_crouch; else - speed = speed.normalize() * walkspeed_max; - - f32 inc = walk_acceleration * BS * dtime; - - // Faster acceleration if fast and free movement - if(free_move && fast_move && superspeed) - inc = walk_acceleration * BS * dtime * 10; - + speedH = speedH.normalize() * movement_speed_walk; + + // Acceleration increase + f32 incH = 0; // Horizontal (X, Z) + f32 incV = 0; // Vertical (Y) + if((!touching_ground && !free_move && !is_climbing && !in_liquid) || (!free_move && m_can_jump && control.jump)) + { + // Jumping and falling + if(superspeed || (fast_move && control.aux1)) + incH = movement_acceleration_fast * BS * dtime; + else + incH = movement_acceleration_air * BS * dtime; + incV = 0; // No vertical acceleration in air + } + else if(superspeed || (fast_move && control.aux1)) + incH = incV = movement_acceleration_fast * BS * dtime; + else if ((in_liquid || in_liquid_stable) && fast_or_aux1_descends) + // Always use fast when aux1_descends & fast_move are enabled in liquid, since the aux1 button would mean both turbo and "swim down" causing a conflict + incH = incV = movement_acceleration_fast * BS * dtime; + else + incH = incV = movement_acceleration_default * BS * dtime; + // Accelerate to target speed with maximum increment - accelerate(speed, inc); + accelerateHorizontal(speedH, incH); + accelerateVertical(speedV, incV); } v3s16 LocalPlayer::getStandingNodePos() diff --git a/src/player.cpp b/src/player.cpp index 36f12c77b..0c34c4cdf 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -27,10 +27,11 @@ with this program; if not, write to the Free Software Foundation, Inc., Player::Player(IGameDef *gamedef): touching_ground(false), - in_water(false), - in_water_stable(false), + in_liquid(false), + in_liquid_stable(false), + liquid_viscosity(0), is_climbing(false), - swimming_up(false), + swimming_vertical(false), camera_barely_in_ceiling(false), inventory(gamedef->idef()), hp(PLAYER_MAX_HP), @@ -56,19 +57,35 @@ Player::Player(IGameDef *gamedef): "list[current_player;main;0,3.5;8,4;]" "list[current_player;craft;3,0;3,3;]" "list[current_player;craftpreview;7,1;1,1;]"; + + // Initialize movement settings at default values, so movement can work if the server fails to send them + movement_acceleration_default = 2 * BS; + movement_acceleration_air = 0.5 * BS; + movement_acceleration_fast = 8 * BS; + movement_speed_walk = 4 * BS; + movement_speed_crouch = 1.35 * BS; + movement_speed_fast = 20 * BS; + movement_speed_climb = 2 * BS; + movement_speed_jump = 6.5 * BS; + movement_liquid_fluidity = 1 * BS; + movement_liquid_fluidity_smooth = 0.5 * BS; + movement_liquid_sink = 10 * BS; + movement_gravity = 9.81 * BS; } Player::~Player() { } -// Y direction is ignored -void Player::accelerate(v3f target_speed, f32 max_increase) +// Horizontal acceleration (X and Z), Y direction is ignored +void Player::accelerateHorizontal(v3f target_speed, f32 max_increase) { + if(max_increase == 0) + return; + v3f d_wanted = target_speed - m_speed; d_wanted.Y = 0; - f32 dl_wanted = d_wanted.getLength(); - f32 dl = dl_wanted; + f32 dl = d_wanted.getLength(); if(dl > max_increase) dl = max_increase; @@ -76,7 +93,6 @@ void Player::accelerate(v3f target_speed, f32 max_increase) m_speed.X += d.X; m_speed.Z += d.Z; - //m_speed += d; #if 0 // old code if(m_speed.X < target_speed.X - max_increase) @@ -99,6 +115,32 @@ void Player::accelerate(v3f target_speed, f32 max_increase) #endif } +// Vertical acceleration (Y), X and Z directions are ignored +void Player::accelerateVertical(v3f target_speed, f32 max_increase) +{ + if(max_increase == 0) + return; + + f32 d_wanted = target_speed.Y - m_speed.Y; + if(d_wanted > max_increase) + d_wanted = max_increase; + else if(d_wanted < -max_increase) + d_wanted = -max_increase; + + m_speed.Y += d_wanted; + +#if 0 // old code + if(m_speed.Y < target_speed.Y - max_increase) + m_speed.Y += max_increase; + else if(m_speed.Y > target_speed.Y + max_increase) + m_speed.Y -= max_increase; + else if(m_speed.Y < target_speed.Y) + m_speed.Y = target_speed.Y; + else if(m_speed.Y > target_speed.Y) + m_speed.Y = target_speed.Y; +#endif +} + v3s16 Player::getLightPosition() const { return floatToInt(m_position + v3f(0,BS+BS/2,0), BS); diff --git a/src/player.h b/src/player.h index 67b02c344..770afdb37 100644 --- a/src/player.h +++ b/src/player.h @@ -108,8 +108,8 @@ public: m_speed = speed; } - // Y direction is ignored - void accelerate(v3f target_speed, f32 max_increase); + void accelerateHorizontal(v3f target_speed, f32 max_increase); + void accelerateVertical(v3f target_speed, f32 max_increase); v3f getPosition() { @@ -196,17 +196,32 @@ public: bool touching_ground; // This oscillates so that the player jumps a bit above the surface - bool in_water; + bool in_liquid; // This is more stable and defines the maximum speed of the player - bool in_water_stable; + bool in_liquid_stable; + // Gets the viscosity of water to calculate friction + u8 liquid_viscosity; bool is_climbing; - bool swimming_up; + bool swimming_vertical; bool camera_barely_in_ceiling; u8 light; Inventory inventory; + f32 movement_acceleration_default; + f32 movement_acceleration_air; + f32 movement_acceleration_fast; + f32 movement_speed_walk; + f32 movement_speed_crouch; + f32 movement_speed_fast; + f32 movement_speed_climb; + f32 movement_speed_jump; + f32 movement_liquid_fluidity; + f32 movement_liquid_fluidity_smooth; + f32 movement_liquid_sink; + f32 movement_gravity; + u16 hp; float hurt_tilt_timer; diff --git a/src/server.cpp b/src/server.cpp index a8640ad10..572ef4d82 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2344,6 +2344,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) infostream<<"Server: Sending content to " <getFloat("movement_acceleration_default")); + writeF1000(os, g_settings->getFloat("movement_acceleration_air")); + writeF1000(os, g_settings->getFloat("movement_acceleration_fast")); + writeF1000(os, g_settings->getFloat("movement_speed_walk")); + writeF1000(os, g_settings->getFloat("movement_speed_crouch")); + writeF1000(os, g_settings->getFloat("movement_speed_fast")); + writeF1000(os, g_settings->getFloat("movement_speed_climb")); + writeF1000(os, g_settings->getFloat("movement_speed_jump")); + writeF1000(os, g_settings->getFloat("movement_liquid_fluidity")); + writeF1000(os, g_settings->getFloat("movement_liquid_fluidity_smooth")); + writeF1000(os, g_settings->getFloat("movement_liquid_sink")); + writeF1000(os, g_settings->getFloat("movement_gravity")); + + // Make data buffer + std::string s = os.str(); + SharedBuffer data((u8*)s.c_str(), s.size()); + // Send as reliable + con.Send(peer_id, 0, data, true); +} + void Server::SendHP(con::Connection &con, u16 peer_id, u8 hp) { DSTACK(__FUNCTION_NAME); diff --git a/src/server.h b/src/server.h index 86d5513d8..29d47337d 100644 --- a/src/server.h +++ b/src/server.h @@ -602,6 +602,7 @@ private: Static send methods */ + static void SendMovement(con::Connection &con, u16 peer_id); static void SendHP(con::Connection &con, u16 peer_id, u8 hp); static void SendAccessDenied(con::Connection &con, u16 peer_id, const std::wstring &reason);