Updated to handle more things

This commit is contained in:
Shoghi Cervantes Pueyo 2012-12-07 19:47:07 +01:00
parent 02b2e980eb
commit c63240c7a8
12 changed files with 229 additions and 178 deletions

2
README
View File

@ -34,8 +34,10 @@ Currently a work in progress, and used to document http://www.wiki.vg/Pocket_Min
Current features of the server:
- Players can connect and move around the world
- Online list broadcast
- Configurable day/night cycle
- Health and position saving
- server.properties configuration file
- Whitelist and IP Ban files
- Survival & Creative
- Awesome features in server list!
- Multiple worlds and importing!

View File

@ -25,3 +25,60 @@ the Free Software Foundation, either version 3 of the License, or
*/
define("MAP_WIDTH", 256);
define("MAP_HEIGHT", 128);
class ChunkParser{
private $raw = b"";
var $sectorLenght = 4096; //16 * 16 * 16
var $chunkLenght = 86016; //21 * $sectorLenght
function __construct(){
}
public function loadFile($file){
if(!file_exists($file)){
return false;
}
$this->raw = file_get_contents($file);
$this->chunkLenght = $this->sectorLenght * ord($this->raw{0});
return true;
}
private function getOffsetPosition($X, $Z){
$data = substr($this->raw, ($X << 2) + ($Z << 7), 4); //$X * 4 + $Z * 128
return array(ord($data{0}), ord($data{1}), ord($data{2}), ord($data{3}));
}
private function getOffset($X, $Z){
$info = $this->getOffsetPosition($X, $Z);
return 4096 + (($info[1] * $info[0]) << 12) + (($info[2] * $data[0]) << 16);
}
public function getChunk($X, $Z, $header = true){
$X = (int) $X;
$Z = (int) $Z;
if($header === false){
$add = 4;
}else{
$add = 0;
}
return substr($this->raw, $this->getOffset($X, $Z) + $add, $this->chunkLenght - $add);
}
public function getColumn($X, $Z){
}
public function getBlock($x, $y, $z){
$x = (int) $x;
$y = (int) $y;
$z = (int) $z;
$X = $x >> 4;
$Z = $z >> 4;
$block = $this->getOffset($X, $Z) + 4 + (($x << 6) + $y + ($z << 10));
$meta = $this->getOffset($X, $Z) + 4 + (($x << 6) + $y + ($z << 10));
}
}

View File

@ -214,7 +214,32 @@ class CustomPacketHandler{
$this->data["eid"] = Utils::readInt($this->get(4));
}else{
$this->raw .= Utils::writeInt($this->data["eid"]);
}
}
break;
case MC_ADD_ITEM_ENTITY:
if($this->c === false){
$this->data["eid"] = Utils::readInt($this->get(4));
$this->data["block"] = Utils::readShort($this->get(2), false);
$this->data["stack"] = ord($this->get(1));
$this->data["meta"] = Utils::readShort($this->get(2), false);
$this->data["x"] = Utils::readFloat($this->get(4));
$this->data["y"] = Utils::readFloat($this->get(4));
$this->data["z"] = Utils::readFloat($this->get(4));
$this->data["yaw"] = Utils::readByte($this->get(1));
$this->data["pitch"] = Utils::readByte($this->get(1));
$this->data["roll"] = Utils::readByte($this->get(1));
}else{
$this->raw .= Utils::writeInt($this->data["eid"]);
$this->raw .= Utils::writeShort($this->data["block"]);
$this->raw .= chr($this->data["stack"]);
$this->raw .= Utils::writeShort($this->data["meta"]);
$this->raw .= Utils::writeFloat($this->data["x"]);
$this->raw .= Utils::writeFloat($this->data["y"]);
$this->raw .= Utils::writeFloat($this->data["z"]);
$this->raw .= Utils::writeByte($this->data["yaw"]);
$this->raw .= Utils::writeByte($this->data["pitch"]);
$this->raw .= Utils::writeByte($this->data["roll"]);
}
break;
case MC_MOVE_PLAYER:
if($this->c === false){
@ -246,6 +271,21 @@ class CustomPacketHandler{
$this->raw .= chr($this->data["face"]);
}
break;
case MC_UPDATE_BLOCK:
if($this->c === false){
$this->data["block"] = Utils::readShort($this->get(2));
$this->data["meta"] = Utils::readShort($this->get(2));
$this->data["x"] = Utils::readInt($this->get(4));
$this->data["y"] = Utils::readInt($this->get(4));
$this->data["z"] = Utils::readInt($this->get(4));
}else{
$this->raw .= Utils::writeShort($this->data["block"]);
$this->raw .= Utils::writeShort($this->data["meta"]);
$this->raw .= Utils::writeInt($this->data["x"]);
$this->raw .= Utils::writeInt($this->data["y"]);
$this->raw .= Utils::writeInt($this->data["z"]);
}
break;
case MC_REQUEST_CHUNK:
if($this->c === false){
$this->data["x"] = Utils::readInt($this->get(4));
@ -255,6 +295,20 @@ class CustomPacketHandler{
$this->raw .= Utils::writeInt($this->data["y"]);
}
break;
case MC_CHUNK_DATA:
if($this->c === false){
$this->data["x"] = Utils::readInt($this->get(4));
$this->data["z"] = Utils::readInt($this->get(4));
//$this->data["unknown1"] = $this->get(WTF);
$this->data["unknown1"] = Utils::readInt($this->get(4));
$this->data["unknown2"] = Utils::readInt($this->get(4));
//$this->data["unknown3"] = $this->get(WTF);
}else{
$this->raw .= Utils::writeInt($this->data["x"]);
$this->raw .= Utils::writeInt($this->data["y"]);
$this->raw .= $this->data["data"];
}
break;
case MC_PLAYER_EQUIPMENT:
if($this->c === false){
$this->data["eid"] = Utils::readInt($this->get(4));

View File

@ -1,155 +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 MapInterface{
protected $map, $floor, $column, $biome, $material, $biomes;
function __construct($client){
$this->client = $client;
$this->map = $this->client->mapParser;
$this->floor = method_exists($this->map, "getFloor");
$this->column = method_exists($this->map, "getColumn");
//$this->biome = method_exists($this->map, "getBiome");
//include("misc/materials.php");
//$this->material = $material;
//include("misc/biomes.php");
//$this->biomes = $biomes;
}
public function getBiome($x, $z){
$x = (int) $x;
$z = (int) $z;
if($this->biome === true){
return $this->map->getBiome($x, $z);
}else{
return 0;
}
}
/*public function getBiomeName($x, $z){
$biome = $this->getBiome($x, $z);
return isset($this->biomes[$biome]) ? $this->biomes[$biome]:"Unknown";
}*/
public function getBlockName($x, $y, $z){
$block = $this->getBlock($x, $y, $z);
return isset($this->material[$block[0]]) ? $this->material[$block[0]]:"Unknown";
}
public function getFloor($x, $z, $startY = -1){
$x = (int) $x;
$z = (int) $z;
if($this->floor === true){
$map = $this->map->getFloor($x, $z, $startY);
return $map;
}else{
$startY = ((int) $startY) > -1 ? ((int) $startY):HEIGHT_LIMIT - 1;
for($y = $startY; $y > 0; --$y){
$block = $this->getBlock($x, $y, $z);
if(!isset($this->material["nosolid"][$block[0]])){
break;
}
}
return array($y, $block[0], $block[1]);
}
}
public function changeBlock($x, $y, $z, $block, $metadata = 0){
$x = (int) $x;
$y = (int) $y;
$z = (int) $z;
return $this->map->changeBlock($x, $y, $z, $block, $metadata);
}
public function getBlock($x, $y, $z){
$x = (int) $x;
$y = (int) $y;
$z = (int) $z;
return $this->map->getBlock($x, $y, $z);
}
public function getColumn($x, $z){
$x = (int) $x;
$z = (int) $z;
if($this->column === true){
return $this->map->getColumn($x, $z);
}else{
$zone = $this->getZone($x,0,$z,$x,HEIGHT_LIMIT,$z);
$data = array();
foreach($zone as $x => $a){
foreach($a as $y => $b){
foreach($b as $z => $block){
$data[$y] = $block;
}
}
}
return $data;
}
}
public function getEllipse($x, $y, $z, $rX = 4, $rZ = 4, $rY = 4){
$x = (int) $x;
$y = (int) $y;
$z = (int) $z;
$rY = abs((int) $rX);
$rY = abs((int) $rZ);
$rY = abs((int) $rY);
return $this->getZone($x-$rX,max(0,$y-$rY),$z-$rZ,$x+$rX,$y+$rY,$z+$rZ);
}
public function getSphere($x, $y, $z, $r=4){
$x = (int) $x;
$y = (int) $y;
$z = (int) $z;
$r = abs((int) $r);
return $this->getZone($x-$r,max(0,$y-$r),$z-$r,$x+$r,$y+$r,$z+$r);
}
public function getZone($x1, $y1, $z1, $x2, $y2, $z2){
$x1 = (int) $x1;
$y1 = (int) $y1;
$z1 = (int) $z1;
$x2 = (int) $x2;
$y2 = (int) $y2;
$z2 = (int) $z2;
if($x1>$x2 or $y1>$y2 or $z1>$z2){
return array();
}
$blocks = array();
for($x=$x1;$x<=$x2;++$x){
$blocks[$x] = array();
for($z=$z1;$z<=$z2;++$z){
$blocks[$x][$z] = array();
for($y=$y1;$y<=$y2;++$y){
$blocks[$x][$z][$y] = $this->getBlock($x,$y,$z);
}
}
}
return $blocks;
}
}

View File

@ -34,8 +34,8 @@ class NBT {
}
switch(basename($filename, ".dat")){
case "level":
$version = Utils::readInt(strrev(fread($fp, 4)));
$lenght = Utils::readInt(strrev(fread($fp, 4)));
$version = Utils::readLInt(fread($fp, 4));
$lenght = Utils::readLInt(fread($fp, 4));
break;
case "entities":
fread($fp, 12);
@ -65,15 +65,15 @@ class NBT {
case self::TAG_BYTE: // Signed byte (8 bit)
return Utils::readByte(fread($fp, 1));
case self::TAG_SHORT: // Signed short (16 bit, big endian)
return Utils::readShort(strrev(fread($fp, 2)));
return Utils::readLShort(fread($fp, 2));
case self::TAG_INT: // Signed integer (32 bit, big endian)
return Utils::readInt(strrev(fread($fp, 4)));
return Utils::readLInt(fread($fp, 4));
case self::TAG_LONG: // Signed long (64 bit, big endian)
return Utils::readLong(strrev(fread($fp, 8)));
return Utils::readLLong(fread($fp, 8));
case self::TAG_FLOAT: // Floating point value (32 bit, big endian, IEEE 754-2008)
return Utils::readFloat(strrev(fread($fp, 4)));
return Utils::readLFloat(fread($fp, 4));
case self::TAG_DOUBLE: // Double value (64 bit, big endian, IEEE 754-2008)
return Utils::readDouble(strrev(fread($fp, 8)));
return Utils::readLDouble(fread($fp, 8));
case self::TAG_BYTE_ARRAY: // Byte array
$arrayLength = $this->readType($fp, self::TAG_INT);
$array = array();

View File

@ -28,7 +28,7 @@ the Free Software Foundation, either version 3 of the License, or
require_once("classes/Session.class.php");
class PocketMinecraftServer{
var $seed, $protocol, $gamemode, $name, $maxClients, $clients, $eidCnt, $custom, $description, $motd, $timePerSecond, $responses, $spawn, $entities, $mapDir, $mapParser, $map, $level, $tileEntities;
var $seed, $protocol, $gamemode, $name, $maxClients, $clients, $eidCnt, $custom, $description, $motd, $timePerSecond, $responses, $spawn, $entities, $mapDir, $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;
@ -39,7 +39,6 @@ class PocketMinecraftServer{
$this->version = (int) $version;
$this->name = $name;
$this->mapDir = false;
$this->mapParser = false;
$this->map = false;
$this->level = false;
$this->tileEntities = array();
@ -127,6 +126,7 @@ class PocketMinecraftServer{
public function close($reason = "stop"){
$this->chat(false, "Stopping server...");
$this->save();
$this->stop = true;
$this->trigger("onClose");
}
@ -163,10 +163,20 @@ class PocketMinecraftServer{
private function loadMap(){
$this->level = unserialize(file_get_contents($this->mapDir."level.dat"));
console("[INFO] Map: ".$this->level["LevelName"]);
$this->seed = $this->level["RandomSeed"];
$this->time = (int) $this->level["Time"];
$this->level["Time"] = &$this->time;
console("[INFO] Time: ".$this->time);
console("[INFO] Seed: ".$this->seed);
console("[INFO] Gamemode: ".($this->gamemode === 0 ? "survival":"creative"));
console("[DEBUG] Loading entities...");
console("[INFO] Loading map...");
$this->map = new ChunkParser();
if(!$this->map->loadFile($this->mapDir."chunks.dat")){
console("[ERROR] Couldn't load the map \"".$this->level["LevelName"]."\"!", true, true, 0);
$this->map = false;
}else{
}
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);
@ -175,6 +185,11 @@ class PocketMinecraftServer{
++$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");');
}
public function save(){
file_put_contents($this->mapDir."level.dat", serialize($this->level));
}
public function start(){

View File

@ -266,6 +266,9 @@ class Session{
++$this->counter[0];
break;
case MC_READY:
if(is_object($this->entity)){
break;
}
$this->server->trigger("onHealthChange", array("eid" => $this->eid, "health" => $this->data["health"]));
console("[DEBUG] Player with EID ".$this->eid." \"".$this->username."\" spawned!", true, true, 2);
$this->entity = new Entity($this->eid, ENTITY_PLAYER, 0, $this->server);
@ -287,12 +290,33 @@ class Session{
$this->server->trigger("onChat", $this->username." joined the game");
break;
case MC_MOVE_PLAYER:
$this->entity->setPosition($data["x"], $data["y"], $data["z"], $data["x"], $data["yaw"], $data["pitch"]);
$this->entity->setPosition($data["x"], $data["y"], $data["z"], $data["yaw"], $data["pitch"]);
$this->server->trigger("onPlayerMove", $this->eid);
break;
case MC_PLAYER_EQUIPMENT:
console("[DEBUG] EID ".$this->eid." has now ".$data["block"]." with metadata ".$data["meta"]." in their hands!", true, true, 2);
break;
case MC_REQUEST_CHUNK:
console("[DEBUG] Chunk X ".$data["x"]." Z ".$data["z"]." requested", true, true, 2);
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);
$this->send(0x84, array(
$this->counter[0],
0x00,
array(
"id" => MC_ADD_ITEM_ENTITY,
"eid" => $this->server->eidCnt++,
"x" => $data["x"],
"y" => $data["y"],
"z" => $data["z"],
"block" => 1,
"meta" => 0,
"stack" => 1,
),
));
++$this->counter[0];
break;
case MC_RESPAWN:
$this->server->trigger("onHealthChange", array("eid" => $this->eid, "health" => 20));
$this->entity->setPosition($data["x"], $data["y"], $data["z"], $data["x"], 0, 0);

View File

@ -252,14 +252,6 @@ class Utils{
return pack("H*" , $hex);
}
public static function readString($str){
return preg_replace('/\x00(.)/s', '$1', $str);
}
public static function writeString($str){
return preg_replace('/(.)/s', "\x00$1", $str);
}
public static function readBool($b){
return Utils::readByte($b, false) === 0 ? false:true;
}
@ -300,6 +292,21 @@ class Utils{
}
return pack("n", $value);
}
public static function readLShort($str, $signed = true){
list(,$unpacked) = unpack("v", $str);
if($unpacked > 0x7fff and $signed === true){
$unpacked -= 0x10000; // Convert unsigned short to signed short
}
return $unpacked;
}
public static function writeLShort($value){
if($value < 0){
$value += 0x10000;
}
return pack("v", $value);
}
public static function readInt($str){
list(,$unpacked) = unpack("N", $str);
@ -316,6 +323,21 @@ class Utils{
return pack("N", $value);
}
public static function readLInt($str){
list(,$unpacked) = unpack("V", $str);
if($unpacked >= 2147483648){
$unpacked -= 4294967296;
}
return (int) $unpacked;
}
public static function writeLInt($value){
if($value < 0){
$value += 0x100000000;
}
return pack("V", $value);
}
public static function readFloat($str){
list(,$value) = ENDIANNESS === BIG_ENDIAN ? unpack("f", $str):unpack("f", strrev($str));
return $value;
@ -325,6 +347,15 @@ class Utils{
return ENDIANNESS === BIG_ENDIAN ? pack("f", $value):strrev(pack("f", $value));
}
public static function readLFloat($str){
list(,$value) = ENDIANNESS === BIG_ENDIAN ? unpack("f", strrev($str)):unpack("f", $str);
return $value;
}
public static function writeLFloat($value){
return ENDIANNESS === BIG_ENDIAN ? strrev(pack("f", $value)):pack("f", $value);
}
public static function printFloat($value){
return preg_replace("/(\.\d+?)0+$/", "$1", sprintf("%F", $value));
}
@ -337,6 +368,15 @@ class Utils{
public static function writeDouble($value){
return ENDIANNESS === BIG_ENDIAN ? pack("d", $value):strrev(pack("d", $value));
}
public static function readLDouble($str){
list(,$value) = ENDIANNESS === BIG_ENDIAN ? unpack("d", strrev($str)):unpack("d", $str);
return $value;
}
public static function writeLDouble($value){
return ENDIANNESS === BIG_ENDIAN ? strrev(pack("d", $value)):pack("d", $value);
}
public static function readLong($str){
$long = new Math_BigInteger($str, -256);
@ -346,6 +386,16 @@ class Utils{
public static function writeLong($value){
$long = new Math_BigInteger($value, -10);
return str_pad($long->toBytes(true), 8, "\x00", STR_PAD_LEFT);
}
public static function readLLong($str){
$long = new Math_BigInteger(strrev($str), -256);
return $long->toString();
}
public static function writeLLong($value){
$long = new Math_BigInteger($value, -10);
return strrev(str_pad($long->toBytes(true), 8, "\x00", STR_PAD_LEFT));
}
}

View File

@ -8,7 +8,7 @@ white-list=false
debug=1
max-players=20
server-type=normal
time-per-second=10
time-per-second=20
gamemode=1
seed=false
level-name=false

View File

@ -71,7 +71,6 @@ require_once("classes/Utils.class.php");
require_once("classes/UDPSocket.class.php");
require_once("classes/Packet.class.php");
require_once("classes/Entity.class.php");
require_once("classes/MapInterface.class.php");
require_once("classes/ChunkParser.class.php");
require_once("classes/NBT.class.php");
require_once("classes/SerializedPacketHandler.class.php");

View File

@ -47,13 +47,16 @@ define("MC_START_GAME", 0x87);
define("MC_ADD_PLAYER", 0x89);
define("MC_REMOVE_ENTITY", 0x8d);
define("MC_ADD_ITEM_ENTITY", 0x8e);
define("MC_MOVE_ENTITY_POSROT", 0x93);
define("MC_MOVE_PLAYER", 0x94);
define("MC_PLACE_BLOCK", 0x95);
define("MC_REMOVE_BLOCK", 0x96);
define("MC_UPDATE_BLOCK", 0x97);
define("MC_REQUEST_CHUNK", 0x9d);
define("MC_CHUNK_DATA", 0x9e);
define("MC_PLAYER_EQUIPMENT", 0x9f);

View File

@ -47,6 +47,7 @@ $dataName = array(
MC_ADD_PLAYER => "AddPlayer",
MC_REMOVE_ENTITY => "RemoveEntity",
MC_ADD_ITEM_ENTITY => "AddItemEntity",
MC_MOVE_ENTITY_POSROT => "MoveEntity_PosRot",
MC_MOVE_PLAYER => "MovePlayer",
@ -54,6 +55,7 @@ $dataName = array(
MC_REMOVE_BLOCK => "RemoveBlock",
MC_REQUEST_CHUNK => "RequestChunk",
MC_CHUNK_DATA => "ChunkData",
MC_PLAYER_EQUIPMENT => "PlayerEquipment",