Способ Жопарукая HTML5+JS карта для ishtwar

CTAPu4OK

КтапичОК О_О
Старейшина
Репутация
160 / 541
Сидел я как то вечером, делать было нечего, на улице бегали алкаши а в доме стояла духата.
34e9711f1446.jpg

И тут решил сделать что нить этакое, подучить сокеты, подтянуть JS ну и просто убить время. Лазал по Дапфу, разглядывал движки, и вдруг вспомнил, у меня же двиг ishtwar есть О_О (старый, очень старый) но востанавливать мне было его лень) и так как я не особо часто серфю по инету, упустил резил от X-Ray. Когда я на него наткнулся, радости небыло предела) скачал, распаковал, запускаю иии...... понимаю, моя жопорукасть 99лвл... xampp выдал мне ошибку (мы постоянно с ним не ладим))

Забил, поставил свой любимый denwer, установил модули, запустил, хвала жопарукому! все работает)

Взял минуту подготовки к дикампилу файла... установил и настроил Phaser (Афигенная библа для разработчиков игр, работа со спрайтами, колизии, эффекты, TiledMaps и тд) и сформировал некую структуру проекта.

Ну думал щя как сбацаю карту, потом остальные flash примочки под js перепишу... радостный дикомпилю swf файлик lab_302.swf и вижу кучу AS кода... Блеать, что они в этоу бедную swf`очку только не добавили... куча файлов, много не нужного мусора... ну... херня думаю, нашел главный класс который работает с сокетами, посмотрел, вроде нечего колосального там не используют, обычные сокеты, гоняют туда сюда и все.

Скачал socket.io (библиотека для работы с сокетами) написал некий код:
JavaScript:
var socket = io.connect('127.0.0.1:6125', {transports: ['flashsocket']});
localStorage.debug = '*';

var socket_state;
var response_text = '';
var isConnected = false;
// Если сокет закрыт
socket.on('close', function(ev){
    console.log('Close socket' + ev);
    isConnected = false;
    return;
})
// если сокет подключился
socket.on('connect', function(ev){
    console.log('CONECTED '+ev);
    isConnected = true;
    return;
})
// Если возникли ошибки
socket.on('connect_error', function(ev){
    console.log('ioError ' + ev);
    socket.close();
    isConnected = false;
    return;
})
// если возникли ошибки в политике безопасности
socket.on('securityError', function(ev){
    console.log('Security error' + ev);
    socket.close();
    isConnected = false;
    return;
})
// Если нет ошибок и сервер ответил, то получаем и обрабатываем данные
socket.on('socketData', function(ev){
       console.log(ev)
       return;
})
Запускаю и вижу невиданную хуйню...
Код:
socket.io.js:1407 socket.io-client:url parse http://127.0.0.1:6125 +7ms
socket.io.js:1407 socket.io-client new io instance for http://127.0.0.1:6125 +0ms
socket.io.js:1407 socket.io-client:manager readyState closed +0ms
socket.io.js:1407 socket.io-client:manager opening http://127.0.0.1:6125 +0ms
socket.io.js:3722 engine.io-client:socket creating transport "flashsocket" +0ms
socket.io.js:1407 socket.io-client:manager connect attempt will timeout after 20000 +4ms
socket.io.js:1407 socket.io-client:manager readyState opening +0ms
socket.io.js:1407 socket.io-client:manager connect_error +105ms
game.js:108 ioError No transports available
socket.io.js:3722 engine.io-client:socket socket close with reason: "forced close" +109ms

Не доступный тип транспорта.... кхм... WAT? я понимаю Flash уже везде выпиливают, это даже и к лучшему... Но Блеать в моем случае это совсем не весело... Раньше socket.io поддерживало данный транспорт, потом типо незаметно начала ставить проверки

JavaScript:
if(!windows.WebSocket) return;

Потом посложнее... потом в нескольких местах... потом вообще выпелили *sad*

Сколько я не парился с WebSocket, перепробовал все доступные транспорты [websocket, xhr-polling, jsonp-polling, polling] не один не подконектился к ява демону... Запрос отправляют, а обратно хуй, и падают по таймауту... А переписывать яву не хотел, ибо знаний явы не так много)

Хотел уже делать что то вроде моста используя NodeJS или PHP... но тут вспомнил, когда то баловался AS (ActionScript) помню тот замечательный класс ExternalInterface... есть же возможность вызвать функцию JavaScript и получить значение обратно... так почему же не может быть библы для имитации flashsocket ? и отправился я бороздить просторы инета... Перерыл кучу сайтов, библиотек по пальцам пересчитать... и то везде ссылки битые *sad* единственная библа 2008 кода ( ) и то скажем так сырая... ну, хоть исходники есть, експортнул на гит, сгитил, написал некий код:
JavaScript:
function ready()
{
    console.log("socket ready");
    mySocket.connect("127.0.0.1", 6125);
}
function connect(success,data)
{
    if(!success)
    {
        console.log('Disconect: ' + data);
        isConnected = false;
        return
    }
    else
    {
        console.log('CONECTED: ' + success);
        isConnected = true;
    }
    mySocket.write("<policy-file-request/>");
}

function data(content)
{
    console.log('Sending security' + context);
    var _loc_2;

    response_text = response_text + socket.readUTFBytes(socket.bytesAvailable);

    if(socket_state == 'waiting_set_pos')
    {
        socket_state = 'ready';
    }
    else if(socket_state == 'waiting_get_objects')
    {
        if(response_text.indexOf("<end>") != -1)
        {
            _loc_2 = response_text.substring(1 + 38, response_text.length - 5);
            _loc_2 = '<?xml version="1.0" encoding="windows-1251"?>' + _loc_2;
            game.ParseXML(_loc_2);
            socket_state = 'ready';
            setTimeout(GetRoomObjects, 1000);
        }
    }
    return;
}
function close()
{
    console.log('Close socket');
    isConnected = false;
    return;
}
var mySocket = new jSocket(ready,connect,data,close);

mySocket.setup("socket","/map/framework/jsocket/flash/jsocket.advanced.swf");

function onError()
{

}
Запустил, и увидел:
Код:
socket ready
game.js:24 CONECTED: true
260
game.js:32 Uncaught ReferenceError: context is not defined
Тут меня тоже ждал некий мелкий облом, Java кодирует в байт код, flash берет поэтапно каждый байт.... а мой некий эмуль берет одну строчку... но, щя качаю adobe air буду править эмуль)

Надежды на JS карту не даю, но если заброшу, выложу что есть)
все что писал это просто мысли в слух) вдруг кто либо столкнулся с той же херней что и я)
Об оптимизации не думаю, ибо насрать, я для удовольствия копаюсь =D

Тему буду обновлять, по мере поступления гемороя
 
Последнее редактирование:
Иштвар охуенный двиг, мне он тоже нравиться, особенно учитывая, что он был слил через сторонний сайт. Отказывайся полностью от флеша, нафиг он там не нужен, тянуть его нет смысла.
Карту на JS можно взять от , единственное придется поепаться немного с взаимодействием игрок-игрок, игрок-сервер, т.к. она заточена под одиночную игру, но зато под нее есть охуенный редактор карт RPG Maker. Чтобы сделать мультиплеер, думаю без node.js или JSP не обойтись, подобную реализацию можно глянуть тут и тут и тут ну и танчики основа socket.io. Не помню есть ли в моих релизах исходники Java-демонов, если нет - могу выложить, их тоже неплохо было бы переписать, хотя бы на си, чтобы не тянуть java, т.к. она тоже хуйня полная.
 
Иштвар охуенный двиг, мне он тоже нравиться, особенно учитывая, что он был слил через сторонний сайт. Отказывайся полностью от флеша, нафиг он там не нужен, тянуть его нет смысла.
Карту на JS можно взять от , единственное придется поепаться немного с взаимодействием игрок-игрок, игрок-сервер, т.к. она заточена под одиночную игру, но зато под нее есть охуенный редактор карт RPG Maker. Чтобы сделать мультиплеер, думаю без node.js или JSP не обойтись, подобную реализацию можно глянуть тут и тут и тут ну и танчики основа socket.io. Не помню есть ли в моих релизах исходники Java-демонов, если нет - могу выложить, их тоже неплохо было бы переписать, хотя бы на си, чтобы не тянуть java, т.к. она тоже хуйня полная.
Карту сделать не проблема, главный гемор это подстраиваться под ява сервер) исходники демонов есть, но переписывать сервак как то влом) может в дальнейшем, но не сейчас) и то только на nodejs)
 
Сколько я не парился с WebSocket, перепробовал все доступные транспорты
Сервер должен ответить клиенту правильно, иначе соединение не будет установлено)
Вот мой класс для работы с WebSocket на php)
PHP:
<?php

class jSocket
{
    private $socket = false;
    public $all_connect = Array();
   
    public function __construct()
    {
        $this->socket = stream_socket_server("tcp://0.0.0.0:8001", $errno, $errstr);
        if ( !$this->socket ) Server::log('Ошибка создания сокет сервера', 'SOCKET');
        else $this->sRun();
    }
   
    private function sRun()
    {
        Server::log('Сервер создан успешно, начало обработки подключений', 'SOCKET');
        while ( true )
        {
            $read = $this->all_connect;
            $read[] = $this->socket;
            $write = $except = null;
           
            // ожидаем сокеты доступные для чтения (без таймаута)
            if ( !stream_select($read, $write, $except, null) ) break;
           
            // есть новое соединение
            if ( in_array($this->socket, $read) )
            {
                //принимаем новое соединение и производим рукопожатие:
                if ( ( $connect = stream_socket_accept($this->socket, -1) ) and $info = $this->handshake($connect) )
                {
                    $unikay = (int)$this->get_cookie_id($info['Cookie']);
                    if ( !$unikay ) break;
                    $this->all_connect[$unikay] = $connect; // добавляем сокет в список обработки
                    Server::log('NEW ID '.$unikay, 'SOCKET');
                    // Создаем представление персонажа
                    $this->aUser[$unikay] = new Player($unikay);
                    // Говорим время в sys канал
                    $this->chat->new_mess($unikay, Array("", 9, "", "Серверное время ".date('H:i:s')));
                }
                unset( $read[ array_search($this->socket, $read) ] );
            }
           
            // обрабатываем все соединения
            foreach($read as $connect)
            {
                $data = fread($connect, 1000000);
                $id = array_search($connect, $this->all_connect);
                // Обрабатываем отключение юзера
                if ( !$data )
                {
                    Server::log('Отключение ID '.$id, 'SOCKET');
                    fclose( $connect );
                    unset( $this->all_connect[$id] );
                    continue;
                }
                // обрабатываем сигналы от юзера в сервер класс
                $this->onMessage($id, $data);
            }
        }
    }
   
    // Вытаскиваем UID из куки
    private function get_cookie_id($str) { $a = explode('=', $str); return $a[2]; }
   
    // Отправка сообщения юзеру
    public function send($who = Array(), $mess)
    {
        $a = count($who); $err = $a;
        $mess = $this->encode($mess); // Кодируем строку
        if ( !empty($mess) )
        {
            // Если нужно отправить сообщение кому-то конкретно
            if ( is_array( $who ) )
            {
                foreach ( $who as $id )
                {
                    $sock = @$this->all_connect[$id];
                    if ( $sock ) { if ( fwrite($sock, $mess) ) $err--; }
                    else Server::log("Попытка отправки сообщения несуществующему юзеру. UID: {$id}", 'SOCKET');
                }
            }
            elseif ( $who == 'all' ) // отправляем сообщение всем
            {
                foreach ( $this->all_connect as $sock )
                {
                    if ( !fwrite($sock, $mess) ) $err--;
                }
            } else Server::log("Отправка сообщений не выполнена так как не указан тип отправки.", 'SOCKET');
            Server::log("Отправка: всего {$a}, ошибок {$err}.", 'SOCKET');
        } else Server::log("Попытка отправки пустой строки.", 'SOCKET');
    }
   
    #############################################
    // функция рукопожатия
    private function handshake($connect)
    {
        $info = array();
        $line = fgets($connect);
        $header = explode(' ', $line);
        $info['method'] = $header[0];
        $info['uri'] = $header[1];

        //считываем заголовки из соединения
        while ( $line = rtrim(fgets($connect)) )
        {
            if ( preg_match('/\A(\S+): (.*)\z/', $line, $matches) ) $info[$matches[1]] = $matches[2];
            else break;
        }
       
        $address = explode(':', stream_socket_get_name($connect, true)); //получаем адрес клиента
        $info['ip'] = $address[0];
        $info['port'] = $address[1];
       
        if ( empty($info['Sec-WebSocket-Key']) ) return false;
       
        //отправляем заголовок согласно протоколу вебсокета
        $SecWebSocketAccept = base64_encode( pack('H*', sha1($info['Sec-WebSocket-Key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')) );
        $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
            "Upgrade: websocket\r\n" .
            "Connection: Upgrade\r\n" .
            "Sec-WebSocket-Accept:$SecWebSocketAccept\r\n\r\n";
        fwrite($connect, $upgrade);
        return $info;
    }
   
    // Кодирование исходящих данных
    public function encode($payload, $type = 'text', $masked = false)
    {
        $frameHead = array();
        $payloadLength = $payload ? strlen($payload) : 0;
        switch ($type)
        {
            case 'text': $frameHead[0] = 129; break; // first byte indicates FIN, Text-Frame (10000001):
            case 'close': $frameHead[0] = 136; break; // first byte indicates FIN, Close Frame(10001000):
            case 'ping': $frameHead[0] = 137; break; // first byte indicates FIN, Ping frame (10001001):
            case 'pong': $frameHead[0] = 138; break; // first byte indicates FIN, Pong frame (10001010):
        }
        // set mask and payload length (using 1, 3 or 9 bytes)
        if ($payloadLength > 65535)
        {
            $payloadLengthBin = str_split(sprintf('%064b', $payloadLength), 8); $frameHead[1] = ($masked === true) ? 255 : 127; for ($i = 0; $i < 8; $i++) $frameHead[$i + 2] = bindec($payloadLengthBin[$i]);
            // most significant bit MUST be 0
            if ($frameHead[2] > 127) return array('type' => '', 'payload' => '', 'error' => 'frame too large (1004)');
        }
        elseif ($payloadLength > 125) { $payloadLengthBin = str_split(sprintf('%016b', $payloadLength), 8); $frameHead[1] = ($masked === true) ? 254 : 126; $frameHead[2] = bindec($payloadLengthBin[0]); $frameHead[3] = bindec($payloadLengthBin[1]); }
        else $frameHead[1] = ($masked === true) ? $payloadLength + 128 : $payloadLength;
        // convert frame-head to string:
        foreach ( array_keys($frameHead) as $i ) $frameHead[$i] = chr($frameHead[$i]);
        // generate a random mask:
        if ($masked === true) { $mask = array(); for ($i = 0; $i < 4; $i++) $mask[$i] = chr(rand(0, 255)); $frameHead = array_merge($frameHead, $mask); }
        $frame = implode('', $frameHead);
        // append payload to frame:
        for ($i = 0; $i < $payloadLength; $i++) $frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i];
        return $frame;
    }
   
    // Декодирование входящих данных
    public function decode($data)
    {
        $unmaskedPayload = '';
        $decodedData = array();
        // estimate frame type:
        $firstByteBinary = sprintf('%08b', ord($data[0]));
        $secondByteBinary = sprintf('%08b', ord($data[1]));
        $opcode = bindec(substr($firstByteBinary, 4, 4));
        $isMasked = ($secondByteBinary[0] == '1') ? true : false;
        $payloadLength = ord($data[1]) & 127;
        // unmasked frame is received:
        if (!$isMasked) return array('type' => '', 'payload' => '', 'error' => 'protocol error (1002)');
        switch ($opcode)
        {
            case 1: $decodedData['type'] = 'text'; break; // text frame:
            case 2: $decodedData['type'] = 'binary'; break;
            case 8: $decodedData['type'] = 'close'; break; // connection close frame:
            case 9: $decodedData['type'] = 'ping'; break; // ping frame:
            case 10: $decodedData['type'] = 'pong'; break; // pong frame:
            default: return array('type' => '', 'payload' => '', 'error' => 'unknown opcode (1003)');
        }
        if ($payloadLength === 126) { $mask = substr($data, 4, 4); $payloadOffset = 8; $dataLength = bindec(sprintf('%08b', ord($data[2])) . sprintf('%08b', ord($data[3]))) + $payloadOffset; }
        elseif ($payloadLength === 127) { $mask = substr($data, 10, 4); $payloadOffset = 14; $tmp = ''; for ($i = 0; $i < 8; $i++) $tmp .= sprintf('%08b', ord($data[$i + 2])); $dataLength = bindec($tmp) + $payloadOffset; unset($tmp); }
        else { $mask = substr($data, 2, 4); $payloadOffset = 6; $dataLength = $payloadLength + $payloadOffset; }
        /* We have to check for large frames here. socket_recv cuts at 1024 bytes so if websocket-frame is > 1024 bytes we have to wait until whole data is transferd. */
        if (strlen($data) < $dataLength) return false;
        if ($isMasked) { for ($i = $payloadOffset; $i < $dataLength; $i++) { $j = $i - $payloadOffset; if (isset($data[$i])) $unmaskedPayload .= $data[$i] ^ $mask[$j % 4]; } $decodedData['payload'] = $unmaskedPayload; }
        else { $payloadOffset = $payloadOffset - 4; $decodedData['payload'] = substr($data, $payloadOffset); }
        return $decodedData;
    }
   
    public function __destruct(){}
}

?>
Server::log("Отправка: всего {$a}, ошибок {$err}.", 'SOCKET');
PHP:
    public function log($msg, $who = 'DEAMON')
    {
        echo "(".Server::getMemory().")[".date('H:i:s')."] {$who}: {$msg}\n";
    }
    public function getMemory()
    {
        $mem_usage = memory_get_usage(true);
        if ($mem_usage < 1024) $r = $mem_usage.' Bit'; 
        elseif ($mem_usage < 1048576) $r = round($mem_usage/1024, 2).' kBit'; 
        else $r = round($mem_usage/1048576, 2).' MB';
        return $r;
    }
 
Сервер должен ответить клиенту правильно, иначе соединение не будет установлено)

Ога, от только сервер на flashsocket))) если не ошибаюсь это нечто xml socket`а ))) php под сервер не тема использовать) яб с удовольствием) но как по мне, php чисто веб)
 
php норм тянет под сервер) можно даже
 
Вообщем, меня ишвар прям вдохновил, пришлось выкинуть и его и демонов и флеши всякие...
Взял NodeJS и JS... пишу с нуля)
cd9edc12d83c.jpg

Что есть на данный момент:
Проверка персонажа по ID (так как персонажей может быть много, а аккаунт один... в дальнейшем прикручу сессии) (синяя точка это перс:)
Инициализация персонажа
Передвижение персонажа при клике (со стороны сервера работает пока что криво)
реген хп и мп
кривое взаимодействие с объектами
ну и карта, камера следующая за персом, все как положено)
 
pharser используешь...
Почему именно его? Еще ведь есть много всяких интересных фреймворков типа cocos или threeJS, или даже тот же pixi ?
 
pharser используешь...
Почему именно его? Еще ведь есть много всяких интересных фреймворков типа cocos или threeJS, или даже тот же pixi ?
cocosjs если не ошибаюсь базируется только на cloud, такое не по мне)
threeJS слышал, но не использовал
а phaser базируется на том же pixi )

а если честно хз, что первое в руки попало, то и использую) да и сайт у них прикольный ^_^
 
cocosjs если не ошибаюсь базируется только на cloud, такое не по мне)
threeJS слышал, но не использовал
а phaser базируется на том же pixi )

а если честно хз, что первое в руки попало, то и использую) да и сайт у них прикольный ^_^
Исходниками начала поделишься?)
А то все есть желание начать изучать тайловые карты, да никак руки не доходят до чтения статей...
 
Исходниками начала поделишься?)
А то все есть желание начать изучать тайловые карты, да никак руки не доходят до чтения статей...
В эти выходные выложу что есть) так сказать для развития остальных пользователей (отход от бк:) а дальше видно будет)
Так как сейчас намудрил с передвижением, чтоб убрать уязвимость телепортов, использовал келбаеки) из за этого начал стопарится сервак))) в итоге макс 75 конектов и падает:) сижу решаю)))
 
В эти выходные выложу что есть) так сказать для развития остальных пользователей (отход от бк:) а дальше видно будет)
Так как сейчас намудрил с передвижением, чтоб убрать уязвимость телепортов, использовал келбаеки) из за этого начал стопарится сервак))) в итоге макс 75 конектов и падает:) сижу решаю)))
Какие характеристики сервера?
 
Какие характеристики сервера?
Исходя из логики можно предположить, что это домашний пк.

Процессор Intel Core i5
ОЗУ DDR3 : 4 ГБ
Жесткий диск: 1000 гб + ssd для операционной системы.


Имхо предположение мое может угадаю :)
 
Исходя из логики можно предположить, что это домашний пк.

Процессор Intel Core i5
ОЗУ DDR3 : 4 ГБ
Жесткий диск: 1000 гб + ssd для операционной системы.


Имхо предположение мое может угадаю :)
ddr2 - 4гб, dual core 2.8 разогнаный до 3 без всяких ssd:) nodejs асинхронный, но при использовании келлебеков сервак стопарится и не пускает другие коннекты пока не завершиться текущий) из за этого такие тормоза)))
при проверке движения (попытался использовать отсылать попиксельно и двигать перса....) вышла неудача, в данный момент вспоминаю линейную алгебру и пытаюсь проверить движение игрока на сервере, зная только точку А и Б + время и дистанцию) но как проверить, не изменил ли он (куда двигаться) на +100500px ....
можно сделать проверку макс 1200px... но опять же он сможет прыгать до 1200)))
 
У тебя винда или линукс? Сколько максимальное стоит ограничение соединений?
 
У тебя винда или линукс? Сколько максимальное стоит ограничение соединений?
винда, ограничения +99999999 ) на максималке) это не из за компа) именно сервак на nodejs, я тупо залочил асинхронность) сейчас думаю как сделать иначе) если до выходных не справлюсь, выложу как есть, с уязвимостью телепортов) может кто подскажет :)
 
шифрование перемещения, если смогут расшифровать, то получат чит на перемещение, если нет - хуй.
 
шифрование перемещения, если смогут расшифровать, то получат чит на перемещение, если нет - хуй.
Шифрование не годится) у меня сейчас тупо по клику по карте, вызывается:
JavaScript:
net.sock.send(toJSON('@w', {x:pointer.worldX,y:pointer.worldY}));
далее на сервере чекается:
JavaScript:
playerMove: function(xy,callback)
    {
        var duration = (this.distanceToXY({x:this.x,y:this.y}, xy.x, xy.y) / 65) * 1000;
        var distance = this.distanceToXY({x:this.x,y:this.y}, xy.x, xy.y);
       
        if(distance < 1400)
        {
            for(var i = 0; i< distance; i++)
            {
                if(con)
                {
                    if(xy.x < this.x)
                        this.x -= i;
                    else
                        this.x += i;
                   
                    if(xy.y < this.y)
                        this.y -= i;
                    else
                        this.y += i;
                  
                    callback({x:this.x, y:this.y, duration:d}); // или true - движение разрешено
                    console.log(this.x + ' - '+ this.y);
                   
                }
                else break;
            }
           
           
            function move()
            {
               
            }
           
            global.con.update('profiles',{x:this.x, y:this.y},{id:this.id}, function(err, affectedRows) {
                console.log(affectedRows)
            });
        }
    },

и далее в клиенте:
JavaScript:
tween = game.add.tween(player).to({ x:data.x, y: data.y }, data.duration, Phaser.Easing.Linear.None, true);

более нету)

эм... цикл это старый) когда я хотел попиксельно отпровлять))) в основном приходят координаты куда идти, я проверяю на дистанцию и все... как проверить на время (успел ли он пройти дистанцию до socket close) я знаю, но как оповестить об этом клиент хз)))
 
хз, TLS вроде норм тема к чему эти все мучения с лишними проверками я не понимаю.
 
хз, TLS вроде норм тема к чему эти все мучения с лишними проверками я не понимаю.
проблема кроется в другом) тут ssl даже не поможет) открываешь firebug и пишешь player.x = 100; player.y = 100 (а в данный момент ты находишься на 500 - 500) жмякаешь enter тебя сразу переносит на 100 - 100 при этом обращения к серверу небыло.... и тут ты жмякаешь на 700 - 700... идет передача данных на 700 - 700 от самой игры... на сервер)
или же... ты на 100 - 100.... ты жмякаешь 1000 - 1000 тебе лень ждать пока перс перейдет туда... ты обновляешь страницу и сразу же твои координаты становятся 1000 - 1000...
 
Сверху