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 bool ServerRemotePlayer::unlimitedTransferDistance() const
250 void ServerRemotePlayer::step(float dtime, bool send_recommended)
252 if(send_recommended == false)
255 if(m_position_not_sent)
257 m_position_not_sent = false;
259 std::ostringstream os(std::ios::binary);
260 // command (0 = update position)
263 writeV3F1000(os, getPosition());
265 writeF1000(os, getYaw());
266 // create message and add to list
267 ActiveObjectMessage aom(getId(), false, os.str());
268 m_messages_out.push_back(aom);
272 std::string ServerRemotePlayer::getClientInitializationData()
274 std::ostringstream os(std::ios::binary);
278 os<<serializeString(getName());
280 writeV3F1000(os, getPosition());
282 writeF1000(os, getYaw());
286 std::string ServerRemotePlayer::getStaticData()
292 void ServerRemotePlayer::punch(ServerActiveObject *puncher,
293 float time_from_last_punch)
298 // "Material" properties of a player
299 MaterialProperties mp;
300 mp.diggability = DIGGABLE_NORMAL;
301 mp.crackiness = -1.0;
302 mp.cuttability = 1.0;
304 ToolDiggingProperties tp;
305 puncher->getWieldDiggingProperties(&tp);
307 HittingProperties hitprop = getHittingProperties(&mp, &tp,
308 time_from_last_punch);
310 infostream<<"1. getHP()="<<getHP()<<std::endl;
311 setHP(getHP() - hitprop.hp);
312 infostream<<"2. getHP()="<<getHP()<<std::endl;
313 puncher->damageWieldedItem(hitprop.wear);
316 void ServerRemotePlayer::rightClick(ServerActiveObject *clicker)
320 void ServerRemotePlayer::setPos(v3f pos)
323 // Movement caused by this command is always valid
324 m_last_good_position = pos;
325 m_last_good_position_age = 0;
327 void ServerRemotePlayer::moveTo(v3f pos, bool continuous)
330 // Movement caused by this command is always valid
331 m_last_good_position = pos;
332 m_last_good_position_age = 0;
335 void ServerRemotePlayer::getWieldDiggingProperties(ToolDiggingProperties *dst)
337 IGameDef *gamedef = m_env->getGameDef();
338 IToolDefManager *tdef = gamedef->tdef();
340 InventoryItem *item = getWieldedItem();
341 if(item == NULL || std::string(item->getName()) != "ToolItem"){
342 *dst = ToolDiggingProperties();
345 ToolItem *titem = (ToolItem*)item;
346 *dst = tdef->getDiggingProperties(titem->getToolName());
349 void ServerRemotePlayer::damageWieldedItem(u16 amount)
351 infostream<<"Damaging "<<getName()<<"'s wielded item for amount="
353 InventoryList *list = inventory.getList("main");
356 InventoryItem *item = list->getItem(m_selected_item);
357 if(item && (std::string)item->getName() == "ToolItem"){
358 ToolItem *titem = (ToolItem*)item;
359 bool weared_out = titem->addWear(amount);
361 list->deleteItem(m_selected_item);
364 bool ServerRemotePlayer::addToInventory(InventoryItem *item)
366 infostream<<"Adding "<<item->getName()<<" into "<<getName()
367 <<"'s inventory"<<std::endl;
369 InventoryList *ilist = inventory.getList("main");
373 // In creative mode, just delete the item
374 if(g_settings->getBool("creative_mode")){
378 // Skip if inventory has no free space
379 if(ilist->roomForItem(item) == false)
381 infostream<<"Player inventory has no free space"<<std::endl;
386 InventoryItem *leftover = ilist->addItem(item);
389 m_inventory_not_sent = true;
393 void ServerRemotePlayer::addToInventoryLater(InventoryItem *item)
395 infostream<<"Adding (later) "<<item->getName()<<" into "<<getName()
396 <<"'s inventory"<<std::endl;
397 m_additional_items.push_back(item);
399 void ServerRemotePlayer::clearAddToInventoryLater()
401 for (std::vector<InventoryItem*>::iterator
402 i = m_additional_items.begin();
403 i != m_additional_items.end(); i++)
407 m_additional_items.clear();
409 void ServerRemotePlayer::completeAddToInventoryLater(u16 preferred_index)
411 InventoryList *ilist = inventory.getList("main");
414 clearAddToInventoryLater();
418 // In creative mode, just delete the items
419 if(g_settings->getBool("creative_mode"))
421 clearAddToInventoryLater();
425 for (std::vector<InventoryItem*>::iterator
426 i = m_additional_items.begin();
427 i != m_additional_items.end(); i++)
429 InventoryItem *item = *i;
430 InventoryItem *leftover = item;
431 leftover = ilist->addItem(preferred_index, leftover);
432 leftover = ilist->addItem(leftover);
435 m_additional_items.clear();
436 m_inventory_not_sent = true;
438 void ServerRemotePlayer::setHP(s16 hp_)
442 // FIXME: don't hardcode maximum HP, make configurable per object
450 m_hp_not_sent = true;
452 s16 ServerRemotePlayer::getHP()
462 LocalPlayer::LocalPlayer(IGameDef *gamedef):
464 m_sneak_node(32767,32767,32767),
465 m_sneak_node_exists(false)
467 // Initialize hp to 0, so that no hearts will be shown if server
468 // doesn't support health points
472 LocalPlayer::~LocalPlayer()
476 void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
477 core::list<CollisionInfo> *collision_info)
479 INodeDefManager *nodemgr = m_gamedef->ndef();
481 v3f position = getPosition();
482 v3f oldpos = position;
483 v3s16 oldpos_i = floatToInt(oldpos, BS);
485 v3f old_speed = m_speed;
487 /*std::cout<<"oldpos_i=("<<oldpos_i.X<<","<<oldpos_i.Y<<","
488 <<oldpos_i.Z<<")"<<std::endl;*/
491 Calculate new position
493 position += m_speed * dtime;
495 // Skip collision detection if a special movement mode is used
496 bool free_move = g_settings->getBool("free_move");
499 setPosition(position);
507 // Player position in nodes
508 v3s16 pos_i = floatToInt(position, BS);
511 Check if player is in water (the oscillating value)
514 // If in water, the threshold of coming out is at higher y
517 v3s16 pp = floatToInt(position + v3f(0,BS*0.1,0), BS);
518 in_water = nodemgr->get(map.getNode(pp).getContent()).isLiquid();
520 // If not in water, the threshold of going in is at lower y
523 v3s16 pp = floatToInt(position + v3f(0,BS*0.5,0), BS);
524 in_water = nodemgr->get(map.getNode(pp).getContent()).isLiquid();
527 catch(InvalidPositionException &e)
533 Check if player is in water (the stable value)
536 v3s16 pp = floatToInt(position + v3f(0,0,0), BS);
537 in_water_stable = nodemgr->get(map.getNode(pp).getContent()).isLiquid();
539 catch(InvalidPositionException &e)
541 in_water_stable = false;
545 Check if player is climbing
549 v3s16 pp = floatToInt(position + v3f(0,0.5*BS,0), BS);
550 v3s16 pp2 = floatToInt(position + v3f(0,-0.2*BS,0), BS);
551 is_climbing = ((nodemgr->get(map.getNode(pp).getContent()).climbable ||
552 nodemgr->get(map.getNode(pp2).getContent()).climbable) && !free_move);
554 catch(InvalidPositionException &e)
560 Collision uncertainty radius
561 Make it a bit larger than the maximum distance of movement
563 //f32 d = pos_max_d * 1.1;
564 // A fairly large value in here makes moving smoother
567 // This should always apply, otherwise there are glitches
568 assert(d > pos_max_d);
570 float player_radius = BS*0.35;
571 float player_height = BS*1.7;
573 // Maximum distance over border for sneaking
574 f32 sneak_max = BS*0.4;
577 If sneaking, player has larger collision radius to keep from
581 player_radius = sneak_max + d*1.1;*/
584 If sneaking, keep in range from the last walked node and don't
587 if(control.sneak && m_sneak_node_exists)
589 f32 maxd = 0.5*BS + sneak_max;
590 v3f lwn_f = intToFloat(m_sneak_node, BS);
591 position.X = rangelim(position.X, lwn_f.X-maxd, lwn_f.X+maxd);
592 position.Z = rangelim(position.Z, lwn_f.Z-maxd, lwn_f.Z+maxd);
594 f32 min_y = lwn_f.Y + 0.5*BS;
595 if(position.Y < min_y)
599 //v3f old_speed = m_speed;
606 // Report fall collision
607 if(old_speed.Y < m_speed.Y - 0.1)
610 info.t = COLLISION_FALL;
611 info.speed = m_speed.Y - old_speed.Y;
612 collision_info->push_back(info);
619 Calculate player collision box (new and old)
621 core::aabbox3d<f32> playerbox(
622 position.X - player_radius,
624 position.Z - player_radius,
625 position.X + player_radius,
626 position.Y + player_height,
627 position.Z + player_radius
629 core::aabbox3d<f32> playerbox_old(
630 oldpos.X - player_radius,
632 oldpos.Z - player_radius,
633 oldpos.X + player_radius,
634 oldpos.Y + player_height,
635 oldpos.Z + player_radius
639 If the player's feet touch the topside of any node, this is
642 Player is allowed to jump when this is true.
644 touching_ground = false;
646 /*std::cout<<"Checking collisions for ("
647 <<oldpos_i.X<<","<<oldpos_i.Y<<","<<oldpos_i.Z
649 <<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z
652 bool standing_on_unloaded = false;
655 Go through every node around the player
657 for(s16 y = oldpos_i.Y - 1; y <= oldpos_i.Y + 2; y++)
658 for(s16 z = oldpos_i.Z - 1; z <= oldpos_i.Z + 1; z++)
659 for(s16 x = oldpos_i.X - 1; x <= oldpos_i.X + 1; x++)
661 bool is_unloaded = false;
663 // Player collides into walkable nodes
664 if(nodemgr->get(map.getNode(v3s16(x,y,z))).walkable == false)
667 catch(InvalidPositionException &e)
670 // Doing nothing here will block the player from
671 // walking over map borders
674 core::aabbox3d<f32> nodebox = getNodeBox(v3s16(x,y,z), BS);
677 See if the player is touching ground.
679 Player touches ground if player's minimum Y is near node's
680 maximum Y and player's X-Z-area overlaps with the node's
683 Use 0.15*BS so that it is easier to get on a node.
686 //fabs(nodebox.MaxEdge.Y-playerbox.MinEdge.Y) < d
687 fabs(nodebox.MaxEdge.Y-playerbox.MinEdge.Y) < 0.15*BS
688 && nodebox.MaxEdge.X-d > playerbox.MinEdge.X
689 && nodebox.MinEdge.X+d < playerbox.MaxEdge.X
690 && nodebox.MaxEdge.Z-d > playerbox.MinEdge.Z
691 && nodebox.MinEdge.Z+d < playerbox.MaxEdge.Z
693 touching_ground = true;
695 standing_on_unloaded = true;
698 // If player doesn't intersect with node, ignore node.
699 if(playerbox.intersectsWithBox(nodebox) == false)
703 Go through every axis
706 v3f(0,0,1), // back-front
707 v3f(0,1,0), // top-bottom
708 v3f(1,0,0), // right-left
710 for(u16 i=0; i<3; i++)
713 Calculate values along the axis
715 f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[i]);
716 f32 nodemin = nodebox.MinEdge.dotProduct(dirs[i]);
717 f32 playermax = playerbox.MaxEdge.dotProduct(dirs[i]);
718 f32 playermin = playerbox.MinEdge.dotProduct(dirs[i]);
719 f32 playermax_old = playerbox_old.MaxEdge.dotProduct(dirs[i]);
720 f32 playermin_old = playerbox_old.MinEdge.dotProduct(dirs[i]);
723 Check collision for the axis.
724 Collision happens when player is going through a surface.
728 // Make it easier to get on top of a node
731 bool negative_axis_collides =
732 (nodemax > playermin && nodemax <= playermin_old + neg_d
733 && m_speed.dotProduct(dirs[i]) < 0);
734 bool positive_axis_collides =
735 (nodemin < playermax && nodemin >= playermax_old - pos_d
736 && m_speed.dotProduct(dirs[i]) > 0);*/
737 bool negative_axis_collides =
738 (nodemax > playermin && nodemax <= playermin_old + d
739 && m_speed.dotProduct(dirs[i]) < 0);
740 bool positive_axis_collides =
741 (nodemin < playermax && nodemin >= playermax_old - d
742 && m_speed.dotProduct(dirs[i]) > 0);
743 bool main_axis_collides =
744 negative_axis_collides || positive_axis_collides;
747 Check overlap of player and node in other axes
749 bool other_axes_overlap = true;
750 for(u16 j=0; j<3; j++)
754 f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[j]);
755 f32 nodemin = nodebox.MinEdge.dotProduct(dirs[j]);
756 f32 playermax = playerbox.MaxEdge.dotProduct(dirs[j]);
757 f32 playermin = playerbox.MinEdge.dotProduct(dirs[j]);
758 if(!(nodemax - d > playermin && nodemin + d < playermax))
760 other_axes_overlap = false;
766 If this is a collision, revert the position in the main
769 if(other_axes_overlap && main_axis_collides)
771 //v3f old_speed = m_speed;
773 m_speed -= m_speed.dotProduct(dirs[i]) * dirs[i];
774 position -= position.dotProduct(dirs[i]) * dirs[i];
775 position += oldpos.dotProduct(dirs[i]) * dirs[i];
779 // Report fall collision
780 if(old_speed.Y < m_speed.Y - 0.1)
783 info.t = COLLISION_FALL;
784 info.speed = m_speed.Y - old_speed.Y;
785 collision_info->push_back(info);
794 Check the nodes under the player to see from which node the
795 player is sneaking from, if any.
798 v3s16 pos_i_bottom = floatToInt(position - v3f(0,BS/2,0), BS);
799 v2f player_p2df(position.X, position.Z);
800 f32 min_distance_f = 100000.0*BS;
801 // If already seeking from some node, compare to it.
802 /*if(m_sneak_node_exists)
804 v3f sneaknode_pf = intToFloat(m_sneak_node, BS);
805 v2f sneaknode_p2df(sneaknode_pf.X, sneaknode_pf.Z);
806 f32 d_horiz_f = player_p2df.getDistanceFrom(sneaknode_p2df);
807 f32 d_vert_f = fabs(sneaknode_pf.Y + BS*0.5 - position.Y);
808 // Ignore if player is not on the same level (likely dropped)
809 if(d_vert_f < 0.15*BS)
810 min_distance_f = d_horiz_f;
812 v3s16 new_sneak_node = m_sneak_node;
813 for(s16 x=-1; x<=1; x++)
814 for(s16 z=-1; z<=1; z++)
816 v3s16 p = pos_i_bottom + v3s16(x,0,z);
817 v3f pf = intToFloat(p, BS);
818 v2f node_p2df(pf.X, pf.Z);
819 f32 distance_f = player_p2df.getDistanceFrom(node_p2df);
820 f32 max_axis_distance_f = MYMAX(
821 fabs(player_p2df.X-node_p2df.X),
822 fabs(player_p2df.Y-node_p2df.Y));
824 if(distance_f > min_distance_f ||
825 max_axis_distance_f > 0.5*BS + sneak_max + 0.1*BS)
829 // The node to be sneaked on has to be walkable
830 if(nodemgr->get(map.getNode(p)).walkable == false)
832 // And the node above it has to be nonwalkable
833 if(nodemgr->get(map.getNode(p+v3s16(0,1,0))).walkable == true)
836 catch(InvalidPositionException &e)
841 min_distance_f = distance_f;
845 bool sneak_node_found = (min_distance_f < 100000.0*BS*0.9);
847 if(control.sneak && m_sneak_node_exists)
850 m_sneak_node = new_sneak_node;
854 m_sneak_node = new_sneak_node;
855 m_sneak_node_exists = sneak_node_found;
859 If sneaking, the player's collision box can be in air, so
860 this has to be set explicitly
862 if(sneak_node_found && control.sneak)
863 touching_ground = true;
869 setPosition(position);
876 // Report fall collision
877 if(old_speed.Y < m_speed.Y - 0.1 && !standing_on_unloaded)
880 info.t = COLLISION_FALL;
881 info.speed = m_speed.Y - old_speed.Y;
882 collision_info->push_back(info);
887 void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d)
889 move(dtime, map, pos_max_d, NULL);
892 void LocalPlayer::applyControl(float dtime)
898 f32 walk_acceleration = 4.0 * BS;
899 f32 walkspeed_max = 4.0 * BS;
901 setPitch(control.pitch);
904 v3f move_direction = v3f(0,0,1);
905 move_direction.rotateXZBy(getYaw());
907 v3f speed = v3f(0,0,0);
909 bool free_move = g_settings->getBool("free_move");
910 bool fast_move = g_settings->getBool("fast_move");
911 bool continuous_forward = g_settings->getBool("continuous_forward");
913 if(free_move || is_climbing)
915 v3f speed = getSpeed();
920 // Whether superspeed mode is used or not
921 bool superspeed = false;
923 // If free movement and fast movement, always move fast
924 if(free_move && fast_move)
927 // Auxiliary button 1 (E)
932 // In free movement mode, aux1 descends
933 v3f speed = getSpeed();
937 speed.Y = -walkspeed_max;
942 v3f speed = getSpeed();
948 // If not free movement but fast is allowed, aux1 is
955 if(continuous_forward)
956 speed += move_direction;
960 if(continuous_forward)
963 speed += move_direction;
967 speed -= move_direction;
971 speed += move_direction.crossProduct(v3f(0,1,0));
975 speed += move_direction.crossProduct(v3f(0,-1,0));
981 v3f speed = getSpeed();
985 speed.Y = walkspeed_max;
988 else if(touching_ground)
990 v3f speed = getSpeed();
992 NOTE: The d value in move() affects jump height by
993 raising the height at which the jump speed is kept
994 at its starting value
999 // Use the oscillating value for getting out of water
1000 // (so that the player doesn't fly on the surface)
1003 v3f speed = getSpeed();
1008 else if(is_climbing)
1010 v3f speed = getSpeed();
1016 // The speed of the player (Y is ignored)
1018 speed = speed.normalize() * walkspeed_max * 5.0;
1019 else if(control.sneak)
1020 speed = speed.normalize() * walkspeed_max / 3.0;
1022 speed = speed.normalize() * walkspeed_max;
1024 f32 inc = walk_acceleration * BS * dtime;
1026 // Faster acceleration if fast and free movement
1027 if(free_move && fast_move)
1028 inc = walk_acceleration * BS * dtime * 10;
1030 // Accelerate to target speed with maximum increment
1031 accelerate(speed, inc);