From ea1725ed57930dbf5e0ea631fa997d3bbca576cf Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Pueyo Date: Fri, 7 Dec 2012 02:23:57 +0100 Subject: [PATCH] SQLite database, Entities, position, health --- .gitignore | 1 + classes/CustomPacketHandler.class.php | 42 +++++- classes/Entity.class.php | 157 +++++++++++++++++++++++ classes/PocketMinecraftServer.class.php | 158 +++++++++++++++++------ classes/Session.class.php | 162 +++++++++++++++++++----- classes/Utils.class.php | 9 ++ LICENSE => common/LICENSE | 0 README => common/README | 0 common/config.php | 2 +- common/default.properties | 1 + common/dependencies.php | 1 + client.php => example/client.php | 0 pstruct/5.php | 2 + pstruct/dataName.php | 2 + server.php | 12 +- 15 files changed, 472 insertions(+), 77 deletions(-) create mode 100644 classes/Entity.class.php rename LICENSE => common/LICENSE (100%) rename README => common/README (100%) rename client.php => example/client.php (100%) diff --git a/.gitignore b/.gitignore index 9171a6910..72426657a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +data/* *.log *.bat server.properties diff --git a/classes/CustomPacketHandler.class.php b/classes/CustomPacketHandler.class.php index 8dc45e2ef..b7d6ecc11 100644 --- a/classes/CustomPacketHandler.class.php +++ b/classes/CustomPacketHandler.class.php @@ -184,6 +184,31 @@ class CustomPacketHandler{ $this->raw .= Utils::writeFloat($this->data["z"]); } break; + case MC_ADD_PLAYER: + if($this->c === false){ + $this->data["clientID"] = Utils::readLong($this->get(8)); + $this->data["username"] = $this->get(Utils::readShort($this->get(2), false)); + $this->data["eid"] = Utils::readInt($this->get(4)); + $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::readFloat($this->get(4)); + $this->data["pitch"] = Utils::readFloat($this->get(4)); + $this->data["block"] = Utils::readShort($this->get(2), false); + $this->data["meta"] = Utils::readShort($this->get(2), false); + }else{ + $this->raw .= Utils::writeLong($this->data["clientID"]); + $this->raw .= Utils::writeShort(strlen($this->data["username"])).$this->data["username"]; + $this->raw .= Utils::writeInt($this->data["eid"]); + $this->raw .= Utils::writeFloat($this->data["x"]); + $this->raw .= Utils::writeFloat($this->data["y"]); + $this->raw .= Utils::writeFloat($this->data["z"]); + $this->raw .= Utils::writeFloat($this->data["yaw"]); + $this->raw .= Utils::writeFloat($this->data["pitch"]); + $this->raw .= Utils::writeShort($this->data["block"]); + $this->raw .= Utils::writeShort($this->data["meta"]); + } + break; case MC_REMOVE_ENTITY: if($this->c === false){ $this->data["eid"] = Utils::readInt($this->get(4)); @@ -233,8 +258,8 @@ class CustomPacketHandler{ case MC_PLAYER_EQUIPMENT: if($this->c === false){ $this->data["eid"] = Utils::readInt($this->get(4)); - $this->data["block"] = Utils::readShort($this->get(2), true); - $this->data["meta"] = Utils::readShort($this->get(2), true); + $this->data["block"] = Utils::readShort($this->get(2), false); + $this->data["meta"] = Utils::readShort($this->get(2), false); }else{ $this->raw .= Utils::writeInt($this->data["eid"]); $this->raw .= Utils::writeShort($this->data["block"]); @@ -246,6 +271,19 @@ class CustomPacketHandler{ $this->data["health"] = ord($this->get(1)); }else{ $this->raw .= chr($this->data["health"]); + } + break; + case MC_RESPAWN: + if($this->c === false){ + $this->data["eid"] = Utils::readInt($this->get(4)); + $this->data["x"] = Utils::readFloat($this->get(4)); + $this->data["y"] = Utils::readFloat($this->get(4)); + $this->data["z"] = Utils::readFloat($this->get(4)); + }else{ + $this->raw .= Utils::writeInt($this->data["eid"]); + $this->raw .= Utils::writeFloat($this->data["x"]); + $this->raw .= Utils::writeFloat($this->data["y"]); + $this->raw .= Utils::writeFloat($this->data["z"]); } break; case MC_CLIENT_MESSAGE: diff --git a/classes/Entity.class.php b/classes/Entity.class.php new file mode 100644 index 000000000..81d2b398d --- /dev/null +++ b/classes/Entity.class.php @@ -0,0 +1,157 @@ + player + $this->server = $server; + $this->eid = (int) $eid; + $this->type = (int) $type; + $this->class = (int) $class; + $this->attach = false; + $this->status = 0; + $this->health = 20; + $this->dead = false; + $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"); + switch($this->class){ + case ENTITY_PLAYER: + case ENTITY_ITEM: + break; + + case ENTITY_MOB: + $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)); + break; + }*/ + } + + public function __destruct(){ + $this->server->query("DELETE FROM entities WHERE EID = ".$this->eid.";"); + $this->server->trigger("onEntityRemove", $this->eid); + } + + public function getEID(){ + return $this->eid; + } + + public function getName(){ + return $this->name; + } + + public function setName($name){ + $this->name = $name; + $this->server->query("UPDATE entities SET name = '".str_replace("'", "", $this->name)."' WHERE EID = ".$this->eid.";"); + } + + 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.";"); + } + + 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.";"); + } + + 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.";"); + } + + 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.";"); + return true; + } + + public function getPosition($round = false){ + return !isset($this->position) ? false:($round === true ? array_map("floor", $this->position):$this->position); + } + + public function setHealth($health){ + $this->health = (int) $health; + $this->server->query("UPDATE entities SET health = ".$this->health." WHERE EID = ".$this->eid.";"); + } + + public function getHealth(){ + return $this->health; + } + +} + +?> \ No newline at end of file diff --git a/classes/PocketMinecraftServer.class.php b/classes/PocketMinecraftServer.class.php index e6e2fe163..450363dff 100644 --- a/classes/PocketMinecraftServer.class.php +++ b/classes/PocketMinecraftServer.class.php @@ -28,11 +28,14 @@ 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; - protected $interface, $entities, $player, $cnt, $events, $version, $serverType; + var $seed, $protocol, $gamemode, $name, $maxClients, $clients, $eidCnt, $custom, $description, $motd, $timePerSecond, $responses, $spawn, $entities; + 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->gamemode = (int) $gamemode; $this->port = (int) $port; + console("[INFO] Starting Minecraft PE Server at *:".$this->port); + console("[INFO] Loading database..."); + $this->startDatabase(); + $this->gamemode = (int) $gamemode; $this->version = (int) $version; $this->name = $name; $this->custom = array(); @@ -44,23 +47,16 @@ class PocketMinecraftServer{ $this->bannedIPs = array(); $this->motd = "Welcome to ".$name; $this->serverID = $serverID === false ? Utils::readLong(Utils::getRandomBytes(8)):$serverID; - $this->seed = $seed === false ? Utils::readInt(Utils::getRandomBytes(4)):((int) $seed); - $this->events = array("disabled" => array()); - $this->actions = array(); + $this->seed = $seed === false ? Utils::readInt(Utils::getRandomBytes(4)):$seed; $this->clients = array(); $this->protocol = (int) $protocol; + $this->spawn = array("x" => 128.5,"y" => 100,"z" => 128.5); $this->time = 0; - //$this->event("onTick", "onTick", true); - $this->event("onChat", "eventHandler", true); - $this->action(1000000, '$this->time += 10;$this->trigger("onTimeChange", $this->time);'); - $this->action(1000000 * 60, '$this->reloadConfig();'); - $this->action(1000000 * 60 * 10, '$this->custom = array();'); + $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); - console("[INFO] Starting Minecraft PE Server at *:".$this->port); - $this->action(1000000 * 3 * 60, '$this->chat(false, "This server uses Pocket-Minecraft-PHP");'); - sleep(2); - $this->action(1000000 * 3 * 60, '$this->chat(false, "Check it at http://bit.ly/RE7uaW");'); $this->reloadConfig(); console("[INFO] Server Name: ".$this->name); console("[INFO] Server GUID: ".$this->serverID); @@ -69,6 +65,37 @@ class PocketMinecraftServer{ console("[INFO] Gamemode: ".($this->gamemode === 0 ? "survival":"creative")); console("[INFO] Max Clients: ".$this->maxClients); $this->stop = false; + console("[INFO] Server started!"); + } + + public function loadEvents(){ + $this->events = array("disabled" => array()); + + $this->event("onChat", "eventHandler", true); + + $this->action(1000000, '$this->time += $this->timePerSecond;$this->trigger("onTimeChange", $this->time);'); + $this->action(1000000 * 60, '$this->reloadConfig();'); + $this->action(1000000 * 60 * 10, '$this->custom = array();'); + $this->action(1000000 * 80, '$list = ""; foreach($this->clients as $c){$list .= ", ".$c->username;}$this->chat(false, count($this->clients)."/".$this->maxClients." online: ".substr($list, 2));'); + $this->action(1000000 * 3 * 60, '$this->chat(false, "This server uses Pocket-Minecraft-PHP");'); + $this->action(1000000 * 3 * 60, '$this->chat(false, "Check it at http://bit.ly/RE7uaW");'); + $this->action(1000000 * 75, '$this->debugInfo(true);'); + } + + public function startDatabase(){ + $this->database = new SQLite3(":memory:"); + $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, disabled INTEGER);"); + } + + public function query($sql, $fetch = false){ + $result = $this->database->query($sql) or console("[ERROR] [SQL Error] ".$this->database->lastErrorMsg().". Query: ".$sql, true, true, 0); + if($fetch === true and ($result !== false and $result !== true)){ + $result = $result->fetchArray(SQLITE3_ASSOC); + } + return $result; } public function reloadConfig(){ @@ -78,6 +105,23 @@ class PocketMinecraftServer{ $this->bannedIPs = explode("\n", str_replace(array(" ","\t","\r"), "", file_get_contents(FILE_PATH."banned-ips.txt"))); } + public function debugInfo($console = false){ + $info = array(); + $info["memory_usage"] = round((memory_get_usage(true) / 1024) / 1024, 2)."MB"; + $info["memory_peak_usage"] = round((memory_get_peak_usage(true) / 1024) / 1024, 2)."MB"; + $info["entities"] = $this->query("SELECT count(EID) as count FROM entities;", true); + $info["entities"] = $info["entities"]["count"]; + $info["events"] = $this->query("SELECT count(ID) as count FROM events;", true); + $info["events"] = $info["events"]["count"]; + $info["actions"] = $this->query("SELECT count(ID) as count FROM actions;", true); + $info["actions"] = $info["actions"]["count"]; + $info["garbage"] = gc_collect_cycles(); + if($console === true){ + console("[DEBUG] Memory usage: ".$info["memory_usage"]." (Peak ".$info["memory_peak_usage"]."), Entities: ".$info["entities"].", Events: ".$info["events"].", Actions: ".$info["actions"].", Garbage: ".$info["garbage"], true, true, 2); + } + return $info; + } + public function close($reason = "stop"){ $this->chat(false, "Stopping server..."); $this->stop = true; @@ -113,28 +157,20 @@ class PocketMinecraftServer{ } } - public function action($microseconds, $code){ - $this->actions[] = array($microseconds / 1000000, microtime(true), $code); - console("[INTERNAL] Attached to action ".$microseconds, true, true, 3); - } - public function start(){ declare(ticks=15); - register_tick_function(array($this, "tickerFunction")); - $this->action(50000, '$this->trigger("onTick", $time);'); + register_tick_function(array($this, "tick")); + $this->event("onTick", "tickerFunction", true); $this->event("onReceivedPacket", "packetHandler", true); register_shutdown_function(array($this, "close")); $this->process(); } - public function tickerFunction(){ - //actions that repeat every x time will go here + public function tick(){ $time = microtime(true); - foreach($this->actions as $id => $action){ - if($action[1] <= ($time - $action[0])){ - $this->actions[$id][1] = $time; - eval($action[2]); - } + if($this->lastTick <= ($time - 0.05)){ + $this->lastTick = $time; + $this->trigger("onTick", $time); } } @@ -201,9 +237,8 @@ class PocketMinecraftServer{ $port = $data[2]; $MTU = $data[3]; $clientID = $data[4]; - $EID = $this->eidCnt++; - $this->clients[$CID] = new Session($this, $clientID, $EID, $packet["ip"], $packet["port"]); - $entities[$EID] = &$this->clients[$CID]; + $eid = $this->eidCnt++; + $this->clients[$CID] = new Session($this, $clientID, $eid, $packet["ip"], $packet["port"]); $this->clients[$CID]->handle(0x07, $data); break; } @@ -229,25 +264,61 @@ class PocketMinecraftServer{ } public function trigger($event, $data = ""){ + $events = $this->query("SELECT ID FROM events WHERE eventName = '".$event."' AND disabled = 0;"); + if($events === false or $events === true){ + return; + } console("[INTERNAL] Event ". $event, true, true, 3); - if(isset($this->events[$event]) and !isset($this->events["disabled"][$event])){ - foreach($this->events[$event] as $evid => $ev){ - if(isset($ev[1]) and ($ev[1] === true or is_object($ev[1]))){ - $this->responses[$evid] = call_user_func(array(($ev[1] === true ? $this:$ev[1]), $ev[0]), $data, $event, $this); - }else{ - $this->responses[$evid] = call_user_func($ev[0], $data, $event, $this); - } + 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); } - } + } } + + public function response($eid){ + if(isset($this->responses[$eid])){ + $res = $this->responses[$eid]; + unset($this->responses[$eid]); + return $res; + } + return false; + } + + public function action($microseconds, $code, $repeat = true){ + $this->query("INSERT INTO actions (interval, last, code, repeat) VALUES(".($microseconds / 1000000).", ".microtime(true).", '".str_replace("'", "\\'", $code)."', ".($repeat === true ? 1:0).");"); + console("[INTERNAL] Attached to action ".$microseconds, true, true, 3); + } + + public function tickerFunction(){ + //actions that repeat every x time will go here + $time = microtime(true); + $actions = $this->query("SELECT ID,code,repeat FROM actions WHERE last <= (".$time." - interval);"); + if($actions === false or $actions === true){ + return; + } + while($action = $actions->fetchArray(SQLITE3_ASSOC)){ + eval($action["code"]); + if($action["repeat"] === 0){ + $this->query("DELETE FROM actions WHERE ID = ".$action["ID"].";"); + } + } + $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){ @@ -255,18 +326,23 @@ class PocketMinecraftServer{ if(!isset($this->events[$event])){ $this->events[$event] = array(); } + $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; } public function deleteEvent($event, $id = -1){ + $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)."';"); } } } diff --git a/classes/Session.class.php b/classes/Session.class.php index 5c3529017..b45ece243 100644 --- a/classes/Session.class.php +++ b/classes/Session.class.php @@ -28,13 +28,15 @@ the Free Software Foundation, either version 3 of the License, or class Session{ protected $server, $serverID, $timeout, $connected, $evid; - var $clientID, $ip, $port, $counter, $username, $EID; - function __construct($server, $clientID, $EID, $ip, $port){ + var $clientID, $ip, $port, $counter, $username, $eid, $data; + function __construct($server, $clientID, $eid, $ip, $port){ $this->server = $server; $this->clientID = $clientID; $this->CID = $this->server->clientID($ip, $port); - $this->EID = $EID; + $this->eid = $eid; + $this->data = array(); $this->ip = $ip; + $this->entity = false; $this->port = $port; $this->serverID =& $this->server->serverID; $this->timeout = microtime(true) + 25; @@ -52,6 +54,21 @@ class Session{ } } + function __destruct(){ + //$this->close("destruct"); + } + + 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"], + ); + } + file_put_contents(FILE_PATH."data/players/".str_replace("/", "", $this->username).".dat", serialize($this->data)); + } + public function close($reason = "server stop", $msg = true){ foreach($this->evid as $ev){ $this->server->deleteEvent($ev[0], $ev[1]); @@ -60,12 +77,69 @@ class Session{ if($msg === true){ $this->server->trigger("onChat", $this->username." left the game"); } + $this->save(); + if(is_object($this->entity)){ + $this->entity->__destruct(); + } console("[INFO] Session with ".$this->ip.":".$this->port." closed due to ".$reason); + unset($this->server->entities[$this->eid]); unset($this->server->clients[$this->CID]); } public function eventHandler($data, $event){ switch($event){ + case "onHealthChange": + if($data["eid"] === $this->eid){ + $this->send(0x84, array( + $this->counter[0], + 0x00, + array( + "id" => MC_SET_HEALTH, + "health" => $data["health"], + ), + )); + $this->data["health"] = $data["health"]; + if(is_object($this->entity)){ + $this->entity->setHealth($data["health"]); + } + } + break; + case "onPlayerAdd": + if($data["eid"] === $this->eid){ + break; + } + $this->send(0x84, array( + $this->counter[0], + 0x00, + array( + "id" => MC_ADD_PLAYER, + "clientID" => $data["clientID"], + "username" => $data["username"], + "eid" => $data["eid"], + "x" => $data["x"], + "y" => $data["y"], + "z" => $data["z"], + "yaw" => $data["yaw"], + "pitch" => $data["pitch"], + "block" => $data["block"], + "meta" => $data["meta"], + ), + )); + break; + case "onEntityRemove": + if($data === $this->eid){ + $this->close("despawn"); + }else{ + $this->send(0x84, array( + $this->counter[0], + 0x00, + array( + "id" => MC_ENTITY_REMOVE, + "eid" => $data, + ), + )); + } + break; case "onTimeChange": $this->send(0x84, array( $this->counter[0], @@ -133,14 +207,39 @@ class Session{ break; case MC_LOGIN: - $this->username = $data["username"]; + $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"); + } + } if($this->server->whitelist !== false and !in_array($this->username, $this->server->whitelist)){ $this->close("\"".$this->username."\" not being on white-list", false); break; } console("[INFO] Player \"".$this->username."\" connected from ".$this->ip.":".$this->port); + if(!file_exists(FILE_PATH."data/players/".$this->username.".dat")){ + console("[NOTICE] Player data not found for \"".$this->username."\", creating new"); + $this->data = array( + "spawn" => array( + "x" => $this->server->spawn["x"], + "y" => $this->server->spawn["y"], + "z" => $this->server->spawn["z"], + ), + "health" => 20, + "lastIP" => $this->ip, + "lastID" => $this->clientID, + ); + }else{ + $this->data = unserialize(file_get_contents(FILE_PATH."data/players/".str_replace("/", "", $this->username).".dat")); + $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("onPlayerAdd", $this->server->event("onPlayerAdd", array($this, "eventHandler"))); + $this->evid[] = array("onEntityDespawn", $this->server->event("onEntityDespawn", array($this, "eventHandler"))); + $this->evid[] = array("onHealthChange", $this->server->event("onHealthChange", array($this, "eventHandler"))); $this->send(0x84, array( $this->counter[0], 0x00, @@ -156,44 +255,47 @@ class Session{ array( "id" => MC_START_GAME, "seed" => $this->server->seed, - "x" => 128.5, - "y" => 100, - "z" => 128.5, + "x" => $this->data["spawn"]["x"], + "y" => $this->data["spawn"]["y"], + "z" => $this->data["spawn"]["z"], "unknown1" => 0, "gamemode" => $this->server->gamemode, - "eid" => $this->EID, + "eid" => $this->eid, ), )); ++$this->counter[0]; break; case MC_READY: - $this->send(0x84, array( - $this->counter[0], - 0x00, - array( - "id" => MC_SET_TIME, - "time" => $this->server->time, - ), - )); - console("[DEBUG] Player with EID ".$this->EID." \"".$this->username."\" spawned!", true, true, 2); - $this->server->trigger("onChat", $this->username." joined the game"); + $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); + $this->entity->setName($this->username); + $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"], + "yaw" => $this->data["spawn"]["yaw"], + "pitch" => $this->data["spawn"]["pitch"], + "block" => 0, + "meta" => 0, + ));*/ $this->eventHandler($this->server->motd, "onChat"); + $this->server->trigger("onChat", $this->username." joined the game"); break; case MC_MOVE_PLAYER: - console("[DEBUG] EID ".$this->EID." moved: X ".$data["x"].", Y ".$data["y"].", Z ".$data["z"].", Pitch ".$data["pitch"].", Yaw ".$data["yaw"], true, true, 2); + $this->entity->setPosition($data["x"], $data["y"], $data["z"], $data["x"], $data["yaw"], $data["pitch"]); + $this->server->trigger("onPlayerMove", $this->eid); break; case MC_PLAYER_EQUIPMENT: - $this->send(0x84, array( - $this->counter[0], - 0x00, - array( - "id" => MC_PLAYER_EQUIPMENT, - "eid" => 0, - "block" => 323, - "meta" => 0, - ), - )); - console("[DEBUG] EID ".$this->EID." has now ".$data["block"]." with metadata ".$data["meta"]." in their hands!", true, true, 2); + console("[DEBUG] EID ".$this->eid." has now ".$data["block"]." with metadata ".$data["meta"]." in their hands!", true, true, 2); + 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); break; } diff --git a/classes/Utils.class.php b/classes/Utils.class.php index 8628b6c97..0db81e1c5 100644 --- a/classes/Utils.class.php +++ b/classes/Utils.class.php @@ -38,6 +38,15 @@ console("[DEBUG] Endianness: ".(ENDIANNESS === LITTLE_ENDIAN ? "Little Endian":" class Utils{ + /*public static function hashCode($str){ + $h = 0; + $len = strlen($str); + for($i = 0; $i < $len; ++$i) { + $h = 31 * $h + $str[$i]; + } + return $h; + }*/ + public static function getOS(){ $uname = strtoupper(php_uname("s")); if(strpos($uname, "WIN") !== false){ diff --git a/LICENSE b/common/LICENSE similarity index 100% rename from LICENSE rename to common/LICENSE diff --git a/README b/common/README similarity index 100% rename from README rename to common/README diff --git a/common/config.php b/common/config.php index bd6b8b4a0..d6891130d 100644 --- a/common/config.php +++ b/common/config.php @@ -31,7 +31,7 @@ ini_set("display_errors", 1); ini_set('default_charset', 'utf-8'); define("FILE_PATH", dirname(__FILE__)."/../"); set_include_path(get_include_path() . PATH_SEPARATOR . FILE_PATH . PATH_SEPARATOR . FILE_PATH . "/classes/"); -ini_set("memory_limit", "512M"); +ini_set("memory_limit", "1024M"); define("CURRENT_PROTOCOL", 5); define("CURRENT_VERSION", 1); define("LOG", true); diff --git a/common/default.properties b/common/default.properties index a661d36e5..1da7adcbb 100644 --- a/common/default.properties +++ b/common/default.properties @@ -9,6 +9,7 @@ seed=false server-id=false server-type=normal max-players=20 +time-per-second=10 white-list=false debug=1 spawn=128.5;100;128.5 diff --git a/common/dependencies.php b/common/dependencies.php index 9e2ed4470..de718675e 100644 --- a/common/dependencies.php +++ b/common/dependencies.php @@ -70,6 +70,7 @@ if($errors > 0){ 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/SerializedPacketHandler.class.php"); require_once("classes/CustomPacketHandler.class.php"); require_once("classes/MinecraftInterface.class.php"); diff --git a/client.php b/example/client.php similarity index 100% rename from client.php rename to example/client.php diff --git a/pstruct/5.php b/pstruct/5.php index d5ada1af9..db02bd596 100644 --- a/pstruct/5.php +++ b/pstruct/5.php @@ -44,6 +44,8 @@ define("MC_CHAT", 0x85); define("MC_SET_TIME", 0x86); define("MC_START_GAME", 0x87); +define("MC_ADD_PLAYER", 0x89); + define("MC_REMOVE_ENTITY", 0x8d); define("MC_MOVE_ENTITY_POSROT", 0x93); diff --git a/pstruct/dataName.php b/pstruct/dataName.php index 7445e8400..a20cf75bd 100644 --- a/pstruct/dataName.php +++ b/pstruct/dataName.php @@ -44,6 +44,8 @@ $dataName = array( MC_SET_TIME => "SetTime", MC_START_GAME => "StartGame", + MC_ADD_PLAYER => "AddPlayer", + MC_REMOVE_ENTITY => "RemoveEntity", MC_MOVE_ENTITY_POSROT => "MoveEntity_PosRot", diff --git a/server.php b/server.php index 165cbfc0f..3d9131dc2 100644 --- a/server.php +++ b/server.php @@ -28,7 +28,6 @@ the Free Software Foundation, either version 3 of the License, or require_once("common/dependencies.php"); require_once("classes/PocketMinecraftServer.class.php"); file_put_contents("packets.log", ""); -file_put_contents("console.log", ""); if(!file_exists(FILE_PATH."white-list.txt")){ console("[WARNING] No white-list.txt found, creating blank file"); @@ -43,8 +42,11 @@ if(!file_exists(FILE_PATH."banned-ips.txt")){ if(!file_exists(FILE_PATH."server.properties")){ console("[WARNING] No server.properties found, using default settings"); copy(FILE_PATH."common/default.properties", FILE_PATH."server.properties"); - } + +@mkdir(FILE_PATH."data/entities/", 0777, true); +@mkdir(FILE_PATH."data/players/", 0777); + $prop = file_get_contents(FILE_PATH."server.properties"); $prop = explode("\n", str_replace("\r", "", $prop)); $config = array(); @@ -65,11 +67,13 @@ foreach($prop as $line){ case "max-players": case "port": case "debug": + case "time-per-second": $v = (int) $v; break; case "seed": case "server-id": - $v = trim($v) == "false" ? false:preg_replace("/^[0-9\-]/", "", $v); + $v = trim($v); + $v = $v == "false" ? false:(preg_match("/[^0-9\-]/", $v) > 0 ? Utils::readInt(substr(md5($v, true), 0, 4)):$v); break; case "spawn": $v = explode(";", $v); @@ -86,9 +90,11 @@ define("DEBUG", $config["debug"]); $server = new PocketMinecraftServer($config["server-name"], $config["gamemode"], $config["seed"], $config["protocol"], $config["port"], $config["server-id"]); $server->setType($config["type"]); +$server->timePerSecond = $config["time-per-second"]; $server->maxClients = $config["max-players"]; $server->description = $config["description"]; $server->motd = $config["motd"]; +$server->spawn = $config["spawn"]; $server->whitelist = $config["white-list"]; $server->reloadConfig();