PocketMine-MP/src/Player.php
Shoghi Cervantes Pueyo 9a3f887f44 Attacking entities fixed
2013-05-16 16:41:54 +02:00

1416 lines
42 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/*
-
/ \
/ \
/ PocketMine \
/ MP \
|\ @shoghicp /|
|. \ / .|
| .. \ / .. |
| .. | .. |
| .. | .. |
\ | /
\ | /
\ | /
\ | /
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
*/
class Player{
private $server;
private $queue = array();
private $buffer = "";
private $nextBuffer = 0;
private $recovery = array();
private $evid = array();
private $lastMovement = 0;
private $timeout;
private $connected = true;
private $clientID;
private $ip;
private $port;
private $counter = array(0, 0, 0);
private $username;
private $iusername;
private $eid = false;
public $data;
public $entity = false;
public $auth = false;
public $CID;
public $MTU;
public $spawned = false;
public $inventory;
public $equipment;
public $armor;
public $loggedIn = false;
public $gamemode;
public $lastBreak;
public $windowCnt = 0;
public $windows = array();
public $blocked = true;
private $chunksLoaded = array();
private $chunksOrder = array();
private $lag = array(0, 0);
private $spawnPosition;
private $freedChunks = true;
public $itemEnforcement;
public $lastCorrect;
public function __get($name){
if(isset($this->{$name})){
return ($this->{$name});
}
return null;
}
public function __construct($clientID, $ip, $port, $MTU){
$this->MTU = $MTU;
$this->server = ServerAPI::request();
$this->lastBreak = microtime(true);
$this->clientID = $clientID;
$this->CID = $this->server->clientID($ip, $port);
$this->ip = $ip;
$this->port = $port;
$this->itemEnforcement = $this->server->api->getProperty("item-enforcement");
$this->spawnPosition = $this->server->spawn;
$this->timeout = microtime(true) + 20;
$this->inventory = array_fill(0, 36, array(AIR, 0, 0));
$this->armor = array_fill(0, 4, array(AIR, 0, 0));
$this->gamemode = $this->server->gamemode;
$this->level = $this->server->api->level->getDefault();
$this->equipment = BlockAPI::getItem(AIR);
$this->evid[] = $this->server->event("server.tick", array($this, "onTick"));
$this->evid[] = $this->server->event("server.close", array($this, "close"));
console("[DEBUG] New Session started with ".$ip.":".$port.". MTU ".$this->MTU.", Client ID ".$this->clientID, true, true, 2);
}
public function setSpawn(Vector3 $pos, $level){
if(($level = $this->server->api->level->get($level)) === false){
$level = $this->server->api->level->getDefault();
}
$this->spawnPosition = new Position($pos->x, $pos->y, $pos->z, $level);
$this->dataPacket(MC_SET_SPAWN_POSITION, array(
"x" => (int) $this->spawnPosition->x,
"y" => (int) $this->spawnPosition->y,
"z" => (int) $this->spawnPosition->z,
));
}
public function orderChunks(){
if(!($this->entity instanceof Entity) or $this->connected === false){
return false;
}
$X = $this->entity->x / 16;
$Z = $this->entity->z / 16;
$Y = $this->entity->y / 16;
$v = new Vector3($X, $Y / 4, $Z);
$this->chunksOrder = array();
for($x = 0; $x < 16; ++$x){
for($z = 0; $z < 16; ++$z){
for($y = 0; $y < 8; ++$y){
$d = $x.":".$y.":".$z;
if(!isset($this->chunksLoaded[$d])){
$this->chunksOrder[$d] = $v->distance(new Vector3($x + 0.5, $y / 4, $z + 0.5));
}
}
}
}
asort($this->chunksOrder);
}
public function getNextChunk($repeat = false){
if($this->connected === false){
return false;
}
$c = key($this->chunksOrder);
$d = @$this->chunksOrder[$c];
if($c === null or $d > $this->server->api->getProperty("view-distance")){
if($this->freedChunks === false){
foreach($this->chunksOrder as $c => $d){
$id = explode(":", $c);
$X = $id[0];
$Z = $id[2];
$this->level->freeChunk($X, $Z, $this);
}
$this->freedChunks = true;
}
$this->server->schedule(50, array($this, "getNextChunk"));
return false;
}
$this->freedChunks = false;
array_shift($this->chunksOrder);
$this->chunksLoaded[$c] = true;
$id = explode(":", $c);
$X = $id[0];
$Z = $id[2];
$Y = $id[1];
$x = $X << 4;
$z = $Z << 4;
$y = $Y << 4;
$MTU = $this->MTU - 16;
$this->level->useChunk($X, $Z, $this);
$chunk = $this->level->getOrderedMiniChunk($X, $Z, $Y, $MTU);
foreach($chunk as $d){
$this->dataPacket(MC_CHUNK_DATA, array(
"x" => $X,
"z" => $Z,
"data" => $d,
));
}
$tiles = $this->server->query("SELECT ID FROM tileentities WHERE spawnable = 1 AND x >= ".($x - 1)." AND x < ".($x + 17)." AND z >= ".($z - 1)." AND z < ".($z + 17)." AND y >= ".($y - 1)." AND y < ".($y + 17).";");
if($tiles !== false and $tiles !== true){
while(($tile = $tiles->fetchArray(SQLITE3_ASSOC)) !== false){
$this->server->api->tileentity->spawnTo($tile["ID"], $this);
}
}
if($repeat === false){
$this->getNextChunk(true);
}
$this->server->schedule(1, array($this, "getNextChunk"));
}
public function onTick($time, $event){
if($event !== "server.tick" or $this->connected === false){
return;
}
if($time > $this->timeout){
$this->close("timeout");
}else{
if(!empty($this->queue)){
$maxtime = $time + 0.0025;
while(microtime(true) < $maxtime){
$p = array_shift($this->queue);
if($p === null){
break;
}
switch($p[0]){
case 0:
$this->dataPacket($p[1]["id"], $p[1], false);
break;
case 1:
eval($p[1]);
break;
}
}
}
if($this->nextBuffer <= $time and strlen($this->buffer) > 0){
$this->sendBuffer();
}
}
}
public function save(){
if($this->entity instanceof Entity){
$this->data->set("position", array(
"level" => $this->entity->level->getName(),
"x" => $this->entity->x,
"y" => $this->entity->y,
"z" => $this->entity->z,
));
$this->data->set("spawn", array(
"level" => $this->spawnPosition->level->getName(),
"x" => $this->spawnPosition->x,
"y" => $this->spawnPosition->y,
"z" => $this->spawnPosition->z,
));
$this->data->set("inventory", $this->inventory);
$this->data->set("armor", $this->armor);
$this->data->set("gamemode", $this->gamemode);
}
}
public function close($reason = "", $msg = true){
if($this->connected === true){
foreach($this->evid as $ev){
$this->server->deleteEvent($ev);
}
if($this->username != ""){
$this->server->api->handle("player.quit", $this);
$this->save();
}
$reason = $reason == "" ? "server stop":$reason;
$this->eventHandler(new Container("You have been kicked. Reason: ".$reason), "server.chat");
$this->directDataPacket(MC_DISCONNECT);
$this->sendBuffer();
$this->level->freeAllChunks($this);
$this->buffer = null;
unset($this->buffer);
$this->recovery = null;
unset($this->recovery);
$this->queue = null;
unset($this->queue);
$this->connected = false;
if($msg === true and $this->username != ""){
$this->server->api->chat->broadcast($this->username." left the game");
}
console("[INFO] \x1b[33m".$this->username."\x1b[0m[/".$this->ip.":".$this->port."] logged out due to ".$reason);
$this->server->api->player->remove($this->CID);
}
}
public function addItem($type, $damage, $count){
while($count > 0){
$add = 0;
foreach($this->inventory as $s => $data){
if($data[0] === AIR){
$add = min(64, $count);
$this->inventory[$s] = array($type, $damage, $add);
$this->sendInventorySlot($s);
break;
}elseif($data[0] === $type and $data[1] === $damage){
$add = min(64 - $data[2], $count);
if($add <= 0){
continue;
}
$this->inventory[$s] = array($type, $damage, $data[2] + $add);
$this->sendInventorySlot($s);
break;
}
}
if($add === 0){
return false;
}
$count -= $add;
}
return true;
}
public function removeItem($type, $damage, $count){
while($count > 0){
$remove = 0;
foreach($this->inventory as $s => $data){
if($data[0] === $type and $data[1] === $damage){
$remove = min($count, $data[2]);
if($remove < $data[2]){
$this->inventory[$s][2] -= $remove;
}else{
$this->inventory[$s] = array(AIR, 0, 0);
}
$this->sendInventorySlot($s);
break;
}
}
if($remove === 0){
return false;
}
$count -= $remove;
}
return true;
}
public function setSlot($slot, Item $item){
$this->inventory[(int) $slot] = array($item->getID(), $item->getMetadata(), $item->count);
$this->sendInventorySlot((int) $slot);
return true;
}
public function getSlot($slot){
$slot = $this->inventory[(int) $slot];
return BlockAPI::getItem($slot[0], $slot[1], $slot[2]);
return true;
}
public function sendInventorySlot($s){
$s = (int) $s;
if(!isset($this->inventory[$s]) or $this->itemEnforcement === false){
return false;
}
$this->sendInventory(); //Fallback
return true;
//Can't do this :(
/*$slot = BlockAPI::getItem($this->inventory[$s][0], $this->inventory[$s][1], $this->inventory[$s][2]);
$this->dataPacket(MC_CONTAINER_SET_SLOT, array(
"windowid" => 0,
"slot" => (int) $s,
"block" => $slot->getID(),
"stack" => $slot->count,
"meta" => $slot->getMetadata(),
));
return true;*/
}
public function hasItem($type, $damage = false){
if($type === AIR){
return true;
}
foreach($this->inventory as $s => $data){
if($data[0] === $type and ($data[1] === $damage or $damage === false) and $data[2] > 0){
return true;
}
}
return false;
}
public function eventHandler($data, $event){
switch($event){
case "tile.container.slot":
if($player === $this){
break;
}
foreach($this->windows as $id => $w){
if($w === $data["tile"]){
$this->dataPacket(MC_CONTAINER_SET_SLOT, array(
"windowid" => $id,
"slot" => $data["slot"],
"block" => $data["slotdata"]->getID(),
"stack" => $data["slotdata"]->count,
"meta" => $data["slotdata"]->getMetadata(),
));
}
}
break;
case "player.armor":
if($data["eid"] === $this->eid){
$data["eid"] = 0;
$this->armor = array();
for($i = 0; $i < 4; ++$i){
if($data["slot".$i] > 0){
$this->armor[$i] = array($data["slot".$i] + 256, 0, 1);
}else{
$this->armor[$i] = array(AIR, 0, 0);
}
}
$this->dataPacket(MC_PLAYER_ARMOR_EQUIPMENT, $data);
}elseif($data["player"]->level === $this->level){
$this->dataPacket(MC_PLAYER_ARMOR_EQUIPMENT, $data);
}
break;
case "player.pickup":
if($data["eid"] === $this->eid){
$data["eid"] = 0;
$this->dataPacket(MC_TAKE_ITEM_ENTITY, $data);
if(($this->gamemode & 0x01) === 0x00){
$this->addItem($data["entity"]->type, $data["entity"]->meta, $data["entity"]->stack);
}
}elseif($data["entity"]->level === $this->level){
$this->dataPacket(MC_TAKE_ITEM_ENTITY, $data);
}
break;
case "player.equipment.change":
if($data["eid"] === $this->eid or $data["player"]->level !== $this->level){
break;
}
$this->dataPacket(MC_PLAYER_EQUIPMENT, $data);
break;
case "block.change":
if($data["position"]->level !== $this->level){
break;
}
$this->dataPacket(MC_UPDATE_BLOCK, array(
"x" => $data["position"]->x,
"y" => $data["position"]->y,
"z" => $data["position"]->z,
"block" => $data["block"]->getID(),
"meta" => $data["block"]->getMetadata(),
));
break;
case "entity.move":
if($data->eid === $this->eid or $data->level !== $this->level){
break;
}
$this->dataPacket(MC_MOVE_ENTITY_POSROT, array(
"eid" => $data->eid,
"x" => $data->x,
"y" => $data->y,
"z" => $data->z,
"yaw" => $data->yaw,
"pitch" => $data->pitch,
));
break;
case "entity.motion":
/*if($data->eid === $this->eid or $data->level !== $this->level){
break;
}
$this->dataPacket(MC_SET_ENTITY_MOTION, array(
"eid" => $data->eid,
"speedX" => (int) ($data->speedX * 32000),
"speedY" => (int) ($data->speedY * 32000),
"speedZ" => (int) ($data->speedZ * 32000),
));
break;*/
case "entity.remove":
if($data->eid === $this->eid or $data->level !== $this->level){
break;
}
$this->dataPacket(MC_REMOVE_ENTITY, array(
"eid" => $data->eid,
));
break;
case "time.change":
if($data["level"] === $this->level){
$this->dataPacket(MC_SET_TIME, array(
"time" => $data["time"],
));
}
break;
case "entity.animate":
if($data["eid"] === $this->eid or $data["entity"]->level !== $this->level){
break;
}
$this->dataPacket(MC_ANIMATE, array(
"eid" => $data["eid"],
"action" => $data["action"],
));
break;
case "entity.metadata":
if($data->eid === $this->eid){
$eid = 0;
}else{
$eid = $data->eid;
}
if($data->level === $this->level){
$this->dataPacket(MC_SET_ENTITY_DATA, array(
"eid" => $eid,
"metadata" => $data->getMetadata(),
));
}
break;
case "entity.event":
if($data["entity"]->eid === $this->eid){
$eid = 0;
}else{
$eid = $data["entity"]->eid;
}
if($data["entity"]->level === $this->level){
$this->dataPacket(MC_ENTITY_EVENT, array(
"eid" => $eid,
"event" => $data["event"],
));
}
break;
case "server.chat":
if(($data instanceof Container) === true){
if(!$data->check($this->username) and !$data->check($this->iusername)){
return;
}else{
$message = $data->get();
}
}else{
$message = (string) $data;
}
$this->sendChat(preg_replace('/\x1b\[[0-9;]*m/', "", $message)); //Remove ANSI codes from chat
break;
}
}
public function sendChat($message){
$mes = explode("\n", $message);
foreach($mes as $m){
if(preg_match_all('#@([@A-Za-z_]{1,})#', $m, $matches, PREG_OFFSET_CAPTURE) > 0){
$offsetshift = 0;
foreach($matches[1] as $selector){
if($selector[0]{0} === "@"){ //Escape!
$m = substr_replace($m, $selector[0], $selector[1] + $offsetshift - 1, strlen($selector[0]) + 1);
--$offsetshift;
continue;
}
switch(strtolower($selector[0])){
case "player":
case "username":
$m = substr_replace($m, $this->username, $selector[1] + $offsetshift - 1, strlen($selector[0]) + 1);
$offsetshift += strlen($selector[0]) - strlen($this->username) + 1;
break;
}
}
}
$this->dataPacket(MC_CHAT, array(
"message" => $m,
));
}
}
public function sendSettings($nametags = true){
/*
bit mask | flag name
0x00000001 world_inmutable
0x00000002 -
0x00000004 -
0x00000008 - (autojump)
0x00000010 -
0x00000020 nametags_visible
0x00000040 ?
0x00000080 ?
0x00000100 ?
0x00000200 ?
0x00000400 ?
0x00000800 ?
0x00001000 ?
0x00002000 ?
0x00004000 ?
0x00008000 ?
0x00010000 ?
0x00020000 ?
0x00040000 ?
0x00080000 ?
0x00100000 ?
0x00200000 ?
0x00400000 ?
0x00800000 ?
0x01000000 ?
0x02000000 ?
0x04000000 ?
0x08000000 ?
0x10000000 ?
0x20000000 ?
0x40000000 ?
0x80000000 ?
*/
$flags = 0;
if(($this->gamemode & 0x02) === 0x02){
$flags |= 0x01; //Not allow placing/breaking blocks, adventure mode
}
if($nametags !== false){
$flags |= 0x20; //Show Nametags
}
$this->dataPacket(MC_ADVENTURE_SETTINGS, array(
"flags" => $flags,
));
}
public function teleport(Vector3 $pos, $yaw = false, $pitch = false, $terrain = true){
if($this->entity instanceof Entity){
$this->entity->check = false;
if($yaw === false){
$yaw = $this->entity->yaw;
}
if($pitch === false){
$pitch = $this->entity->yaw;
}
$this->lastCorrect = $pos;
$this->entity->fallY = false;
$this->entity->fallStart = false;
$this->entity->setPosition($pos, $yaw, $pitch);
$this->entity->resetSpeed();
$this->entity->updateLast();
$this->entity->calculateVelocity();
if($pos instanceof Position and $pos->level !== $this->level){
foreach($this->server->api->entity->getAll($this->level) as $e){
if($e !== $this->entity){
if($e->player instanceof Player){
$e->player->dataPacket(MC_REMOVE_ENTITY, array(
"eid" => $this->eid,
));
}
$this->dataPacket(MC_REMOVE_ENTITY, array(
"eid" => $e->eid,
));
}
}
$this->level->freeAllChunks($this);
$this->level = $pos->level;
$this->chunksLoaded = array();
$this->server->api->entity->spawnToAll($this->level, $this->eid);
$this->server->api->entity->spawnAll($this);
$terrain = true;
}
if($terrain === true){
$this->orderChunks();
$this->getNextChunk();
}
$this->entity->check = true;
}
$this->dataPacket(MC_MOVE_PLAYER, array(
"eid" => 0,
"x" => $pos->x,
"y" => $pos->y,
"z" => $pos->z,
"yaw" => $yaw,
"pitch" => $pitch,
));
}
public function getGamemode(){
switch($this->gamemode){
case SURVIVAL:
return "survival";
case CREATIVE:
return "creative";
case ADVENTURE:
return "adventure";
case VIEW:
return "view";
}
}
public function setGamemode($gm){
if($gm < 0 or $gm > 3 or $this->gamemode === $gm){
return false;
}
if($this->server->api->dhandle("player.gamemode.change", array("player" => $player, "gamemode" => $gm)) === false){
return false;
}
$inv =& $this->inventory;
if(($this->gamemode & 0x01) === ($gm & 0x01)){
if(($gm & 0x01) === 0x01 and ($gm & 0x02) === 0x02){
$inv = array();
foreach(BlockAPI::$creative as $item){
$inv[] = array(DANDELION, 0, 1);
}
}elseif(($gm & 0x01) === 0x01){
$inv = array();
foreach(BlockAPI::$creative as $item){
$inv[] = array($item[0], $item[1], 1);
}
}
$this->gamemode = $gm;
$this->eventHandler("Your gamemode has been changed to ".$this->getGamemode().".", "server.chat");
}else{
$inv = array_fill(0, 36, array(AIR, 0, 0));
$this->blocked = true;
$this->gamemode = $gm;
$this->eventHandler("Your gamemode has been changed to ".$this->getGamemode().", you've to do a forced reconnect.", "server.chat");
$this->server->schedule(30, array($this, "close"), "gamemode change"); //Forces a kick
if(($gm & 0x01) === 0x01){
$this->itemEnforcement = true;
}
}
$this->inventory = $inv;
$this->sendSettings();
if($this->itemEnforcement === true){
$this->sendInventory();
}
return true;
}
public function measureLag(){
if($this->connected === false){
return false;
}
$this->lag[0] = microtime(true) * 1000;
$this->dataPacket(MC_PING, array(
"time" => (int) $this->lag[0],
));
$this->sendBuffer();
}
public function getLag(){
return $this->lag[1] - $this->lag[0];
}
public function handle($pid, $data){
if($this->connected === true){
$this->timeout = microtime(true) + 20;
switch($pid){
case 0xa0: //NACK
foreach($data[0] as $count){
if(isset($this->recovery[$count])){
$this->directDataPacket($this->recovery[$count]["id"], $this->recovery[$count], $count);
}
}
break;
case 0xc0: //ACK
foreach($data[0] as $count){
if($count > $this->counter[2]){
$this->counter[2] = $count;
}
$this->recovery[$count] = null;
unset($this->recovery[$count]);
}
$limit = microtime(true) - 2; //max lag
foreach($this->recovery as $count => $d){
$diff = $this->counter[2] - $count;
if($diff > 16 and $d["sendtime"] < $limit){
$this->directDataPacket($d["id"], $d, $count);
}
}
break;
case 0x07:
if($this->loggedIn === true){
break;
}
$this->send(0x08, array(
RAKNET_MAGIC,
$this->server->serverID,
$this->port,
$data[3],
0,
));
break;
case 0x80:
case 0x81:
case 0x82:
case 0x83:
case 0x84:
case 0x85:
case 0x86:
case 0x87:
case 0x88:
case 0x89:
case 0x8a:
case 0x8b:
case 0x8c:
case 0x8d:
case 0x8e:
case 0x8f:
if(isset($data[0])){
$diff = $data[0] - $this->counter[1];
if($diff > 1){ //Packet recovery
$arr = array();
for($i = $this->counter[1]; $i < $data[0]; ++$i){
$arr[] = $i;
}
$this->send(0xa0, array($arr));
$this->counter[1] = $data[0];
}elseif($diff === 1){
$this->counter[1] = $data[0];
}
$this->send(0xc0, array(array($data[0])));
}
if(!isset($data["id"])){
break;
}
switch($data["id"]){
case 0x01:
break;
case MC_PONG:
$this->lag[1] = microtime(true) * 1000;
break;
case MC_PING:
$t = (int) (microtime(true) * 1000);
$this->dataPacket(MC_PONG, array(
"ptime" => $data["time"],
"time" => (int) (microtime(true) * 1000),
));
$this->sendBuffer();
break;
case MC_DISCONNECT:
$this->close("client disconnect");
break;
case MC_CLIENT_CONNECT:
if($this->loggedIn === true){
break;
}
$this->dataPacket(MC_SERVER_HANDSHAKE, array(
"port" => $this->port,
"session" => $data["session"],
"session2" => Utils::readLong("\x00\x00\x00\x00\x04\x44\x0b\xa9"),
));
break;
case MC_CLIENT_HANDSHAKE:
if($this->loggedIn === true){
break;
}
break;
case MC_LOGIN:
if($this->loggedIn === true){
break;
}
if(count($this->server->clients) >= $this->server->maxClients){
$this->close("server is full!", false);
return;
}
if($data["protocol1"] !== CURRENT_PROTOCOL){
if($data["protocol1"] < CURRENT_PROTOCOL){
$this->directDataPacket(MC_LOGIN_STATUS, array(
"status" => 1,
));
}else{
$this->directDataPacket(MC_LOGIN_STATUS, array(
"status" => 2,
));
}
$this->close("Incorrect protocol", false);
break;
}
if(preg_match('#^[a-zA-Z0-9_]{2,16}$#', $data["username"])){
$this->username = $data["username"];
$this->iusername = strtolower($this->username);
}else{
$this->close("Bad username", false);
break;
}
if($this->server->api->handle("player.connect", $this) === false){
$this->close("Unknown reason", false);
return;
}
if($this->server->whitelist === true and !$this->server->api->ban->inWhitelist($this->iusername)){
$this->close("Server is white-listed", false);
return;
}elseif($this->server->api->ban->isBanned($this->iusername) or $this->server->api->ban->isIPBanned($this->ip)){
$this->close("You are banned!", false);
return;
}
$this->loggedIn = true;
$u = $this->server->api->player->get($this->iusername);
if($u !== false){
$u->close("logged in from another location");
}
$this->server->api->player->add($this->CID);
if($this->server->api->handle("player.join", $this) === false){
$this->close("join cancelled", false);
return;
}
if(!($this->data instanceof Config)){
$u->close("no config created", false);
return;
}
$this->auth = true;
if(!$this->data->exists("inventory") or ($this->gamemode & 0x01) === 0x01){
if(($this->gamemode & 0x01) === 0x01){
$this->itemEnforcement = true;
$this->inventory = array();
if(($this->gamemode & 0x02) === 0x02){
foreach(BlockAPI::$creative as $item){
$this->inventory[] = array(DANDELION, 0, 1);
}
}else{
foreach(BlockAPI::$creative as $item){
$this->inventory[] = array($item[0], $item[1], 1);
}
}
}
$this->data->set("inventory", $this->inventory);
}
$this->data->set("caseusername", $this->username);
$this->inventory = $this->data->get("inventory");
$this->armor = $this->data->get("armor");
$this->data->set("lastIP", $this->ip);
$this->data->set("lastID", $this->clientID);
if($this->data instanceof Config){
$this->server->api->player->saveOffline($this->data);
}
$this->dataPacket(MC_LOGIN_STATUS, array(
"status" => 0,
));
$this->dataPacket(MC_START_GAME, array(
"seed" => $this->server->seed,
"x" => $this->data->get("position")["x"],
"y" => $this->data->get("position")["y"],
"z" => $this->data->get("position")["z"],
"unknown1" => 0,
"gamemode" => ($this->gamemode & 0x01),
"eid" => 0,
));
if(($this->gamemode & 0x01) === 0x01){
$this->equipment = BlockAPI::getItem($this->inventory[7][0], $this->inventory[7][1], $this->inventory[7][2]);
}
$this->entity = $this->server->api->entity->add($this->level, ENTITY_PLAYER, 0, array("player" => $this));
$this->eid = $this->entity->eid;
$this->server->query("UPDATE players SET EID = ".$this->eid." WHERE clientID = ".$this->clientID.";");
$this->entity->x = $this->data->get("position")["x"];
$this->entity->y = $this->data->get("position")["y"];
$this->entity->z = $this->data->get("position")["z"];
$this->entity->setName($this->username);
$this->entity->data["clientID"] = $this->clientID;
$this->evid[] = $this->server->event("time.change", array($this, "eventHandler"));
$this->evid[] = $this->server->event("server.chat", array($this, "eventHandler"));
$this->evid[] = $this->server->event("entity.remove", array($this, "eventHandler"));
$this->evid[] = $this->server->event("entity.move", array($this, "eventHandler"));
$this->evid[] = $this->server->event("entity.motion", array($this, "eventHandler"));
$this->evid[] = $this->server->event("entity.animate", array($this, "eventHandler"));
$this->evid[] = $this->server->event("entity.event", array($this, "eventHandler"));
$this->evid[] = $this->server->event("entity.metadata", array($this, "eventHandler"));
$this->evid[] = $this->server->event("player.equipment.change", array($this, "eventHandler"));
$this->evid[] = $this->server->event("player.armor", array($this, "eventHandler"));
$this->evid[] = $this->server->event("player.pickup", array($this, "eventHandler"));
$this->evid[] = $this->server->event("block.change", array($this, "eventHandler"));
$this->evid[] = $this->server->event("tile.container.slot", array($this, "eventHandler"));
$this->server->schedule(40, array($this, "measureLag"), array(), true);
console("[INFO] \x1b[33m".$this->username."\x1b[0m[/".$this->ip.":".$this->port."] logged in with entity id ".$this->eid." at (".$this->entity->level->getName().", ".round($this->entity->x, 2).", ".round($this->entity->y, 2).", ".round($this->entity->z, 2).")");
break;
case MC_READY:
if($this->loggedIn === false){
break;
}
switch($data["status"]){
case 1: //Spawn!!
if($this->spawned !== false){
break;
}
$this->spawned = true;
$this->server->api->entity->spawnAll($this);
$this->server->api->entity->spawnToAll($this->level, $this->eid);
$this->server->schedule(5, array($this->entity, "update"), array(), true);
$this->sendArmor();
$this->eventHandler(new Container($this->server->motd), "server.chat");
if($this->MTU <= 548){
$this->eventHandler("Your connection is bad, you may experience lag and slow map loading.", "server.chat");
}
if($this->iusername === "steve" or $this->iusername === "stevie"){
$this->eventHandler("You're using the default username. Please change it on the Minecraft PE settings.", "server.chat");
}
$this->sendInventory();
$this->sendSettings();
$this->server->schedule(50, array($this, "orderChunks"), array(), true);
$this->blocked = false;
$this->teleport(new Position($this->data->get("position")["x"], $this->data->get("position")["y"], $this->data->get("position")["z"], $this->level));
$this->setSpawn(new Vector3($this->data->get("spawn")["x"], $this->data->get("spawn")["y"], $this->data->get("spawn")["z"]), $this->data->get("spawn")["level"]);
break;
case 2://Chunk loaded?
break;
}
break;
case MC_MOVE_PLAYER:
if($this->spawned === false){
break;
}
if(($this->entity instanceof Entity) and $data["counter"] > $this->lastMovement){
$this->lastMovement = $data["counter"];
$speed = $this->entity->getSpeed();
if($this->blocked === true or ($speed > 6 and ($this->gamemode & 0x01) === 0x00) or $speed > 15 or $this->server->api->handle("player.move", $this->entity) === false){
if($this->lastCorrect instanceof Vector3){
$this->teleport($this->lastCorrect, $this->entity->yaw, $this->entity->pitch, false);
}
console("[WARNING] ".$this->username." moved too quickly!");
}else{
$this->entity->setPosition(new Vector3($data["x"], $data["y"], $data["z"]), $data["yaw"], $data["pitch"]);
}
}
break;
case MC_PLAYER_EQUIPMENT:
if($this->spawned === false){
break;
}
$data["eid"] = $this->eid;
$data["player"] = $this;
$data["item"] = BlockAPI::getItem($data["block"], $data["meta"]);
if(($this->hasItem($data["block"], $data["meta"]) or $this->itemEnforcement !== true) and $this->server->handle("player.equipment.change", $data) !== false){
$this->equipment = $data["item"];
}elseif($this->itemEnforcement === true){
$this->sendSettings();
}
break;
case MC_REQUEST_CHUNK:
if($this->spawned === false){
break;
}
break;
case MC_USE_ITEM:
if($this->spawned === false){
break;
}
$data["eid"] = $this->eid;
if($this->blocked === true or Utils::distance($this->entity->position, $data) > 10){
break;
}elseif(!$this->hasItem($data["block"], $data["meta"]) and $this->itemEnforcement === true){
$this->sendInventory();
break;
}
$this->server->api->block->playerBlockAction($this, new Vector3($data["x"], $data["y"], $data["z"]), $data["face"], $data["fx"], $data["fy"], $data["fz"]);
break;
case MC_REMOVE_BLOCK:
if($this->spawned === false){
break;
}
if($this->blocked === true or $this->entity->distance(new Vector3($data["x"], $data["y"], $data["z"])) > 8){
break;
}
$this->server->api->block->playerBlockBreak($this, new Vector3($data["x"], $data["y"], $data["z"]));
break;
case MC_PLAYER_ARMOR_EQUIPMENT:
if($this->spawned === false){
break;
}
$data["eid"] = $this->eid;
$this->server->handle("player.armor", $data);
break;
case MC_INTERACT:
if($this->spawned === false){
break;
}
$target = $this->server->api->entity->get($data["target"]);
if($this->gamemode !== VIEW and $this->blocked === false and ($target instanceof Entity) and $this->entity->distance($target) <= 8){
$data["targetentity"] = $target;
$data["entity"] = $this->entity;
if(!($target instanceof Entity)){
break;
}elseif($target->class === ENTITY_PLAYER and ($this->server->api->getProperty("pvp") == false or $this->server->difficulty <= 0 or ($target->player->gamemode & 0x01) === 0x01)){
break;
}elseif($this->server->handle("player.interact", $data) !== false){
switch($this->equipment->getID()){
case WOODEN_SWORD:
case GOLD_SWORD:
$damage = 4;
break;
case STONE_SWORD:
$damage = 5;
break;
case IRON_SWORD:
$damage = 6;
break;
case DIAMOND_SWORD:
$damage = 7;
break;
case WOODEN_AXE:
case GOLD_AXE:
$damage = 3;
break;
case STONE_AXE:
$damage = 4;
break;
case IRON_AXE:
$damage = 5;
break;
case DIAMOND_AXE:
$damage = 6;
break;
case WOODEN_PICKAXE:
case GOLD_PICKAXE:
$damage = 2;
break;
case STONE_PICKAXE:
$damage = 3;
break;
case IRON_PICKAXE:
$damage = 4;
break;
case DIAMOND_PICKAXE:
$damage = 5;
break;
case WOODEN_SHOVEL:
case GOLD_SHOVEL:
$damage = 1;
break;
case STONE_SHOVEL:
$damage = 2;
break;
case IRON_SHOVEL:
$damage = 3;
break;
case DIAMOND_SHOVEL:
$damage = 4;
break;
default:
$damage = 1;//$this->server->difficulty;
}
$this->server->api->entity->harm($data["target"], $damage, $this->eid);
}
}
break;
case MC_ANIMATE:
if($this->spawned === false){
break;
}
$this->server->api->dhandle("entity.animate", array("eid" => $this->eid, "entity" => $this->entity, "action" => $data["action"]));
break;
case MC_RESPAWN:
if($this->spawned === false){
break;
}
if($this->entity->dead === false){
break;
}
$this->blocked = false;
$this->entity->fire = 0;
$this->entity->air = 300;
$this->entity->setHealth(20, "respawn");
$this->entity->updateMetadata();
$this->sendInventory();
$this->teleport($this->spawnPosition);
break;
case MC_SET_HEALTH:
if($this->spawned === false){
break;
}
if(($this->gamemode & 0x01) === 0x01){
break;
}
//$this->entity->setHealth($data["health"], "client");
break;
case MC_ENTITY_EVENT:
if($this->spawned === false){
break;
}
$data["eid"] = $this->eid;
switch($data["event"]){
case 9: //Eating
$items = array(
APPLE => 2, //Apples
282 => 10, //Stew
BREAD => 5, //Bread
319 => 3,
320 => 8,
363 => 3,
364 => 8,
);
if(isset($items[$this->equipment->getID()])){
if($this->removeItem($this->equipment->getID(), $this->equipment->getMetadata(), 1) === true or $this->itemEnforcement !== true){
$this->dataPacket(MC_ENTITY_EVENT, array(
"eid" => 0,
"event" => 9,
));
$this->entity->heal($items[$this->equipment->getID()], "eating");
}
}
break;
}
break;
case MC_DROP_ITEM:
if($this->spawned === false){
break;
}
$item = BlockAPI::getItem($data["block"], $data["meta"], $data["stack"]);
$data["item"] = $item;
if($this->blocked === false and $this->server->handle("player.drop", $data) !== false){
if($this->removeItem($item->getID(), $item->getMetadata(), $item->count) === true or $this->itemEnforcement !== true){
$this->server->api->block->drop(new Vector3($this->entity->x - 0.5, $this->entity->y, $this->entity->z - 0.5), $item);
}
}
break;
case MC_SIGN_UPDATE:
if($this->spawned === false){
break;
}
$t = $this->server->api->tileentity->get(new Position($data["x"], $data["y"], $data["z"], $this->level));
if(($t[0] instanceof TileEntity) and $t[0]->class === TILE_SIGN){
$t = $t[0];
if($t->data["creator"] !== $this->username){
$t->spawn($this);
}else{
$t->data["Text1"] = $data["line0"];
$t->data["Text2"] = $data["line1"];
$t->data["Text3"] = $data["line2"];
$t->data["Text4"] = $data["line3"];
$this->server->handle("tile.update", $t);
$this->server->api->tileentity->spawnToAll($this->level, $t);
}
}
break;
case MC_CHAT:
if($this->spawned === false){
break;
}
$message = $data["message"];
if($message{0} === "/"){ //Command
$this->server->api->console->run(substr($message, 1), $this);
}else{
if($this->server->api->dhandle("player.chat", array("player" => $this, "message" => $message)) !== false){
$this->server->api->send($this, $message);
}
}
break;
case MC_CONTAINER_CLOSE:
if($this->spawned === false){
break;
}
unset($this->windows[$data["windowid"]]);
$this->dataPacket(MC_CONTAINER_CLOSE, array(
"windowid" => $id,
));
break;
case MC_CONTAINER_SET_SLOT:
if($this->spawned === false){
break;
}
if(!isset($this->windows[$data["windowid"]])){
break;
}
$tile = $this->windows[$data["windowid"]];
if(($tile->class !== TILE_CHEST and $tile->class !== TILE_FURNACE) or $data["slot"] < 0 or ($tile->class === TILE_CHEST and $data["slot"] >= CHEST_SLOTS) or ($tile->class === TILE_FURNACE and $data["slot"] >= FURNACE_SLOTS)){
break;
}
$done = false;
$item = BlockAPI::getItem($data["block"], $data["meta"], $data["stack"]);
$slot = $tile->getSlot($data["slot"]);
$done = true;
if($this->server->api->dhandle("player.container.slot", array(
"tile" => $tile,
"slot" => $data["slot"],
"slotdata" => $slot,
"itemdata" => $item,
"player" => $this,
)) === false){
$this->dataPacket(MC_CONTAINER_SET_SLOT, array(
"windowid" => $data["windowid"],
"slot" => $data["slot"],
"block" => $slot->getID(),
"stack" => $slot->count,
"meta" => $slot->getMetadata(),
));
break;
}
if($item->getID() !== AIR and $slot->getID() == $item->getID()){
if($slot->count < $item->count){
if($this->removeItem($item->getID(), $item->getMetadata(), $item->count - $slot->count) === false and $this->itemEnforcement === true){
break;
}
}elseif($slot->count > $item->count){
$this->addItem($item->getID(), $item->getMetadata(), $slot->count - $item->count);
}
}else{
if($this->removeItem($item->getID(), $item->getMetadata(), $item->count) === false and $this->itemEnforcement === true){
break;
}
$this->addItem($slot->getID(), $slot->getMetadata(), $slot->count);
}
$tile->setSlot($data["slot"], $item);
break;
case MC_SEND_INVENTORY: //TODO, Mojang, enable this ´^_^`
if($this->spawned === false){
break;
}
break;
default:
console("[DEBUG] Unhandled 0x".dechex($data["id"])." Data Packet for Client ID ".$this->clientID.": ".print_r($data, true), true, true, 2);
break;
}
break;
}
}
}
public function sendArmor(){
$this->server->api->dhandle("player.armor", array("player" => $this, "eid" => $this->eid, "slot0" => ($this->armor[0][0] > 0 ? ($this->armor[0][0] - 256):AIR), "slot1" => ($this->armor[1][0] > 0 ? ($this->armor[1][0] - 256):AIR), "slot2" => ($this->armor[2][0] > 0 ? ($this->armor[2][0] - 256):AIR), "slot3" => ($this->armor[3][0] > 0 ? ($this->armor[3][0] - 256):AIR)));
}
public function sendInventory(){
/*
//OLD WAY
foreach($this->inventory as $s => $data){
if($data[0] > 0 and $data[2] >= 0){
$e = $this->server->api->entity->add(ENTITY_ITEM, $data[0], array(
"x" => $this->entity->x + 0.5,
"y" => $this->entity->y + 0.19,
"z" => $this->entity->z + 0.5,
"meta" => $data[1],
"stack" => $data[2],
));
$this->server->api->entity->spawnTo($e->eid, $this);
}
$this->inventory[$s] = array(AIR, 0, 0);
}*/
$inv = array();
foreach($this->inventory as $s => $data){
if($data[0] > AIR and $data[2] >= 0){
$inv[] = BlockAPI::getItem($data[0], $data[1], $data[2]);
}else{
$inv[] = BlockAPI::getItem(AIR, 0, 0);
$this->inventory[$s] = array(AIR, 0, 0);
}
}
$this->dataPacket(MC_CONTAINER_SET_CONTENT, array(
"windowid" => 0,
"count" => count($inv),
"slots" => $inv
));
/*
//Future
$inv = array();
foreach($this->inventory as $s => $data){
if($data[0] > 0 and $data[2] >= 0){
$inv[] = BlockAPI::getItem($data[0], $data[1], $data[2]);
}else{
$inv[] = BlockAPI::getItem(AIR, 0, 0);
$this->inventory[$s] = array(AIR, 0, 0);
}
}
$this->dataPacket(MC_SEND_INVENTORY, array(
"eid" => 0,
"windowid" => 0,
"slots" => $inv,
"armor" => array(
0 => BlockAPI::getItem($this->armor[0][0], $this->armor[0][1], $this->armor[0][2], $this->armor[0][3]),
1 => BlockAPI::getItem($this->armor[1][0], $this->armor[1][1], $this->armor[1][2], $this->armor[1][3]),
2 => BlockAPI::getItem($this->armor[2][0], $this->armor[2][1], $this->armor[2][2], $this->armor[2][3]),
3 => BlockAPI::getItem($this->armor[3][0], $this->armor[3][1], $this->armor[3][2], $this->armor[3][3]),
),
));
*/
}
public function send($pid, $data = array(), $raw = false){
if($this->connected === true){
$this->server->send($pid, $data, $raw, $this->ip, $this->port);
}
}
public function actionQueue($code){
$this->queue[] = array(1, $code);
}
public function sendBuffer(){
if(strlen($this->buffer) > 0){
$this->directDataPacket(false, array("raw" => $this->buffer));
}
$this->buffer = "";
$this->nextBuffer = microtime(true) + 0.1;
}
public function directDataPacket($id, $data = array(), $count = false){
if($this->connected === false){
return false;
}
$data["id"] = $id;
$data["sendtime"] = microtime(true);
if($count === false){
$count = $this->counter[0];
++$this->counter[0];
if(count($this->recovery) >= 1024){
reset($this->recovery);
$k = key($this->recovery);
$this->recovery[$k] = null;
unset($this->recovery[$k]);
end($this->recovery);
}
}
$this->recovery[$count] = $data;
$this->send(0x80, array(
$count,
0x00,
$data,
));
}
public function dataPacket($id, $data = array(), $queue = false){
$data["id"] = $id;
if($queue === true){
$this->queue[] = array(0, $data);
}else{
$data = new CustomPacketHandler($id, "", $data, true);
$len = strlen($data->raw) + 1;
$MTU = $this->MTU - 7;
if((strlen($this->buffer) + $len) >= $MTU){
$this->sendBuffer();
}
$this->buffer .= ($this->buffer === "" ? "":"\x00").Utils::writeShort($len << 3) . chr($id) . $data->raw;
}
}
function __toString(){
if($this->username != ""){
return $this->username;
}
return $this->clientID;
}
}