Map saving, breaking blocks. Huge rewrite

This commit is contained in:
Shoghi Cervantes Pueyo 2012-12-16 23:33:18 +01:00
parent 72c4cc7a30
commit 3a2010696f
15 changed files with 423 additions and 484 deletions

3
TODO Normal file
View File

@ -0,0 +1,3 @@
- Save broken/placed blocks and relay them to other players.
- Fix spawn position resetting to 0,128,0
- Mob spawning, item drop and pick up

View File

@ -31,11 +31,14 @@ class ConsoleAPI{
$this->help = array();
$this->server = $server;
$this->input = fopen(FILE_PATH."console.in", "w+b");
}
public function init(){
$this->event = $this->server->event("onTick", array($this, "handle"));
}
function __destroy(){
$this->server->deleteEvent("onTick", $this->event);
$this->server->deleteEvent($this->event);
fclose($this->input);
}

94
classes/API/EntityAPI.php Normal file
View File

@ -0,0 +1,94 @@
<?php
/*
-
/ \
/ \
/ POCKET \
/ MINECRAFT PHP \
|\ @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 EntityAPI{
private $server;
function __construct($server){
$this->server = $server;
}
public function init(){
$this->server->api->console->register("give", "Give items to a player", array($this, "commandHandler"));
}
public function commandHandler($cmd, $params){
switch($cmd){
case "give":
break;
}
}
public function get($eid){
if(isset($this->server->entities[$eid])){
return $this->server->entities[$eid];
}
return false;
}
public function getAll(){
return $this->server->entities;
}
public function add($class, $type = 0, $data = array()){
$eid = $this->server->eidCnt++;
$this->server->entities[$eid] = new Entity($this->server, $eid, $class, $type, $data);
return $this->server->entities[$eid];
}
public function spawnTo($eid, $player){
$e = $this->get($eid);
if($e === false){
return false;
}
$e->spawn($player);
}
public function spawnToAll($eid){
$e = $this->get($eid);
if($e === false){
return false;
}
foreach($this->server->api->player->getAll() as $player){
if($player->eid !== false){
$e->spawn($player);
}
}
}
public function spawnAll($player){
foreach($this->getAll() as $e){
$e->spawn($player);
}
}
public function remove($eid){
if(isset($this->server->entities[$eid])){
$this->server->entities[$eid]->close();
unset($this->server->entities[$eid]);
}
}
}

View File

@ -35,6 +35,32 @@ class LevelAPI{
}
}
public function init(){
//$this->server->event("onBlockBreak", array($this, "handle"));
}
public function handle($data, $event){
switch($event){
case "onBlockBreak":
$block = $this->getBlock($data["x"], $data["y"], $data["z"]);
console("[DEBUG] EID ".$data["eid"]." broke block ".$block[0].":".$block[1]." at X ".$data["x"]." Y ".$data["y"]." Z ".$data["z"], true, true, 2);
if($block[0] === 0){
break;
}
$this->setBlock($data["x"], $data["y"], $data["z"], 0, 0);
$data["block"] = $block[0];
$data["meta"] = $block[1];
$data["stack"] = 1;
$data["x"] += mt_rand(2, 8) / 10;
$data["y"] += mt_rand(2, 8) / 10;
$data["z"] += mt_rand(2, 8) / 10;
$e = $this->server->api->entity->add(ENTITY_ITEM, $block[0], $data);
$this->server->api->entity->spawnToAll($e->eid);
break;
}
}
private function check(){
if($this->active === false and $this->server->map === false){
return false;
@ -57,6 +83,19 @@ class LevelAPI{
return array(0,0);
}
public function setBlock($x, $y, $z, $block, $meta = 0){
if($this->check()){
$this->map->setBlock($x, $y, $z, $block, $meta);
}
$this->server->trigger("onBlockUpdate", array(
"x" => $x,
"y" => $y,
"z" => $z,
"block" => $block,
"meta" => $meta,
));
}
public function getOrderedChunk($X, $Z, $columnsPerPacket = 2){
$columnsPerPacket = max(1, (int) $columnsPerPacket);
$c = $this->getChunk($X, $Z);

View File

@ -73,7 +73,7 @@ class PlayerAPI{
case "list":
console("[INFO] Player list:");
foreach($this->server->clients as $c){
console("[INFO] ".$c->username." (".$c->ip.":".$c->port."), ClientID ".$c->clientID.", (".round($c->username->entity->position["x"], 2).", ".round($c->username->entity->position["y"], 2).", ".round($c->username->entity->position["z"], 2).")");
console("[INFO] ".$c->username." (".$c->ip.":".$c->port."), ClientID ".$c->clientID.", (".round($c->username->entity->x, 2).", ".round($c->username->entity->y, 2).", ".round($c->username->entity->z, 2).")");
}
break;
}
@ -82,7 +82,7 @@ class PlayerAPI{
public function teleport($name, $target){
$target = $this->get($target);
if($target !== false){
$this->tppos($name, $target->entity->position["x"], $target->entity->position["y"], $target->entity->position["z"]);
$this->tppos($name, $target->entity->x, $target->entity->y, $target->entity->z);
}
}
@ -102,6 +102,10 @@ class PlayerAPI{
return false;
}
public function getAll(){
return $this->server->clients;
}
public function getByEID($eid){
$eid = (int) $eid;
$CID = $this->server->query("SELECT ip,port FROM players WHERE EID = '".$eid."';", true);
@ -112,6 +116,16 @@ class PlayerAPI{
return false;
}
public function getByClientID($clientID){
$clientID = (int) $clientID;
$CID = $this->server->query("SELECT ip,port FROM players WHERE clientID = '".$clientID."';", true);
$CID = $this->server->clientID($CID["ip"], $CID["port"]);
if(isset($this->server->clients[$CID])){
return $this->server->clients[$CID];
}
return false;
}
public function online(){
$o = array();
foreach($this->server->clients as $p){
@ -127,7 +141,7 @@ class PlayerAPI{
$player = $this->server->clients[$CID];
console("[INFO] Player \"".$player->username."\" connected from ".$player->ip.":".$player->port);
$player->data = $this->getOffline($player->username);
$this->server->query("INSERT OR REPLACE INTO players (clientID, EID, ip, port, name) VALUES (".$player->clientID.", ".$player->eid.", '".$player->ip."', ".$player->port.", '".$player->username."');");
$this->server->query("INSERT OR REPLACE INTO players (clientID, ip, port, name) VALUES (".$player->clientID.", '".$player->ip."', ".$player->port.", '".$player->username."');");
}
}

View File

@ -99,6 +99,8 @@ class ServerAPI extends stdClass{ //Yay! I can add anything to this class in run
$this->$a->init();
}
}
$this->server->loadEntities();
}
public function getList(){

View File

@ -26,7 +26,7 @@ the Free Software Foundation, either version 3 of the License, or
*/
class ChunkParser{
private $location, $raw = b"";
private $location, $raw = b"", $file;
var $sectorLenght = 4096; //16 * 16 * 16
var $chunkLenght = 86016; //21 * $sectorLenght
var $map;
@ -62,6 +62,7 @@ class ChunkParser{
if(!file_exists($file)){
return false;
}
$this->file = $file;
$this->raw = file_get_contents($file);
$this->chunkLenght = $this->sectorLenght * ord($this->raw{0});
return true;
@ -78,15 +79,25 @@ class ChunkParser{
return 0x1000 + (($Z * $sectors) << 12) + (($X * $sectors) << 16);
}
public function getChunk($X, $Z, $header = true){
public function getChunk($X, $Z){
$X = (int) $X;
$Z = (int) $Z;
if($header === false){
$add = 4;
}else{
$add = 0;
return substr($this->raw, $this->getOffset($X, $Z), $this->chunkLenght);
}
public function writeChunk($X, $Z){
$X = (int) $X;
$Z = (int) $Z;
if(!isset($this->map[$X][$Z])){
return false;
}
return substr($this->raw, $this->getOffset($X, $Z) + $add, $this->chunkLenght - $add);
$chunk = "";
foreach($this->map[$X][$Z] as $section => $data){
foreach($data as $r){
$chunk .= $r;
}
}
return Utils::writeLInt(strlen($chunk)).$chunk;
}
public function parseChunk($X, $Z){
@ -154,6 +165,20 @@ class ChunkParser{
console("[DEBUG] Chunks loaded!", true, true, 2);
}
public function saveMap(){
console("[DEBUG] Saving chunks...", true, true, 2);
$fp = fopen($this->file, "r+b");
flock($fp, LOCK_EX);
foreach($this->map as $x => $d){
foreach($d as $z => $chunk){
fseek($fp, $this->getOffset($x, $z));
fwrite($fp, $this->writeChunk($x, $z), $this->chunkLenght);
}
}
flock($fp, LOCK_UN);
fclose($fp);
}
public function getBlock($x, $y, $z){
$x = (int) $x;
$y = (int) $y;
@ -164,8 +189,32 @@ class ChunkParser{
$aZ = $z - ($Z << 4);
$index = $aX + ($aZ << 4);
$block = ord($this->map[$X][$Z][0][$index]{$y});
//$meta = $this->getOffset($X, $Z) + 4 + (($x << 6) + $y + ($z << 10));
return array($block, 0);
$meta = ord($this->map[$X][$Z][1][$index]{$y >> 1});
if(($y & 1) === 0){
$meta = $meta & 0x0F;
}else{
$meta = $meta >> 4;
}
return array($block, $meta);
}
public function setBlock($x, $y, $z, $block, $meta = 0){
$x = (int) $x;
$y = (int) $y;
$z = (int) $z;
$X = $x >> 4;
$Z = $z >> 4;
$aX = $x - ($X << 4);
$aZ = $z - ($Z << 4);
$index = $aX + ($aZ << 4);
$this->map[$X][$Z][0][$index]{$y} = chr($block);
$old_meta = ord($this->map[$X][$Z][1][$index]{$y >> 1});
if(($y & 1) === 0){
$meta = ($old_meta & 0xF0) | ($meta & 0x0F);
}else{
$meta = (($meta << 4) & 0xF0) | ($old_meta & 0x0F);
}
$this->map[$X][$Z][1][$index]{$y >> 1} = chr($meta);
}
}

View File

@ -186,7 +186,7 @@ class CustomPacketHandler{
break;
case MC_ADD_MOB:
if($this->c === false){
$this->data["int"] = Utils::readInt($this->get(4));
$this->data["eid"] = Utils::readInt($this->get(4));
$this->data["type"] = Utils::readInt($this->get(4));
$this->data["x"] = Utils::readFloat($this->get(4));
$this->data["y"] = Utils::readFloat($this->get(4));

View File

@ -32,17 +32,17 @@ define("ENTITY_OBJECT", 2);
define("ENTITY_ITEM", 3);
define("ENTITY_PAINTING", 4);
class Entity{
var $eid, $type, $name, $position, $dead, $metadata, $class, $attach, $data, $closed;
protected $health, $client;
class Entity extends stdClass{
var $eid, $type, $name, $x, $y, $z, $yaw, $pitch, $dead, $data, $class, $attach, $metadata, $closed, $player;
function __construct($eid, $class, $type = 0, $server){ //$type = 0 ---> player
function __construct($server, $eid, $class, $type = 0, $data = array()){
$this->server = $server;
$this->eid = (int) $eid;
$this->type = (int) $type;
$this->class = (int) $class;
$this->player = false;
$this->attach = false;
$this->data = array();
$this->data = $data;
$this->status = 0;
$this->health = 20;
$this->dead = false;
@ -50,19 +50,72 @@ class Entity{
$this->name = "";
$this->server->query("INSERT OR REPLACE INTO entities (EID, type, class, health) VALUES (".$this->eid.", ".$this->type.", ".$this->class.", ".$this->health.");");
$this->metadata = array();
/*include("misc/entities.php");
$this->x = isset($this->data["x"]) ? $this->data["x"]:0;
$this->y = isset($this->data["y"]) ? $this->data["y"]:0;
$this->z = isset($this->data["z"]) ? $this->data["z"]:0;
$this->yaw = isset($this->data["yaw"]) ? $this->data["yaw"]:0;
$this->pitch = isset($this->data["pitch"]) ? $this->data["pitch"]:0;
$this->position = array("x" => &$this->x, "y" => &$this->y, "z" => &$this->z, "yaw" => &$this->yaw, "pitch" => &$this->pitch);
switch($this->class){
case ENTITY_PLAYER:
case ENTITY_ITEM:
$this->player = $this->data["player"];
$this->health = &$this->player->data["health"];
break;
case ENTITY_ITEM:
$this->meta = (int) $this->data["meta"];
$this->stack = (int) $this->data["stack"];
break;
case ENTITY_MOB:
$this->setName((isset($mobs[$this->type]) ? $mobs[$this->type]:$this->type));
//$this->setName((isset($mobs[$this->type]) ? $mobs[$this->type]:$this->type));
break;
case ENTITY_OBJECT:
$this->setName((isset($objects[$this->type]) ? $objects[$this->type]:$this->type));
//$this->setName((isset($objects[$this->type]) ? $objects[$this->type]:$this->type));
break;
}*/
}
}
public function spawn($player){
if(!is_object($player)){
$player = $this->server->api->player->get($player);
}
if($player->eid === $this->eid){
return false;
}
switch($this->class){
case ENTITY_PLAYER:
$player->dataPacket(MC_ADD_PLAYER, array(
"clientID" => $this->player->clientID,
"username" => $this->player->username,
"eid" => $this->eid,
"x" => $this->x,
"y" => $this->y,
"z" => $this->z,
));
break;
case ENTITY_ITEM:
$player->dataPacket(MC_ADD_ITEM_ENTITY, array(
"eid" => $this->eid,
"x" => $this->x,
"y" => $this->y,
"z" => $this->z,
"block" => $this->type,
"meta" => $this->meta,
"stack" => $this->stack,
));
break;
case ENTITY_MOB:
$player->dataPacket(MC_ADD_MOB, array(
"type" => $this->type,
"eid" => $this->eid,
"x" => $this->x,
"y" => $this->y,
"z" => $this->z,
));
break;
case ENTITY_OBJECT:
//$this->setName((isset($objects[$this->type]) ? $objects[$this->type]:$this->type));
break;
}
}
public function close(){
@ -93,59 +146,36 @@ class Entity{
public function look($pos2){
$pos = $this->getPosition();
$angle = Utils::angle3D($pos2, $pos);
$this->position["yaw"] = $angle["yaw"];
$this->position["pitch"] = $angle["pitch"];
$this->server->query("UPDATE entities SET pitch = ".$this->position["pitch"].", yaw = ".$this->position["yaw"]." WHERE EID = ".$this->eid.";");
$this->yaw = $angle["yaw"];
$this->pitch = $angle["pitch"];
$this->server->query("UPDATE entities SET pitch = ".$this->pitch.", yaw = ".$this->yaw." WHERE EID = ".$this->eid.";");
}
public function setCoords($x, $y, $z){
if(!isset($this->position)){
$this->position = array(
"x" => 0,
"y" => 0,
"z" => 0,
"yaw" => 0,
"pitch" => 0,
"ground" => 0,
);
}
$this->position["x"] = $x;
$this->position["y"] = $y;
$this->position["z"] = $z;
$this->server->query("UPDATE entities SET x = ".$this->position["x"].", y = ".$this->position["y"].", z = ".$this->position["z"]." WHERE EID = ".$this->eid.";");
$this->x = $x;
$this->y = $y;
$this->z = $z;
$this->server->query("UPDATE entities SET x = ".$this->x.", y = ".$this->y.", z = ".$this->z." WHERE EID = ".$this->eid.";");
}
public function move($x, $y, $z, $yaw = 0, $pitch = 0){
if(!isset($this->position)){
$this->position = array(
"x" => 0,
"y" => 0,
"z" => 0,
"yaw" => 0,
"pitch" => 0,
"ground" => 0,
);
}
$this->position["x"] += $x;
$this->position["y"] += $y;
$this->position["z"] += $z;
$this->position["yaw"] += $yaw;
$this->position["yaw"] %= 360;
$this->position["pitch"] += $pitch;
$this->position["pitch"] %= 90;
$this->server->query("UPDATE entities SET x = ".$this->position["x"].", y = ".$this->position["y"].", z = ".$this->position["z"].", pitch = ".$this->position["pitch"].", yaw = ".$this->position["yaw"]." WHERE EID = ".$this->eid.";");
$this->x += $x;
$this->y += $y;
$this->z += $z;
$this->yaw += $yaw;
$this->yaw %= 360;
$this->pitch += $pitch;
$this->pitch %= 90;
$this->server->query("UPDATE entities SET x = ".$this->x.", y = ".$this->y.", z = ".$this->z.", pitch = ".$this->pitch.", yaw = ".$this->yaw." WHERE EID = ".$this->eid.";");
}
public function setPosition($x, $y, $z, $yaw, $pitch){
$this->position = array(
"x" => $x,
"y" => $y,
"z" => $z,
"yaw" => $yaw,
"pitch" => $pitch,
"ground" => $ground,
);
$this->server->query("UPDATE entities SET x = ".$this->position["x"].", y = ".$this->position["y"].", z = ".$this->position["z"].", pitch = ".$this->position["pitch"].", yaw = ".$this->position["yaw"]." WHERE EID = ".$this->eid.";");
$this->x = $x;
$this->y = $y;
$this->z = $z;
$this->yaw = $yaw;
$this->pitch = $pitch;
$this->server->query("UPDATE entities SET x = ".$this->x.", y = ".$this->y.", z = ".$this->z.", pitch = ".$this->pitch.", yaw = ".$this->yaw." WHERE EID = ".$this->eid.";");
return true;
}

View File

@ -26,8 +26,8 @@ the Free Software Foundation, either version 3 of the License, or
*/
class MinecraftInterface{
var $pstruct, $name, $protocol, $client, $buffer, $dataName;
private $socket;
var $pstruct, $name, $protocol, $client, $dataName;
private $socket, $data;
function __construct($server, $protocol = CURRENT_PROTOCOL, $port = 25565, $listen = false, $client = true){
$this->socket = new UDPSocket($server, $port, (bool) $listen);
$this->protocol = (int) $protocol;
@ -88,8 +88,7 @@ class MinecraftInterface{
$p = "[".(microtime(true) - $this->start)."] [".((($origin === "client" and $this->client === true) or ($origin === "server" and $this->client === false)) ? "CLIENT->SERVER":"SERVER->CLIENT")." ".$ip.":".$port."]: Error, bad packet id 0x".Utils::strTohex(chr($pid))." [length ".strlen($raw)."]".PHP_EOL;
$p .= Utils::hexdump($data[0]);
$p .= PHP_EOL;
logg($p, "packets", true, 2);
$this->buffer = "";
logg($p, "packets", true, 2);
return false;
}

View File

@ -1,242 +0,0 @@
<?php
/*
-
/ \
/ \
/ POCKET \
/ MINECRAFT PHP \
|\ @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 PocketMinecraftClient{
protected $interface, $protocol, $entities, $player, $cnt, $events, $username, $version, $clientID, $connected, $serverID, $start;
var $serverList = array(), $counter;
function __construct($username, $protocol = CURRENT_PROTOCOL, $clientID = false, $version = CURRENT_VERSION){
//$this->player = new Player($username);
$this->start = microtime(true);
$this->version = (int) $version;
$this->username = $username;
$this->connected = false;
$this->cnt = 1;
$this->clientID = $clientID === false ? Utils::readLong(Utils::getRandomBytes(8)):$clientID;
$this->events = array("disabled" => array());
$this->actions = array();
$this->interface = new MinecraftInterface("255.255.255.255", $protocol, 19132);
console("[INFO] Starting Minecraft Client");
console("[INFO] Username: ".$this->username);
console("[INFO] Client GUID: ".$this->clientID);
$this->event("onReceivedPacket", "packetHandler", true);
$this->stop = false;
$this->counter = array(0,0);
declare(ticks=15);
register_tick_function(array($this, "tickerFunction"));
}
public function action($microseconds, $code){
$this->actions[] = array($microseconds / 1000000, microtime(true), $code);
console("[INTERNAL] Attached to action ".$microseconds, true, true, 3);
}
public function tickerFunction(){
//actions that repeat every x time will go here
$time = microtime(true);
foreach($this->actions as $id => $action){
if($action[1] <= ($time - $action[0])){
$this->actions[$id][1] = $time;
eval($action[2]);
}
}
}
public function start($ip){
$this->stop = false;
$this->action(50000, '$this->trigger("onTick", $time);');
$this->interface = new MinecraftInterface($ip, $this->protocol, 19132);
console("[INFO] Connecting to Server ".$ip);
$this->send(0x05, array(
MAGIC,
$this->version,
str_repeat("\x00", 1447),
));
$this->process();
}
public function getServerList(){
$this->action(1000000, '$this->send(0x02, array(((microtime(true) - $this->start) * 1000)));');
$this->action(5000000, '$this->actions = array();$this->stop = true;');
$this->process();
$list = array();
foreach($this->serverList as $ip => $info){
$info["ip"] = $ip;
$list[] = $info;
}
return $list;
}
public function packetHandler($packet, $event){
$data =& $packet["data"];
switch($packet["pid"]){
case 0x1c:
$pingID = $data[0];
$this->serverID = $data[1];
$info = explode(";", $data[3]);
$this->serverList[$packet["ip"]] = array("serverID" => $serverID, "username" => array_pop($info));
break;
case 0x06:
$serverID = $data[1];
$length = $data[3];
$this->send(0x07, array(
MAGIC,
"\x04\x3f\x57\xfe\xfd",
19132,
$length,
$this->clientID,
));
break;
case 0x08:
$serverID = $data[1];
$this->send(0x84, array(
$this->counter[0],
0x00,
array(
"id" => 0x09,
"clientID" => $this->clientID,
"session" => Utils::readLong("\x00\x00\x00\x00\x03\x1c\xaf\x05"),
),
));
++$this->counter[0];
break;
case 0x84:
if(isset($data[0])){
$this->counter[1] = $data[0];
$this->send(0xc0, array(1, true, $data[0]));
}
switch($data["id"]){
case 0x10:
$this->send(0x84, array(
$this->counter[0],
0x00,
array(
"id" => 0x13,
"port" => 19132,
"dataArray" => $data["dataArray"],
"session" => $data["session"],
"session2" => $data["session2"],
),
));
++$this->counter[0];
$this->send(0x84, array(
$this->counter[0],
0x00,
array(
"id" => 0x00,
"payload" => $data["session"],
),
));
++$this->counter[0];
$this->send(0x84, array(
$this->counter[0],
0x00,
array(
"id" => 0x82,
"username" => $this->username,
),
));
++$this->counter[0];
/*$this->send(0x84, array(
$this->counter[0],
0x00,
array(
"id" => 0xb1,
"message" => $this->username,
),
));
++$this->counter[0];*/
break;
case 0x86:
console("[DEBUG] Time: ".$data["time"], true, true, 2);
break;
}
break;
}
}
public function send($pid, $data = array(), $raw = false, $dest = false, $port = false){
$this->trigger($pid, $data);
$this->trigger("onSentPacket", $data);
$this->interface->writePacket($pid, $data, $raw, $dest, $port);
}
public function process(){
while($this->stop === false){
$packet = $this->interface->readPacket();
if($packet !== false){
$this->trigger("onReceivedPacket", $packet);
$this->trigger($packet["pid"], $packet);
}else{
usleep(10000);
}
}
}
public function trigger($event, $data = ""){
console("[INTERNAL] Event ". $event, true, true, 3);
if(isset($this->events[$event]) and !isset($this->events["disabled"][$event])){
foreach($this->events[$event] as $eid => $ev){
if(isset($ev[1]) and ($ev[1] === true or is_object($ev[1]))){
$this->responses[$eid] = call_user_func(array(($ev[1] === true ? $this:$ev[1]), $ev[0]), $data, $event, $this);
}else{
$this->responses[$eid] = call_user_func($ev[0], $data, $event, $this);
}
}
}
}
public function toggleEvent($event){
if(isset($this->events["disabled"][$event])){
unset($this->events["disabled"][$event]);
console("[INTERNAL] Enabled event ".$event, true, true, 3);
}else{
$this->events["disabled"][$event] = false;
console("[INTERNAL] Disabled event ".$event, true, true, 3);
}
}
public function event($event, $func, $in = false){
++$this->cnt;
if(!isset($this->events[$event])){
$this->events[$event] = array();
}
$this->events[$event][$this->cnt] = array($func, $in);
console("[INTERNAL] Attached to event ".$event, true, true, 3);
return $this->cnt;
}
public function deleteEvent($event, $id = -1){
if($id === -1){
unset($this->events[$event]);
}else{
unset($this->events[$event][$id]);
if(isset($this->events[$event]) and count($this->events[$event]) === 0){
unset($this->events[$event]);
}
}
}
}

View File

@ -31,9 +31,12 @@ class PocketMinecraftServer extends stdClass{
var $seed, $protocol, $gamemode, $name, $maxClients, $clients, $eidCnt, $custom, $description, $motd, $timePerSecond, $responses, $spawn, $entities, $mapDir, $mapName, $map, $level, $tileEntities;
private $database, $interface, $cnt, $events, $version, $serverType, $lastTick;
function __construct($name, $gamemode = 1, $seed = false, $protocol = CURRENT_PROTOCOL, $port = 19132, $serverID = false, $version = CURRENT_VERSION){
$this->port = (int) $port;
$this->port = (int) $port; //19132 - 19135
console("[INFO] Pocket-Minecraft-PHP by @shoghicp, LGPL License. http://bit.ly/RE7uaW", true, true, 0);
console("[INFO] Starting Minecraft PE Server at *:".$this->port);
if($this->port < 19132 or $this->port > 19135){
console("[WARNING] You've selected a not-standard port. Normal port range is from 19132 to 19135 included");
}
console("[INFO] Loading database...");
$this->startDatabase();
$this->gamemode = (int) $gamemode;
@ -47,7 +50,7 @@ class PocketMinecraftServer extends stdClass{
$this->tileEntities = array();
$this->entities = array();
$this->custom = array();
$this->cnt = 1;
$this->cnt = 0;
$this->eidCnt = 1;
$this->maxClients = 20;
$this->description = "";
@ -61,8 +64,6 @@ class PocketMinecraftServer extends stdClass{
$this->spawn = array("x" => 128.5,"y" => 100,"z" => 128.5);
$this->time = 0;
$this->timePerSecond = 10;
console("[INFO] Loading events...");
$this->loadEvents();
$this->setType("normal");
$this->interface = new MinecraftInterface("255.255.255.255", $this->protocol, $this->port, true, false);
$this->reloadConfig();
@ -73,9 +74,7 @@ class PocketMinecraftServer extends stdClass{
$this->stop = false;
}
public function loadEvents(){
$this->events = array("disabled" => array());
public function loadEvents(){
$this->event("onChat", "eventHandler", true);
$this->event("onPlayerDeath", "eventHandler", true);
$this->event("onPlayerAdd", "eventHandler", true);
@ -85,7 +84,9 @@ class PocketMinecraftServer extends stdClass{
$this->action(5000000, '$this->trigger("onHealthRegeneration", 1);');
$this->action(1000000 * 60, '$this->reloadConfig();');
$this->action(1000000 * 60 * 10, '$this->custom = array();');
$this->action(1000000 * 80, '$this->chat(false, count($this->clients)."/".$this->maxClients." online: ".implode(", ",$this->api->player->online()));');
if($this->api !== false){
$this->action(1000000 * 80, '$this->chat(false, count($this->clients)."/".$this->maxClients." online: ".implode(", ",$this->api->player->online()));');
}
$this->action(1000000 * 75, '$this->debugInfo(true);');
}
@ -95,7 +96,7 @@ class PocketMinecraftServer extends stdClass{
$this->query("CREATE TABLE entities (EID INTEGER PRIMARY KEY, type NUMERIC, class NUMERIC, name TEXT, x NUMERIC, y NUMERIC, z NUMERIC, yaw NUMERIC, pitch NUMERIC, health NUMERIC);");
$this->query("CREATE TABLE metadata (EID INTEGER PRIMARY KEY, name TEXT, value TEXT);");
$this->query("CREATE TABLE actions (ID INTEGER PRIMARY KEY, interval NUMERIC, last NUMERIC, code TEXT, repeat NUMERIC);");
$this->query("CREATE TABLE events (ID INTEGER PRIMARY KEY, eventName TEXT, priority NUMERIC, disabled INTEGER);");
$this->query("CREATE TABLE events (ID INTEGER PRIMARY KEY, eventName TEXT, priority NUMERIC);");
}
public function query($sql, $fetch = false){
@ -216,18 +217,6 @@ class PocketMinecraftServer extends stdClass{
}else{
$this->map->loadMap();
}
if($this->map !== false){
console("[INFO] Loading entities...");
$entities = unserialize(file_get_contents($this->mapDir."entities.dat"));
foreach($entities as $entity){
$this->entities[$this->eidCnt] = new Entity($this->eidCnt, ENTITY_MOB, $entity["id"], $this);
$this->entities[$this->eidCnt]->setPosition($entity["Pos"][0], $entity["Pos"][1], $entity["Pos"][2], $entity["Rotation"][0], $entity["Rotation"][1]);
$this->entities[$this->eidCnt]->setHealth($entity["Health"]);
++$this->eidCnt;
}
console("[DEBUG] Loaded ".count($this->entities)." Entities", true, true, 2);
$this->action(1000000 * 60 * 15, '$this->chat(false, "Forcing save...");$this->save();$this->chat(false, "Done");');
}
}else{
console("[INFO] Time: ".$this->time);
console("[INFO] Seed: ".$this->seed);
@ -235,16 +224,47 @@ class PocketMinecraftServer extends stdClass{
}
}
public function loadEntities(){
if($this->map !== false){
console("[INFO] Loading entities...");
$entities = unserialize(file_get_contents($this->mapDir."entities.dat"));
foreach($entities as $entity){
if(!isset($entity["id"])){
break;
}
if(!isset($this->api) or $this->api === false){
$this->entities[$this->eidCnt] = new Entity($his, $this->eidCnt, ENTITY_MOB, $entity["id"]);
$this->entities[$this->eidCnt]->setPosition($entity["Pos"][0], $entity["Pos"][1], $entity["Pos"][2], $entity["Rotation"][0], $entity["Rotation"][1]);
$this->entities[$this->eidCnt]->setHealth($entity["Health"]);
++$this->eidCnt;
}else{
$e = $this->api->entity->add(ENTITY_MOB, $entity["id"]);
$e->setPosition($entity["Pos"][0], $entity["Pos"][1], $entity["Pos"][2], $entity["Rotation"][0], $entity["Rotation"][1]);
$e->setHealth($entity["Health"]);
}
}
console("[DEBUG] Loaded ".count($this->entities)." Entities", true, true, 2);
$this->action(1000000 * 60 * 15, '$this->chat(false, "Forcing save...");$this->save();$this->chat(false, "Done");');
}
}
public function save(){
if($this->mapName !== false){
file_put_contents($this->mapDir."level.dat", serialize($this->level));
$this->map->saveMap();
}
}
public function start(){
if($this->mapName !== false and $this->map === false){
$this->loadMap();
$this->loadEntities();
}
console("[INFO] Loading events...");
$this->loadEvents();
declare(ticks=15);
register_tick_function(array($this, "tick"));
$this->event("onTick", array($this, "tickerFunction"));
$this->event("onTick", "tickerFunction", true);
register_shutdown_function(array($this, "close"));
console("[INFO] Server started!");
$this->process();
@ -327,8 +347,7 @@ class PocketMinecraftServer extends stdClass{
$port = $data[2];
$MTU = $data[3];
$clientID = $data[4];
$eid = $this->eidCnt++;
$this->clients[$CID] = new Session($this, $clientID, $eid, $packet["ip"], $packet["port"], $MTU);
$this->clients[$CID] = new Session($this, $clientID, $packet["ip"], $packet["port"], $MTU);
$this->clients[$CID]->handle(0x07, $data);
break;
}
@ -351,18 +370,14 @@ class PocketMinecraftServer extends stdClass{
}
public function trigger($event, $data = ""){
$events = $this->query("SELECT ID FROM events WHERE eventName = '".$event."' AND disabled = 0;");
$events = $this->query("SELECT ID FROM events WHERE eventName = '".$event."';");
if($events === false or $events === true){
return;
}
//console("[INTERNAL] Event ". $event, true, true, 3);
while($evn = $events->fetchArray(SQLITE3_ASSOC)){
$ev = $this->events[$event][$evn["ID"]];
if(isset($ev[1]) and ($ev[1] === true or is_object($ev[1]))){
$this->responses[$evn["ID"]] = call_user_func(array(($ev[1] === true ? $this:$ev[1]), $ev[0]), $data, $event, $this);
}else{
$this->responses[$evn["ID"]] = call_user_func($ev[0], $data, $event, $this);
}
$evid = (int) $evn["ID"];
$this->responses[$evid] = call_user_func($this->events[$evid], $data, $event, $this);
}
return true;
}
@ -397,42 +412,23 @@ class PocketMinecraftServer extends stdClass{
$this->query("UPDATE actions SET last = ".$time." WHERE last <= (".$time." - interval);");
}
public function toggleEvent($event){
if(isset($this->events["disabled"][$event])){
unset($this->events["disabled"][$event]);
$this->query("UPDATE events SET disabled = 0 WHERE eventName = '".$event."';");
console("[INTERNAL] Enabled event ".$event, true, true, 3);
}else{
$this->events["disabled"][$event] = false;
$this->query("UPDATE events SET disabled = 1 WHERE eventName = '".$event."';");
console("[INTERNAL] Disabled event ".$event, true, true, 3);
}
}
public function event($event, $func, $in = false){
++$this->cnt;
if(!isset($this->events[$event])){
$this->events[$event] = array();
if($in === true){
$func = array($this, $func);
}
$this->query("INSERT INTO events (ID, eventName, disabled) VALUES (".$this->cnt.", '".str_replace("'", "\\'", $event)."', 0);");
$this->events[$event][$this->cnt] = array($func, $in);
console("[INTERNAL] Attached to event ".$event, true, true, 3);
return $this->cnt;
if(!is_callable($func)){
return false;
}
$this->events[$this->cnt] = $func;
$this->query("INSERT INTO events (ID, eventName) VALUES (".$this->cnt.", '".str_replace("'", "\\'", $event)."');");
console("[INTERNAL] Attached ".(is_array($func) ? get_class($func[0])."::".$func[1]:$func)." to event ".$event." (ID ".$this->cnt.")", true, true, 3);
return $this->cnt++;
}
public function deleteEvent($event, $id = -1){
public function deleteEvent($id){
$id = (int) $id;
if($id === -1){
unset($this->events[$event]);
$this->query("DELETE FROM events WHERE eventName = '".str_replace("'", "\\'", $event)."';");
}else{
unset($this->events[$event][$id]);
$this->query("DELETE FROM events WHERE ID = ".$id.";");
if(isset($this->events[$event]) and count($this->events[$event]) === 0){
unset($this->events[$event]);
$this->query("DELETE FROM events WHERE eventName = '".str_replace("'", "\\'", $event)."';");
}
}
unset($this->events[$id]);
$this->query("DELETE FROM events WHERE ID = ".$id.";");
}
}

View File

@ -29,22 +29,22 @@ the Free Software Foundation, either version 3 of the License, or
class Session{
private $server, $timeout, $connected, $evid, $queue, $buffer;
var $clientID, $ip, $port, $counter, $username, $eid, $data, $entity, $auth, $CID, $MTU;
function __construct($server, $clientID, $eid, $ip, $port, $MTU){
function __construct($server, $clientID, $ip, $port, $MTU){
$this->queue = array();
$this->buffer = array();
$this->MTU = $MTU;
$this->server = $server;
$this->clientID = $clientID;
$this->CID = $this->server->clientID($ip, $port);
$this->eid = $eid;
$this->eid = false;
$this->data = array();
$this->ip = $ip;
$this->entity = false;
$this->port = $port;
$this->timeout = microtime(true) + 25;
$this->evid = array();
$this->evid[] = array("onTick", $this->server->event("onTick", array($this, "onTick")));
$this->evid[] = array("onClose", $this->server->event("onClose", array($this, "close")));
$this->evid[] = $this->server->event("onTick", array($this, "onTick"));
$this->evid[] = $this->server->event("onClose", array($this, "close"));
console("[DEBUG] New Session started with ".$ip.":".$port.". MTU ".$this->MTU.", Client ID ".$this->clientID, true, true, 2);
$this->connected = true;
$this->auth = false;
@ -83,9 +83,9 @@ class Session{
public function save(){
if(is_object($this->entity)){
$this->data["spawn"] = array(
"x" => $this->entity->position["x"],
"y" => $this->entity->position["y"],
"z" => $this->entity->position["z"],
"x" => $this->entity->x,
"y" => $this->entity->y,
"z" => $this->entity->z,
);
}
}
@ -94,7 +94,7 @@ class Session{
$reason = $reason == "" ? "server stop":$reason;
$this->save();
foreach($this->evid as $ev){
$this->server->deleteEvent($ev[0], $ev[1]);
$this->server->deleteEvent($ev);
}
$this->eventHandler("You have been kicked. Reason: ".$reason, "onChat");
$this->dataPacket(MC_DISCONNECT);
@ -113,6 +113,9 @@ class Session{
$this->server->trigger("onPlayerDeath", array("name" => $this->username, "cause" => $data["cause"]));
}
break;
case "onBlockUpdate":
$this->dataPacket(MC_UPDATE_BLOCK, $data);
break;
case "onTeleport":
if($data["eid"] !== $this->eid){
break;
@ -133,11 +136,11 @@ class Session{
$entity = $this->server->entities[$data];
$this->dataPacket(MC_MOVE_ENTITY_POSROT, array(
"eid" => $data,
"x" => $entity->position["x"],
"y" => $entity->position["y"],
"z" => $entity->position["z"],
"yaw" => $entity->position["yaw"],
"pitch" => $entity->position["pitch"],
"x" => $entity->x,
"y" => $entity->y,
"z" => $entity->z,
"yaw" => $entity->yaw,
"pitch" => $entity->pitch,
));
break;
case "onHealthRegeneration":
@ -151,24 +154,11 @@ class Session{
"health" => $data["health"],
));
$this->data["health"] = $data["health"];
if(is_object($this->entity)){
/*if(is_object($this->entity)){
$this->entity->setHealth($data["health"]);
}
}*/
}
break;
case "onPlayerAdd":
if($data["eid"] === $this->eid){
break;
}
$this->dataPacket(MC_ADD_PLAYER, array(
"clientID" => $data["clientID"],
"username" => $data["username"],
"eid" => $data["eid"],
"x" => $data["x"],
"y" => $data["y"],
"z" => $data["z"],
));
break;
case "onEntityRemove":
if($data === $this->eid){
break;
@ -260,10 +250,13 @@ class Session{
break;
case MC_LOGIN:
$this->username = str_replace("/", "", $data["username"]);
foreach($this->server->clients as $c){
if($c->eid !== $this->eid and $c->username === $this->username){
$c->close("logged in from another location");
}
$u = $this->server->api->player->get($this->username);
$c = $this->server->api->player->getByClientID($this->clientID);
if($u !== false){
$c->close("logged in from another location");
}
if($c !== false){
$c->close("logged in from another location");
}
if($this->server->whitelist !== false and !in_array($this->username, $this->server->whitelist)){
$this->close("\"".$this->username."\" not being on white-list", false);
@ -273,16 +266,6 @@ class Session{
$this->auth = true;
$this->data["lastIP"] = $this->ip;
$this->data["lastID"] = $this->clientID;
$this->evid[] = array("onTimeChange", $this->server->event("onTimeChange", array($this, "eventHandler")));
$this->evid[] = array("onChat", $this->server->event("onChat", array($this, "eventHandler")));
$this->evid[] = array("onDeath", $this->server->event("onDeath", array($this, "eventHandler")));
$this->evid[] = array("onPlayerAdd", $this->server->event("onPlayerAdd", array($this, "eventHandler")));
$this->evid[] = array("onEntityRemove", $this->server->event("onEntityRemove", array($this, "eventHandler")));
$this->evid[] = array("onEntityMove", $this->server->event("onEntityMove", array($this, "eventHandler")));
$this->evid[] = array("onHealthChange", $this->server->event("onHealthChange", array($this, "eventHandler")));
$this->evid[] = array("onHealthRegeneration", $this->server->event("onHealthRegeneration", array($this, "eventHandler")));
$this->evid[] = array("onAnimate", $this->server->event("onAnimate", array($this, "eventHandler")));
$this->evid[] = array("onTeleport", $this->server->event("onTeleport", array($this, "eventHandler")));
$this->dataPacket(MC_LOGIN_STATUS, array(
"status" => 0,
));
@ -300,43 +283,25 @@ class Session{
if(is_object($this->entity)){
break;
}
$this->server->trigger("onHealthChange", array("eid" => $this->eid, "health" => $this->data["health"], "cause" => "respawn"));
console("[DEBUG] Player with EID ".$this->eid." \"".$this->username."\" spawned!", true, true, 2);
$this->entity = new Entity($this->eid, ENTITY_PLAYER, 0, $this->server);
$this->entity = $this->server->api->entity->add(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->setName($this->username);
$this->entity->setHealth($this->data["health"]);
$this->entity->data["clientID"] = $this->clientID;
$this->server->entities[$this->eid] = &$this->entity;
$this->server->trigger("onPlayerAdd", array(
"clientID" => $this->clientID,
"username" => $this->username,
"eid" => $this->eid,
"x" => $this->data["spawn"]["x"],
"y" => $this->data["spawn"]["y"],
"z" => $this->data["spawn"]["z"],
));
foreach($this->server->entities as $entity){
if($entity->eid !== $this->eid){
if($entity->class === ENTITY_PLAYER){
$this->eventHandler(array(
"clientID" => $entity->data["clientID"],
"username" => $entity->name,
"eid" => $entity->eid,
"x" => $entity->position["x"],
"y" => $entity->position["y"],
"z" => $entity->position["z"],
), "onPlayerAdd");
}else{
$this->dataPacket(MC_ADD_MOB, array(
"eid" => $entity->eid,
"type" => $entity->type,
"x" => $entity->position["x"],
"y" => $entity->position["y"],
"z" => $entity->position["z"],
));
}
}
}
$this->server->api->entity->spawnAll($this);
$this->server->api->entity->spawnToAll($this->eid);
$this->evid[] = $this->server->event("onTimeChange", array($this, "eventHandler"));
$this->evid[] = $this->server->event("onChat", array($this, "eventHandler"));
$this->evid[] = $this->server->event("onDeath", array($this, "eventHandler"));
$this->evid[] = $this->server->event("onEntityRemove", array($this, "eventHandler"));
$this->evid[] = $this->server->event("onEntityMove", array($this, "eventHandler"));
$this->evid[] = $this->server->event("onHealthChange", array($this, "eventHandler"));
$this->evid[] = $this->server->event("onHealthRegeneration", array($this, "eventHandler"));
$this->evid[] = $this->server->event("onAnimate", array($this, "eventHandler"));
$this->evid[] = $this->server->event("onTeleport", array($this, "eventHandler"));
$this->evid[] = $this->server->event("onBlockUpdate", array($this, "eventHandler"));
console("[DEBUG] Player with EID ".$this->eid." \"".$this->username."\" spawned!", true, true, 2);
$this->eventHandler($this->server->motd, "onChat");
if($this->MTU <= 548){
$this->eventHandler("Your connection is bad, you may experience lag and slow map loading.", "onChat");
@ -363,40 +328,22 @@ class Session{
), true);
}
');
console("[DEBUG] Chunk X ".$data["x"]." Z ".$data["z"]." requested", true, true, 2);
console("[INTERNAL] Chunk X ".$data["x"]." Z ".$data["z"]." requested", true, true, 3);
break;
case MC_PLACE_BLOCK:
var_dump($data);
break;
case MC_REMOVE_BLOCK:
console("[DEBUG] EID ".$this->eid." broke block at X ".$data["x"]." Y ".$data["y"]." Z ".$data["z"], true, true, 2);
$block = $this->server->api->level->getBlock($data["x"], $data["y"], $data["z"]);
if($block[0] === 0){
break;
}
$this->dataPacket(MC_ADD_ITEM_ENTITY, array(
"eid" => $this->server->eidCnt++,
"x" => $data["x"] + mt_rand(0, 100)/100,
"y" => $data["y"],
"z" => $data["z"] + mt_rand(0, 100)/100,
"block" => $block[0],
"meta" => $block[1],
"stack" => 1,
));
/*$this->send(0x84, array(
$this->counter[0],
0x00,
array(
"id" => MC_UPDATE_BLOCK,
"x" => $data["x"],
"y" => $data["y"],
"z" => $data["z"],
"block" => 56,
"meta" => 0,
),
));
++$this->counter[0];*/
$data["eid"] = $this->eid;
$this->server->api->level->handle($data, "onBlockBreak");
//$this->server->trigger("onBlockBreak", $data);
break;
case MC_INTERACT:
if($this->server->gamemode !== 1 and $this->server->difficulty > 0 and isset($this->server->entities[$data["target"]]) and Utils::distance($this->entity->position, $this->server->entities[$data["target"]]->position) <= 8){
if(isset($this->server->entities[$data["target"]]) and Utils::distance($this->entity->position, $this->server->entities[$data["target"]]->position) <= 8){
console("[DEBUG] EID ".$this->eid." attacked EID ".$data["target"], true, true, 2);
if($this->server->gamemode !== 1 and $this->server->difficulty > 0){
}
$this->server->trigger("onHealthChange", array("eid" => $data["target"], "health" => $this->server->entities[$data["target"]]->getHealth() - $this->server->difficulty, "cause" => $this->eid));
}
break;

View File

@ -28,6 +28,7 @@ the Free Software Foundation, either version 3 of the License, or
require_once(dirname(__FILE__)."/config.php");
require_once("common/functions.php");
//set_error_handler("error_handler");
$errors = 0;

View File

@ -120,6 +120,10 @@ function console($message, $EOL = true, $log = true, $level = 1){
}
}
function error_handler($errno, $errstr, $errfile, $errline){
console("[ERROR] A level ".$errno." error happened: \"$errstr\" in \"$errfile\" at line $errline", true, true, 0);
}
function logg($message, $name, $EOL = true, $level = 2, $close = false){
global $fpointers;
if((!defined("DEBUG") or DEBUG >= $level) and (!defined("LOG") or LOG === true)){