Witajcie, w kontynuacji dawno zaczętej serii, w której przyglądam się największym „perełkom” pośród skryptów PHP. Dzisiaj pod nóż pójdzie coś specjalnego, prawdziwy as wśród asów. Drodzy Państwo, przywitajcie xNovę.
Najpierw krótko o samym skrypcie. xNova to skrypt, który ma umożliwić tworzenie gier MMO via WWW wyglądających dokładnie jak OGame (mówimy tutaj o starej wersji, bo od czasu wydania ocenianej wersji skryptu, OGame doczekał się całkowitej przebudowy interfejsu).
Ciężko jest nawet powiedzieć jaką wersję będę opisywał, bo xNova od wieków nie
posiada oficjalnej strony/forum/bloga. Tak więc namnożyło się bez liku wersji
(najczęściej po prostu przeróbek prywatnych osób, które połatały 3% bugów
i zoptymalizowały kilka zapytań). Wersja którą mam, była opisana jako „xNova
0.3 Multilanguage”, ale zdefiniowana wewnątrz skryptu stała VERSION
miała
wartość 0.8, więc mamy niezły rozrzut.
W związku z ilością bugów oraz masą amatorsko tworzonych „nowych wersji” najczęstszymi poradami na bugi zamieszczonymi na „profesjonalnych suportach xNovy” nie są wcale poprawki błędnej linijki, tylko zdanie „podmień sobie plik z wersji 1.1A/5.5/x.x by Ktośtam” – to też bardzo skutecznie tworzy chaos w plikach.
No, ale przejdźmy do właściwej części wpisu:
-
Już patrząc w strukturę katalogów możemy mieć pewne pojęcie o skrypcie. W katalogu głównym umieszczono foldery
scripts
ijs
. Oba zawierają skrypty JavaScripts, część z nich jest identyczna i po analizie można dojść do tego, że około 12 z plików nie jest nigdzie używanych. -
Wygląd skryptu opiera się na dwóch folderach:
templates
, w którym leżą pliki dla systemu szablonów iskins
, w którym leżą obrazki i pliki CSS. Bardzo piękne założenie, szkoda tylko , że poza tymi folderami w katalogu głównym mamy jeszcze folderycss
iimages
, więc zmiana szablonu nie daje możliwości pełnej modyfikacji wyglądu bez modyfikacji zawartości tych folderów. Ponadto wg mnie pliki JavaScript też powinny być zależne od wybranego theme’u, a więc leżeć w jego katalogu. -
Przejdźmy do instalacji. Uruchamiam folder z xNovą a tam bardzo prosty do zdiagnozowania błąd. Skrypt wymaga włączonej dyrektywy
short_open_tag
Wracamy po szybkim restarcie serwera :) -
Po zmianie konfiguracji próbuję jeszcze raz i co widzę? Błąd połączenia z bazą danych zamiast instalatora. W pliku
index.php
jest odpowiednia instrukcja, która przekierowuje do instalatora jeśliconfig.php
jest pusty. Niestety, ktoś udostępniając tą paczkę ludziom nie pomyślał i zostawił swojego wypełnionego configa. Tak więc poprawmy jego błędy i wyczyśćmy plikconfig.php
. Do trzech razy sztuka… -
Próbujemy jeszcze raz i oto naszym oczom ukazuje się przepiękny instalator… po francusku. Szkoda tylko, że silnik posiada możliwość zmiany języka w całej grze, w instalatorze także. Niestety nikt nie pomyślał nad możliwością wyboru języka instalacji.
-
Lecąc na czuja wypełniamy tabelkę z danymi do DB, standardowy układ jak w każdym skrypcie. Ukazuje nam się podstrona z pociesznym napisem „La base de donnée a bien été installée!”. Szkoda tylko, że ktoś jej pożarł całego CSS-a.
-
Styl powraca, tworzymy konto admina i mamy rejestrację z głowy. Czas się zalogować.
-
Bez problemu loguję się do gry, a tam wszystko po… tak, zgadliście, po francusku. Instalator z automatu ustawia adminowi język na francuski. Trzeba go przestawić w opcjach konta/w bazie danych.
-
Teraz, kiedy gra już działa, przyjrzyjmy się plikom: widać wyraźnie, że każda z podstron najpierw dołącza plik
common.php
, a więc to jemu przyjrzymy się na początku. Co my tam mamy? Widać, że istnieje możliwość ustawienia własnych ścieżek do kilku folderów oraz domyślnego języka gry. Tylko dlaczego to nie jest w configu? Potem widzimy dołączanie kilku plików, w tymtodofleetcontrol.php
. -
Ten plik jest na tyle wyjątkowy, że zdecydowałem się go pokazać w całości:
<?php /** * * * @version 1 * @copyright 2008 By Chlorel for XNova */ // Fonctions deja 'au propre' include($ugamela_root_path . 'includes/functions/FlyingFleetHandler.'.$phpEx); include($ugamela_root_path . 'includes/functions/MissionCaseAttack.'.$phpEx); include($ugamela_root_path . 'includes/functions/MissionCaseStay.'.$phpEx); include($ugamela_root_path . 'includes/functions/MissionCaseStayAlly.'.$phpEx); include($ugamela_root_path . 'includes/functions/MissionCaseTransport.'.$phpEx); include($ugamela_root_path . 'includes/functions/MissionCaseSpy.'.$phpEx); include($ugamela_root_path . 'includes/functions/MissionCaseRecycling.'.$phpEx); include($ugamela_root_path . 'includes/functions/MissionCaseDestruction.'.$phpEx); include($ugamela_root_path . 'includes/functions/MissionCaseColonisation.'.$phpEx); include($ugamela_root_path . 'includes/functions/MissionCaseExpedition.'.$phpEx); include($ugamela_root_path . 'includes/functions/SendSimpleMessage.'.$phpEx); include($ugamela_root_path . 'includes/functions/SpyTarget.'.$phpEx); include($ugamela_root_path . 'includes/functions/RestoreFleetToPlanet.'.$phpEx); include($ugamela_root_path . 'includes/functions/StoreGoodsToPlanet.'.$phpEx); include($ugamela_root_path . 'includes/functions/CheckPlanetBuildingQueue.'.$phpEx); include($ugamela_root_path . 'includes/functions/CheckPlanetUsedFields.'.$phpEx); include($ugamela_root_path . 'includes/functions/CreateOneMoonRecord.'.$phpEx); include($ugamela_root_path . 'includes/functions/CreateOnePlanetRecord.'.$phpEx); include($ugamela_root_path . 'includes/functions/InsertJavaScriptChronoApplet.'.$phpEx); include($ugamela_root_path . 'includes/functions/IsTechnologieAccessible.'.$phpEx); include($ugamela_root_path . 'includes/functions/GetBuildingTime.'.$phpEx); include($ugamela_root_path . 'includes/functions/GetRestPrice.'.$phpEx); include($ugamela_root_path . 'includes/functions/GetElementPrice.'.$phpEx); include($ugamela_root_path . 'includes/functions/GetBuildingPrice.'.$phpEx); include($ugamela_root_path . 'includes/functions/IsElementBuyable.'.$phpEx); include($ugamela_root_path . 'includes/functions/CheckCookies.'.$phpEx); include($ugamela_root_path . 'includes/functions/ChekUser.'.$phpEx); include($ugamela_root_path . 'includes/functions/InsertGalaxyScripts.'.$phpEx); include($ugamela_root_path . 'includes/functions/GalaxyCheckFunctions.'.$phpEx); include($ugamela_root_path . 'includes/functions/ShowGalaxyRows.'.$phpEx); include($ugamela_root_path . 'includes/functions/GetPhalanxRange.'.$phpEx); include($ugamela_root_path . 'includes/functions/GetMissileRange.'.$phpEx); include($ugamela_root_path . 'includes/functions/GalaxyRowPos.'.$phpEx); include($ugamela_root_path . 'includes/functions/GalaxyRowPlanet.'.$phpEx); include($ugamela_root_path . 'includes/functions/GalaxyRowPlanetName.'.$phpEx); include($ugamela_root_path . 'includes/functions/GalaxyRowMoon.'.$phpEx); include($ugamela_root_path . 'includes/functions/GalaxyRowDebris.'.$phpEx); include($ugamela_root_path . 'includes/functions/GalaxyRowUser.'.$phpEx); include($ugamela_root_path . 'includes/functions/GalaxyRowava.'.$phpEx); include($ugamela_root_path . 'includes/functions/GalaxyRowAlly.'.$phpEx); include($ugamela_root_path . 'includes/functions/GalaxyRowActions.'.$phpEx); include($ugamela_root_path . 'includes/functions/ShowGalaxySelector.'.$phpEx); include($ugamela_root_path . 'includes/functions/ShowGalaxyMISelector.'.$phpEx); include($ugamela_root_path . 'includes/functions/ShowGalaxyTitles.'.$phpEx); include($ugamela_root_path . 'includes/functions/GalaxyLegendPopup.'.$phpEx); include($ugamela_root_path . 'includes/functions/ShowGalaxyFooter.'.$phpEx); include($ugamela_root_path . 'includes/functions/GetMaxConstructibleElements.'.$phpEx); include($ugamela_root_path . 'includes/functions/GetElementRessources.'.$phpEx); include($ugamela_root_path . 'includes/functions/ElementBuildListBox.'.$phpEx); include($ugamela_root_path . 'includes/functions/ElementBuildListQueue.'.$phpEx); include($ugamela_root_path . 'includes/functions/FleetBuildingPage.'.$phpEx); include($ugamela_root_path . 'includes/functions/DefensesBuildingPage.'.$phpEx); include($ugamela_root_path . 'includes/functions/ResearchBuildingPage.'.$phpEx); include($ugamela_root_path . 'includes/functions/BatimentBuildingPage.'.$phpEx); include($ugamela_root_path . 'includes/functions/CheckLabSettingsInQueue.'.$phpEx); include($ugamela_root_path . 'includes/functions/InsertBuildListScript.'.$phpEx); include($ugamela_root_path . 'includes/functions/AddBuildingToQueue.'.$phpEx); include($ugamela_root_path . 'includes/functions/ShowBuildingQueue.'.$phpEx); include($ugamela_root_path . 'includes/functions/HandleTechnologieBuild.'.$phpEx); include($ugamela_root_path . 'includes/functions/BuildingSavePlanetRecord.'.$phpEx); include($ugamela_root_path . 'includes/functions/BuildingSaveUserRecord.'.$phpEx); include($ugamela_root_path . 'includes/functions/RemoveBuildingFromQueue.'.$phpEx); include($ugamela_root_path . 'includes/functions/CancelBuildingFromQueue.'.$phpEx); include($ugamela_root_path . 'includes/functions/SetNextQueueElementOnTop.'.$phpEx); include($ugamela_root_path . 'includes/functions/ShowTopNavigationBar.'.$phpEx); include($ugamela_root_path . 'includes/functions/SetSelectedPlanet.'.$phpEx); include($ugamela_root_path . 'includes/functions/MessageForm.'.$phpEx); include($ugamela_root_path . 'includes/functions/PlanetResourceUpdate.'.$phpEx); include($ugamela_root_path . 'includes/functions/BuildFlyingFleetTable.'.$phpEx); include($ugamela_root_path . 'includes/functions/SendNewPassword.'.$phpEx); include($ugamela_root_path . 'includes/functions/HandleElementBuildingQueue.'.$phpEx); include($ugamela_root_path . 'includes/functions/UpdatePlanetBatimentQueueList.'.$phpEx); include($ugamela_root_path . 'includes/functions/IsOfficierAccessible.'.$phpEx); include($ugamela_root_path . 'includes/functions/CheckInputStrings.'.$phpEx); include($ugamela_root_path . 'includes/functions/MipCombatEngine.'.$phpEx); include($ugamela_root_path . 'includes/functions/DeleteSelectedUser.'.$phpEx); include($ugamela_root_path . 'includes/functions/SortUserPlanets.'.$phpEx); include($ugamela_root_path . 'includes/functions/BuildFleetEventTable.'.$phpEx); include($ugamela_root_path . 'includes/functions/BuildRessourcePage.'.$phpEx); ?>
Co my tu mamy? Bezmyślne dołączanie 78 plików za każdym wywołaniem strony. Nawet gdy się logujemy, rejestrujemy, oglądamy podstronę kontakt itd. silnik ładuje 78 plików potrzebnych tylko w grze.
-
Kolejnym plikiem ładowanym w
common.php
jest plik/includes/db.php
. On też jest w pewien sposób wyjątkowy: jedyne co robi to include’uje kolejny (jeden) plik. Gdzie tu logika? -
Wymieniony wyżej plik
includes/db.php
dołącza plikdb/mysql.php
. Jego zawartość też jest warta przytoczenia:<? function doquery($query, $table, $fetch = false){ global $numqueries,$link,$debug,$ugamela_root_path; require($ugamela_root_path.'config.php');if(!$link) {$link = mysql_connect($dbsettings["server"], $dbsettings["user"], $dbsettings["pass"]) or $debug->error(mysql_error()." $query","SQL Error"); mysql_select_db($dbsettings["name"]) or $debug->error(mysql_error()." $query","SQL Error"); mysql_query("SET NAMES latin2"); echo mysql_error();} $sql = str_replace("{{table}}", $dbsettings["prefix"].$table, $query); $sqlquery = mysql_query($sql) or $debug->error(mysql_error()." $sql ","SQL Error"); unset($dbsettings); $numqueries++; $arr = debug_backtrace(); $file = end(explode('/',$arr[1]['file'])); $line = $arr[1]['line']; $debug->add(" Query $numqueries:$query$file($line)$table$fetch "); if($fetch) {$sqlrow = mysql_fetch_array($sqlquery); return $sqlrow; }else{return $sqlquery;}} ?>
Pierwszą rzeczą jaka rzuciła mi się w oczy poza genialnym układem kodu był fakt, że przy każdym zapytaniu jest dołączany
config.php
. No ktoś tam nie ma mózgu… nie można po prostu dołączyć go wcommon.php
? Tak samo ewentualne łączenie powinno się odbywać nie funkcji wykonującej zapytania tylko tam. -
W folderze
includes
znajduje się plikdatabaseinfos.php
, który na pierwszy rzut oka jest używany do tworzenia tabel przez instalator, a krótka analiza wykazuje, że używany jest tylko tam. W takim wypadku, czy nie logiczniej byłoby go usunąć automatycznie po zakończeniu instalacji (tak jak zresztą cały skrypt). Ponadto prościej byłoby chyba, gdyby znajdował się po prostu w folderzeinstall
. -
Samo nazewnictwo plików jest lekko mówiąc dziwne. Czy to mówiąc o sposobie zapisu (
add_moon.php
, ale jużactiveplanet.php
), czy o języku (errors.php
,paneladmina.php
,verband.php
). Do wyboru, do koloru! -
Jeśli chodzi o bazę danych, to widać jak na dłoni, że była dorabiana po kawałku zależnie od konieczności. Jednak nikt nie zadał sobie trudu, aby spojrzeć na nią całościowo, przeprojektować i choćby pousuwać zbędne pola z tabel czy ustawić bardziej optymalne typy dla pól. Jako najlepszy przykład można podać fakt, że informacje o występujących w grze Księżycach są trzymane w dwóch tabelach, przy czym jedna zawiera po prostu część danych z pierwszej tabeli. Niestety, aby to poprawić trzeba dokonać dość mocnej przeróbki skryptu, a mało kto ma tyle zawzięcia.
Powyższy skrypt jest bardzo wymownym przykładem na to jak nie powinno się tworzyć jakichkolwiek aplikacji. Zero organizacji pracy i początkowych założeń daje właśnie takie efekty. Można tylko współczuć ludziom którzy z wielkim zapałem chcą zrobić "super wypasionom grem na xNovie" nie zaglądając nawet do "jej wnętrza".