3 Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include "connection.h"
23 #include "constants.h"
26 #include <ITextSceneNode.h>
28 #include "main.h" // For g_settings
31 #include "environment.h"
33 #include "content_sao.h"
35 #include "materials.h"
37 Player::Player(IGameDef *gamedef):
38 touching_ground(false),
40 in_water_stable(false),
43 inventory_backup(NULL),
44 craftresult_is_preview(true),
46 peer_id(PEER_ID_INEXISTENT),
55 updateName("<not set>");
61 delete inventory_backup;
64 void Player::wieldItem(u16 item)
66 m_selected_item = item;
69 void Player::resetInventory()
72 inventory.addList("main", PLAYER_INVENTORY_SIZE);
73 inventory.addList("craft", 9);
74 inventory.addList("craftresult", 1);
77 // Y direction is ignored
78 void Player::accelerate(v3f target_speed, f32 max_increase)
80 v3f d_wanted = target_speed - m_speed;
82 f32 dl_wanted = d_wanted.getLength();
87 v3f d = d_wanted.normalize() * dl;
94 if(m_speed.X < target_speed.X - max_increase)
95 m_speed.X += max_increase;
96 else if(m_speed.X > target_speed.X + max_increase)
97 m_speed.X -= max_increase;
98 else if(m_speed.X < target_speed.X)
99 m_speed.X = target_speed.X;
100 else if(m_speed.X > target_speed.X)
101 m_speed.X = target_speed.X;
103 if(m_speed.Z < target_speed.Z - max_increase)
104 m_speed.Z += max_increase;
105 else if(m_speed.Z > target_speed.Z + max_increase)
106 m_speed.Z -= max_increase;
107 else if(m_speed.Z < target_speed.Z)
108 m_speed.Z = target_speed.Z;
109 else if(m_speed.Z > target_speed.Z)
110 m_speed.Z = target_speed.Z;
114 v3s16 Player::getLightPosition() const
116 return floatToInt(m_position + v3f(0,BS+BS/2,0), BS);
119 void Player::serialize(std::ostream &os)
121 // Utilize a Settings object for storing values
123 args.setS32("version", 1);
124 args.set("name", m_name);
125 //args.set("password", m_password);
126 args.setFloat("pitch", m_pitch);
127 args.setFloat("yaw", m_yaw);
128 args.setV3F("position", m_position);
129 args.setBool("craftresult_is_preview", craftresult_is_preview);
130 args.setS32("hp", hp);
134 os<<"PlayerArgsEnd\n";
136 // If actual inventory is backed up due to creative mode, save it
137 // instead of the dummy creative mode inventory
139 inventory_backup->serialize(os);
141 inventory.serialize(os);
144 void Player::deSerialize(std::istream &is)
151 throw SerializationError
152 ("Player::deSerialize(): PlayerArgsEnd not found");
154 std::getline(is, line);
155 std::string trimmedline = trim(line);
156 if(trimmedline == "PlayerArgsEnd")
158 args.parseConfigLine(line);
161 //args.getS32("version"); // Version field value not used
162 std::string name = args.get("name");
163 updateName(name.c_str());
164 setPitch(args.getFloat("pitch"));
165 setYaw(args.getFloat("yaw"));
166 setPosition(args.getV3F("position"));
168 craftresult_is_preview = args.getBool("craftresult_is_preview");
169 }catch(SettingNotFoundException &e){
170 craftresult_is_preview = true;
173 hp = args.getS32("hp");
174 }catch(SettingNotFoundException &e){
178 inventory.deSerialize(is, m_gamedef);
185 ServerRemotePlayer::ServerRemotePlayer(ServerEnvironment *env):
186 Player(env->getGameDef()),
187 ServerActiveObject(env, v3f(0,0,0)),
188 m_last_good_position(0,0,0),
189 m_last_good_position_age(0),
190 m_additional_items(),
191 m_inventory_not_sent(false),
192 m_hp_not_sent(false),
193 m_respawn_active(false),
194 m_is_in_environment(false),
195 m_position_not_sent(false)
198 ServerRemotePlayer::ServerRemotePlayer(ServerEnvironment *env, v3f pos_, u16 peer_id_,
200 Player(env->getGameDef()),
201 ServerActiveObject(env, pos_),
202 m_inventory_not_sent(false),
203 m_hp_not_sent(false),
204 m_is_in_environment(false),
205 m_position_not_sent(false)
211 ServerRemotePlayer::~ServerRemotePlayer()
213 clearAddToInventoryLater();
216 void ServerRemotePlayer::setPosition(const v3f &position)
218 Player::setPosition(position);
219 ServerActiveObject::setBasePosition(position);
220 m_position_not_sent = true;
223 InventoryItem* ServerRemotePlayer::getWieldedItem()
225 InventoryList *list = inventory.getList("main");
227 return list->getItem(m_selected_item);
231 /* ServerActiveObject interface */
233 void ServerRemotePlayer::addedToEnvironment()
235 assert(!m_is_in_environment);
236 m_is_in_environment = true;
239 void ServerRemotePlayer::removingFromEnvironment()
241 assert(m_is_in_environment);
242 m_is_in_environment = false;
245 void ServerRemotePlayer::step(float dtime, bool send_recommended)
247 if(send_recommended == false)
250 if(m_position_not_sent)
252 m_position_not_sent = false;
254 std::ostringstream os(std::ios::binary);
255 // command (0 = update position)
258 writeV3F1000(os, getPosition());
260 writeF1000(os, getYaw());
261 // create message and add to list
262 ActiveObjectMessage aom(getId(), false, os.str());
263 m_messages_out.push_back(aom);
267 std::string ServerRemotePlayer::getClientInitializationData()
269 std::ostringstream os(std::ios::binary);
273 os<<serializeString(getName());
275 writeV3F1000(os, getPosition());
277 writeF1000(os, getYaw());
281 std::string ServerRemotePlayer::getStaticData()
287 void ServerRemotePlayer::punch(ServerActiveObject *puncher,
288 float time_from_last_punch)
293 // "Material" properties of a player
294 MaterialProperties mp;
295 mp.diggability = DIGGABLE_NORMAL;
296 mp.crackiness = -1.0;
297 mp.cuttability = 1.0;
299 ToolDiggingProperties tp;
300 puncher->getWieldDiggingProperties(&tp);
302 HittingProperties hitprop = getHittingProperties(&mp, &tp,
303 time_from_last_punch);
305 infostream<<"1. getHP()="<<getHP()<<std::endl;
306 setHP(getHP() - hitprop.hp);
307 infostream<<"2. getHP()="<<getHP()<<std::endl;
308 puncher->damageWieldedItem(hitprop.wear);
311 void ServerRemotePlayer::rightClick(ServerActiveObject *clicker)
315 void ServerRemotePlayer::setPos(v3f pos)
318 // Movement caused by this command is always valid
319 m_last_good_position = pos;
320 m_last_good_position_age = 0;
322 void ServerRemotePlayer::moveTo(v3f pos, bool continuous)
325 // Movement caused by this command is always valid
326 m_last_good_position = pos;
327 m_last_good_position_age = 0;
330 void ServerRemotePlayer::getWieldDiggingProperties(ToolDiggingProperties *dst)
332 IGameDef *gamedef = m_env->getGameDef();
333 IToolDefManager *tdef = gamedef->tdef();
335 InventoryItem *item = getWieldedItem();
336 if(item == NULL || std::string(item->getName()) != "ToolItem"){
337 *dst = ToolDiggingProperties();
340 ToolItem *titem = (ToolItem*)item;
341 *dst = tdef->getDiggingProperties(titem->getToolName());
344 void ServerRemotePlayer::damageWieldedItem(u16 amount)
346 infostream<<"Damaging "<<getName()<<"'s wielded item for amount="
348 InventoryList *list = inventory.getList("main");
351 InventoryItem *item = list->getItem(m_selected_item);
352 if(item && (std::string)item->getName() == "ToolItem"){
353 ToolItem *titem = (ToolItem*)item;
354 bool weared_out = titem->addWear(amount);
356 list->deleteItem(m_selected_item);
359 bool ServerRemotePlayer::addToInventory(InventoryItem *item)
361 infostream<<"Adding "<<item->getName()<<" into "<<getName()
362 <<"'s inventory"<<std::endl;
364 InventoryList *ilist = inventory.getList("main");
368 // In creative mode, just delete the item
369 if(g_settings->getBool("creative_mode")){
373 // Skip if inventory has no free space
374 if(ilist->roomForItem(item) == false)
376 infostream<<"Player inventory has no free space"<<std::endl;
381 InventoryItem *leftover = ilist->addItem(item);
384 m_inventory_not_sent = true;
388 void ServerRemotePlayer::addToInventoryLater(InventoryItem *item)
390 infostream<<"Adding (later) "<<item->getName()<<" into "<<getName()
391 <<"'s inventory"<<std::endl;
392 m_additional_items.push_back(item);
394 void ServerRemotePlayer::clearAddToInventoryLater()
396 for (std::vector<InventoryItem*>::iterator
397 i = m_additional_items.begin();
398 i != m_additional_items.end(); i++)
402 m_additional_items.clear();
404 void ServerRemotePlayer::completeAddToInventoryLater(u16 preferred_index)
406 InventoryList *ilist = inventory.getList("main");
409 clearAddToInventoryLater();
413 // In creative mode, just delete the items
414 if(g_settings->getBool("creative_mode"))
416 clearAddToInventoryLater();
420 for (std::vector<InventoryItem*>::iterator
421 i = m_additional_items.begin();
422 i != m_additional_items.end(); i++)
424 InventoryItem *item = *i;
425 InventoryItem *leftover = item;
426 leftover = ilist->addItem(preferred_index, leftover);
427 leftover = ilist->addItem(leftover);
430 m_additional_items.clear();
431 m_inventory_not_sent = true;
433 void ServerRemotePlayer::setHP(s16 hp_)
437 // FIXME: don't hardcode maximum HP, make configurable per object
445 m_hp_not_sent = true;
447 s16 ServerRemotePlayer::getHP()
457 LocalPlayer::LocalPlayer(IGameDef *gamedef):
459 m_sneak_node(32767,32767,32767),
460 m_sneak_node_exists(false)
462 // Initialize hp to 0, so that no hearts will be shown if server
463 // doesn't support health points
467 LocalPlayer::~LocalPlayer()
471 void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
472 core::list<CollisionInfo> *collision_info)
474 INodeDefManager *nodemgr = m_gamedef->ndef();
476 v3f position = getPosition();
477 v3f oldpos = position;
478 v3s16 oldpos_i = floatToInt(oldpos, BS);
480 v3f old_speed = m_speed;
482 /*std::cout<<"oldpos_i=("<<oldpos_i.X<<","<<oldpos_i.Y<<","
483 <<oldpos_i.Z<<")"<<std::endl;*/
486 Calculate new position
488 position += m_speed * dtime;
490 // Skip collision detection if a special movement mode is used
491 bool free_move = g_settings->getBool("free_move");
494 setPosition(position);
502 // Player position in nodes
503 v3s16 pos_i = floatToInt(position, BS);
506 Check if player is in water (the oscillating value)
509 // If in water, the threshold of coming out is at higher y
512 v3s16 pp = floatToInt(position + v3f(0,BS*0.1,0), BS);
513 in_water = nodemgr->get(map.getNode(pp).getContent()).isLiquid();
515 // If not in water, the threshold of going in is at lower y
518 v3s16 pp = floatToInt(position + v3f(0,BS*0.5,0), BS);
519 in_water = nodemgr->get(map.getNode(pp).getContent()).isLiquid();
522 catch(InvalidPositionException &e)
528 Check if player is in water (the stable value)
531 v3s16 pp = floatToInt(position + v3f(0,0,0), BS);
532 in_water_stable = nodemgr->get(map.getNode(pp).getContent()).isLiquid();
534 catch(InvalidPositionException &e)
536 in_water_stable = false;
540 Check if player is climbing
544 v3s16 pp = floatToInt(position + v3f(0,0.5*BS,0), BS);
545 v3s16 pp2 = floatToInt(position + v3f(0,-0.2*BS,0), BS);
546 is_climbing = ((nodemgr->get(map.getNode(pp).getContent()).climbable ||
547 nodemgr->get(map.getNode(pp2).getContent()).climbable) && !free_move);
549 catch(InvalidPositionException &e)
555 Collision uncertainty radius
556 Make it a bit larger than the maximum distance of movement
558 //f32 d = pos_max_d * 1.1;
559 // A fairly large value in here makes moving smoother
562 // This should always apply, otherwise there are glitches
563 assert(d > pos_max_d);
565 float player_radius = BS*0.35;
566 float player_height = BS*1.7;
568 // Maximum distance over border for sneaking
569 f32 sneak_max = BS*0.4;
572 If sneaking, player has larger collision radius to keep from
576 player_radius = sneak_max + d*1.1;*/
579 If sneaking, keep in range from the last walked node and don't
582 if(control.sneak && m_sneak_node_exists)
584 f32 maxd = 0.5*BS + sneak_max;
585 v3f lwn_f = intToFloat(m_sneak_node, BS);
586 position.X = rangelim(position.X, lwn_f.X-maxd, lwn_f.X+maxd);
587 position.Z = rangelim(position.Z, lwn_f.Z-maxd, lwn_f.Z+maxd);
589 f32 min_y = lwn_f.Y + 0.5*BS;
590 if(position.Y < min_y)
594 //v3f old_speed = m_speed;
601 // Report fall collision
602 if(old_speed.Y < m_speed.Y - 0.1)
605 info.t = COLLISION_FALL;
606 info.speed = m_speed.Y - old_speed.Y;
607 collision_info->push_back(info);
614 Calculate player collision box (new and old)
616 core::aabbox3d<f32> playerbox(
617 position.X - player_radius,
619 position.Z - player_radius,
620 position.X + player_radius,
621 position.Y + player_height,
622 position.Z + player_radius
624 core::aabbox3d<f32> playerbox_old(
625 oldpos.X - player_radius,
627 oldpos.Z - player_radius,
628 oldpos.X + player_radius,
629 oldpos.Y + player_height,
630 oldpos.Z + player_radius
634 If the player's feet touch the topside of any node, this is
637 Player is allowed to jump when this is true.
639 touching_ground = false;
641 /*std::cout<<"Checking collisions for ("
642 <<oldpos_i.X<<","<<oldpos_i.Y<<","<<oldpos_i.Z
644 <<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z
647 bool standing_on_unloaded = false;
650 Go through every node around the player
652 for(s16 y = oldpos_i.Y - 1; y <= oldpos_i.Y + 2; y++)
653 for(s16 z = oldpos_i.Z - 1; z <= oldpos_i.Z + 1; z++)
654 for(s16 x = oldpos_i.X - 1; x <= oldpos_i.X + 1; x++)
656 bool is_unloaded = false;
658 // Player collides into walkable nodes
659 if(nodemgr->get(map.getNode(v3s16(x,y,z))).walkable == false)
662 catch(InvalidPositionException &e)
665 // Doing nothing here will block the player from
666 // walking over map borders
669 core::aabbox3d<f32> nodebox = getNodeBox(v3s16(x,y,z), BS);
672 See if the player is touching ground.
674 Player touches ground if player's minimum Y is near node's
675 maximum Y and player's X-Z-area overlaps with the node's
678 Use 0.15*BS so that it is easier to get on a node.
681 //fabs(nodebox.MaxEdge.Y-playerbox.MinEdge.Y) < d
682 fabs(nodebox.MaxEdge.Y-playerbox.MinEdge.Y) < 0.15*BS
683 && nodebox.MaxEdge.X-d > playerbox.MinEdge.X
684 && nodebox.MinEdge.X+d < playerbox.MaxEdge.X
685 && nodebox.MaxEdge.Z-d > playerbox.MinEdge.Z
686 && nodebox.MinEdge.Z+d < playerbox.MaxEdge.Z
688 touching_ground = true;
690 standing_on_unloaded = true;
693 // If player doesn't intersect with node, ignore node.
694 if(playerbox.intersectsWithBox(nodebox) == false)
698 Go through every axis
701 v3f(0,0,1), // back-front
702 v3f(0,1,0), // top-bottom
703 v3f(1,0,0), // right-left
705 for(u16 i=0; i<3; i++)
708 Calculate values along the axis
710 f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[i]);
711 f32 nodemin = nodebox.MinEdge.dotProduct(dirs[i]);
712 f32 playermax = playerbox.MaxEdge.dotProduct(dirs[i]);
713 f32 playermin = playerbox.MinEdge.dotProduct(dirs[i]);
714 f32 playermax_old = playerbox_old.MaxEdge.dotProduct(dirs[i]);
715 f32 playermin_old = playerbox_old.MinEdge.dotProduct(dirs[i]);
718 Check collision for the axis.
719 Collision happens when player is going through a surface.
723 // Make it easier to get on top of a node
726 bool negative_axis_collides =
727 (nodemax > playermin && nodemax <= playermin_old + neg_d
728 && m_speed.dotProduct(dirs[i]) < 0);
729 bool positive_axis_collides =
730 (nodemin < playermax && nodemin >= playermax_old - pos_d
731 && m_speed.dotProduct(dirs[i]) > 0);*/
732 bool negative_axis_collides =
733 (nodemax > playermin && nodemax <= playermin_old + d
734 && m_speed.dotProduct(dirs[i]) < 0);
735 bool positive_axis_collides =
736 (nodemin < playermax && nodemin >= playermax_old - d
737 && m_speed.dotProduct(dirs[i]) > 0);
738 bool main_axis_collides =
739 negative_axis_collides || positive_axis_collides;
742 Check overlap of player and node in other axes
744 bool other_axes_overlap = true;
745 for(u16 j=0; j<3; j++)
749 f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[j]);
750 f32 nodemin = nodebox.MinEdge.dotProduct(dirs[j]);
751 f32 playermax = playerbox.MaxEdge.dotProduct(dirs[j]);
752 f32 playermin = playerbox.MinEdge.dotProduct(dirs[j]);
753 if(!(nodemax - d > playermin && nodemin + d < playermax))
755 other_axes_overlap = false;
761 If this is a collision, revert the position in the main
764 if(other_axes_overlap && main_axis_collides)
766 //v3f old_speed = m_speed;
768 m_speed -= m_speed.dotProduct(dirs[i]) * dirs[i];
769 position -= position.dotProduct(dirs[i]) * dirs[i];
770 position += oldpos.dotProduct(dirs[i]) * dirs[i];
774 // Report fall collision
775 if(old_speed.Y < m_speed.Y - 0.1)
778 info.t = COLLISION_FALL;
779 info.speed = m_speed.Y - old_speed.Y;
780 collision_info->push_back(info);
789 Check the nodes under the player to see from which node the
790 player is sneaking from, if any.
793 v3s16 pos_i_bottom = floatToInt(position - v3f(0,BS/2,0), BS);
794 v2f player_p2df(position.X, position.Z);
795 f32 min_distance_f = 100000.0*BS;
796 // If already seeking from some node, compare to it.
797 /*if(m_sneak_node_exists)
799 v3f sneaknode_pf = intToFloat(m_sneak_node, BS);
800 v2f sneaknode_p2df(sneaknode_pf.X, sneaknode_pf.Z);
801 f32 d_horiz_f = player_p2df.getDistanceFrom(sneaknode_p2df);
802 f32 d_vert_f = fabs(sneaknode_pf.Y + BS*0.5 - position.Y);
803 // Ignore if player is not on the same level (likely dropped)
804 if(d_vert_f < 0.15*BS)
805 min_distance_f = d_horiz_f;
807 v3s16 new_sneak_node = m_sneak_node;
808 for(s16 x=-1; x<=1; x++)
809 for(s16 z=-1; z<=1; z++)
811 v3s16 p = pos_i_bottom + v3s16(x,0,z);
812 v3f pf = intToFloat(p, BS);
813 v2f node_p2df(pf.X, pf.Z);
814 f32 distance_f = player_p2df.getDistanceFrom(node_p2df);
815 f32 max_axis_distance_f = MYMAX(
816 fabs(player_p2df.X-node_p2df.X),
817 fabs(player_p2df.Y-node_p2df.Y));
819 if(distance_f > min_distance_f ||
820 max_axis_distance_f > 0.5*BS + sneak_max + 0.1*BS)
824 // The node to be sneaked on has to be walkable
825 if(nodemgr->get(map.getNode(p)).walkable == false)
827 // And the node above it has to be nonwalkable
828 if(nodemgr->get(map.getNode(p+v3s16(0,1,0))).walkable == true)
831 catch(InvalidPositionException &e)
836 min_distance_f = distance_f;
840 bool sneak_node_found = (min_distance_f < 100000.0*BS*0.9);
842 if(control.sneak && m_sneak_node_exists)
845 m_sneak_node = new_sneak_node;
849 m_sneak_node = new_sneak_node;
850 m_sneak_node_exists = sneak_node_found;
854 If sneaking, the player's collision box can be in air, so
855 this has to be set explicitly
857 if(sneak_node_found && control.sneak)
858 touching_ground = true;
864 setPosition(position);
871 // Report fall collision
872 if(old_speed.Y < m_speed.Y - 0.1 && !standing_on_unloaded)
875 info.t = COLLISION_FALL;
876 info.speed = m_speed.Y - old_speed.Y;
877 collision_info->push_back(info);
882 void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d)
884 move(dtime, map, pos_max_d, NULL);
887 void LocalPlayer::applyControl(float dtime)
893 f32 walk_acceleration = 4.0 * BS;
894 f32 walkspeed_max = 4.0 * BS;
896 setPitch(control.pitch);
899 v3f move_direction = v3f(0,0,1);
900 move_direction.rotateXZBy(getYaw());
902 v3f speed = v3f(0,0,0);
904 bool free_move = g_settings->getBool("free_move");
905 bool fast_move = g_settings->getBool("fast_move");
906 bool continuous_forward = g_settings->getBool("continuous_forward");
908 if(free_move || is_climbing)
910 v3f speed = getSpeed();
915 // Whether superspeed mode is used or not
916 bool superspeed = false;
918 // If free movement and fast movement, always move fast
919 if(free_move && fast_move)
922 // Auxiliary button 1 (E)
927 // In free movement mode, aux1 descends
928 v3f speed = getSpeed();
932 speed.Y = -walkspeed_max;
937 v3f speed = getSpeed();
943 // If not free movement but fast is allowed, aux1 is
950 if(continuous_forward)
951 speed += move_direction;
955 if(continuous_forward)
958 speed += move_direction;
962 speed -= move_direction;
966 speed += move_direction.crossProduct(v3f(0,1,0));
970 speed += move_direction.crossProduct(v3f(0,-1,0));
976 v3f speed = getSpeed();
980 speed.Y = walkspeed_max;
983 else if(touching_ground)
985 v3f speed = getSpeed();
987 NOTE: The d value in move() affects jump height by
988 raising the height at which the jump speed is kept
989 at its starting value
994 // Use the oscillating value for getting out of water
995 // (so that the player doesn't fly on the surface)
998 v3f speed = getSpeed();
1003 else if(is_climbing)
1005 v3f speed = getSpeed();
1011 // The speed of the player (Y is ignored)
1013 speed = speed.normalize() * walkspeed_max * 5.0;
1014 else if(control.sneak)
1015 speed = speed.normalize() * walkspeed_max / 3.0;
1017 speed = speed.normalize() * walkspeed_max;
1019 f32 inc = walk_acceleration * BS * dtime;
1021 // Faster acceleration if fast and free movement
1022 if(free_move && fast_move)
1023 inc = walk_acceleration * BS * dtime * 10;
1025 // Accelerate to target speed with maximum increment
1026 accelerate(speed, inc);