From 1579b016fdce41d4da4006dbff2896bad94ab2d5 Mon Sep 17 00:00:00 2001 From: Hendrik Boll Date: Sun, 12 May 2019 11:07:07 +0200 Subject: [PATCH] rewrote the tmserver container to only host tmserver and not xaseco --- Dockerfile | 10 +- docker-compose.yml | 94 +- entrypoint.sh => entrypoint-tmserver.sh | 38 +- .../Config/{default.txt => config.txt} | 2 +- tmserver/GameData/Game.FidCache.Gbx | Bin 40741 -> 38023 bytes .../{default.txt => playlist.txt} | 0 tmserver/Logs/ConsoleLog.1352.txt | 16 + tmserver/tmserver | 2 +- xaseco/Aseco.sh | 3 - xaseco/DOCS/Features_080.html | 208 - xaseco/DOCS/Features_095.html | 890 --- xaseco/DOCS/Features_103.html | 913 --- xaseco/DOCS/Features_116.html | 895 --- xaseco/DOCS/ListCallbacksForever.html | 149 - xaseco/DOCS/ListCallbacksNations.html | 88 - xaseco/DOCS/ListDedimania.html | 189 - xaseco/DOCS/ListMethodsForever.html | 717 -- xaseco/DOCS/ListMethodsNations.html | 432 -- xaseco/DOCS/OLD/Jfreu install.txt | 24 - xaseco/DOCS/OLD/Jfreu's plugin.txt | 121 - xaseco/DOCS/OLD/README-autotime.txt | 23 - xaseco/DOCS/OLD/README.txt | 144 - xaseco/DOCS/OLD/ReadMe.pdf | Bin 215757 -> 0 bytes xaseco/DOCS/OLD/plugin.sminfo.php | 119 - xaseco/DOCS/OLD/sminfofetcher.inc.php | 149 - xaseco/DOCS/admin_abilities.html | 194 - xaseco/DOCS/aseco_commands.doc | Bin 69120 -> 0 bytes xaseco/DOCS/aseco_commands.html | 498 -- xaseco/DOCS/index.html | 421 -- xaseco/DOCS/overview.html | 331 - xaseco/DOCS/quickstart_tmf.html | 254 - xaseco/DOCS/quickstart_tmn.html | 226 - xaseco/DOCS/repairnations.php | 432 -- xaseco/DOCS/repairrecs.php | 128 - xaseco/DOCS/sm_hub.html | 112 - xaseco/DOCS/tm2_hub.html | 296 - xaseco/DOCS/tmf_hub.html | 127 - xaseco/DOCS/updatepanels.php | 76 - xaseco/DOCS/upgrades.html | 1128 ---- xaseco/access.xml | 93 - xaseco/adminops.xml | 320 - xaseco/aseco.php | 2562 -------- xaseco/autotime.xml | 15 - xaseco/bannedips.xml | 6 - xaseco/config.xml | 219 - xaseco/dedimania.xml | 74 - xaseco/html.tpl | 28 - xaseco/includes/GbxRemote.bem.php | 891 --- xaseco/includes/GbxRemote.inc.php | 854 --- xaseco/includes/GbxRemote.response.php | 218 - xaseco/includes/basic.inc.php | 830 --- xaseco/includes/gbxchallinfo.inc.php | 131 - xaseco/includes/gbxdatafetcher.inc.php | 1431 ---- xaseco/includes/manialinks.inc.php | 1404 ---- xaseco/includes/ogg_comments.inc.php | 154 - xaseco/includes/rasp.funcs.php | 1949 ------ xaseco/includes/tmndatafetcher.inc.php | 290 - xaseco/includes/tmxinfofetcher.inc.php | 297 - xaseco/includes/tmxinfosearcher.inc.php | 288 - xaseco/includes/types.inc.php | 511 -- xaseco/includes/urlsafebase64.php | 26 - xaseco/includes/web_access.inc.php | 1341 ---- xaseco/includes/xmlparser.inc.php | 121 - xaseco/includes/xmlrpc_db.inc.php | 385 -- xaseco/jfreu.config.php | 136 - xaseco/localdatabase.xml | 22 - xaseco/localdb/aseco.sql | 56 - xaseco/localdb/extra.sql | 18 - xaseco/localdb/rasp.sql | 47 - xaseco/matchsave.xml | 100 - xaseco/musicserver.xml | 55 - xaseco/panels/00README.txt | 34 - xaseco/panels/AdminAboveCPList.xml | 10 - xaseco/panels/AdminAboveCPListWide.xml | 10 - xaseco/panels/AdminAboveChat.xml | 10 - xaseco/panels/AdminAboveChatWide.xml | 10 - xaseco/panels/AdminAboveSpeed.xml | 10 - xaseco/panels/AdminAboveSpeed2.xml | 11 - xaseco/panels/AdminBelowChat.xml | 10 - xaseco/panels/AdminBottomCenter.xml | 10 - xaseco/panels/AdminBottomCenterWide.xml | 10 - xaseco/panels/AdminCallVote.xml | 10 - xaseco/panels/AdminCallVoteBlur.xml | 10 - xaseco/panels/AdminLeftEdge.xml | 10 - xaseco/panels/AdminRightEdge.xml | 10 - xaseco/panels/AdminTopCenter.xml | 10 - xaseco/panels/AdminTopCenterWide.xml | 10 - xaseco/panels/DonateBelowCPList.xml | 13 - xaseco/panels/DonateBelowCPListRM.xml | 13 - xaseco/panels/DonateLeftEdge.xml | 13 - xaseco/panels/DonateLeftEdge2.xml | 13 - xaseco/panels/DonateLeftSmall.xml | 13 - xaseco/panels/DonateRightEdge.xml | 13 - xaseco/panels/DonateRightEdge2.xml | 13 - xaseco/panels/DonateRightSmall.xml | 13 - xaseco/panels/DonateTopLeft.xml | 13 - xaseco/panels/RecordsLeftBlackBold.xml | 13 - xaseco/panels/RecordsLeftBlue.xml | 13 - xaseco/panels/RecordsLeftBlueBold.xml | 13 - xaseco/panels/RecordsLeftBlueLight.xml | 13 - xaseco/panels/RecordsLeftGray.xml | 13 - xaseco/panels/RecordsLeftGrayBold.xml | 13 - xaseco/panels/RecordsLeftItalic.xml | 13 - xaseco/panels/RecordsLeftOrange.xml | 13 - xaseco/panels/RecordsLeftSize1.xml | 13 - xaseco/panels/RecordsLeftSize1NoDedi.xml | 11 - xaseco/panels/RecordsLeftSize2.xml | 13 - xaseco/panels/RecordsLeftSize2NoDedi.xml | 11 - xaseco/panels/RecordsLeftWhite.xml | 13 - xaseco/panels/RecordsRightBottom.xml | 12 - xaseco/panels/RecordsRightBottomNoDedi.xml | 10 - xaseco/panels/RecordsRightBottomRM.xml | 12 - xaseco/panels/StatsNations.xml | 18 - xaseco/panels/StatsUnited.xml | 20 - xaseco/panels/VoteBelowChat.xml | 7 - xaseco/panels/VoteBottomCenter.xml | 7 - xaseco/panels/VoteBottomCenterTransp.xml | 8 - xaseco/panels/VoteCallVote.xml | 7 - xaseco/panels/VoteTopCenter.xml | 7 - xaseco/plugins.xml | 44 - xaseco/plugins/chat.admin.php | 5772 ----------------- xaseco/plugins/chat.dedimania.php | 941 --- xaseco/plugins/chat.help.php | 32 - xaseco/plugins/chat.laston.php | 37 - xaseco/plugins/chat.lastwin.php | 40 - xaseco/plugins/chat.me.php | 31 - xaseco/plugins/chat.players.php | 141 - xaseco/plugins/chat.players2.php | 217 - xaseco/plugins/chat.records.php | 210 - xaseco/plugins/chat.records2.php | 888 --- xaseco/plugins/chat.recrels.php | 306 - xaseco/plugins/chat.server.php | 424 -- xaseco/plugins/chat.songmod.php | 63 - xaseco/plugins/chat.stats.php | 357 - xaseco/plugins/chat.wins.php | 23 - xaseco/plugins/jfreu.chat.php | 1866 ------ xaseco/plugins/jfreu.lite.php | 178 - xaseco/plugins/jfreu.plugin.php | 1791 ----- xaseco/plugins/jfreu/jfreu.bans.xml | 8 - xaseco/plugins/jfreu/jfreu.config.xml | 29 - xaseco/plugins/jfreu/jfreu.vips.xml | 8 - xaseco/plugins/mistral.idlekick.php | 175 - xaseco/plugins/plugin.access.php | 448 -- xaseco/plugins/plugin.autotime.php | 157 - xaseco/plugins/plugin.chatlog.php | 108 - xaseco/plugins/plugin.checkpoints.php | 809 --- xaseco/plugins/plugin.dedimania.php | 1142 ---- xaseco/plugins/plugin.donate.php | 258 - xaseco/plugins/plugin.localdatabase.php | 1023 --- xaseco/plugins/plugin.matchsave.php | 1370 ---- xaseco/plugins/plugin.ml_howto.php | 137 - xaseco/plugins/plugin.msglog.php | 106 - xaseco/plugins/plugin.musicserver.php | 851 --- xaseco/plugins/plugin.muting.php | 247 - xaseco/plugins/plugin.panels.php | 898 --- xaseco/plugins/plugin.rasp.php | 1065 --- xaseco/plugins/plugin.rasp_chat.php | 472 -- xaseco/plugins/plugin.rasp_irc.php | 166 - xaseco/plugins/plugin.rasp_jukebox.php | 1902 ------ xaseco/plugins/plugin.rasp_karma.php | 334 - xaseco/plugins/plugin.rasp_nextmap.php | 78 - xaseco/plugins/plugin.rasp_nextrank.php | 80 - xaseco/plugins/plugin.rasp_votes.php | 1158 ---- xaseco/plugins/plugin.rounds.php | 133 - xaseco/plugins/plugin.rpoints.php | 262 - xaseco/plugins/plugin.style.php | 202 - xaseco/plugins/plugin.tmxinfo.php | 313 - xaseco/plugins/plugin.track.php | 184 - xaseco/plugins/plugin.uptodate.php | 147 - xaseco/rasp.settings.php | 158 - xaseco/rasp.xml | 81 - xaseco/styles/00README.txt | 24 - xaseco/styles/Black.xml | 31 - xaseco/styles/BlackBlur.xml | 31 - xaseco/styles/Blue.xml | 31 - xaseco/styles/BlueBlur.xml | 31 - xaseco/styles/Cyan.xml | 31 - xaseco/styles/CyanBlur.xml | 31 - xaseco/styles/DarkBlur.xml | 31 - xaseco/styles/DarkTransp.xml | 31 - xaseco/styles/Gray.xml | 31 - xaseco/styles/GrayBlur.xml | 31 - xaseco/styles/Green.xml | 31 - xaseco/styles/GreenBlur.xml | 31 - xaseco/styles/LightTransp.xml | 31 - xaseco/styles/LightTransp2.xml | 31 - xaseco/styles/LightTransp3.xml | 31 - xaseco/styles/Orange.xml | 31 - xaseco/styles/OrangeBlur.xml | 31 - xaseco/styles/ProgressBar.xml | 32 - xaseco/styles/WhiteCard.xml | 31 - xaseco/styles/WhiteRect.xml | 31 - xaseco/styles/WhiteRound.xml | 31 - xaseco/text.tpl | 11 - xaseco/votes.config.php | 151 - 195 files changed, 73 insertions(+), 53531 deletions(-) rename entrypoint.sh => entrypoint-tmserver.sh (67%) rename tmserver/GameData/Config/{default.txt => config.txt} (98%) rename tmserver/GameData/Tracks/MatchSettings/{default.txt => playlist.txt} (100%) create mode 100644 tmserver/Logs/ConsoleLog.1352.txt delete mode 100644 xaseco/Aseco.sh delete mode 100644 xaseco/DOCS/Features_080.html delete mode 100644 xaseco/DOCS/Features_095.html delete mode 100644 xaseco/DOCS/Features_103.html delete mode 100644 xaseco/DOCS/Features_116.html delete mode 100644 xaseco/DOCS/ListCallbacksForever.html delete mode 100644 xaseco/DOCS/ListCallbacksNations.html delete mode 100644 xaseco/DOCS/ListDedimania.html delete mode 100644 xaseco/DOCS/ListMethodsForever.html delete mode 100644 xaseco/DOCS/ListMethodsNations.html delete mode 100644 xaseco/DOCS/OLD/Jfreu install.txt delete mode 100644 xaseco/DOCS/OLD/Jfreu's plugin.txt delete mode 100644 xaseco/DOCS/OLD/README-autotime.txt delete mode 100644 xaseco/DOCS/OLD/README.txt delete mode 100644 xaseco/DOCS/OLD/ReadMe.pdf delete mode 100644 xaseco/DOCS/OLD/plugin.sminfo.php delete mode 100644 xaseco/DOCS/OLD/sminfofetcher.inc.php delete mode 100644 xaseco/DOCS/admin_abilities.html delete mode 100644 xaseco/DOCS/aseco_commands.doc delete mode 100644 xaseco/DOCS/aseco_commands.html delete mode 100644 xaseco/DOCS/index.html delete mode 100644 xaseco/DOCS/overview.html delete mode 100644 xaseco/DOCS/quickstart_tmf.html delete mode 100644 xaseco/DOCS/quickstart_tmn.html delete mode 100644 xaseco/DOCS/repairnations.php delete mode 100644 xaseco/DOCS/repairrecs.php delete mode 100644 xaseco/DOCS/sm_hub.html delete mode 100644 xaseco/DOCS/tm2_hub.html delete mode 100644 xaseco/DOCS/tmf_hub.html delete mode 100644 xaseco/DOCS/updatepanels.php delete mode 100644 xaseco/DOCS/upgrades.html delete mode 100644 xaseco/access.xml delete mode 100644 xaseco/adminops.xml delete mode 100644 xaseco/aseco.php delete mode 100644 xaseco/autotime.xml delete mode 100644 xaseco/bannedips.xml delete mode 100644 xaseco/config.xml delete mode 100644 xaseco/dedimania.xml delete mode 100644 xaseco/html.tpl delete mode 100644 xaseco/includes/GbxRemote.bem.php delete mode 100644 xaseco/includes/GbxRemote.inc.php delete mode 100644 xaseco/includes/GbxRemote.response.php delete mode 100644 xaseco/includes/basic.inc.php delete mode 100644 xaseco/includes/gbxchallinfo.inc.php delete mode 100644 xaseco/includes/gbxdatafetcher.inc.php delete mode 100644 xaseco/includes/manialinks.inc.php delete mode 100644 xaseco/includes/ogg_comments.inc.php delete mode 100644 xaseco/includes/rasp.funcs.php delete mode 100644 xaseco/includes/tmndatafetcher.inc.php delete mode 100644 xaseco/includes/tmxinfofetcher.inc.php delete mode 100644 xaseco/includes/tmxinfosearcher.inc.php delete mode 100644 xaseco/includes/types.inc.php delete mode 100644 xaseco/includes/urlsafebase64.php delete mode 100644 xaseco/includes/web_access.inc.php delete mode 100644 xaseco/includes/xmlparser.inc.php delete mode 100644 xaseco/includes/xmlrpc_db.inc.php delete mode 100644 xaseco/jfreu.config.php delete mode 100644 xaseco/localdatabase.xml delete mode 100644 xaseco/localdb/aseco.sql delete mode 100644 xaseco/localdb/extra.sql delete mode 100644 xaseco/localdb/rasp.sql delete mode 100644 xaseco/matchsave.xml delete mode 100644 xaseco/musicserver.xml delete mode 100644 xaseco/panels/00README.txt delete mode 100644 xaseco/panels/AdminAboveCPList.xml delete mode 100644 xaseco/panels/AdminAboveCPListWide.xml delete mode 100644 xaseco/panels/AdminAboveChat.xml delete mode 100644 xaseco/panels/AdminAboveChatWide.xml delete mode 100644 xaseco/panels/AdminAboveSpeed.xml delete mode 100644 xaseco/panels/AdminAboveSpeed2.xml delete mode 100644 xaseco/panels/AdminBelowChat.xml delete mode 100644 xaseco/panels/AdminBottomCenter.xml delete mode 100644 xaseco/panels/AdminBottomCenterWide.xml delete mode 100644 xaseco/panels/AdminCallVote.xml delete mode 100644 xaseco/panels/AdminCallVoteBlur.xml delete mode 100644 xaseco/panels/AdminLeftEdge.xml delete mode 100644 xaseco/panels/AdminRightEdge.xml delete mode 100644 xaseco/panels/AdminTopCenter.xml delete mode 100644 xaseco/panels/AdminTopCenterWide.xml delete mode 100644 xaseco/panels/DonateBelowCPList.xml delete mode 100644 xaseco/panels/DonateBelowCPListRM.xml delete mode 100644 xaseco/panels/DonateLeftEdge.xml delete mode 100644 xaseco/panels/DonateLeftEdge2.xml delete mode 100644 xaseco/panels/DonateLeftSmall.xml delete mode 100644 xaseco/panels/DonateRightEdge.xml delete mode 100644 xaseco/panels/DonateRightEdge2.xml delete mode 100644 xaseco/panels/DonateRightSmall.xml delete mode 100644 xaseco/panels/DonateTopLeft.xml delete mode 100644 xaseco/panels/RecordsLeftBlackBold.xml delete mode 100644 xaseco/panels/RecordsLeftBlue.xml delete mode 100644 xaseco/panels/RecordsLeftBlueBold.xml delete mode 100644 xaseco/panels/RecordsLeftBlueLight.xml delete mode 100644 xaseco/panels/RecordsLeftGray.xml delete mode 100644 xaseco/panels/RecordsLeftGrayBold.xml delete mode 100644 xaseco/panels/RecordsLeftItalic.xml delete mode 100644 xaseco/panels/RecordsLeftOrange.xml delete mode 100644 xaseco/panels/RecordsLeftSize1.xml delete mode 100644 xaseco/panels/RecordsLeftSize1NoDedi.xml delete mode 100644 xaseco/panels/RecordsLeftSize2.xml delete mode 100644 xaseco/panels/RecordsLeftSize2NoDedi.xml delete mode 100644 xaseco/panels/RecordsLeftWhite.xml delete mode 100644 xaseco/panels/RecordsRightBottom.xml delete mode 100644 xaseco/panels/RecordsRightBottomNoDedi.xml delete mode 100644 xaseco/panels/RecordsRightBottomRM.xml delete mode 100644 xaseco/panels/StatsNations.xml delete mode 100644 xaseco/panels/StatsUnited.xml delete mode 100644 xaseco/panels/VoteBelowChat.xml delete mode 100644 xaseco/panels/VoteBottomCenter.xml delete mode 100644 xaseco/panels/VoteBottomCenterTransp.xml delete mode 100644 xaseco/panels/VoteCallVote.xml delete mode 100644 xaseco/panels/VoteTopCenter.xml delete mode 100644 xaseco/plugins.xml delete mode 100644 xaseco/plugins/chat.admin.php delete mode 100644 xaseco/plugins/chat.dedimania.php delete mode 100644 xaseco/plugins/chat.help.php delete mode 100644 xaseco/plugins/chat.laston.php delete mode 100644 xaseco/plugins/chat.lastwin.php delete mode 100644 xaseco/plugins/chat.me.php delete mode 100644 xaseco/plugins/chat.players.php delete mode 100644 xaseco/plugins/chat.players2.php delete mode 100644 xaseco/plugins/chat.records.php delete mode 100644 xaseco/plugins/chat.records2.php delete mode 100644 xaseco/plugins/chat.recrels.php delete mode 100644 xaseco/plugins/chat.server.php delete mode 100644 xaseco/plugins/chat.songmod.php delete mode 100644 xaseco/plugins/chat.stats.php delete mode 100644 xaseco/plugins/chat.wins.php delete mode 100644 xaseco/plugins/jfreu.chat.php delete mode 100644 xaseco/plugins/jfreu.lite.php delete mode 100644 xaseco/plugins/jfreu.plugin.php delete mode 100644 xaseco/plugins/jfreu/jfreu.bans.xml delete mode 100644 xaseco/plugins/jfreu/jfreu.config.xml delete mode 100644 xaseco/plugins/jfreu/jfreu.vips.xml delete mode 100644 xaseco/plugins/mistral.idlekick.php delete mode 100644 xaseco/plugins/plugin.access.php delete mode 100644 xaseco/plugins/plugin.autotime.php delete mode 100644 xaseco/plugins/plugin.chatlog.php delete mode 100644 xaseco/plugins/plugin.checkpoints.php delete mode 100644 xaseco/plugins/plugin.dedimania.php delete mode 100644 xaseco/plugins/plugin.donate.php delete mode 100644 xaseco/plugins/plugin.localdatabase.php delete mode 100644 xaseco/plugins/plugin.matchsave.php delete mode 100644 xaseco/plugins/plugin.ml_howto.php delete mode 100644 xaseco/plugins/plugin.msglog.php delete mode 100644 xaseco/plugins/plugin.musicserver.php delete mode 100644 xaseco/plugins/plugin.muting.php delete mode 100644 xaseco/plugins/plugin.panels.php delete mode 100644 xaseco/plugins/plugin.rasp.php delete mode 100644 xaseco/plugins/plugin.rasp_chat.php delete mode 100644 xaseco/plugins/plugin.rasp_irc.php delete mode 100644 xaseco/plugins/plugin.rasp_jukebox.php delete mode 100644 xaseco/plugins/plugin.rasp_karma.php delete mode 100644 xaseco/plugins/plugin.rasp_nextmap.php delete mode 100644 xaseco/plugins/plugin.rasp_nextrank.php delete mode 100644 xaseco/plugins/plugin.rasp_votes.php delete mode 100644 xaseco/plugins/plugin.rounds.php delete mode 100644 xaseco/plugins/plugin.rpoints.php delete mode 100644 xaseco/plugins/plugin.style.php delete mode 100644 xaseco/plugins/plugin.tmxinfo.php delete mode 100644 xaseco/plugins/plugin.track.php delete mode 100644 xaseco/plugins/plugin.uptodate.php delete mode 100644 xaseco/rasp.settings.php delete mode 100644 xaseco/rasp.xml delete mode 100644 xaseco/styles/00README.txt delete mode 100644 xaseco/styles/Black.xml delete mode 100644 xaseco/styles/BlackBlur.xml delete mode 100644 xaseco/styles/Blue.xml delete mode 100644 xaseco/styles/BlueBlur.xml delete mode 100644 xaseco/styles/Cyan.xml delete mode 100644 xaseco/styles/CyanBlur.xml delete mode 100644 xaseco/styles/DarkBlur.xml delete mode 100644 xaseco/styles/DarkTransp.xml delete mode 100644 xaseco/styles/Gray.xml delete mode 100644 xaseco/styles/GrayBlur.xml delete mode 100644 xaseco/styles/Green.xml delete mode 100644 xaseco/styles/GreenBlur.xml delete mode 100644 xaseco/styles/LightTransp.xml delete mode 100644 xaseco/styles/LightTransp2.xml delete mode 100644 xaseco/styles/LightTransp3.xml delete mode 100644 xaseco/styles/Orange.xml delete mode 100644 xaseco/styles/OrangeBlur.xml delete mode 100644 xaseco/styles/ProgressBar.xml delete mode 100644 xaseco/styles/WhiteCard.xml delete mode 100644 xaseco/styles/WhiteRect.xml delete mode 100644 xaseco/styles/WhiteRound.xml delete mode 100644 xaseco/text.tpl delete mode 100644 xaseco/votes.config.php diff --git a/Dockerfile b/Dockerfile index cda6652..d211f94 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,17 @@ FROM fanyx/php:5.6 -RUN mkdir /opt/tmserver /opt/xaseco +RUN mkdir /opt/tmserver COPY tmserver/ /opt/tmserver/ -COPY xaseco/ /opt/xaseco/ -COPY ./entrypoint.sh / +COPY ./entrypoint-tmserver.sh / RUN apt update \ && apt install pwgen RUN groupadd trackmania RUN useradd -M -g trackmania trackmania RUN chown -R trackmania:trackmania /opt/tmserver -RUN chown -R trackmania:trackmania /opt/xaseco -RUN chown trackmania:trackmania /entrypoint.sh +RUN chown trackmania:trackmania /entrypoint-tmserver.sh USER trackmania WORKDIR /opt/tmserver -CMD ["bash"] +CMD ["/entrypoint-tmserver.sh"] diff --git a/docker-compose.yml b/docker-compose.yml index cf2c39e..25ed07f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,52 +1,46 @@ version: '3' - services: - db: - image: mysql:5 - container_name: trackmania_db - restart: always - environment: - - MYSQL_ROOT_PASSWORD= - - MYSQL_DATABASE=trackmania - - MYSQL_USER=trackmania - - MYSQL_PASSWORD= - volumes: - - /db-data:/var/lib/mysql - pma: - image: phpmyadmin/phpmyadmin - container_name: trackmania_phpmyadmin - depends_on: - - db - environment: - - PMA_ARBITRARY=1 - restart: always - ports: - - "8080:80" - tmserver: - image: fanyx/tmserver - container_name: trackmania_tmserver - depends_on: - - db - restart: always - environment: - - SERVER_LOGIN= - - SERVER_LOGIN_PASSWORD= - - DB_HOST=db - - DB_NAME=trackmania - - DB_LOGIN=trackmania - - DB_LOGIN_PASSWORD= - - SERVER_PORT=2350 - - SERVER_P2P_PORT=3450 - - SERVER_SA_PASSWORD= - - SERVER_ADMIN_PASSWORD= - - SERVER_NAME=Trackmania Server - - SERVER_COMMENT=This is a Trackmania Server - - SERVER_PASSWORD= - volumes: - - /xaseco:/opt/xaseco - - /tmserver:/opt/tmserver - ports: - - "2350" - - "2350/udp" - - "3450" - - "3450/udp" + services: + db: + image: mysql:5 + container_name: trackmania_db + restart: always + environment: + - MYSQL_ROOT_PASSWORD= + - MYSQL_DATABASE=trackmania + - MYSQL_USER=trackmania + - MYSQL_PASSWORD= + volumes: + - /db-data:/var/lib/mysql + pma: + image: phpmyadmin/phpmyadmin + container_name: trackmania_phpmyadmin + depends_on: + - db + environment: + - PMA_ARBITRARY=1 + restart: always + ports: + - "8080:80" + tmserver: + build: . + image: fanyx/tmserver + container_name: trackmania_tmserver + depends_on: + - db + restart: always + environment: + - SERVER_LOGIN= + - SERVER_LOGIN_PASSWORD= + - SERVER_PORT=2350 + - SERVER_P2P_PORT=3450 + - SERVER_SA_PASSWORD= + - SERVER_ADMIN_PASSWORD= + - SERVER_NAME=Trackmania Server + - SERVER_COMMENT=This is a Trackmania Server + - SERVER_PASSWORD= + ports: + - "2350:2350" + - "2350:2350/udp" + - "3450:3450" + - "3450:3450/udp" diff --git a/entrypoint.sh b/entrypoint-tmserver.sh similarity index 67% rename from entrypoint.sh rename to entrypoint-tmserver.sh index df42c90..5e1570f 100755 --- a/entrypoint.sh +++ b/entrypoint-tmserver.sh @@ -3,6 +3,11 @@ set -e +if [[ -e /etc/tmserver/env ]] +then + . /etc/tmserver/env +fi + # Evaluate all the available environment variables if [[ -z "${SERVER_LOGIN}" ]]; then echo "Server account login is missing. Server cannot start." @@ -12,22 +17,6 @@ if [[ -z "${SERVER_LOGIN_PASSWORD}" ]]; then echo "Server account password is missing. Server cannot start." exit 9 fi -if [[ -z "${DB_HOST}" ]]; then - echo "MySQL database host was not set. Defaulting to 'db' for the docker-compose configuration" - DB_HOST="db" -fi -if [[ -z "${DB_NAME}" ]]; then - echo "No database name was set. Defaulting to 'trackmania' for the docker-compose configuration" - DB_NAME="trackmania" -fi -if [[ -z "${DB_LOGIN}" ]]; then - echo "No database user was set. Defaulting to 'trackmania' for th docker-compose configuration" - DB_LOGIN="trackmania" -fi -if [[ -z "${DB_LOGIN_PASSWORD}" ]]; then - echo "No database user password was set. The server cannot connect to the database. Aborting" - exit 9 -fi if [[ -z "${SERVER_PORT}" ]]; then echo "No server port was set. Defaulting to port 2350" SERVER_PORT="2350" @@ -60,21 +49,7 @@ fi # Evaluation over # Commencing substition in config files -#Xaseco files -cd /opt/xaseco -pwd - -sed -i -e "s/--\$SERVER_SA_PASSWORD--/$SERVER_SA_PASSWORD/" /opt/xaseco/config.xml - -sed -i -e "s/--\$DB_HOST--/$DB_HOST/" \ - -e "s/--\$DB_LOGIN--/$DB_LOGIN/" \ - -e "s/--\$DB_LOGIN_PASSWORD--/$DB_LOGIN_PASSWORD/" \ - -e "s/--\$DB_NAME--/$DB_NAME/" \ - /opt/xaseco/localdatabase.xml - #Trackmania Files -cd /opt/tmserver -pwd sed -i -e "s/--\$SERVER_SA_PASSWORD--/$SERVER_SA_PASSWORD/" \ -e "s/--\$SERVER_ADM_PASSWORD--/$SERVER_ADM_PASSWORD/" \ @@ -85,5 +60,6 @@ sed -i -e "s/--\$SERVER_SA_PASSWORD--/$SERVER_SA_PASSWORD/" \ -e "s/--\$SERVER_PASSWORD--/$SERVER_PASSWORD/" \ -e "s/--\$SERVER_PORT--/$SERVER_PORT/" \ -e "s/--\$SERVER_P2P_PORT--/$SERVER_P2P_PORT/" \ - /opt/tmserver/GameData/Config/default.txt + /opt/tmserver/GameData/Config/config.txt +exec "/opt/tmserver/TrackmaniaServer" "/nodaemon" "/internet" "/game_settings=MatchSettings/playlist.txt" "/dedicated_cfg=config.txt" diff --git a/tmserver/GameData/Config/default.txt b/tmserver/GameData/Config/config.txt similarity index 98% rename from tmserver/GameData/Config/default.txt rename to tmserver/GameData/Config/config.txt index 457fd90..f6c9394 100644 --- a/tmserver/GameData/Config/default.txt +++ b/tmserver/GameData/Config/config.txt @@ -70,7 +70,7 @@ 600 - --$SERVER_XMLRPC_PORT-- + 5000 True diff --git a/tmserver/GameData/Game.FidCache.Gbx b/tmserver/GameData/Game.FidCache.Gbx index 932b4ee77352ecbb3c2dc7fdd8f38544c4844eaa..d852b97946d94c98c459a7b66f63aaec1cacdf62 100644 GIT binary patch delta 3973 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 delta 6761 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 J{&xfb{|`7zV|f4o diff --git a/tmserver/GameData/Tracks/MatchSettings/default.txt b/tmserver/GameData/Tracks/MatchSettings/playlist.txt similarity index 100% rename from tmserver/GameData/Tracks/MatchSettings/default.txt rename to tmserver/GameData/Tracks/MatchSettings/playlist.txt diff --git a/tmserver/Logs/ConsoleLog.1352.txt b/tmserver/Logs/ConsoleLog.1352.txt new file mode 100644 index 0000000..92290ff --- /dev/null +++ b/tmserver/Logs/ConsoleLog.1352.txt @@ -0,0 +1,16 @@ +[2019/05/12 10:20:20] Initializing... +[2019/05/12 10:20:20] Loading system configuration... +[2019/05/12 10:20:20] ...ERROR: No configuration file. +...Could not load the system configuration +[2019/05/12 10:20:20] Loading cache... +[2019/05/12 10:20:20] ...OK +[2019/05/12 10:20:20] Listening for xml-rpc commands on port 5000. +[2019/05/12 10:20:20] No match settings file +[2019/05/12 10:20:20] Server not started: no ServerName specified. +[2019/05/12 10:20:20] Connecting to master server... +[2019/05/12 10:20:21] ...OK +[2019/05/12 10:20:21] Identifying on master server... +[2019/05/12 10:20:21] ...ERROR: invalid login +[2019/05/12 10:20:21] Please wait, loading... +[2019/05/12 10:20:23] Server not running, exiting. +[2019/05/12 10:20:24] Exiting... diff --git a/tmserver/tmserver b/tmserver/tmserver index c761b38..9f4de94 100755 --- a/tmserver/tmserver +++ b/tmserver/tmserver @@ -61,7 +61,7 @@ case "$1" in fi cd $TMDIR - ./TrackmaniaServer /internet /game_settings=${GAME_SETTINGS} /dedicated_cfg=${DEDICATED_CFG} & + ./TrackmaniaServer /nodaemon /internet /game_settings=${GAME_SETTINGS} /dedicated_cfg=${DEDICATED_CFG} & PID=$! ps -p ${PID} > /dev/null 2>&1 if [[ "$?" -ne "0" ]] diff --git a/xaseco/Aseco.sh b/xaseco/Aseco.sh deleted file mode 100644 index ce78be1..0000000 --- a/xaseco/Aseco.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -php aseco.php TMN aseco.log 2>&1 & -echo $! diff --git a/xaseco/DOCS/Features_080.html b/xaseco/DOCS/Features_080.html deleted file mode 100644 index b66204d..0000000 --- a/xaseco/DOCS/Features_080.html +++ /dev/null @@ -1,208 +0,0 @@ - - - -TrackMania Nations - ASECO/RASP new features v0.8 - - - - - - - - - - -

-TrackMania Nations -


- -

New features and other changes in the v0.8 release of ASECO/RASP:

- -
    -
  • FAST-like records handling:
    - with every new/improved record, players see the old place and the time difference compared to their old record - -
  • show secured records (incremental improvements of the same place) and equaled records to all players - -
  • FAST-like record reports:
    - before each track (optionally) show the places & times of the first 8 (configurable) players, along with those of all other online players;
    - after each track show the places & times of the first 8 (configurable) players, along with any new/improved records, and the total count of new/improved records;
    - also, all online players are shown in italics - -
  • the record report before a track is also shown to a newly connecting player - -
  • enhanced /recs command to display all records (paginated) on current track, not just the first 5 - -
  • new /newrecs command to show newly driven records so far on current track - -
  • new /liverecs command to show the top-6 (configurable) records and records of online players on the current track, whether new or not - -
  • new /firstrec and /lastrec commands to quickly show the best and worst ranked records on a track (without browsing /recs) - -
  • new /diffrec command to quickly show the time difference between your /pb and the /firstrec on a track - -
  • new /nextrec command to quickly show the next better ranked record (compared to your /pb) to beat on a track - -
  • new /recrange command to show the time difference between the first and last ranked records on a track - -
  • FAST-like rounds reports:
    - after each run show the places & times of the first 8 (configurable) players, and just the places of the rest;
    - new/improved records are highlighted and always shown with the time - -
  • created an EndRound event in the events handling for the aforementioned rounds reports - -
  • connect new player message (via Jfreu plugin) enhanced to show nation, ladder rank & server rank
    - (also, if connecting player is an admin, show 'Admin' instead of 'New Player') - -
  • new /list options to complement /list nofinish:
    - /list norank displays all tracks the player finished but doesn't have a ranked record on
    - /list nogold displays all tracks the player finished but didn't beat the Gold time on - -
  • new /jukebox display option to display tracks & requesters in a pop-up - -
  • new /jukebox drop option to allow a player to drop his/her track in order to jukebox a different one - -
  • new /jukebox help option to display all jukebox facilities - -
  • new /best & /worst commands allowing players to display their records sorted in best or worst order - -
  • new /summary command to show the total number of ranked records, and the top 3 of the best ones - -
  • new /toprecs command to display the top-100 of players with the most ranked records - -
  • /top5 command replaced by /top10 command, and new /top100 command added to display the server ranks of many more players than just the first 5 - -
  • enhanced /stats command to display lots more details about the player (supersedes /infos command from Jfreu plugin) - -
  • moved Jfreu's /write_list command functionality to /jfreu writelists - -
  • new /server command to display lots of details about the server, including the admin contact
    - (if you'd like to display more contact details, better use slly's chat.contact.php plugin) - -
  • new /nations command to display the top-10 nations with the most visiting players - -
  • new /ranks command to display the server ranks of all online players - -
  • new /clans command to display which clans all online players belong to - -
  • new /topclans command to display top-10 of all clans (determined by averaging the ranks of all players in each clan) - -
  • new /wins command to quickly show the player's win count - -
  • new /topwins command to display the top-100 of players with the most wins - -
  • new /active command to display the top-100 players that spent the most time on the server - -
  • new /track command to show details about the current track - -
  • new /history command to show the 10 most recently played tracks - -
  • new /playtime command to show the time the current track has been playing - -
  • optional showing of total playtime at the end of the track (only in Rounds & Team modes - playtime is fixed in Time Attack & Laps) - -
  • new /time command to show the current time and date on the server - -
  • new /bootme command so the player can leave (kick himself) without using the Escape menu - -
  • /msg command renamed to /pm (private message) and updated to show who's sending the message to whom - -
  • new /pma command so an admin can send private message to a player and also to all other online admins (ie. like carbon-copy in mails) - -
  • new admin command: /admin pm to send private message from one admin to all other online admins - -
  • enhancement of /players command: now shows column of player IDs, which can subsequently be used with /pm (for all users) and /pma (for admins) instead of logins (which are sometimes hard to type);
    - also shows Nations of all players - -
  • added shortcuts for common admin commands:
    - /admin nextmap or /admin next
    - /admin restartmap or /admin res
    - /admin cancel or /admin can
    - /admin clearjukebox or /admin cjb - -
  • new admin command: /admin endround (or shortcut /admin er) forces end of a round without restarting the whole track - -
  • new admin command: /admin replaymap (or shortcut /admin replay) to requeue the current track at the start of the jukebox so it will play again, after it ends and ladder points have been awarded - -
  • new admin command: /admin dropjukebox # (shortcut /admin djb) to drop any track from the jukebox - -
  • new admin commands: /admin showbanlist and /admin showblacklist to display the lists of banned and blacklisted player(s) - -
  • new admin commands: /admin writeblacklist and /admin readblacklist to write/read the player blacklist to/from disk - -
  • enhancement of /admin warn, /admin kick, /admin ban & /admin black to accept player IDs from /players command - -
  • new admin command: /admin kickghost to kick a partially disconnected player without checking for valid login (doesn't work with player ID) - -
  • enhancement of /admin warn,kick,kickghost,ban,black,er,res,next,can,cjb commands to show nickname of the admin performing them - -
  • more admin powers for the jukebox: admins can jukebox a track that was recently played (players have to wait until it expires from the track history), and admins can add multiple tracks (players can add only one) - -
  • other admin powers: admin can use player logins or IDs with /stats, /best, /worst, /summary - -
  • new option $allowpublickarma to disable public karma votes (so that only /++ and /-- work) - -
  • new option $feature_jukebox to disable actual jukebox-ing of tracks, while leaving /list enabled (as long as plugin remains included) - -
  • new option $jukebox_skipleft to enable skipping of a jukebox-ed track after its requester left - -
  • new option $prune_records_times to enable pruning at start-up of records/rs_times for deleted tracks (recommended so they don't clutter up the /best & /worst lists) - -
  • new option to log all player chat, not just use of chat commands - -
  • various other logging enhancements - -
  • enhanced Idle-kick plugin by Mistral: kicks AFK players after two tracks, except speccers (configurable) - -
  • tracks downloaded from TMX are saved by track name, not TMX ID - -
  • new general chat commands: /gr, /bgm, /thx, /brb, /afk - -
  • new /helpall command to display complete paginated help overview of all commands, and also pagination of /admin help & /jfreu help - -
  • numerous cosmetic tweaks, e.g. consistent formatting of all messages to players, and improved contrast in all pop-ups - -
  • improved stripColors function based on Bilge/AM's TMU stripFormatting function - -
  • many bug fixes, e.g. in /list help, the original records handling, in /nextmap, player connects, and more (see below) - -
  • numerous code optimizations and layout tweaks, including a consistent Tab-based layout that's customizable with vim's modeline settings while preserving correct indentation throughout the entire code base - -
  • Jfreu's up-to-date check at start-up is disabled due to timeouts on the link - -
  • Jfreu's childish /fake command has been disabled ;) as well as the non-functional /password command -
- -

Bug fixes in the v0.8 release of ASECO/RASP:

- -
    -
  • player connect: very rarely a player connects to the server but the player data is not received in ASECO, resulting in a sort of zombie player. Despite several different approaches, this couldn't be fixed. So instead, to prevent further problems for such a player (like records not being stored), an explanation is displayed (in chat and pop-up) and the player is kicked in order to re-join. - -
  • /players <string>: using a string with the /players command allows you to search for a specific player with that string in the login and/or nickname. This string didn't match if it occurred at the beginning of the player's login/nickname – fixed. - -
  • /list help: after using /list help, further /list commands would not always work properly – fixed. - -
  • /nextmap: this didn't work if the current track was the last one in the set, and for servers with more than 300 tracks if the current track was beyond that 300th track – fixed. - -
  • minimum records: the minimum number of ranked records required for a server rank was actually $minrecs+1 instead of $minrecs – fixed - -
  • special characters: very rarely a player nickname contains a special character that could prevent it from being stored into the database – fixed by consistently quoting all strings sent to the database - -
  • records: messages about new records were inconsistent ("claimed", "took") – fixed. - -
  • server messages: inconsistent indications whether a server message was broadcast to all players or sent to one – fixed, all broadcasts start with ">>" and all messages to the player with ">". -
- -
-
-Copyright © 2007-2013 – Frans P. de Vries <tm@gamers.org> -            -Last updated 24-Oct-2007 -
- - diff --git a/xaseco/DOCS/Features_095.html b/xaseco/DOCS/Features_095.html deleted file mode 100644 index fd422d5..0000000 --- a/xaseco/DOCS/Features_095.html +++ /dev/null @@ -1,890 +0,0 @@ - - - -TrackMania Nations - ASECO/RASP release notes v0.95 - - - - - - - - - - -

-TrackMania Nations -


- -

New features and other changes in the v0.95 release of ASECO/RASP:

- -
    -
  • new Dedimania world records support (derived from FAST) with the following features: -
      -
    • Dedimania is an online World Records database for all TrackMania games with the official site at http://www.dedimania.com/ and its records database at http://www.dedimania.com/tmstats/?do=stat ; it maintains 30 records for each track in any game (TMO/TMS/TMN/TMU) and any mode (TimeAttack, Rounds, Team, Laps) - -
    • the Dedimania system consists of two new plugins: plugin.dedimania.php which handles all communication with the central Dedimania server (together with new includes GbxRemote.response.php, xmlrpc_db.inc.php, web_access.inc.php and urlsafebase64.php) and keeps track of new records, and chat.dedimania.php which provides a variety of commands to show records and their relations on the current track - -
    • the following new commands are available for Dedimania world records, analogous to the existing local records commands: -
        -
      • /helpdedi: Displays info about the Dedimania records system (alias: /dedihelp) -
      • /dedirecs: Displays all Dedimania records on current track -
      • /dedinew: Shows new Dedimania records on current track -
      • /dedilive: Shows Dedimania records of online players -
      • /dedipb: Shows your Dedimania personal best on current track -
      • /dedifirst: Shows first Dedimania record on current track -
      • /dedilast: Shows last Dedimania record on current track -
      • /dedinext: Shows next better Dedimania record to beat -
      • /dedidiff: Shows your difference to first Dedimania record -
      • /dedirange: Shows difference first to last Dedimania record -
      - -
    • new, improved, secured and equalled Dedimania records are shown via chat messages just like local records, as are the record reports before and after each track, with configuration options identical to those for local records - -
    • the new dedimania.xml configuration file defines various Dedimania parameters and record display settings (akin to config.xml), the server's account (from dedicated.cfg), and all Dedimania messages - -
    • the Dedimania system communicates with the central Dedimania server over port 8003 by default, so you must open that port on your firewall/router; if that's not possible the system falls back on port 80 (http) - -
    • the Dedimania system requires plugin.checkpoints.php (the checkpoints tracking plugin) to be included in plugins.xml, before plugin.dedimania.php - -
    • new <dedimsg> and <dedimsg> colors are defined in config.xml and used in all relevant Dedimania messages so that they can easily be customized - -
    • a new onEverySecond event (which, as the name implies, is triggered once every second) is defined in aseco.php to act as a "heart beat" in communicating with the central Dedimania server - -
    • Laps mode is not supported due to the way checkpoints and laps information is supplied by the TM server - -
    • LAN logins (with IP and port) are ignored by the Dedimania system - -
    • mega thanks to Slig for his generous help in building this system J -
    -
    - -
  • the /tmxinfo output now includes the Awards count for a TMX track - -
  • the /tmxinfo and /tmxrecs commands use cached TMX data for the current track to avoid sending multiple requests to the TMX site - -
  • updated the TMXInfoFetcher class to time out when the TMX site is down, and to return several more info fields including the awards count - -
  • updated the TMNDataFetcher class to time out when the master/ladder servers are down - -
  • the minimum number of ranked players that's included in the computation of the /topclans ranking is now configurable via new config.xml setting <topclans_minplayers> (default: 2) - -
  • new /admin setgamemode command (parameter: ta, rounds, team, laps, stunts) to change the game mode for the next track into the specified mode - -
  • rewrote /admin prev to consult the server's track list instead of maintaining a track filename variable - -
  • /admin replay will now check whether the currect track is already in the jukebox to get replayed, and if so, not queue it again - -
  • the pop-up window displayed by the /admin mta/wall command now starts with the title and nickname of the admin sending the message - -
  • new /admin shutdown command to shut down the TM server and ASECO/RASP - -
  • it is now possible to have chat-based voting commands temporarily disabled while an admin (of any tier) is online (configured via $disable_upon_admin in votes.config.php) - -
  • added /votehelp alias for /helpvote - -
  • the <limit> configuration setting in localdatabase.xml restricts the highest records that are shown to all players since v0.85, but now higher records are still shown to the pertaining player only - -
  • updated the GBXDataFetcher class to handle all known Challenge variants, including Forever - -
  • updated RASP's getChallengeData() function to use the GBXDataFetcher class - -
  • added extra check for 'SuperAdmin' login before connecting to the TM server - -
  • added extra check for TM server build '2006-05-30' during start-up - -
  • added a server Name entry to the start-up header in the log - -
  • improved speed of /server and /nextrank commands - -
  • adjusted some timeouts in the GbxRemote module (thanks Slig and Assembler Maniac) - -
  • an alternate (but untested) GbxRemote.bem.php module is included for big-endian machines (thanks again AM) -
- -

Bug fixes in the v0.95 release of ASECO/RASP:

- -
    -
  • occasionally an ASECO warning could come up during the jukeboxing & track switching process – fixed by the /admin prev rewrite above - -
  • the /list norecent output includes tracks that are no longer present on the server – fixed - -
  • a harmless "Start index out of bound" warning would be logged when the server has exactly 300 tracks (or a multiple thereof) – fixed - -
  • case-sensitivity in a part of the /add and /admin add code could cause a problem loading a track with a case-insensitive duplicate filename – fixed - -
  • renaming tracks downloaded from TMX into a sanitized filename with a unique sequence number (as introduced in v0.91) could still lead to a problem loading a different track with a duplicate filename into the TM server – fixed, the filename now also includes the TMX ID - -
  • /tmxrecs displays an almost empty pop-up when a track has no TMX records – fixed, an error message is shown instead - -
  • the TMXInfoFetcher class doesn't handle tracks with 26-character UIDs – fixed -
- -
- -

New features and other changes in the v0.93 release of ASECO/RASP:

- -
    -
  • new checkpoints tracking support (via plugin.checkpoints.php) with the following features: - -
      -
    • all checkpoints and finishes are now tracked internally -
    • new /cps command to enable/disable displaying the checkpoint passages (since the TM client has it built in for TimeAttack mode, this is only useful in Rounds/Team modes) -
    • the first run on a track only stores all checkpoint passages (as long as the finish is reached) -
    • if /cps is enabled, subsequent runs will display a small pop-up window at each checkpoint for two seconds (just as long as the TM client displays the checkpoint time), containing the time and the difference to the best run so far -
    • if a run has a better finish time, it's stored together with those checkpoints as the new best run -
    • the finish is also a checkpoint, but no pop-up is displayed there -
    • the checkpoints are only remembered for the duration of each track, not stored in the database -
    • a player's display setting is remembered until the next ASECO/RASP restart -
    -
    - -
  • new TMX information support (based on the new TMXInfoFetcher class) with the following features: - -
      -
    • new /tmxinfo command to display the TMX information on the current track or, when given a track ID (from /list) or a TMX ID, any other track; note that with a TMX ID, the UID field in the output remains empty -
    • new /tmxrecs command to display the TMX top-10 world records on the current track or, when given a track ID (from /list) or a TMX ID, any other track -
    • optionally show the TMX World Record at the start of a new track (configurable via new config.xml setting <show_tmxrec>) -
    -
    - -
  • new /song command (based on the new GBXDataFetcher class) to show the (file)name for the current track's song; no method is currently known to decode the title/artist from the song file - -
  • new /admin prev (alias /admin previous) command to quickly revisit the previous track - -
  • new /list option: /list oldest # displays the # oldest tracks added to the server (default: 10) to complement /list newest - -
  • the /statsall output now includes the Wins/Losses (W/L) ratio - -
  • the rounds reports now also work in Team mode - -
  • finalized the optimization (in v0.92) of the jukeboxing & track switching process - -
  • when a TMX /add or chat-based vote is cancelled by the server during the Tab scoreboard, that reset is also logged - -
  • documented the dependencies of all plugins in their respective headers -
- -

Bug fixes in the v0.93 release of ASECO/RASP:

- -
    -
  • when using jfreu.plugin.php (not jfreu.lite.php), if rank limiting is off then newly connected players are not properly entered into the internal players list – fixed - -
  • at ASECO/RASP start-up, authentication errors are ignored (oops) – fixed - -
  • if the same track is /add-ed or /admin add-ed multiple times, multiple files are created in the 'GameData/Tracks/Challenges/TMX/' or 'GameData/Tracks/Challenges/TMXtmp/' directories – fixed, duplicate files for identical tracks are prevented - -
  • if a file already exists in the 'GameData/Tracks/Challenges/TMXtmp/' directory for a /add-ed track and that track is then /add-ed again, the file (with an incremented name) is stored in the 'GameData/Tracks/Challenges/TMX/' directory instead – fixed - -
  • in the TMNDataFetcher, if the serverlogin for an online player starts with a non-alphanumeric character (e.g. '-') then the servernation isn't found – fixed - -
  • if plugin.matchsave.php is not included in plugins.xml (which is the default), /admin match begin crashes the system – fixed, a warning is shown if the plugin is not included - -
  • the administrator check for the chat_tc_listen ability doesn't work – fixed -
- -
- -

New features and other changes in the v0.92b release of ASECO/RASP:

- -
    -
  • all temporary bans are now stored in a dynamic bans file, configurable via $bans_file in jfreu.config.php (default: plugins/jfreu/jfreu.bans.xml), and automatically restored after an ASECO/RASP restart -
- -

Bug fixes in the v0.92b release of ASECO/RASP:

- -
    -
  • sometimes when a player connects, the TeamName is not available to store in the 'players' table, and thus it doesn't show up in the /clans command – fixed this long-standing issue (which had become worse due to the faster GbxRemote module) with a TMNDataFetcher call - -
  • the /jfreu unban command doesn't properly clear the ban – fixed - -
  • there are some minor formatting glitches in the output of /stats, /statsall and /jfreu listbans – fixed -
- -
- -

New features and other changes in the v0.92 release of ASECO/RASP:

- -
    -
  • new /statsall command to display any player's world stats (based on an updated TMNDataFetcher class by F*ckfish); you can use a player ID when the player is online on the current server, and otherwise any login (even of players that never visited the current server) - -
  • a new version of GbxRemote.inc.php not only addresses some low-level communication problems on AMD64 systems, but more importantly speeds up communication with the TM server – and therefore the responsiveness of all commands – very significantly (thanks Xerrez & Slig); the effect that the small chat window goes blank for a little while is also reduced - -
  • optimized calculation of all players' server ranks after each track - -
  • more optimization of the jukeboxing & track switching process to (hopefully definitively) prevent stuck tracks - -
  • the /nextrank command now optionally shows the difference in record positions (RP), i.e. the combined number of positions that your records need to be improved in order to catch up with the next better ranked player (configured via $nextrank_show_rp in rasp.settings.php) - -
  • by appending an 'h' (case-insensitive) to the time parameter of /jfreu banfor, the specified value is converted from minutes into hours (e.g. /jfreu banfor 4H <login> for a four-hour ban) - -
  • correspondingly, all temporary ban messages now show the remaining time as "X hours XX mins" if it's more than an hour - -
  • new /jfreu unban command to cancel the temporary ban, from the badword filter and /jfreu banfor, of a player (login or ID) - -
  • new /jfreu listbans command to display the list of temporarily banned players; if the remaining time of a ban is more than an hour, it's shown as XhXX - -
  • the /stats output now includes the last online information (like /laston) - -
  • the /laston command can now be used without a parameter (however little use that has) - -
  • added a server Version entry to the start-up header in the log - -
  • the commands list (in HTML and Word) now includes all /admin and /jfreu commands as well -
- -

Bug fixes in the v0.92 release of ASECO/RASP:

- -
    -
  • when a player equals his/her record, it's still stored in the 'records' table with the new date/time, in effect changing (increasing) its position if there are other players with the exact same time driven after that record's original date/time (ugly oops) – fixed, equalled records are not stored again; to repair the records that were affected by this bug, you can use this PHP script - -
  • when using jfreu.plugin.php (not jfreu.lite.php), if a player is banned by the badword filter or /jfreu banfor command, then the "left the game" message is still shown – fixed - -
  • also when using jfreu.plugin.php, if a player is kicked once by the ranklimit system, badword filter or /jfreu banfor command, which skip the "left the game" message, then that message would no longer be shown when that player leaves normally again after a subsequent visit – fixed - -
  • Player_IDs are not accepted when the pertaining player is offline, even when that would make sense (e.g. /admin unban <x> after /admin listbans) – fixed - -
  • when two (or more) players have the exact same Avg value, they receive the same server rank – fixed - -
  • when the Avg value shown by /rank, /top10, /top100, etc. rounds to a whole number, the ".0" decimal isn't shown – fixed - -
  • the v0.91 overhaul of the full Jfreu plugin warranted a version update to 0.14 – fixed -
- -
- -

New features and other changes in the v0.91 release of ASECO/RASP:

- -
    -
  • enhancements and fixes for the full Jfreu plugin (jfreu.plugin.php & jfreu.chat.php):

    - -
      -
    • the jfreu.config.php file was moved from the plugins/ directory to the more appropriate includes/ directory - -
    • the player join message shows the administrator title (if appropriate) and the server rank when rank limiting is enabled - -
    • new command /jfreu listlimits to display all rank-limiting related settings in one convenient overview - -
    • new commands /jfreu writeconfig and /jfreu readconfig to write and read the actual Jfreu config settings to/from a new dynamic config file, configurable via $conf_file in jfreu.config.php (default: plugins/jfreu/jfreu.config.xml) -
        -
      • the settings managed this way include all the variables in jfreu.config.php, except $message_start, all the actual info messages, and $badwordslist -
      • changing any setting will cause the jfreu.config.xml file to be rewritten with the current values, so that after an ASECO/RASP restart it can be read back to restore those settings (it is however not read automatically) -
      • /jfreu readconfig shows the current servername after reading the settings, in case that was changed via the autoranking mechanism -
      - -
    • new commands /jfreu listvips and /jfreu listvipteams to display the lists of VIPs and VIP_Teams, respectively - -
    • new command /jfreu readlists to read in the lists of VIPs and VIP_Teams, complementing /jfreu writelists - -
    • new command /jfreu badwordsnum to set the badwords limit, complementing /jfreu badwords and /jfreu badwordsban - -
    • new command /jfreu badword to increment the badword count for a player (login or ID) who 'creatively' and intentionally circumvents the badword filter - -
    • new command /jfreu autochangename to enable or disable automatically changing the servername when the rank limit is updated - -
    • new command /jfreu infomessages to enable or disable the info messages - -
    • new $unspecvote setting in jfreu.config.php, and new command /jfreu unspecvote to allow or disallow SpecOnly players to use the /unspec command - -
    • exchanged the functionality of /jfreu setrank and /jfreu setlimit: now setrank enables/disables the rank limit system (just like autorank for the autoranking feature) while setlimit sets the rank limit value itself (similar to hardlimit for the overall hard limit value) - -
    • corrected /jfreu addteamvip into /jfreu addvipteam, and /jfreu removeteamvip into /jfreu removevipteam (along with all corresponding variables) - -
    • corrected /jfreu autorankminplayer into /jfreu autorankminplayers, and $autorankminplayer into $autorankminplayers in jfreu.config.php (proper plural) - -
    • the /jfreu addvip, /jfreu removevip, /jfreu unspec, /jfreu banfor and /jfreu player commands are now able to use player IDs (in addition to logins) - -
    • added clearer descriptions to the /jfreu helpall output - -
    • all relevant /jfreu commands now show the title and nickname of the admin performing those actions - -
    • added more logging of /jfreu commands usage, and of all the various kick reasons - -
    • an invalid value specified to /jfreu setlimit, /jfreu hardlimit, /jfreu offset, /jfreu autorankminplayers, /jfreu maxplayers and /jfreu kickworst produces an appropriate error message - -
    • a login specified to /jfreu unspec that isn't SpecOnly produces an appropriate error message - -
    • the /jfreu setlimit command without a value now shows the current rank limit - -
    • the jfreu.unspec.php plugin is integrated into jfreu.plugin.php: spectators that manage to start racing without getting unSpec-ed are kicked after passing a checkpoint; the original kick_player_spec() function is therefore redundant and was disabled - -
    • the NoVote feature (automatic cancellation of CallVotes) and the /jfreu cancel command are inactive when chat-based votes are enabled - -
    • fixed and re-enabled the /password command to show the server's player/spectator password to a player or spectator/SpecOnly, respectively - -
    • fixed /jfreu setlimit on to not change the servername if $autochangename is off - -
    • fixed /jfreu message to use the actual servername instead of the initial one, in case that was changed via the autoranking mechanism - -
    • fixed SpecOnly player's rank to get checked against the rank limit instead of 0 if $autorank is off - -
    • fixed players with rank 0 (vanilla account) to not get kicked if $hardlimit is disabled - -
    • fixed players with rank 0 (vanilla account) to get considered by the kick_hirank and kick_worst features before other high ranks - -
    • fixed player with rank higher than $hardlimit to still get shown an explanation message before being kicked - -
    • fixed spectator whose team is in the VIP_Teams list to get marked as VIP instead of SpecOnly - -
    • fixed inclusion (also in jfreu.lite.php) of wrong jfreu.config.php in case of multiple Aseco installs (for multiple TM servers on the same machine) - -
    • general code clean-up, including proper use of booleans for all variables that used only 1/0 values, message corrections, redundant code removal, and lots more -
    -
    - -
  • new /laston command to show when a player (login or ID) was last online on the server - -
  • new /admin players command to display the list of all known players, and search for offline players via a (part of a) login or nickname with /admin players <string> (just like /players <string> to search for online players) - -
  • tracks downloaded via /add are now saved to the new 'GameData/Tracks/Challenges/TMXtmp/' directory as they are added only temporarily to the server's track list, while tracks downloaded via /admin add are saved to the 'GameData/Tracks/Challenges/TMX/' directory and added permanently to the server's track list, as before - -
  • the /admin add command now optionally also adds the downloaded track to the jukebox (configured via $jukebox_adminadd in rasp.settings.php) - -
  • new /admin addthis command to permanently add the current track to the server's track list if it is playing through a TMX /add vote; this will also move the track file from the 'GameData/Tracks/Challenges/TMXtmp/' directory into the 'GameData/Tracks/Challenges/TMX/' one - -
  • the /add trackref command now creates trackref.txt files in both TMX and TMXtmp directories - -
  • tracklists created by /admin writetracklist now include a <random_map_order> filter section so that /admin readtracklist of such files randomizes the track list again (configurable via new config.xml setting <writetracklist_random>) - -
  • /admin black and /admin unblack now automatically perform a writeblacklist, and /admin addguest and /admin removeguest a writeguestlist, so that the blacklist.txt and guestlist.txt files are always in sync in case of a server restart - -
  • on TMO/TMS, /nextmap will also show the environment of the next track - -
  • the chat-based voting configuration options were lifted out of plugin.rasp_votes.php into the new includes/votes.config.php file - -
  • a new TMN & ASECO/RASP quick start guide is included in the DOCS/ directory -
- -

Bug fixes in the v0.91 release of ASECO/RASP:

- -
    -
  • a track added to the jukebox via TMX /add vote is temporarily added to the server's track list while playing it, but if multiple tracks are /add-ed right behind eachother, only the last one would be removed from the track list again – fixed, each track is immediately removed from the server's track list after having been loaded successfully for its (single) turn - -
  • /admin setservername uses only the first specified word to set the servername – fixed, all words are now used - -
  • /admin setservername, /admin setmaxplayers and /admin match don't show the nickname of the admin performing those actions – fixed - -
  • /server always shows the initial servername – fixed, it now displays the actual servername, in case that was changed via the autoranking mechanism - -
  • because tracks downloaded from TMX are renamed to filenames derived from the tracks' (sanitized) names, it is possible for tracks with identical names to get mixed up – fixed, the (sanitized) filename is now made unique with a sequence number, if necessary -
- -
- -

New features and other changes in the v0.90 release of ASECO/RASP:

- -
    -
  • new and comprehensive player muting support with the following features:

    - -
      -
    • player muting (preventing players to chat) automatically becomes available when plugin.muting.php is enabled (uncommented) in plugins.xml -
    • muting can be done globally (by admins with sufficient permissions): -
        -
      • a player can be added to the global mute list with /admin mute and removed again with /admin unmute -
      • admins can view the global mute list with /admin mutelist (alias /admin listmutes) -
      • the global mute list is not stored so an ASECO/RASP restart will result in an empty list – consider it an opportunity for redemption of muted players -
      • all normal chat by a player on the global mute list is muted for all other players -
      • a player on the global mute list is also not allowed to use the /me, /hi, /bye, etc. chat commands (which result in global messages), in order to prevent circumventing the normal chat mute (or possible spamming in retaliation) -
      • admins will probably find it more effective and efficient to warn and kick a troublesome player than to mute him/her -
      • the admin muting commands are always listed in the help, but disabled if the muting plugin is not included -
      • the adminops.xml file has corresponding new entries <mute>, <unmute>, <mutelist> and <listmutes> -
      -
    • ...or individually by any player: -
        -
      • a player can add another player to his/her individual mute list with /mute and remove a player again with /unmute -
      • players can view their individual mute list with /mutelist -
      • the individual mute list is not stored so leaving the server will result in an empty list upon joining – consider it another opportunity for redemption of muted players -
      • all normal chat by a player on another player's mute list is muted only for that other player -
      • a muted player's use of the /me, /hi, /bye, etc. chat commands will still be seen by the player who muted him/her -
      • players cannot mute admins of any tier (MasterAdmin, Admin & Operator) -
      - -
    • all muted players' normal chat can still be read in /chatlog, and is logged as usual (if <log_all_chat> in config.xml is enabled) -
    • directly and completely muting a player is not possible because the TM server first sends out a normal chat line to all other players before ASECO/RASP receives it -
    • therefore a mute buffer of the size of the large chat window (18 lines) is maintained for each individual player, containing the global server messages (e.g. records, track changes, votes, etc.) as well as the chat lines from all unmuted players; this buffer also stores any private messages a player sent and received (via /pm, /pma & /admin pm), but not the local messages from most chat commands (e.g. /help, /pb, /wins, /recrange, etc.) nor any error messages -
    • whenever a muted player enters a chat line, that line is not appended but instead the individual buffers are sent back as fast as possible to any players that muted him/her, in effect pushing the muted player's line out of the chat window -
    • while this is the best possible approach to muting, given the TM server's limitations, it does have a few problems: -
        -
      • since local and error messages are not buffered, a muted player entering a chat line will cause those messages to be lost for any players that muted him/her as their buffers are sent back to the chat window; for this reason a global mute may be too confusing and an admin warn/kick more practical -
      • because of the delay between the TM server sending chat lines directly to all players and the mute buffers of some (or all) players being sent back to them, a chat line by a muted player is usually visible for a short time; that timing issue is inherent to this approach -
      • because of the above timing problem, it's also possible that an unmuted player enters a chat line in between a muted player's line and sending out any mute buffers, which causes that unmuted line to get pushed out of the chat window as well; it is still stored in the mute buffers though, and will be restored to the chat window the next time a muting action occurs -
      • to speed up that restore, players who notice they're missing a chat line from an unmuted player can use the new /refresh command to restore their chat window -
      - -
    -
    - -
  • new idlekick features in mistral.idlekick.php:
    -if spectator kicking is enabled ($kickSpecToo = true), a separate idle challenge count is in effect, called $kickSpecAfter (default: 4);
    -the idle kicking system now takes tiered admins and their player/spectator status into account: -
      -
    • MasterAdmins are never idle kicked -
    • Admins and Operators are not kicked if they're in player mode and the new <noidlekick_play> ability in their respective ability lists (in adminops.xml) is enabled -
    • Admins and Operators are not kicked if they're in spectator mode and the new <noidlekick_spec> ability in their respective ability lists is enabled -
    -
    - -
  • new /list options:
    -/list longest/shortest display the longest and shortest tracks as determined by Author time
    -/list newest # displays the # newest tracks added to the server (default: 10) - -
  • in all /list output, recently played tracks (as defined by $buffersize in rasp.settings.php) are now shown in light grey to indicate they cannot be jukeboxed yet again - -
  • long lines in the output of /chatlog, /pmlog and /admin pmlog are now cleanly wrapped on words, instead of breaking off in the middle of words - -
  • new /admin wall (alias /admin mta) command to display a message as a pop-up window to all players (familiar from the *nix wall command); the default message color is red but all color and attribute tags can be used, and the adminops.xml file has corresponding new entries <wall> and <mta> - -
  • the warning message sent via /admin warn can now be customized via a new <warning> message in config.xml - -
  • the output of /admin commands that generate a list of players (listbans, listblacks, listguests, listmasters, listadmins, listops) now uses the same blue color for the logins as the /players output - -
  • a new <karma> color is defined in config.xml and used in all relevant karma messages so that they can easily be customized - -
  • the aforementioned light grey and blue colors are defined in config.xml too, as <grey> and <login> respectively; the red color used for nickname headers in all pop-up windows is also configurable, via the new <nick> color - -
  • showing the records range before, during (with /liverecs) and after a track if there are no new records, is now optional via new config.xml setting <show_recs_range>; if False, no range is shown - -
  • the messages for new, improved, secured and equaled records (in localdatabase.xml) have been shortened to reduce wrapping in the small chat window - -
  • if showing the total playtime at the end of each track is enabled, that playtime is also logged - -
  • the MySQL connection can become lost on servers that remain empty for a long time (the default wait_timeout is 8 hours), therefore when that happens an automatic MySQL reconnect is now performed (thanks Dagobert) - -
  • renaming the blacklist, guestlist and tracklist extensions into ".xml" in v0.89 was not such a good idea (because the TMN server will create an empty blacklist and guestlist upon starting if it doesn't find the ".txt" versions), so they have been reverted to ".txt" - -
  • added logging of more /admin commands -
- -

Bug fixes in the v0.90 release of ASECO/RASP:

- -
    -
  • when using a track ID with /karma, it still reports "Current Track Karma" – fixed, now the pertaining track name is shown - -
  • spectators are never idle-kicked even if $kickSpecToo in mistral.idlekick.php is true (ugly oops) – fixed - -
  • if a /replay vote passes, another could be started and if it passes too, the same track is queued twice in the jukebox – fixed, /replay is no longer allowed after the track is queued once - -
  • if a track fails to load twice from the jukebox and is dropped, the corresponding chat message is empty – fixed - -
  • /list karma 0 shows the entire track list – fixed, it now shows only tracks with karma less than or equal to 0 - -
  • messages sent via /pma show up twice in the sending admin's /pmlog output &ndash fixed - -
  • the <skipmap> and <skip> abilities are missing from the adminops.xml file – fixed - -
  • in a new installation, the very first line written to logfile.txt is actually lost – fixed -
- -
- -

New features and other changes in the v0.89 release of ASECO/RASP:

- -
    -
  • info on actual karma votes: the karma messages can optionally show each player's actual votes, or lack thereof (configured via $karma_show_votes in rasp.settings.php) - -
  • requiring finishes before karma voting: a player can optionally be required to finish a track a minimum number of times before being allowed to karma vote for it (configured via $karma_require_finish in rasp.settings.php); this is the total number of finishes since the first day a player tried a track, not the number in the current session - -
  • the /karma command now accepts an optional track ID (from your last /list output) to display the karma of that track instead of the current one - -
  • when changing your already existing karma vote, instead of "Vote Successful" the message now is "Vote Changed" - -
  • new /pmlog command to display the most recent 30 (default) lines of your own private messages (both sent and received) in a pop-up window - -
  • /list <xxx> now allows searching for track names/authors with multiple words (previously one word was required, otherwise the entire list was displayed) - -
  • the /admin help command now shows the command list in the chat window, while the new /admin helpall command displays the list with descriptions in a pop-up as before; similarly for /jfreu help and the new /jfreu helpall command – this makes them consistent with the user /help and /helpall commands - -
  • the jfreu.player.php plugin has been renamed to jfreu.lite.php, containing the info messages system and (as before) the player join/leave messages, but nothing else of the full Jfreu plugin functionality - -
  • in the /players output, strip wide font ($w) from nicknames so that the window width isn't stretched out too far by players with long wide nicks and long logins - -
  • wide fonts are also stripped from the sender and receiver nicknames in /pm, /pma and /admin pm - -
  • when showing records before (<show_recs_before> is True), after (<show_recs_after> is True) or during (/liverecs) a track and there are no new records, the record range is included instead - -
  • the last page in a multi-page pop-up now uses an 'OK' button (instead of 'Close'), for consistency with all single-page pop-ups - -
  • if an admin (with the chat_jb_recent ability) jukeboxes a recently played track, a warning about the repeat track is shown but it's still jukeboxed again - -
  • the /replay message was changed from "Replay Track upon Completion" into "Replay Track after Finish", which is hopefully easier to understand for non-English speaking players - -
  • all chat-based and TMX /add votes are now cancelled by the server during the Tab scoreboard before the next track is fetched from the jukebox, to prevent interference from a /replay vote passing at the last possible moment - -
  • the default percentage of a track after which a /skip vote is no longer allowed, was reduced from 70% to 50% - -
  • the /admin writetracklist command now accepts an optional filename parameter to write the current match settings to another file than the default - -
  • new /admin readtracklist command for reading the default match settings file, or an alternate one specified with an optional filename parameter, to complement /admin writetracklist; it shows the number of tracks loaded from the file - -
  • the default filename used for the above match settings file is shortened from "rasp-tracklist.txt" to "tracklist.xml", and it's written to and read from the more appropriate "GameData/Tracks/MatchSettings/" directory (previously in "GameData/Tracks/") - -
  • the extension of the blacklist, guestlist and tracklist files (as read and written by the pertaining /admin commands) is updated from ".txt" to the more relevant ".xml" - -
  • the /match load command in plugin.matchsave.php was disabled as it's now redundant with /admin readtracklist - -
  • the /admin acdl command now takes "ON" or "OFF" (case-insensitive) as parameter instead of "Yes"/"No", for consistency with all other boolean (/jfreu) admin commands - -
  • a new <admin> color is defined in config.xml and used in all relevant /admin messages so that they can easily be customized - -
  • if you still use the old chat.vote.php plugin, that will now show the current score when a track is loaded - -
  • the <account> field in the <tmserver> section of config.xml became redundant, and was removed - -
  • an /ad shortcut for /admin and a /jb shortcut for /jukebox become available by uncommenting the appropriate two lines at the start of chat.admin.php and plugin.rasp_jukebox.php, respectively - -
  • aseco.php now determines whether it's running on Win* or *nix operating systems, so it's no longer necessary to define the CRLF constant manually - -
  • lots of code optimization and clean-up, like more efficient handling of TM server calls (thanks Assembler Maniac), better error handling throughout the entire system, and other tid bits -
- -

Bug fixes in the v0.89 release of ASECO/RASP:

- -
    -
  • /kick votes are denied when there's no vote in progress, and allowed to start when another vote already is (oops) – fixed - -
  • /admin ban produces an RPC error in the log from the Kick call because the Ban call already kicks the player – fixed by removing the Kick call - -
  • if a track is deleted before pruning its records (/admin prunerecs), those are still counted in players' /summary results (though not /topsums) – fixed - -
  • /pm and /pma send a message even if it's empty – fixed, empty messages are ignored - -
  • in /add and /admin add, after renaming the numeric filename to a (sanitized) track filename, multiple spaces and underscores are not properly reduced to single ones – fixed - -
  • in the original v0.88 zip file (which was fixed one day after release), /jfreu didn't work for any admin tier – fixed -
- -
- -

New features and other changes in the v0.88 release of ASECO/RASP:

- -
    -
  • new, flexible and fully integrated tiered administration support with the following features:

    - -
      -
    • there are three tiers of administrators: MasterAdmins, Admins and Operators -
    • all three tiers of administrators can see who is in each tier with the new /admin listmasters, /admin listadmins and /admin listops commands -
    • if a player is in more than one tier, the highest one takes precedence -
    • MasterAdmins have no restrictions to their abilities, while the abilities of Admins and Operators can be defined per individual command -
    • the list of MasterAdmins is fixed in the <masteradmins> section in config.xml; the lists of Admins and Operators, and their respective abilities, are stored in the new adminops.xml file (configurable via new config.xml setting <adminsops_file>) and automatically restored after an ASECO/RASP restart -
    • MasterAdmins can add and remove Admins dynamically, using the new /admin addadmin and /admin removeadmin commands -
    • both MasterAdmins and Admins can add and remove Operators dynamically, using the new /admin addop and /admin removeop commands -
    • MasterAdmins can dynamically enable and disable all abilities for Admins and Operators – even their ability to change their own abilities, if you so desire – using the new /admin adminability and /admin opability commands -
    • the ability lists also define (non-/admin) chat commands where Admins and Operators have additional capabilities that users don't -
    • the /admin help command dynamically lists only the available commands for each tier depending on their allowed abilities -
    • the new /admin listabilities command displays which non-/admin abilities (if any) are available to Admins and Operators, with a description; for MasterAdmins, /admin listabilities {admin/op} displays the abilities of the pertaining administrator tier -
    • there are no separate commands (e.g. /op), all tiers use the same /admin commands (so there's no code duplication) -
    • all /admin chat and log messages show whether a MasterAdmin, Admin or Operator performed the pertaining action -
    • the player join message from jfreu.plugin.php (or jfreu.player.php) shows the appropriate administrator title -
    • the titles with which MasterAdmins, Admins and Operators are shown in chat and join messages, are configurable via the <titles> section in adminops.xml -
    • if e.g. Operators do not have the ability to use /pma and /admin pm, they will also not receive such private messages when sent by (Master)Admins -
    • any server owner's LAN logins (with IP and port) should be in the <masteradmins> section of config.xml and will be skipped by the /admin listmasters command -
    • all Jfreu admin commands can be used by MasterAdmins and Admins, but not Operators; also, the /jfreu addadmin command was disabled, and the corresponding <admin_list> section from plugins/jfreu/jfreu.vips.xml (previously jfreu.lists.xml) was removed -
    • servers that don't need Admins and Operators can simply leave those login lists in adminops.xml empty, and not have the tiered system interfere (phew J) -
    • see this table for the default list of abilities -
    -
    - -
  • new karma details: details about the vote counts and percentages can optionally be shown with karma messages (configured via $karma_show_details in rasp.settings.php) - -
  • new /admin listdupes command to display a list of duplicate tracks on the server (ready for /admin remove/erase) - -
  • new /admin prunerecs command to delete the records/rs_times database entries for a specific track (by track ID from /list, so you must prune recs for a track before removing/erasing it, otherwise you can't /list it anymore) - -
  • the number of rounds (in Rounds/Team modes) or seconds (in TimeAttack/Laps/Stunts modes) by which a vote expires can now be configured per individual type of TMX /add and chat-based vote ($r_expire_limit and $ta_expire_limit in plugin.rasp_votes.php) - -
  • the output from /list and /list <xxx> now includes the corresponding ranked records, just like /list norecent - -
  • the /best and /worst commands are now also available via /list best and /list worst - -
  • the /server output now shows the total playing time of all players combined - -
  • the /best, /worst, /summary, /stats, /admin ban and /admin black commands can now also be used on offline logins (only by admins as defined by their abilities) - -
  • moved all jukebox and /add related message strings into rasp.xml for easy customization and localization - -
  • the all-caps UNRANKED text for unranked records in the /pb message is now in narrow font to make it less intrusive - -
  • in Jfreu's player join messages and the /stats output, the ladder ranks are now formatted with spaces between the thousands (like in the Tab scoreboard) - -
  • if Jfreu's info messages are disabled, the "Messages: OFF" reminder isn't shown anymore - -
  • the Jfreu plugin "Loaded" message is no longer shown to a connecting player (only on ASECO/RASP start-up) - -
  • the name of Jfreu's vip/team_vip file is now configurable via $vips_file in jfreu.config.php (default: plugins/jfreu/jfreu.vips.xml) - -
  • some code clean-up, like more efficient formatColors and resetRanks functions, a non-looping /nextmap (thanks Assembler Maniac), and other tid bits - -
  • all documentation files included in the .zip are now grouped together in their own DOCS subdirectory -
- -

Bug fixes in the v0.88 release of ASECO/RASP:

- -
    -
  • if the jukebox is empty and the current track is deleted (/admin removethis/erasethis), /nextmap hangs up the ASECO/RASP system – fixed by the non-looping rewrite above - -
  • the karma reminders at every finish ($remind_karma = 2) could occasionally produce an RPC error in the log – fixed - -
  • there is a typo in plugin.rasp_irc.php causing the IRC connection to fail – fixed - -
  • a couple of /jfreu commands are not shown in the /jfreu help overview – fixed - -
  • /admin removethis/erasethis does not properly log the name of the deleted track – fixed - -
  • the second line of the default Welcome message text was shown without the shadow attribute – fixed -
- -
- -

New features and other changes in the v0.86 release of ASECO/RASP:

- -
    -
  • new /list options to complement /list nogold:
    -/list noauthor displays all tracks the player finished but didn't beat the Author time on
    -/list norecent displays all tracks the player finished but didn't race recently (includes ranked records) - -
  • the output from /list nogold and /list noauthor now shows how much your time is over the track's Gold and Author time, respectively - -
  • new karma vote reminders: if a player (but not a spectator) hasn't karma voted for a track, an optional reminder can be sent upon every finish or at the end of the track (configured via $remind_karma in rasp.settings.php) - -
  • new /admin pass command to force any TMX /add and chat-based vote to pass - -
  • the /topclans output now shows how many clan members were counted to compute the clan's average server rank - -
  • the chat-based voting plugin has two new options ($allow_spec_startvote and $allow_spec_voting) to allow or disallow spectators to start votes and/or to vote /y themselves; this also applies to TMX /add votes, but admins are always allowed to vote /y - -
  • the track history is now written to a file that is automatically restored upon ASECO/RASP start-up; this prevents players from jukeboxing a recently played track after a restart (a frequent event on my server when developing J) - -
  • moved more message strings into config.xml and rasp.xml for easy customization and localization - -
  • some code clean-up, like removing duplicate getPlayerId functions, adding an isSpectator function, and other tid bits -
- -

Bug fixes in the v0.86 release of ASECO/RASP:

- -
    -
  • when adding from TMX via /add or /admin add, a track with special characters (besides '?' and '*') in its filename could cause it to not load and get stuck in the jukebox – fixed by changing most special characters into '_' (underscore) - -
  • the special 'trackref' parameter to /add doesn't work – fixed parameter processing and made it an admin-only option; also fixed non-existent 'tracktype' field in the trackref.txt file into 'environment' - -
  • a replayed track is entered multiple times into the track history – fixed - -
  • if a track failed to load from the jukebox, the next track from the server's track list that would get loaded in its place isn't entered into the track history – fixed - -
  • if an admin forces a replay (/admin replay) during a /replay vote, that vote isn't cancelled, potentially allowing a double replay – fixed - -
  • the XML parser changes '+' characters in message strings into spaces – fixed -
- -
- -

New features and other changes in the v0.85 release of ASECO/RASP:

- -
    -
  • new /topsums command (short for: top summaries) to display the top-100 of players with the most top-3 ranked records (i.e. of /summary results) - -
  • updated /best and /worst commands to include an track ID column so that tracks can be jukeboxed directly (without going through /list) - -
  • new /admin removethis and /admin erasethis commands to quickly remove the current track from rotation and, if erasethis, also delete the track file - -
  • new up-to-date check at start-up (optional) and /admin uptodate command to check for the current version of ASECO/RASP; consequently, Jfreu's /uptodate command was disabled - -
  • new <limit> configuration setting in localdatabase.xml to restrict the highest records that are still displayed - -
  • computation of the required number of votes for TMX /add and chat-based votes adjusted for low player counts - -
  • the informational /helpvote messages sent to all players or just a new player can now be turned off completely - -
  • an extra check for a "stuck" track in the jukebox is done when loading the next one, and if it fails to load twice it's dropped - -
  • if <show_recs_before> in config.xml is true, the original Current Record message is redundant with the top-8 records list, and no longer shown - -
  • if <show_recs_before> in config.xml is true and the player has a ranked record, then the Personal Best message can optionally be skipped via $always_show_pb = false in rasp.settings.php -
- -

Bug fixes in the v0.85 release of ASECO/RASP:

- -
    -
  • in determining the required number of votes, spectators are still included for TMX /add votes – fixed - -
  • when adding from TMX via /add or /admin add, a track with a '?' or '*' in its filename causes it to not load and get stuck in the jukebox – fixed by changing all occurrences of '?' and '*' into '_' - -
  • in very rare conditions (running on WinXP with a buggy PHP time functions library in a country using 8-bit characters) the /server and /stats commands could fail silently because the 'strftime' system call returns a bogus timezone – fixed by changing to 'date' system calls - -
  • using '$g$m' to clear text attributes after track names and player nicknames in various messages could result in inconsistencies with italics – fixed by replacing '$g$m' with '$z$s' (or occasionally '$z$s$i') -
- -
- -

New features and other changes in the v0.84 release of ASECO/RASP:

- -
    -
  • new and sophisticated chat-based voting; by including plugin.rasp_votes.php and setting $feature_votes in rasp.settings.php to true, normal CallVotes are disabled and the following features become available:

    - -
      -
    • new /helpvote command to display usage info and the actual voting ratios for the new voting commands -
    • new /endround vote to end a round (only enabled in Rounds/Team modes) -
    • new /ladder vote to restart the current track immediately when the ladder is down -
    • new /replay vote to replay the current track after it's done (prepends it to jukebox) -
    • new /skip vote to skip the current track immediately -
    • new /kick vote to kick a player -
    • new /cancel command for a player to cancel his/her own vote (also works with TMX /add votes; admins can /cancel any vote) -
    • extensive configuration options in the new votes.config.php file:

      - -
        -
      • separate voting ratios per type of vote, so e.g. a kick vote is harder to pass than an endround vote -
      • option to enable /kick vote (default: on) and enable kicking of admins (default: off); if disabled, /kick also isn't listed in the /helpvote output -
      • option to perform /ladder restart via quick ChallengeRestart or by prepending track to jukebox and doing NextChallenge -
      • option to automatically vote for the vote starter, otherwise the old way remains where the starter also has to vote /y -
      • vote expiration options: -
        in Rounds/Team modes all votes (except /endround) expire within 3 rounds (default); -
        in TimeAttack/Laps/Stunts modes all votes expire after approximately 120 seconds / 2 minutes (default); -
        expiration is necessary, otherwise unpassed votes will linger on until the end of the track, preventing players from starting another vote -
      • vote reminder options: -
        in Rounds/Team modes a reminder can be shown after every round until the vote passes or expires; -
        in TimeAttack/Laps/Stunts modes a reminder can be shown approximately every 30 seconds (default) until the vote passes or expires; -
        reminders help players not forget about ongoing votes after the last vote message has scrolled out of the chat window -
      • optional limits to the number of /ladder, /replay & /skip votes per track (default max: 2); if a limit is 0, that particular vote is disabled -
      • optional limits to when /ladder, /replay & /skip votes are allowed in Rounds and TimeAttack mode: -
        in Rounds the limit is based on the number of points that the first player already has, compared to the server's points limit; -
        in TimeAttack the limit is based on the time the track is already running, compared to the server's time limit; -
        /ladder is disabled after 40%, /replay is disabled before 50%, and /skip is disabled after 50% of those limits (all percentages configurable) -
      • the rationale behind these optional limits is that it's: annoying to go through the same type of vote more than twice; annoying to go through a ladder restart vote when the track is already a few minutes under way; too early to decide whether to play a track again if when it's not even halfway done; and annoying to go through a skip vote when a track is close to completion -
      • there's no limit to the number of endround and kick votes per track, because you may need more than a few of them J -
      • there are no /ladder, /replay & /skip limits in Team, Laps & Stunts modes -
      -
      - -
    • there's no /ban vote command because IMHO only admins should be able to ban -
    • in determining the required number of votes, spectators are not included; this now also applies to TMX /add votes -
    • if chat-based voting is enabled, the /server output will show a pointer to /helpvote instead of the CallVote timeout and ratio -
    • in Rounds/Team modes a running /endround vote is automatically terminated by the EndRound event, i.e. when all players 'Enter' as yet or by an /admin endround/er command -
    • the vote expiration and reminder options, and the automatic vote for starter option, also work with TMX /add votes -
    • all votes terminate automatically at the start of a new track -
    • when a new player connects, an explanatory message that chat-based voting is in effect is shown to that player, or optionally to all players; you can use the latter during an introduction period until most players are familiar with the new voting system -
    • integrates properly with TMX /add votes whether or not those are enabled, and whether or not chat-based votes are enabled -
    • voting messages use a new pink color (<vote> in config.xml) that's sufficiently distinct from the colors of all other message types (but /add related messages retain their normal orange color) -
    • for further understanding of the features and configuration options, study the detailed comments in function init_votes() in plugins.rasp_votes.php - -
    -
    - -
  • new /admin pmlog command to display the most recent 30 (default) lines of admin pm's in a pop-up window - -
  • updated /admin cancel to cancel TMX & chat votes too - -
  • added more aliases to /admin nextmap command, for consistency with the /skip vote: /admin skipmap and /admin skip - -
  • long lines in the /chatlog pop-up window are now split up with a continuation symbol (...) at the start of the second and following parts - -
  • if <log_all_chat> is true, non-existent chat commands – lines starting with '/' that cannot be executed – are also logged (but they're not shown in /chatlog) - -
  • (almost) all function calls were updated to remove the deprecated pass-by-reference '&' from parameters - -
  • tweaked various jukebox related messages -
- -

Bug fixes in the v0.84 release of ASECO/RASP:

- -
    -
  • in the /chatlog output, strip wide font ($w) from nicknames so that the window width isn't stretched out too far by players with long wide nicks and lengthy chat lines - -
  • with a large number of long chat lines in the /chatlog output, it could hang up the TMN client – fixed - -
  • all player tests w.r.t. jukebox/add actions are done against nickname, potentially causing conflicts between players with identical nicknames – fixed by changing those tests to logins - -
  • if a player voted /y in one TMX /add vote, he/she wasn't be able to vote for another /add vote during the same track – fixed - -
  • if a player tries to /add an existing track, the preceding track in the server's track list is jukeboxed instead of that requested one – fixed - -
  • if a player tries to /add the same track that was just /add-ed (but not yet loaded), another vote is started – fixed - -
  • /add doesn't properly insert the track into the jukebox such that it can be dropped again – fixed - -
  • if /add finds the requested track is already present on the server, the temporary track file wouldn't be deleted – fixed - -
  • using '$z' to clear text attributes after track names and player nicknames in various messages would also turn off the default 'shadowed' style for the remainder of those messages – fixed by replacing '$z' with '$g$m' -
- -
- -

New features and other changes in the v0.82 release of ASECO/RASP:

- -
    -
  • new /nextrank command to show the next better ranked player - -
  • new /admin unban and /admin unblack commands to remove players from the ban and black lists; you can use the player IDs from the /admin showbanlist and /admin showblacklist output, respectively, as well as any valid logins - -
  • new /admin addguest and /admin removeguest commands to add players to and remove from the guest list (even offline ones); with addguest you can use player IDs from the /players output, with removeguest from /admin showguestlist, as well as any valid logins - -
  • new /admin cleanbanlist, /admin cleanblacklist and /admin cleanguestlist commands to completely clean the ban, black and guest lists - -
  • added aliases for admin commands: /admin showbanlist or /admin listbans, /admin showblacklist or /admin listblacks, and /admin showguestlist or /admin listguests - -
  • the IP address of a connecting player is now logged along with the rest of the 'join' message - -
  • the updated Matchsave plugin (plugin.matchsave.php, matchsave.xml) by F*ckfish is now part of the standard distribution, although by default not included in plugins.xml - -
  • all function declarations were updated to remove the deprecated pass-by-reference '&' from parameters -
- -

Bug fixes in the v0.82 release of ASECO/RASP:

- -
    -
  • if $prune_records_times is enabled and there is a syntax error in rasp.xml, there is a possibility for the records/times of all tracks to be deleted – fixed, a syntax error will no longer invoke pruning - -
  • fields 2 & 3 in <record_first> in config.xml are out of order – fixed, although it isn't visible anyway as the same (and correct) message from localdatabase.xml supersedes it - -
  • the /admin showbanlist/showblacklist/showguestlist commands are not properly storing the player IDs & logins for later use – fixed - -
  • the "Player dropped...track... from jukebox" message is shown in yellow instead of orange, like all other jukebox messages – fixed -
- -
- -

New features and other changes in the v0.81 release of ASECO/RASP:

- -
    -
  • new /chatlog command to display the most recent 30 (default) lines of player chat in a pop-up window - -
  • showing a FAST-like record report after each track is now optional too, via new config.xml setting <show_recs_after>; if False, the old top-5 from v0.7 is shown - -
  • new /admin showguestlist command to display the list of guest player(s) - -
  • new /admin writeguestlist and /admin readguestlist commands to write/read the player guestlist to/from disk - -
  • the player join/leave messages are now available from a separate jfreu.player.php plugin, in case you don't want/need the rest of the Jfreu functionality (rank limiting, unspec voting, badword filtering, admin commands, etc.) - -
  • the Jfreu unspec-fix plugin (jfreu.unspec.php) by F*ckfish is now part of the standard distribution, although by default not included in plugins.xml -
- -

Bug fixes in the v0.81 release of ASECO/RASP:

- -
    -
  • when a player connected, the before record report is shown to all players, instead of just the new one – fixed -
- -
- -

Known problems in the v0.8+ releases of ASECO/RASP:

- -
    -
  • If all (non-spectator) players press DEL during the scoreboard at the end of a track, the track switching process is disturbed and the next track in the jukebox will not be loaded. No solution known, this appears to be a TMN server quirk. -
- -
- -

Initial release notes

- -
-
-Copyright © 2007-2013 – Frans P. de Vries <tm@gamers.org> -            -Last updated 27-Mar-2008 -
- - diff --git a/xaseco/DOCS/Features_103.html b/xaseco/DOCS/Features_103.html deleted file mode 100644 index 5dd0bca..0000000 --- a/xaseco/DOCS/Features_103.html +++ /dev/null @@ -1,913 +0,0 @@ - - - -TrackMania Nations - XASECO release notes v1.03 - - - - - - - - - - -

-TrackMania Nations -


- -

New features and other changes in the v1.03 release of XASECO:

- -
    -
  • new support for an admin lock password: -
      -
    • a lock password for /admin and /jfreu commands, as well as other admin abilities and the admin panel, can be configured via new config.xml setting <lock_password> -
    • if the password setting is non-empty, admins of any tier have to enter a password before being able to use admin commands and features -
    • the new /admin unlock command compares the supplied password with the above setting, and unlocks the admin commands and features if valid -
    • the unlock status is remembered for the duration of the player session, so after every reconnect admins will have to enter the password again before they are fully authorized -
    • when an /admin or /jfreu command is not permitted, the error message does not distinguish between a login without admin abilities and the absence of its unlock status, so a player with a spoofed login is left guessing whether the login indeed belongs to an admin or whether an unlock password is required -
    - -
  • new support for admin IP addresses: -
      -
    • after each <tmlogin> field in the <masteradmins> section in config.xml there is now an <ipaddress> field which, if not empty, should contain the IP address that this MasterAdmin login is authorized from -
    • similarly, after each <tmlogin> field in the <admins> and <operators> sections in adminops.xml there are now <ipaddress> fields which, if not empty, should contain the IP addresses that these Admin and Operator logins are authorized from -
    • if the specified IP address for a given login does not match, a player with a spoofed MasterAdmin, Admin or Operator login will only have normal player abilities, and any unauthorized attempts to use admin commands are logged -
    • when starting v1.03 for the first time it is not necessary to add <ipaddress> fields to your adminops.xml file, as empty fields will be created and saved automatically the next time that this file is written (e.g. with /admin writeabilities) – IP addresses for existing logins can then be added manually, and the file reloaded with /admin readabilities -
    • when using this security feature for the <masteradmins> section in config.xml, you do have to manually add the <ipaddress> fields to the file, whether empty or with actual IP addresses -
    • newly added admins and operators will always be saved to adminops.xml with their IP address, so if you are not using this security feature you can manually delete the address from the file, and then reload it -
    • note to plugin authors: because of these two security features the parameter type for the allowAbility, isMasterAdmin, isAdmin, isOperator and isAnyAdmin functions changed from a login string to a player object -
    - -
  • new support for IP address bans, in co-operation with regular server bans (which are not preserved across TM server restarts): -
      -
    • the new bannedips.xml file can contain a list of banned IP addresses, which is automatically loaded at XASECO start-up -
    • the IP addresses of players are checked against the list upon connecting, and banned players are kicked with a banned IP message -
    • the existing /admin ban and /admin unban commands also add/remove the IP address of the pertaining player to/from the banned IPs list -
    • the new /admin banip command adds a manually specified IP address to the list, and /admin unbanip removes an IP address from the list – /admin banip does not require the pertaining player to be online like /admin ban does -
    • the new /admin showiplist command (alias /admin listips) displays the current list of banned IP addresses – on TMF if config.xml setting <clickable_lists> is True, the addresses in the output are buttons to invoke /admin unbanip on the selected IP -
    • the new /admin cleaniplist command deletes the entire list of banned IP addresses -
    • all commands automatically write the updated bannedips.xml file, but the new /admin writeiplist and /admin readiplist commands can also be used to write/read the list whenever you want -
    -
    - -
  • on TMF when plugin.msglog.php is enabled: -
      -
    • a small arrow button is displayed in the lower-left corner above the chat window button that will invoke /msglog, allowing to quickly review the system message history -
    • the system message window now always displays the last 5 message lines, and the /msglog output has been reduced to a single-page window of 21 lines to avoid having to page in order to read the latest messages -
    • global jukebox messages can now be diverted to the window, configurable via rasp.settings.php setting $jukebox_in_window (true/false) -
    • global chat-based vote messages can also be diverted to the window, configurable via votes.config.php setting $vote_in_window (true/false) -
    - -
  • new /admin writeabilities and /admin readabilities commands to write and read the admins/operators/abilities list to and from disk - -
  • the player join/leave messages (for both the full and lite Jfreu plugins) are defined via new jfreu.config.php settings $player_join, $player_joins, and $player_left - -
  • an optional divider message at the start of each track shows the name, author and author time, configurable via new config.xml setting <show_curtrack> (2 = TMF message window, 1 = chat, 0 = off) - -
  • it's now possible to specify multiple track IDs to /admin add (but not to /add due to the voting process) in order to quickly download more than one track from TMX or ShareMania – the optional TMX section or SM parameter should be last - -
  • when the target player of a /kick vote leaves before the vote completes, the vote is now cancelled - -
  • the karma message at the start of each track can now be disabled via new rasp.settings.php setting $karma_show_start (true/false) - -
  • the Dedimania welcome message for a newly connecting player can also be disabled via new dedimania.xml setting <show_welcome> (True/False) - -
  • the default name for the blacklist file used by /admin readblacklist and /admin writeblacklist is now configurable via new config.xml setting <blacklist_file> (default: blacklist.txt) – note that the TM server will continue to load the blacklist file with its default name at start-up, unless you adjust <blacklist_filename> in dedicated_cfg.txt as well - -
  • the default name for the guestlist file used by /admin readguestlist and /admin writeguestlist is also configurable via new config.xml setting <guestlist_file> (default: guestlist.txt) – note that the TM server will continue to load the guestlist file with its default name at start-up, unless you adjust <guestlist_filename> in dedicated_cfg.txt as well - -
  • the default name for the track history file is now configurable via new config.xml setting <trackhist_file> (default: trackhist.txt) - -
  • the output of /admin players is limited to at most 10000 entries to prevent possible memory overruns - -
  • the background of the TMF system message window is changed from transparent into blurry to enhance readability of the messages - -
  • a high-level outline of all of XASECO's features and plugins can be found on the new Overview page - -
  • the Upgrade notes for earlier versions are now archived on a separate page - -
  • this release requires the latest server build 2008-08-05 -
- -

Bug fixes in the v1.03 release of XASECO:

- -
    -
  • on TMF with the Jfreu info messages diverted to the system message window, the /message command makes the window pop up much longer than the <window_timeout> value – fixed - -
  • if <cache_tags> in musicserver.xml is disabled, the /music list output isn't formatted correctly – fixed - -
  • attempts to /admin ban offline players are allowed (and naturally always fail) – fixed - -
  • the Z-value of the TMF /admin pay dialog puts it behind the full chat window – fixed -
- -
- -

New features and other changes in the v1.02 release of XASECO:

- -
    -
  • new support for a system message window on TMF with the following features: -
      -
    • the message window becomes available when plugin.msglog.php is enabled (uncommented) in plugins.xml -
    • various system messages that are sent to all players in the chat window can now be diverted into the message window in order to reduce chat clutter -
    • the window normally pops up with the last 4 message lines whenever a new message is received, using a timeout that is configurable via new config.xml setting <window_timeout> (default: 6 seconds) -
    • during the scoreboard at the end of each track, the window pops up with the last 6 lines if there are any new messages, for the same duration as the server's ChatTime setting (default: 10 seconds) plus an extra 5 seconds for the podium animation before the scoreboard comes up -
    • the window size is restricted to 4 and 6 lines respectively due to the available screen space between the notice area and drop-down menu above it, and the main command window or scoreboard (at its largest size) below it -
    • the new /msglog command displays the last 30 message lines so players can check back the system message history -
    • messages to be diverted into this window can be any or all from: -
        -
      • new/improved/secured/equalled local records, configurable via new config.xml setting <recs_in_window> (True/False) -
      • new/improved/secured/equalled Dedimania records, configurable via new dedimania.xml setting <recs_in_window> (True/False) -
      • the local records report before a track, configurable via config.xml setting <show_recs_before> (add 4 to original value: 6 = full report in window, 5 = track record in window, 2 = full report in chat, 1 = track record in chat, 0 = off) -
      • the local records report after a track, configurable via config.xml setting <show_recs_after> (add 4 to original value: 6 = full report in window, 5 = top-5 in window, 2 = full report in chat, 1 = top-5 in chat, 0 = off) -
      • the Dedimania records report before a track, configurable via dedimania.xml setting <show_recs_before> (2 = message window, 1 = chat, 0 = off) -
      • the Dedimania records report after a track, configurable via dedimania.xml setting <show_recs_after> (2 = message window, 1 = chat, 0 = off) -
      • the round finish reports in Rounds mode, configurable via new config.xml setting <rounds_in_window> (True/False) -
      • the TMX World Record message at the start of a track, configurable via config.xml setting <show_tmxrec> (2 = message window, 1 = chat, 0 = off) -
      • the total playtime message at the end of a track, configurable via config.xml setting <show_playtime> (2 = message window, 1 = chat, 0 = off) -
      • the Jfreu Info message at the end of a track, configurable via jfreu.config.php setting $infomessages (2 = message window, 1 = chat, 0 = off) -
      -
    -
    - -
  • new /admin call command for MasterAdmins to directly execute a server call, essentially incorporating most of the RPC Utilities, with the following options: -
      -
    • help: Displays help information -
    • help Method: Displays help for method -
    • list: Lists all available methods -
    • Method {params}: Executes method with any necessary parameter(s) and displays any result(s) -
    • boolean parameters should be entered as 'true' and 'false' (without the single quotes); string parameters consisting of multiple words need to be entered between double quotes (") -
    • methods that require an array or struct as parameter are not supported -
    • Admins and Operators cannot use this command regardless of the <call> setting in adminops.xml, as it would permit them to bypass all the normal ability checks -
    -
    - -
  • new support for .OGG comments (ID3 tags) in the TMF music server plugin: -
      -
    • the new <cache_tags> setting in musicserver.xml defines whether to cache and display comments (typically ID3 tags) for all .OGG files in the song list -
    • the new <cache_file> setting defines the filename where the cached comments are stored (default: musictagscache.xml) -
    • when the cache file doesn't exist upon restarting XASECO, it is created, and all .OGG files are read and their comments extracted -
    • upon further XASECO restarts, and when using /music reload, the cache file is used to load the comments for all known songs, and only the .OGG files for new songs are read -
    • only the first block (512 bytes) of each .OGG file is read to minimize server traffic and start-up time, but obviously this can still take some time when building the initial cache for a large song collection -
    • the cache file is then immediately written out with the title and artist for all songs, ready for future restarts and reloads -
    • if comments are enabled, the /music current and /music settings commands include the title and artist in their output, and the /music list command displays a secondary line with the title and artist for every song -
    • the cache file can be edited manually to make adjustments or corrections to the title and artist information -
    • .MUX files are ignored because they are encrypted and cannot be parsed for their comments -
    -
    - -
  • on TMF (except when /style is off) all windows now show an icon in the left corner of the header that's appropriate for that window's content - -
  • on TMF if config.xml setting <clickable_lists> is True, the karma values in the /list karma output are buttons to invoke /karma on the selected track - -
  • on TMF in the /jukebox display output, admins with the clearjukebox ability get buttons at the end of every page to invoke /admin clearjukebox - -
  • the /recs and /dedirecs commands now accept the following options: -
      -
    • help: Displays help information -
    • pb: Runs the /pb or /dedipb command, respectively -
    • new: Runs the /newrecs or /dedinew command -
    • live: Runs the /liverecs or /dedilive command -
    • first: Runs the /firstrec or /dedifirst command -
    • last: Runs the /lastrec or /dedilast command -
    • next: Runs the /nextrec or /dedinext command -
    • diff: Runs the /diffrec or /dedidiff command -
    • range: Runs the /recrange or /dedirange command -
    • without an option, they display the local or Dedimania records for the current track as before -
    - -
  • the main aseco.php program now contains a few configuration definitions that cannot be stored in a .php or .xml file: -
      -
    • ABBREV_COMMANDS controls whether to define abbreviations for some chat commands: /ad for /admin, /jb for /jukebox, and /aj for /autojuke – this was previously controlled by uncommenting a few lines in the pertaining source files -
    • INHIBIT_RECCMDS controls whether all local and Dedimania record relations commands are disabled from the /help and /helpall lists; these are: /pb, /newrecs, /liverecs, /firstrec, /lastrec, /nextrec, /diffrec, /recrange as well as /dedipb, /dedinew, /dedilive, /dedifirst, /dedilast, /dedinext, /dedidiff, /dedirange – note that the commands can still be used (and on TMN will be echoed to the chat window when doing so) -
    • MONTHLY_LOGSDIR defines whether to write the log in monthly chunks in a "logs/" subdirectory – this was previously controlled by the $log_monthly_logsdir variable in function doLog() in includes/basic.inc.php -
    - -
  • on TMF, tweaked the Z-values of the single- and multi-page windows so that they clearly stay "on top of" the full chat window (except when /style is off, as those windows were already displayed correctly) - -
  • if $always_show_pb = false in rasp.settings.php, the Personal Best message at the start of each track is also skipped if the player uses the records panel on TMF - -
  • the default name for the matchsettings file used by /admin readtracklist and /admin writetracklist is now configurable via new config.xml setting <default_tracklist> (default: tracklist.txt) - -
  • it's now possible to add an extra explanation to the /help output to help new players, configurable via new config.xml setting <help_explanation> (default: False) - -
  • chat-based voting commands can be temporarily disabled during the scoreboard at the end of a track, configurable via new votes.config.php setting $disable_while_sb (default: true) - -
  • the default number of tracks displayed by /list newest and /list oldest is now 50 - -
  • the dedimania.xml settings <log_news>, <show_recs_range> and <display_recs> should now be specified as True/False instead of 1/0 - -
  • the Dedimania system "refresh" is reduced from once every 3 minutes to once every 4 minutes - -
  • the /donpanel command is now disabled if plugin.donate.php is disabled in plugins.xml - -
  • optimized URL parsing in the get_file functions of the SMInfoFetcher, TMNDataFetcher, TMXInfoFetcher and TMXInfoSearcher modules - -
  • new window style ProgressBar, admin panel AboveSpeed2, donate panels LeftSmall and RightSmall, and vote panel BottomCenterTransp, courtesy of nouseforname - -
  • this release requires the latest server build 2008-07-22 -
- -

Bug fixes in the v1.02 release of XASECO:

- -
    -
  • on some PHP systems all ManiaLink windows are broken – fixed by setting locale LC_NUMERIC = "C" - -
  • on some servers players that leave have their win counts cleared – fixed this long-standing issue - -
  • /ladder and /replay are unable to reload the current track (temporarily added via /add) if that has just been added permanently via /admin addthis – fixed - -
  • disabling the default donate panel in config.xml also disables the admin panel – fixed -
- -
- -

New features and other changes in the v1.01 release of XASECO:

- -
    -
  • new TMX search support: -
      -
    • there is a new TMXInfoSearcher class to search TMX with the following features: -
        -
      • find tracks matching name, author and/or environment, returning up to 500 results from the TMNF/TMU sections and up to 20 from the TMO/TMS/TMN sections (due to an API limitation) -
      • list the 10 most recent tracks (max. one per author) on any section -
      • the TMXInfoSearcher object is an iterator that allows you to 'foreach' over it to obtain TMXInfo objects with all the info on each track -
      -
    • new /xlist command to list tracks on TMX with the following options and features: -
        -
      • help: Displays help information -
      • recent: Lists the 10 most recent tracks (max. one per author) -
      • <xxx>: Lists tracks the matching (partial) name -
      • auth:<yyy>: Lists tracks the matching (partial) author -
      • env:<zzz>: Where <zzz> is an environment from: Stadium, Bay, Coast, Island, Snow/Alpine, Desert/Speed, Rally (case-insensitive) -
      • <xxx> auth:<yyy> env:<zzz>: Combines any of the name, author and/or environment searches -
      • <tmx>: Where <tmx> is a TMX section from: TMO, TMS, TMN, TMNF, TMU (case-insensitive) -
      • the section option must be the last one but can be combined with any of the above searches; on a TMNF server the default section is TMNF, on a TMUF server it's TMU, and of course the TMN section on a TMN server -
      • on TMF if config.xml setting <clickable_lists> is True, the TMX ids are buttons to invoke /tmxinfo on the selected track; the track names invoke /add on the selected track; admins with the add ability also get Add buttons to invoke /admin add on the selected track; and the author names invoke /xlist auth: on the selected author -
      • on TMN you can list tracks in other TMX sections, but obviously only add them from the TMN section -
      -
    - -
  • new ShareMania support: -
      -
    • with /add <ID> SM and /admin add <ID> SM you can now use the ShareMania site to download tracks ('SM' is case-insensitive) -
    • new /sminfo command (based on the new SMInfoFetcher class) to display the ShareMania information on the current track or, when given a track ID (from /list) or an SM ID, any other track -
    -
    - -
  • new TMUF donate panel support with the following features: -
      -
    • the donate panel shows buttons for seven different donation values, by default: 20, 50, 100, 200, 500, 1000, 2000 (configurable in plugin.donate.php) -
    • the buttons invoke the /donate command with the specified coppers value -
    • the distribution's panels/ subdirectory can contain up to 20 donate panel templates; their filenames must start with 'Donate' -
    • there are 6 standard templates in various positions and sizes all around the HUD -
    • templates define the complete ManiaLink panel with position, size and fonts, so you have full control to develop custom panels -
    • the default donate panel is configurable via new config.xml setting <donate_panel> (default: BelowCPList) -
    • new /admin donpanel <xxx> command to select another default donate panel for newly connecting players (requires panel name, 'off' disables the default) -
    • the new /donpanel command allows players to select an individual donate panel with the following options: -
        -
      • help: Displays help information -
      • list: Displays available panels -
      • default: Resets panel to server default -
      • off: Disables donate panel -
      • <xxx>: Selects donate panel <xxx>, which can be a panel name or the sequence number from /donpanel list -
      • in the /donpanel list overview a player can also click a panel name to dynamically select a new panel (or refresh the current one if its .xml file was updated) -
      -
    • on TMNF servers, players using the /donpanel command (and admins using /admin donpanel) receive a message that a TMUF server is needed; on TMUF servers, a TMNF player receives a message that a TMUF account is needed -
    -
    - -
  • automatically enabling /cps and /dedicps for newly connecting players is now configurable via new config.xml settings <auto_enable_cps> and <auto_enable_dedicps> - -
  • new /admin pay command (TMUF-only) to pay server coppers to a login, including another server login, with the following features: -
      -
    • the required parameters are the login, which can be any valid player or server login, and the coppers amount to pay -
    • the command provides a dialog box, similar to the donation dialog, allowing the admin to confirm or cancel the payment -
    • after confirmation, the payment happens immediately but the Nadeo tax (2 coppers plus 5 % of the transaction) isn't deducted from the server account until any player joins or leaves the server -
    • on TMNF servers, admins using this command receive a message that a TMUF server is needed -
    - -
  • new /admin cleanmutes command (alias /admin cleanignores) to completely clean the global mute/ignore list - -
  • the /admin server command now also works on TMN (albeit with the column alignment difficulties inherent to those windows) - -
  • on TMF, /server and /admin server now show the server's rights (United or Nations) and /stats shows the pertaining player's rights - -
  • on TMNF servers, players using the /donate command now receive a message that a TMUF server is needed; on TMUF servers, a TMNF player receives a message that a TMUF account is needed - -
  • on TMNF servers, admins using the /admin coppers command now receive a message that a TMUF server is needed - -
  • updated the Dedimania system to randomly choose between the existing :8006 and :8007 and new :8011, :8012 and :8013 communication ports (remember to open those ports on your firewall/router as well) in order to distribute the load across more Dedimania servers - -
  • attempts to /add, /admin add or /admin addlocal a track that's larger than 256 KB (the maximum online track size) are aborted with an error message - -
  • when using the full Jfreu plugin, the banning period by the badword filter is now configurable via $badwordstime in jfreu.config.php (default: 10 minutes) - -
  • new /jfreu badwordstime command to set the banning period (in minutes) - -
  • on TMF, unusual characters (such as in challenge names and player nicknames) no longer cause ManiaLink windows not to be displayed - -
  • on TMF, all standard admin panel templates now use the animated Buddy icon instead of the static Buddies one - -
  • on TMF, added the server's login, rights and packmask to the start-up header in the log - -
  • on TMN the default style and panel templates are no longer loaded - -
  • optimized the multi-page ManiaLink windows code on TMF -
- -

Bug fixes in the v1.01 release of XASECO:

- -
    -
  • on TMF, using /style <##>, /admin panel <##>, /recpanel <##> and /votepanel <##> to select the default style/panel doesn't work – fixed - -
  • on TMF, unofficial players (ladder rank -1) can bypass the rank limit checks – fixed - -
  • if a player reconnects within the same second after disconnecting, a MySQL warning is logged – fixed this long-standing bug - -
  • using /add on a track that's already in the server's list does not properly jukebox it anymore – fixed - -
  • on Windows, downloading the same track from TMX again with /add or /admin add can result in a PHP warning – fixed - -
  • if a newly connected player uses /refresh, a PHP warning can result – fixed - -
  • bogus PlayerFinish callbacks (with no valid login) occasionally result in PHP/XASECO warnings – fixed, such callbacks are now ignored - -
  • there are some layout glitches in the /jfreu players output when /style is off – fixed - -
  • the /jfreu badwordsnum command actually shows the /jfreu maxplayers value – fixed - -
  • on TMN, /admin players results in a 'GetIgnoreList' warning – fixed - -
  • a non-responding music server can cause XASECO to time out or crash when loading a song – fixed - -
  • log messages with a "Fatal Error" don't include the source file where the error occurs – fixed -
- -
- -

New features and other changes in the v1.00 release of XASECO:

- -
    -
  • new TMF window styles support with the following features: -
      -
    • the distribution includes a new styles/ subdirectory that can contain up to 50 style templates for the ManiaLink windows -
    • there are no fewer than 20 standard templates in light and dark, and in transparent, blurred and opaque variants -
    • templates define the window, header and body background containers, as well as the header and body text fonts, and the clickable button style -
    • the default window style is configurable via new config.xml setting <window_style> (default: DarkBlur) -
    • new /admin style <xxx> command to select another default window style for newly connecting players (requires style name, 'off' disables the default) -
    • the new /style command allows players to select an individual window style with the following options: -
        -
      • help: Displays help information -
      • list: Displays available styles -
      • default: Resets style to server default -
      • off: Disables TMF window style -
      • <xxx>: Selects window style <xxx>, which can be a style name or the sequence number from /style list -
      • in the /style list overview a player can also click a style name to dynamically select a new style (or refresh the current one if its .xml file was updated) -
      -
    • multi-page windows get ArrowFirst and ArrowLast buttons to jump directly to the first and last pages – as with the plain TMN-like style, windows of more than 5 pages also get ArrowFastPrev (Prev5) and ArrowFastNext (Next5) buttons to jump back and forth by 5 pages -
    • note that on TMNF the Black, Blue, Cyan and Orange styles revert to Green, so you might as well remove those files to prevent player confusion -
    -
    - -
  • new TMF admin panel support with the following features: -
      -
    • the admin panel contains 7 icons to perform common admin tasks, from left to right (or top to bottom):
      - the ClipRewind icon for /admin restartmap, ClipPause for /admin endround, ClipPlay for /admin nextmap, Refresh for /admin replaymap, ArrowGreen for /admin pass, ArrowRed for /admin cancel and Buddies for /admin players live -
    • the distribution includes a new panels/ subdirectory that can contain up to 40 admin panel templates; their filenames must start with 'Admin' -
    • there are 14 standard templates in various positions and sizes all around the HUD -
    • templates define the complete ManiaLink panel with position, size and fonts, so you have full control to develop custom panels -
    • the default admin panel is configurable via new config.xml setting <admin_panel> (default: BelowChat) -
    • new /admin admpanel <xxx> command to select another default admin panel for newly connecting admins (requires panel name, 'off' disables the default) -
    • the new /admin panel command allows admins to select an individual admin panel with the following options: -
        -
      • help: Displays help information -
      • list: Displays available panels -
      • default: Resets panel to server default -
      • off: Disables admin panel -
      • <xxx>: Selects admin panel <xxx>, which can be a panel name or the sequence number from /admin panel list -
      • in the /admin panel list overview an admin can also click a panel name to dynamically select a new panel (or refresh the current one if its .xml file was updated) -
      -
    • as with chat commands, use of the buttons in the admin panel remains governed by the tiered admin system and the ability lists -
    -
    - -
  • new TMF records panel support with the following features: -
      -
    • the records panel lists the Personal Best, Local #1 record, Dedimania #1 record and TMX world record -
    • the times (or scores in TMUF Stunts mode) for these four fields act as buttons to invoke the /topsums, /recs, /dedirecs and /tmxrecs commands, respectively -
    • the distribution's panels/ subdirectory can contain up to 50 records panel templates; their filenames must start with 'Records' -
    • there are 15 standard templates in left- and right-edge variants -
    • templates define the complete ManiaLink panel with position, size and fonts, so you have full control to develop custom panels -
    • the default records panel is configurable via new config.xml setting <records_panel> (default: RightBottom) -
    • new /admin recpanel <xxx> command to select another default records panel for newly connecting players (requires panel name, 'off' disables the default) -
    • the new /recpanel command allows players to select an individual records panel with the following options: -
        -
      • help: Displays help information -
      • list: Displays available panels -
      • default: Resets panel to server default -
      • off: Disables records panel -
      • <xxx>: Selects records panel <xxx>, which can be a panel name or the sequence number from /recpanel list -
      • in the /recpanel list overview a player can also click a panel name to dynamically select a new panel (or refresh the current one if its .xml file was updated) -
      -
    • all players' records panels are updated when a new #1 Local or Dedimania record is driven, a player's own panel when a new Personal Best is driven -
    • when the scoreboard comes up at the end of a track, records panels are temporarily disabled -
    • note that on TMNF some of the Blue fonts revert to Green, so you could rename those files to prevent player confusion -
    -
    - -
  • new TMF vote panel support with the following features: -
      -
    • the vote panel provides Yes and No buttons for TMX /add and all chat-based votes -
    • players can also operate the buttons with the F5 and F6 keys respectively (the keys don't work for spectators) -
    • the Yes button and F5 key invoke the /y command, the No button and F6 key are dummy operations to close the vote panel, which is necessary for a consistent user interface experience -
    • the vote panel is displayed for all players upon entering an /add or chat-based voting command, except for spectators when $allow_spec_voting in votes.config.php is off (but admins always get the vote panel) and for the vote initiator when $auto_vote_starter is on (because that already invokes /y automatically) -
    • a player's vote panel disappears once clicked (or when entering /y), and any remaining panels disappear automatically when a vote passes, expires, or is cancelled -
    • the color of the Yes button matches the type of vote: pink ({#vote} in config.xml) for chat-based votes and orange ({#emotic}) for /add votes; the No button is always dark gray -
    • the distribution's panels/ subdirectory can contain up to 10 vote panel templates; their filenames must start with 'Vote' -
    • there are 4 standard templates in various positions and sizes around the HUD -
    • templates define the complete ManiaLink panel with position, size and fonts, so you have full control to develop custom panels -
    • the default vote panel is configurable via new config.xml setting <vote_panel> (default: BelowChat) -
    • new /admin votepanel <xxx> command to select another default vote panel for newly connecting players (requires panel name, 'off' disables the default) -
    • the new /votepanel command allows players to select an individual vote panel with the following options: -
        -
      • help: Displays help information -
      • list: Displays available panels -
      • default: Resets panel to server default -
      • off: Disables vote panel -
      • <xxx>: Selects vote panel <xxx>, which can be a panel name or the sequence number from /votepanel list -
      • in the /votepanel list overview a player can also click a panel name to dynamically select a new panel (or refresh the current one if its .xml file was updated) – while doing this the panel is briefly displayed to show its new location -
      -
    • when the scoreboard comes up at the end of a track, vote panels are disabled (because the ongoing vote expires at that point) -
    -
    - -
  • the /admin players command now accepts a special option live to display a list of just the online players, rather than any players in the database that match the search string -
  • on TMF the /admin players command now provides a comprehensive player management window: -
      -
    • the nick/login buttons will invoke /stats on the selected player -
    • the operation buttons invoke, from left to right, on the selected player: /admin warn, /admin ignore or /admin unignore, /admin kick, /admin ban or /admin unban, /admin ban or /admin unblack, /admin addguest or /admin removeguest, and /admin forcespec -
    • the toggle operations show the operation that is meaningful with respect to the player's status, e.g. UnIgnore for an ignored player, Add Guest for a non-guest one, and no Force Spec button for a spectator -
    • operations can be performed on both online and offline players, but buttons are only provided when that operation is feasible, e.g. offline players cannot be warned, ignored, kicked or banned but they can still be unbanned and unblacked -
    • the window is refreshed (albeit to the first page) to show the new status when any toggle operation is performed, i.e. (Un)Ignore, Kick, (Un)Ban, (Un)Black, Add/Remove Guest and Force Spec -
    • buttons are only provided for the first 200 players, which should be sufficient for the live list – if the desired player in an offline list is beyond the 200 mark, refine your search string -
    -
    - -
  • new /jfreu players command to display the list of all known players since the last XASECO restart, and search for offline players via a (part of a) login or nickname with /jfreu players <string> -
  • the special option live displays a list of just the online players, rather than any players tracked since the last XASECO restart that match the search string -
  • on TMN this command also includes the current Badword count for each player if $badwords in jfreu.config.php is 1 (true) -
  • on TMF this command provides the second player management window: -
      -
    • as before, the nick/login buttons will invoke /stats on the selected player -
    • the operation buttons invoke, from left to right, on the selected player: /jfreu badword (if $badwords is 1), /jfreu banfor 1H, /jfreu banfor 24H, /jfreu unban, /jfreu addvip or /jfreu removevip, and /jfreu unspec -
    • also shown are the player's current badword count (if $badwords is 1) and the time left in a temporary ban, if any -
    • the single toggle operation in this window shows the operation that is meaningful with respect to the player's status, i.e. Add or Remove a player as VIP -
    • the Spec column provides an UnSpec button for SpecOnly players, and otherwise shows the player's current status (Player, Spec or Offln) -
    • operations can be performed on both online and offline players, but buttons are only provided when that operation is feasible, e.g. offline players cannot have their badword count incremented or be unspec-ed but they can still be temporarily banned -
    • the window is refreshed (albeit to the first page) to show the new status after all of the operations -
    • buttons are only provided for the first 200 players, which should be sufficient for the live list – if the desired player in an offline list is beyond the 200 mark, refine your search string -
    -
    - -
  • on TMF in the output of the various /list lists, /best and /worst, an author column has been added - -
  • on TMF it's now possible to add clickable buttons to relevant lists, configurable via new config.xml setting <clickable_lists> (default: True): -
      -
    • in the various /list lists, /best and /worst the track buttons will jukebox the selected track; no button is displayed for tracks in the track history – supports up to 1900 tracks -
    • in the various /list lists, /best and /worst the author buttons will invoke /list <author> -
    • on TMUF, in the various /list lists, /best and /worst the environment buttons will invoke /list env: for the selected environment -
    • in /music list the song buttons will jukebox the selected song – supports up to 1900 songs -
    • in /jukebox display a player will get a button only for the name of his/her own jukebox track, which can be used to drop it; admins of any tier with the dropjukebox ability get buttons for all track names to drop any of them – supports up to 100 tracks -
    • in /players the nick/login buttons will invoke /stats on the selected player – supports up to 200 players -
    • in /stats the Time Played field is a button to invoke /active, Server Rank invokes /top100, Records invokes /toprecs and Races Won invokes /topwins -
    • in /admin listignores, /admin listbans, /admin listblacks and /admin listguests the nick/login buttons will invoke /admin unignore, /admin unban, /admin unblacks and /admin removeguest (respectively) on the selected player – supports up to 200 players -
    • in /jfreu listbans, /jfreu listvips and /jfreu listvipteams the nick/login/team buttons will invoke /jfreu unban, /jfreu removevip and /jfreu removevipteam (respectively) on the selected player/team – supports up to 200 players/teams -
    • this works with any window style, including the plain TMN-like style -
    -
    - -
  • all clicked commands in the aforementioned windows and panels are logged analogous to chat commands - -
  • when you've added more style or panel templates than the maximum, a warning is logged and the excess templates are ignored - -
  • new /autojuke command to automatically jukebox the first available track from the chosen /list selection (e.g. /autojuke nofinish, /autojuke norank... see /autojuke help) – optional alias /aj becomes available by uncommenting the appropriate two lines at the start of plugin.rasp_jukebox.php - -
  • new /admin forcespec command to force a player into spectator mode (free camera) - -
  • the /stats command now accepts an optional login or player ID by all players, not just admins with the chat_stats ability – that ability was renamed to chat_statsip and defines which admin tiers can see the player's IP address (for privacy reasons this isn't shown to all players) - -
  • on TMF, players' team names are no longer updated (cleared) in the local database upon reconnection, so that a plugin/command (e.g. /teamname in plugin.matchsave.php) can store meaningful team names in the database - -
  • on TMF if a track contains no custom song, /song will advise to try /music current if the music server plugin is enabled - -
  • when the scoreboard comes up at the end of a track, any pop-up windows are now automatically closed - -
  • optimized the checkpoints panel display code - -
  • HTTP GET requests to TMX and for the up-to-date check now include a User-Agent header to allow minimal statistics tracking - -
  • updated the GBXDataFetcher module to rename the GBXDataFetcher class to GBXChallengeFetcher, and add the new GBXReplayFetcher class (not used in XASECO though) -
- -

Bug fixes in the v1.00 release of XASECO:

- -
    -
  • the search order is missing from the /list karma header – fixed - -
  • on TMF, the /admin listignores (aka. ignorelist) listing could get mixed up with previous window output – fixed - -
  • reminder, expiration and cancellation messages for /add votes show the unstripped track name – fixed -
- -
- -

New features and other changes in the v0.99b release of XASECO:

- -
    -
  • new /admin clearhist command to clear part of, or the entire, track history: with no option, it shows the total number of tracks in the history; with the 'all' option, the entire history is cleared; with a positive number, that many of the most recent tracks are cleared, and with a negative number that many of the oldest tracks - -
  • the music server plugin supports song files in subdirectories at the server URL, but now those subdirectories can be excluded in any command that shows songs (configurable via new musicserver.xml setting <strip_subdirs>) - -
  • new /music stripdirs command to change the strip subdirs setting; the /music settings output also includes the StripDirs value - -
  • a flexible way to control the contents of the ManiaLinks <custom_ui> block now allows other plugins and widgets to co-operate with the main XASECO system - -
  • the config.xml settings <show_recs_before> and <show_recs_before> are now integers instead of booleans: 2 = show the full records report, 1 = show just the track record before or the top-5 records after the track, 0 = show no records at all (reduces chat message volume when a records ManiaLink panel is displayed) - -
  • when XASECO is out of date and a master admin connects, he/she will automatically receive an /admin uptodate reminder message – hopefully this will motivate more master admins to keep their XASECO version in sync with the latest release -
- -

Bug fixes in the v0.99b release of XASECO:

- -
    -
  • new local records aren't properly marked anymore in /liverecs, /newrecs, rounds reports and the records report after a track – fixed - -
  • too stringent input checking for /admin setpwd, /admin setspecpwd and /admin setrefpwd prevents clearing those passwords – fixed -
- -
- -

New features and other changes in the v0.99 release of XASECO:

- -
    -
  • new music server support (TMF-only) becomes available when plugin.musicserver.php is enabled (uncommented) in plugins.xml, offering the following features: -
      -
    • the music plugin is configured via musicserver.xml, containing the following entries: -
        -
      • the server URL, an http server that must allow direct download of the song files from the specified directory -
      • a list of song files (case-sensitive) in .OGG or .MUX format that must exist at the server URL – duplicates are removed when loading the XML file -
      • configuration options to control whether to override a track's own music, whether to automatically load the next song upon the next track, and whether to allow jukeboxing songs by players -
      • a list of plugin messages -
      -
    • the new /music command offers the following options: -
        -
      • help: Displays help information – admins see all options, other players only the options they can use -
      • settings: Displays current music settings – for security, only admins see the server URL -
      • list: Displays available songs -
      • current: Shows current song -
      • reload: Reloads musicserver.xml config file – admin-only -
      • next: Skips to next song (upon next track) – admin-only -
      • shuffle: Randomizes song list – admin-only -
      • override: Changes track override setting – admin-only -
      • autonext: Changes automatic next song setting – admin-only -
      • allowjb: Changes allow jukebox setting – admin-only -
      • off: Disables music, auto next & jukebox – admin-only -
      • jukebox (alias jb): Displays upcoming songs in jukebox -
      • drop: Drops your currently added song -
      • <##>: Adds a song to jukebox where <##> is the number from /music list -
      -
    • a new <music> color is defined in config.xml and used in all relevant music messages so that they can easily be customized -
    • a new chat_musicadmin ability controls which tiered admin levels can use the /music admin commands -
    • if autonext is enabled, the next song from the list will be loaded upon the next track, unless an admin manually skipped to the next song or the jukebox specifies another song -
    • if allowjb is enabled, any player can add one song at a time to the jukebox – to jukebox another, first use the drop option -
    • the same song cannot by added to the jukebox more than once -
    • if a track itself contains a song and override is disabled, a possible jukeboxed song is skipped as it's not possible to detect the track song -
    • the off option disables server music upon the next track (so the in-game music returns), clears the jukebox, and disables the autonext and allowjb options so no new songs get loaded – to enable server music again, simply turn either or both options back on -
    • the reload and shuffle options also clear the jukebox -
    • a warning message is logged when a song file cannot be loaded, and no song change takes place until the next track -
    • reminder: to see the filename of a track's own song, use the /song command -
    -
    - -
  • new donation support (TMUF-only) becomes available when plugin.donate.php is enabled (uncommented) in plugins.xml, providing the following features: -
      -
    • new /donate command to give coppers to the server -
    • the minimum donation is 10 coppers (because of Nadeo tax) -
    • donations of 100 and more coppers are awarded with a public appreciation message (below 100 it remains private) -
    • both thresholds are configurable in plugin.donate.php -
    • a new <donate> color is defined in config.xml and used in all relevant donation messages so that they can easily be customized -
    • remember that the server's coppers amount (seen with /admin coppers) isn't updated until any player joins or leaves the server -
    • important: you must make an initial donation from a TMUF player login to your server login via the in-game message system, so that there are sufficient coppers in the account to pay the Nadeo tax on the first /donate transaction -
    • an account rights error is shown when attempting to donate from a TMNF player account or to a TMNF server account -
    - -
  • new /list env:zzz option (TMUF-only) to search for tracks by environment, where zzz can be: Stadium, Bay, Coast, Island, Alpine/Snow, Desert/Speed, Rally (case-insensitive) – can also be combined with a search for track/author name in either order: /list xxx env:zzz or /list env:zzz xxx (remember that xxx itself is allowed to contain spaces too) - -
  • on TMF, the /stats output now includes the time (in days and hours) since the player inscribed in the zone, as well as the last match score, number of wins/draws/losses and win/loss ratio (which are included in the /statsall output on TMN) - -
  • on TMF, the /server output now includes the server comment – note however that special formatting in long comments is not continued after column line breaks - -
  • new /admin server command (TMF-only) to display the server's detailed settings - -
  • new /admin setcomment command to change the server comment - -
  • new /admin setmaxspecs command to change the maximum number of spectators - -
  • new /admin setpwd, /admin setspecpwd and /admin setrefpwd commands to change the player, spectator and referee passwords (referee only on TMF) - -
  • new /admin setrefmode command (TMF-only) to change the Referee mode (0 = top3, 1 = all) - -
  • new /admin forceteam command (TMF-only) to force a player into the 'Blue' or 'Red' team (the names can be abbreviated to 'B' and 'R') – if no team is specified, the player's current team is shown - -
  • new /admin nextenv command (TMUF-only) to skip to the next track within the same environment - -
  • on TMF, the track name is now hyperlinked to the corresponding TMX page (if available) in the /track, /playtime, /newrecs and /liverecs messages as well as the local record reports before and after each track, and the total playtime message at the end of each track in Rounds/Team/Cup mode - -
  • on TMF, the track name is now hyperlinked to the corresponding Dedimania page in the /dedinew and /dedilive messages as well as the Dedimania record reports before and after each track - -
  • in TMUF Stunts mode, updated the TMX World Record message and /tmxrecs output to show scores - -
  • in TMUF Stunts mode, the total playtime message at the end of each track isn't shown anymore (always 5 minutes) - -
  • the /tmxinfo output now includes the TMX section for the track, as well as the download link to the TMX World Record replay (if available) - -
  • when the current track is not found on TMX, /tmxinfo and /tmxrecs now show the track name instead of the UID in the error message - -
  • added new internal onStatusChangeToX (X = 1-5) event for the corresponding TM server status changes, and new internal onKarmaChange event for karma vote changes - -
  • enforced strict and complete error checking on all local database operations - -
  • added a warning if the SuperAdmin password is still set to the default (and insecure) 'SuperAdmin' - -
  • added the operating system to the first log message during start-up - -
  • set a 20 MB memory limit for the XASECO PHP process - -
  • this release requires the latest server build 2008-05-19 -
- -

Bug fixes in the v0.99 release of XASECO:

- -
    -
  • when the TM server goes away (e.g. crashes), XASECO loops forever at high load – fixed, XASECO also shuts down with an appropriate error message - -
  • tracks downloaded via /add and /admin add are corrupted since v0.98 and won't load in-game – fixed - -
  • in TMUF Stunts mode, the new/improved/secured/equalled record messages (both local and Dedimania) specify that the finish value is a Time – fixed into Score - -
  • on TMF, players added to the global mute list are still able to send global messages via the /me, /hi, /bye, etc. chat commands – fixed - -
  • on TMF, the muting system handles only 18 lines of the full chat window – increased to all 28 lines - -
  • on TMF, there is a tiny layout glitch in the /server output – fixed - -
  • there is no log message for /admin setgamemode – fixed -
- -
- -

New features and other changes in the v0.98 release of XASECO:

- -
    -
  • new /dedicps command to select which Dedimania record is used as reference for checkpoints tracking, with the following features: -
      -
    • checkpoint references are stored for all players (according to their preferences) only at the start of a track, not when entering the /dedicps command or when the player (re)connects -
    • with no parameter (/dedicps) the player's own record on each track is selected or, if none is set, the last record on the pertaining track -
    • /dedicps {1-30} selects Dedimania record 1-30 on each track or, if unavailable, again the last record -
    • /dedicps off disables the Dedimania references for checkpoints tracking -
    • as usual, once a player gains a better finish time than the chosen Dedimania reference, the checkpoints panel (TMF) or pop-up (TMN) will use those corresponding checkpoints for further updates during the current track -
    • if the checkpoints for the chosen Dedimania record are invalid (empty, or the last checkpoint doesn't match the finish time) then no record is used as reference during that track for the pertaining player(s) -
    • the chosen /dedicps setting is remembered between a player's reconnects as long as /cps remains enabled, until the next XASECO restart -
    - -
  • new /admin shuffle (alias /admin shufflemaps) command to shuffle (randomize) the current track list; this requires having set <writetracklist_random> to True in config.xml, and defining an $autosave_matchsettings filename in rasp.settings.php – note that autosaving only happens during a track switch, so if you shuffle immediately after adding a track, the addition will be lost - -
  • new /admin addlocal command to add a track to the server's track list from the local 'Challenges/Downloaded/' directory; the track filename cannot contain spaces or non-typable characters, but can be entered case-insensitively and the '.Challenge.gbx' extension is optional – also, the $jukebox_adminadd setting is respected - -
  • new /admin specfree command (TMF-only) to set the camera mode of a spectator (login or ID) to Free – requires build '2008-05-07' or later - -
  • added support for TMF Laps mode: -
      -
    • the checkpoints tracking system (if activated via /cps) will show the difference to the best lap at each checkpoint and at the lap finish, and update the best lap data after each improved lap -
    • if /dedicps is activated, the lap data for the chosen Dedimania record is used as initial best lap according to the rules above -
    • local records are now maintained and compared on lap basis, meaning that those records will usually be unbeatable once the server is switched back to another game mode than TimeAttack, so keep that in mind when picking tracks for TimeAttack and Laps versus other modes -
    • Dedimania records are also maintained and compared on lap basis like the central Dedimania system does (where they're mixed with TimeAttack mode records only), so they are now properly supported -
    - -
  • added support for TMUF Stunts mode: -
      -
    • updated /track, /recs and /dedirecs, /pb and /dedipb, and all record relation commands to show scores -
    • updated the 'before' and 'after' track record messages (both local and Dedimania) to show scores -
    • updated the new/improved/secured/equalled record messages (both local and Dedimania) to show scores -
    • updated all record sorting into descending order -
    • updated /list nogold and /list noauthor to check for player scores worse than the tracks' gold and author scores -
    • adjusted the /list help information -
    • disabled the /list longest/shortest command -
    • disabled all /cps functionality (checkpoints are inactive) -
    • disabled Dedimania records handling until the central Dedimania system supports them -
    - -
  • added explanations why the checkpoints panel doesn't appear immediately when /cps is activated (Stunts mode, spectating, and warm-up phase all disable the panel temporarily) - -
  • on TMF, multi-page ManiaLink windows now show the current and total page numbers in the header - -
  • on TMF, multi-page ManiaLink windows of more than 5 pages now include Prev5 and Next5 buttons to speed up navigation through long lists - -
  • updated the Dedimania system to randomly choose between the :8003 and new :8006 and :8007 communication ports (remember to open those ports on your firewall/router as well) in order to distribute the load across more Dedimania servers - -
  • new records in the /recs and /dedirecs lists are now highlighted with a black time - -
  • on TMF, the /stats output now displays the full location path (sans "World|") instead of just the nation (thanks w1lla) - -
  • on TMF, the /server output now includes the server's location path (again without "World|") – requires build '2008-05-07' or later - -
  • added the capability to write the log in monthly chunks named "logfile-YYYYMM.txt" in an (automatically created) "logs/" subdirectory, instead of the single big "logfile.txt" – this is activated by setting "$log_monthly_logsdir = true" in function doLog() in includes/basic.inc.php, as it cannot be a configuration option because configs aren't read until later during start-up - -
  • various welcome messages and in-game URLs are now hyperlinked to the external browser - -
  • updated the /add and /admin add commands to time out when the TMX site is down - -
  • minor optimizations of all multi-page window code - -
  • an example plugin.ml_howto.php module is included to demonstrate how to create single- and multi-page ManiaLink windows -
- -

Bug fixes in the v0.98 release of XASECO:

- -
    -
  • when <lists_colortracks> is True, recently played tracks are no longer greyed out to visually distinguish them from tracks that can be jukeboxed – fixed - -
  • jukeboxing a track via /admin add and /admin replay leaves out the Environment value – fixed - -
  • even when a player ID cannot be obtained from the local database, there's still an attempt to insert a finish time for the pertaining player login – fixed - -
  • on TMF, the /admin shutdown command produced a PHP warning because /autoquit is now the default – fixed, a QuitGame call is only done if /noautoquit was active -
- -
- -

New features and other changes in the v0.97 release of XASECO:

- -
    -
  • on TMF, the brief /cps pop-up is now a permanent, small transparant panel, mid-center of the screen just above the race time; it is temporarily disabled when a player switches to spectator mode though - -
  • on TMF, the checkpoints panel now also shows the difference against the best run at the finish ('CPF') - -
  • in TMF Cup mode, the end of the warm-up phase no longer generates an onEndRace event, preventing several chat messages that only belong at the end of the track; also, during warm-up the checkpoints panel is temporarily disabled - -
  • on TMF, the /tmxinfo window now displays the track screenshot from TMX - -
  • on TMF, /tmxinfo {ID} and /tmxrecs {ID} accept an optional TMX section parameter (TMN, TMU, TMNF, and even TMO, TMS &ndash all case-insensitive) after the TMX track ID to enable displaying track info from another section than the default (as outlined in the v0.96 release notes) - -
  • new /admin scorepanel command (TMF-only) to show the current Automatic Scorepanel setting – displaying the scoreboard after each run/round, or only at the end of the track – and to toggle it with an ON" or "OFF" (case-insensitive) parameter - -
  • new /admin roundsfinish command (TMF-only) to show the current Rounds Finishpanel setting – displaying the rounds panel for all players when the first player finishes, or not until each player finishes him/herself – and to toggle it with an ON" or "OFF" (case-insensitive) parameter - -
  • displaying colored or stripped player nicknames in /top10, /top100, /toprecs, /topsums, /topwins, /recs, /dedirecs and /active is now configurable via new config.xml setting <lists_colornicks> – because of column formatting caveats, it's recommended to keep nicknames stripped on TMN, as they were before - -
  • displaying colored or stripped tracknames in all /list commands, /best, /worst, /jukebox display and /admin listdupes is now configurable via new config.xml setting <lists_colortracks> – again, it's recommended to keep tracknames stripped on TMN as they were before - -
  • the teamnames in the /topclans output are not color-stripped anymore - -
  • Dedimania system timeouts are handled better to prevent record message flooding; also, improved logging and debugging of the low-level Dedimania layers - -
  • on TMF, any admin with the new server_coppers ability can see the server's coppers amount in the /server output - -
  • new /admin coppers command (TMF-only) to similarly check the server's coppers amount - -
  • on TMUF, the output of all /list commands (including /best, /worst) and of /jukebox display and /admin listdupes now includes an Env(ironment) column - -
  • on TMUF, the chat and log messages for the next jukebox track now specify that track's environment - -
  • added new internal onBillUpdated, onChallengeListModified and onPlayerInfoChanged events for the corresponding new TMF callbacks; also, the onEndRound event is now triggered by the real TMF EndRound callback - -
  • the new onPlayerInfoChanged event is now used to keep track of players' spectator status to avoid an extra GetPlayerInfo call whenever that status is needed (thanks Dagobert) - -
  • updated the stripColors function to strip out the '$h', '$l' and '$p' links for TMF as well - -
  • clarified all error messages related to XML config files not being read or parsed correctly - -
  • fine-tuned column widths of many ManiaLink windows - -
  • when restarting XASECO, all ManiaLinks on all connected clients are cleared -
- -

Bug fixes in the v0.97 release of XASECO:

- -
    -
  • when Dedimania is slow to respond to a track switch, the updated records message for the finished track may not appear until the next track is loaded, and use that new track's name – fixed - -
  • on TMF, very long country names cause layout glitches in the /players output – fixed, country names longer than 14 characters are mapped to their nation abbreviations - -
  • on TMF in Rounds/Team/Cup mode, the last 'Rx>' rounds report isn't shown due to a different callback sequence – fixed by using the real EndRound callback above - -
  • if a player connects without a team name, the TMNDataFetcher is used to try and obtain it from the TMN master server, even on TMF – fixed - -
  • the file_exists_nocase function (used by /add and /admin add) doesn't work correctly on Windows – fixed (hopefully) - -
  • adjusted two function declarations and related calls to avoid pass-by-reference warnings on some PHP setups - -
  • the /summary chat message was largely lacking the shadow attribute – fixed -
- -
- -

New features and other changes in the v0.96b release of XASECO:

- -
    -
  • on TMF, the global mute list now uses the built-in Ignore features instead of the muting plugin, so the actual muting works much better; the plugin is still needed for the individual player muting features though - -
  • accordingly, there are new aliases for the related admins commands: /admin ignore (for /admin mute), /admin unignore (for /admin unmute), /admin ignorelist and /admin listignores (for /admin mutelist and /admin listmutes) - -
  • on TMF, the ManiaZones country names are mapped back to three-letter nation abbreviations for storage in the local database and for Dedimania; to repair the Nation field in the local 'players' table, you can use this script - -
  • new $autosave_matchsettings option in rasp.settings.php to enable autosaving the matchsettings file (with a configurable name) upon every track switch - -
  • on TMF, /add and /admin add accept an optional TMX section parameter (TMN, TMU, TMNF, and even TMO, TMS &ndash all case-insensitive) after the TMX track ID to enable downloading a track from another section than the default (as outlined in the v0.96 release notes) - -
  • when a track is jukeboxed that doesn't match the server packmask, it is skipped with warning, and the next track from the tracklist is loaded (the next jukeboxed track will follow after that) - -
  • on TMF, added another method in votes.config.php to really disable all CallVotes if chat-based votes are enabled - -
  • the pop-up windows in plugin.matchsave.php are now converted to TMF ManiaLink windows too, along with minor touch-ups of the TMN pop-ups -
- -

Bug fixes in the v0.96b release of XASECO:

- -
    -
  • on TMF, the /nextmap command always shows the first track after the current one is removed from the server's track list (via /admin removethis or /admin erasethis) – fixed - -
  • a missing or invalid connection to the TM server (due to configuration errors) can cause a flood of PHP warnings in the log – fixed - -
  • a track ending with no records set can cause a PHP warning in the Dedimania plugin – fixed -
- -
- -

New features and other changes in the v0.96 release of XASECO:

- -
    -
  • the system has been renamed to XASECO to avoid confusion with other ASECO/RASP systems, and to signify it's a complete and integrated solution for server control - -
  • added ManiaLinks windows (via new include manialinks.inc.php) to allow the system to display TMN-style pop-ups on TMF - -
  • added a Prev button to all multi-page ManiaLinks windows when viewing the second and subsequent pages - -
  • added recognition of server type TMF and game mode Cup, including with the /admin setgamemode command - -
  • updated the /server command to handle Cup mode and show the RoundsPerChallenge value (in addition to the points limit) if the server is in Cup mode - -
  • new /admin disablerespawn command (TMF-only) to show the current DisableRespawn setting – allowing players to respawn at checkpoints – and to toggle it with an ON" or "OFF" (case-insensitive) parameter - -
  • new /admin forceshowall command (TMF-only) to show the current ForceShowAllOpponents setting – forcing players to see all opponents – and to toggle it with an ON" or "OFF" (case-insensitive) parameter - -
  • updated the TMXInfoFetcher class to handle the new TMNF section - -
  • on a TMNF server (packmask Stadium) the /tmxinfo and /tmxrecs commands as well as the TMX World Record message at the start of a new track will consult the TMNF section of TMX, on a TMUF server the TMU section, and (as before) on a TMN server the TMN section - -
  • the /add and /admin add commands will consult the TMNF, TMUF and TMN sections of TMX according to the same rules - -
  • new $jukebox_permadd option in rasp.settings.php to allow tracks voted into the jukebox via /add to remain permanently in the server's track list (normally they're removed after they're finished) - -
  • added extra check for non-empty TM server build during start-up - -
  • several minor tweaks to web_access.inc.php to make it more robust and prevent stream_select() warnings (thanks Slig) -
- -

Bug fixes in the v0.96 release of XASECO:

- -
    -
  • when a player equals his/her local or Dedimania record, it would be logged again with the next higher position (but not stored anywhere) – fixed - -
  • new Dedimania record messages would be displayed for LAN logins making records, even though the records aren't actually submitted to the central Dedimania server – fixed - -
  • plugin.matchsave.php used the CRLF constant in a TMN pop-up – fixed into LF -
- -
- -

Known issues in the v0.96+ release of XASECO:

- -
    -
  • the /statsall command doesn't work in TMF because there's no way to obtain a player's world stats (yet?) -
- -
- -

Older release notes

- -
-
-Copyright © 2007-2013 – Frans P. de Vries <tm@gamers.org> -            -Last updated 19-Aug-2008 -
- - diff --git a/xaseco/DOCS/Features_116.html b/xaseco/DOCS/Features_116.html deleted file mode 100644 index e7ae48d..0000000 --- a/xaseco/DOCS/Features_116.html +++ /dev/null @@ -1,895 +0,0 @@ - - - -TrackMania Nations - XASECO release notes v1.16 - - - - - - - - - - -

-TrackMania Nations -


- -

New features and other changes in the v1.16 release of XASECO:

- -
    -
  • new /cptms command to display the checkpoint times (if available, as they're only being stored since v1.06) for all local records on the current track, as a complement to /sectms -
  • new /dedicptms command to display the checkpoint times for all Dedimania records on the current track, as a complement to /dedisectms - -
  • added the Countable interface to the TMXInfoSearcher class - -
  • removed the <max_recs> setting from dedimania.xml to insure that only the Server and player MaxRank values from the Dedimania master server are used, and to avoid confusion - -
  • resetting the challenges list cache at the start of each challenge is now configurable via new rasp.settings.php setting $reset_cache_start (default: true) - -
  • the PHP memory limit for XASECO2 is no longer set to 50 MB but to 128 MB (the PHP default) unless it is already at least that size (thanks Electron) - -
  • updated the GBXDataFetcher module to the latest version -
- -

Bug fixes in the v1.16 release of XASECO:

- -
    -
  • when a map isn't in the database yet, in some situations its name could be stored with stripped colors/formatting – fixed -
- -
- -

New features and other changes in the v1.15b release of XASECO:

- -
    -
  • rewrote the GBXDataFetcher module completely to correctly process ManiaPlanet maps and replays – the new GBXChallMapFetcher and GBXReplayFetcher classes are not compatible with the old GBXChallengeFetcher and GBXReplayFetcher ones, but a new GBXChallengeFetcher wrapper class is included for backwards compatibility with third-party plugins - -
  • small update to the TMXInfoFetcher class to support 24-char UIDs - -
  • removed the obsolete ReplayParser class -
- -

Bug fixes in the v1.15b release of XASECO:

- -
    -
  • two tiny fixes in Auto TimeLimit handling and /tmxinfo - -
  • the error checking in the TMXInfoSearcher class is slightly too strict – fixed -
- -
- -

New features and other changes in the v1.15 release of XASECO:

- -
    -
  • implemented several security enhancements to prevent unauthorised use of chat commands - -
  • in multi-page windows all pages are filled up with empty rows to the same number of rows as the first page to ease browsing (except when using the old TMN-style windows) - -
  • on TMF, the /jukebox, /add, /admin add and /admin addlocal commands now check the chosen track against the server settings and ignore it if it doesn't match - -
  • new support for the Dedimania MaxRank system: -
      -
    • the default MaxRank is 30, meaning that players normally cannot attain a record outside the top-30 -
    • when a server MaxRank has been upgraded (still to be fully implemented by Slig) then all players on that server can attain records up to that larger rank -
    • when a player's MaxRank has been upgraded (ditto) then that player can attain records up to his/her rank, or the server MaxRank if that is still larger -
    • /dedirecs and /dedisectms show all known records for a track, including those outside the top-30 -
    • /dedilast and /dedirange also operate on the last known record, even if that is outside the top-30 -
    • the /dedistats output includes the Server MaxRank and your player MaxRank -
    - -
  • a fatal error is now reported if no masteradmin is configured in config.xml - -
  • added an updatepanels.php script to the DOCS/ directory to allow updating all players' panels of a given type to a new template - -
  • added more logging, including transaction IDs, of copper transactions - -
  • new onDediRecsLoaded event to indicate that the Dedimania records for a new track have been received from the master server (thanks undef.de) - -
  • the checks why Dedimania records are ignored on a track have been improved, and the reasons are now logged always (not just in debug mode) - -
  • improved error checking and handling in the Dedimania system and the local database plugin - -
  • deployed updated web_access.inc.php for Dedimania communication (thanks Slig) - -
  • added a dynamic idle period to the main loop in order to reduce the CPU load if system usage allows it (thanks oliverde8) - -
  • the game mode values are now available as constants Gameinfo::RNDS (0), TA (1), TEAM (2), LAPS (3), STNT (4), CUP (5) - -
  • the game state (racing or scoreboard) is now maintained in $aseco->server->gamestate with constants Server::RACE and Server::SCORE (thanks undef.de) - -
  • improved error reporting in the updated TMXInfoFetcher and TMXInfoSearcher classes - -
  • this release requires the latest server build 2011-02-21 -
- -

Bug fixes in the v1.15 release of XASECO:

- -
    -
  • in Laps mode, the checkpoints panel isn't updated on the first passage through the finish when a previous best lap time is already known – fixed - -
  • UTF-8 characters in (master)admin/operator titles in adminops.xml are not processed correctly – fixed (thanks TheBigG) - -
  • on TMF the /dedisectms header shows the maximum number of local records instead of Dedimania ones – fixed - -
  • /dedipb causes PHP warnings for a player without Dedimania record on a track with fewer than the maximum number of records – fixed - -
  • tiny code and typo fixes discovered while creating XASECO2 -
- -
- -

New features and other changes in the v1.14 release of XASECO:

- -
    -
  • new support for TMF relay servers: -
      -
    • on the master server, relays are now registered separately rather than considered spectating players -
    • on a relay server, the start-up log includes the login and nickname of the master server it relays to -
    • new /admin relays command to display (on the master server) the list of connected relay(s), or show (on a relay server) its master server -
    • the /admin server output is reorganized to make room for a line about the server's master/relay status on the first page -
    • on a relay server, the /server output includes the master server's login/nickname in place of the track count -
    • on a relay server are disabled: -
        -
      • commands (e.g. /jukebox), events (e.g. checkpoints and finishes) and functionality (e.g. player reranking) that make no sense on a relay -
      • commands (e.g. /recs) and functionality (e.g. personal bests) that require information from the master server's database -
      • logging of chat messages from the master server (but they are still shown) -
      -
    • on relay servers, plugins plugin.autotime.php, plugin.matchsave.php, plugin.rasp_votes.php, chat.dedimania.php and plugin.dedimania.php must be disabled, and third-party plugins may not operate correctly (e.g. Fufi's Menu is fine, but Fufi's Widgets won't work) – you may want to disable plugins such as chat.records.php, chat.records2.php, plugin.checkpoints.php, etc. too, as all their commands are disabled anyway -
    - -
  • improvements in the Dedimania plugins due to API enhancements (thanks Slig): -
      -
    • identical logins from different games (e.g. TMN ESWC vs. TMU/TMF) are now associated correctly with their records in relevant commands, new/improved/secured/equalled chat messages, and the reports before and after each track -
    • when a login that is banned from Dedimania joins the server, this is announced with a general chat message, and finishes by that login are ignored (you may also want to activate the Dedimania global blacklist in rasp.settings.php to prevent them from joining in the first place) -
    - -
  • on TMF, the challenge list cache is now also refreshed upon external changes (i.e. by remote controllers) to the server's track list - -
  • /admin setgamemode checks whether the game mode is actually being changed, and mode changes are now taken into account when restarting (thanks undef.de) - -
  • the XML parser (parseXml in xmlparser.inc.php) has a new option to preserve UTF-8 encoding on extracted strings (for historical reasons the default is false), so now any plugin calling the parser can control UTF-8 encoding - -
  • a new aseco.php configuration definition CONFIG_UTF8ENCODE controls whether UTF-8 encoding on strings in config.xml is preserved (default false as above) – config.xml setting <welcome_utf8encode> is now obsolete - -
  • tagged all generic warning/error messages from the dedicated server with the invoking method - -
  • all included HTML documentation now passes the W3C validatator -
- -

Bug fixes in the v1.14 release of XASECO:

- -
    -
  • the challenge list cache isn't always properly refreshed – fixed (thanks jimos94) - -
  • when XASECO is configured to use the global Dedimania blacklist on a server that didn't use it before, the initial bulk merge doesn't work – fixed (thanks soehest) - -
  • merging the global blacklist causes a bogus warning – fixed -
- -
- -

New features and other changes in the v1.13 release of XASECO:

- -
    -
  • new /ignore command to start a chat-based vote to ignore/mute a player – usage and configuration are analogous to /kick, so see the v0.84 release notes for more info - -
  • new /lastwin command (via chat.lastwin.php) to re-open the multi-page window that was most recently closed; on TMN it's opened at the first page, on TMF at the current page – single-page windows are not stored with the player data so those cannot be re-opened - -
  • implemented a challenge list cache mechanism to improve the speed of all /list lists (thanks xai), /best, /worst and /summary, especially on servers with a large number of tracks - -
  • the speed of /toprecs, /topsums and the calculation of all players' server ranks is further improved by optimized MySQL interaction, in addition to the above cache mechanism - -
  • updates for the (TMF-only) music server plugin (thanks undef.de): -
      -
    • for http music servers, spaces and other special characters in the server path and/or song filename (and optional subdirectory) are now supported -
    • after restarting a track, followed by the scoreboard and ChatTime countdown, the current song is now correctly shown -
    • duplicate song removal is handled a litte better -
    • upon XASECO shutdown, server music is disabled (thanks Terron) -
    - -
  • rewrote /admin prev and /admin nextenv using the jukebox system so they keep working when tracks are jukeboxed - -
  • new and updated internal events: -
      -
    • new onVoteUpdated event for handling the new TMF VoteUpdated callback (requires the latest server build 2011-02-21) -
    • new onTracklistChanged event to signal changes to the track list, passing along an action string ('add', 'remove', 'rename', 'juke', 'unjuke', 'read' & 'write') and the pertaining track filename, or null for the 'read' & 'write' actions (thanks undef.de) -
    • updated onJukeboxChanged event with action strings 'previous' and 'nextenv' -
    - -
  • implemented a time-out mechanism in the connection of XASECO to the dedicated server (thanks La beuze): -
      -
    • the mechanism allows you to (re)start the dedicated server and XASECO at the same time (or right after eachother) from the same script, and XASECO will keep retrying to connect until the server reaches status 4 (Running - Play), or the time-out is exhausted -
    • this is especially useful on servers that take a long time to fully start up due to a large number of tracks -
    • the time-out (in seconds) is configurable via new config.xml setting <tmserver><timeout> – a warning is logged when it is missing -
    • the GbxRemote modules have been updated to accommodate this connection time-out -
    - -
  • improved detection of warm-up phase - -
  • enhanced the sorting order of equally-finished players in the rounds reports (Rounds/Team/Cup mode) to match that of the game (thanks .anDy) - -
  • the speed of /best, /worst, /summary and /stats is improved through optimization of the underlying get_recs function (thanks undef.de) - -
  • improved initialisation sequence (and its error handling) of the main program - -
  • on TMF, new 'avatar', 'language' and 'teamid' members in the Player object -
- -

Bug fixes in the v1.13 release of XASECO:

- -
    -
  • /admin delrec doesn't work since v1.12 – fixed (thanks Ouwe) - -
  • when using a player ID after /best, /worst, /summary and /stats, that player's records aren't found since v1.12 – fixed - -
  • the help messages for /donate and /admin pay don't display correctly – fixed (thanks xai) - -
  • when using NewRules in Rounds or Team modes, /server shows the normal points limit – fixed, now the NewRules points limit is shown (thanks undef.de) - -
  • when there are no ranked players, /toprecs and /topsums produce gibberish output and PHP warnings – fixed - -
  • /topwins causes PHP warnings for an empty players table – fixed (thanks relaxedracer) - -
  • mysql_query SELECT results aren't always properly freed up with mysql_free_result – fixed -
- -
- -

New features and other changes in the v1.12 release of XASECO:

- -
    -
  • new database and query optimizations: -
      -
    • these database optimizations are automatically applied once the first time this release is run, but will take a while depending on the size of the database and the speed of your MySQL server, so be patient as they could easily take several minutes (especially the 'rs_times' changes) – interrupting this process could result in a corrupt database, so... don't -
    • the Score fields in the 'records' and 'rs_times' tables are converted from MediumInt to Int, increasing the maximum race time from 140 minutes to 596 hours -
    • the 'records' Id, 'rs_times' ID and 'players' TimePlayed fields are also increased to Int, and the 'rs_karma' Score field is reduced to TinyInt -
    • the keys on the 'records' and 'rs_times' tables are reorganized and expanded to improve the speed of various queries on those tables -
    • RASP's clean-up of unused 'records' and 'rs_times' entries at start-up now only happens if $prune_records_times in rasp.settings.php is true, resulting in faster start-up -
    • the speed of this clean-up is improved by different queries and related processing, and it is logged in more detail -
    • preparation of the player stats panels at the scoreboard is sped up with an improved query and related processing -
    • the speed of the /server and /nations commands is improved by different queries and related processing -
    • the speed of the /topdons command is improved by an appropriate key on the 'players_extra' table -
    • you can verify the result of all conversions against the MySQL schema in the localdb/*.sql files using SHOW CREATE TABLE queries or the 'mysqldump --no-data' command -
    • XASECO's main tables are created automatically the first time this release is run, analogous to RASP's 'rs_*' tables, thus simplifying installation -
    • the 'id' member of the Player object (holding the dedicated server's PlayerId value for a player) is renamed to 'pid' and 'id' now contains the database id from the 'players' table, analogous to the Challenge object – this eliminates most player ID queries -
    • thanks to Tyrann for providing dumps of a large database to analyze & test with -
    -
    - -
  • new and updated internal events (thanks undef.de): -
      -
    • new onDedimaniaRecord event to indicate a new/improved/secured Dedimania record, including its position – note that a Dedimania record is not official until confirmed after the end of a track -
    • the onLocalRecord event also includes a new pos(ition) field -
    • new onJukeboxChanged event to signal changes to the jukebox, passing along an action string ('add', 'clear', 'drop', 'play', 'replay', 'replay', 'restart' & 'skip') and the pertaining track data, or null for the 'clear' action -
    • new onMusicboxReloaded event to alert that the music server config file (musicserver.xml) was reloaded via /music reload -
    • new onDonation event to indicate that a new donation was completed, including login and coppers amount -
    • on TMF, restarting when at least one player already has a finish time or a score results in the scoreboard and ChatTime countdown, thus allowing time for the normal onNewChallenge and onEndRace events instead of the onRestartChallenge one – for instant restarts the latter is issued as before, and the two restart types are logged distinctly -
    -
    - -
  • new support for sector times (not to be confused with sector records, as offered by third-party plugins): -
      -
    • new /sectms command to display the sector times (if available, as they're only being stored since v1.06) for all local records on the current track -
    • new /dedisectms command to display the sector times for all Dedimania records on the current track -
    • on TMN there's room for the first 9 sectors, on TMF for the first 12 sectors of each record – a '+' indicates when there are more sectors than can be displayed -
    - -
  • eliminated checkpoints panel toggling due to temporary spectator status changes at the end and start of tracks, thus significantly reducing outgoing ManiaLink traffic during that period – this also reduces the problem with stalled panels and widgets - -
  • PlayerFinish callbacks are now ignored when not in status 4 (Running - Play), again reducing processing overhead during the end and start of tracks - -
  • once a track is being replayed (after /replay or /admin replay), then /playtime and the total playtime message at the end of that track will include the number of replays and the overall playtime (thanks Milenco) - -
  • at start-up the dedicated server connection, LocalDB and RASP messages are now logged with timestamps - -
  • updated the stripColors function to (even) better reproduce the way the game strips incomplete tags (thanks Slig) - -
  • all Dedimania communication now occurs via port :8002 only, and you should remove the RPC4/server.php link from dedimania.xml - -
  • improved debug logging in the Dedimania plugin - -
  • upon shut down (via the /admin commands) or Fatal Error, all ManiaLinks on all connected clients are cleared (thanks Terron) - -
  • various tweaks and corrections to plugin.matchsave.php - -
  • retired chat.vote.php with its obsolete /vote and /score commands – you can manual delete the empty 'votes' table via a DROP TABLE query -
- -

Bug fixes in the v1.12 release of XASECO:

- -
    -
  • the onLocalRecord event is also issued for equalled records – fixed - -
  • the /list & /autojuke nofinish & norank commands cause PHP warnings for players without finished tracks – fixed - -
  • missing title or artist tags of .OGG songs can cause PHP warnings – fixed - -
  • the TMXInfoFetcher can cause PHP warnings upon empty results from TMX API calls – fixed -
- -
- -

New features and other changes in the v1.11 release of XASECO:

- -
    -
  • new /xaseco command to display a variety of info about your XASECO installation, including uptime, the welcome message (TMF-only), and the masteradmin(s) – the XASECO version is no longer in the /server output - -
  • new /mod command (based on an updated GBXChallengeFetcher class) to show the (file)name for the current track's mod - -
  • if followed by the url (alias loc) parameter, /song and /mod will also display the song's or mod's locator URL (if available), hyperlinked for easy download - -
  • an optional search string after /music list will now list only songs with that string in their filenames or, if <cache_tags> in musicserver.xml is enabled, in the title or artist tags of .OGG songs – the song IDs shown are the original (now possibly non-sequential) ones from the complete list so that jukeboxing still works correctly - -
  • the /chatlog, /pmlog and /admin pmlog output can show the timestamp of each message, configurable via new config.xml setting <chatpmlog_times> (default: True) - -
  • new /list option: /list novote to display all tracks the player didn't karma vote for - -
  • new /autojuke option: /autojuke novote to automatically jukebox the first available track from the /list novote selection - -
  • /list karma 0 now correctly lists all tracks without karma votes as well as those with average karma value 0 – previously this command would list tracks with negative or zero karma but not those without any votes - -
  • new $jukebox_adminnoskip option in rasp.settings.php to not skip tracks (if $jukebox_skipleft is true) that were jukeboxed by admins (of any tier) which left the server - -
  • when a player whose IP address was banned re-enters after the dedicated server was restarted, [s]he is now re-banned instead of just kicked – this insures the dedicated rather than XASECO will keep the player out on subsequent connect attempts, at least until another restart - -
  • updated the Dedimania system to use only the new :8002 communication port (remember to open this port on your firewall/router as well) – the load is now internally distributed on the Dedimania master server - -
  • on TMF, the /stats output now includes the player's client build/version - -
  • changed/added common Rounds points systems on TMF, with f1gp replaced by: -
      -
    • f1old: Formula 1 GP Old – 10,8,6,5,4,3,2,1,... -
    • f1new: Formula 1 GP New – 25,18,15,12,10,8,6,4,2,1,... -
    - -
  • player wins in Stunts mode are now determined and attributed (thanks nouseforname) - -
  • changed the public appreciation message for large donations to prevent a fraud scheme - -
  • optimized MySQL interaction for calculation of players' server ranks (thanks Nanir) - -
  • upon a Fatal Error, an onShutdown event is issued just before shutting down (thanks nocturne) - -
  • the player/spectator and voting values in the $aseco->server object are refreshed at each track switch in case they've been modified (thanks undef.de) - -
  • the Auto TimeLimit and music server plugins now log an initialization message before loading their respective .xml configs - -
  • deployed updated web_access.inc.php for Dedimania communication (thanks Slig) - -
  • removed all ShareMania support as the site was retired -
- -

Bug fixes in the v1.11 release of XASECO:

- -
    -
  • changes in the GameInfo structure aren't detected upon restarts, only upon track switches – fixed - -
  • players that should have been kicked (e.g. for an obsolete client) but weren't, can cause PHP warnings – fixed, and their finishes are ignored - -
  • in Laps mode when /dedicps is active, local records are not detected – fixed (thanks blue) - -
  • there's a global $linelen conflict between plugin.chatlog.php and plugin.msglog.php – fixed - -
  • /list karma is allowed while $feature_karma in rasp.settings.php is false – fixed - -
  • in rare situations /best, /worst and /list best|worst can produce gibberish output – fixed - -
  • following /list with /style ## or any /*panel ## command can cause a PHP warning – fixed - -
  • an XML/RPC communication problem with the Dedimania server can cause a PHP warning – fixed -
- -
- -

New features and other changes in the v1.10 release of XASECO:

- -
    -
  • new support for TMF client version checking upon player connects: -
      -
    • when players connect on TMF, it is now possible to check their client version for a minimum number -
    • if new config.xml setting <player_client_version> is empty, checking is disabled; otherwise it must be a valid TMF version number, 2.11.11 or higher -
    • if checking is enabled and the player's client version is less than the required number (where the empty version of the initial TMF releases counts as 2.11.11), then the player is kicked with appropriate log and chat messages, and the kick dialog also contains a suitable message (configurable in config.xml) -
    • cheating currently happens only with v2.11.11 clients, so in order to prevent potential cheaters (along with other players that haven't upgraded yet) from joining, you can choose to set <player_client_version> to 2.11.12 or higher -
    • when admins of any tier connect on TMF, it is also possible to check their client version for a separate minimum number -
    • if new config.xml setting <admin_client_version> is empty, checking is disabled; otherwise it must be a valid TMF version number, 2.11.11 or higher -
    • if checking is enabled and the admin's client version is less than the required number (where an empty version again counts as 2.11.11), then the admin is similarly notified and kicked -
    • recent client and server versions prevent login spoofing, so it's recommended to set <admin_client_version> to 2.11.19 or higher and have all your server's admins upgrade if necessary -
    • note that digitally distributed editions of TMF are not easily upgraded with the manual patches – e.g. the Steam edition is currently at 2.11.16 – so be careful about setting the versions too high -
    - -
  • new /settings command to display your personal settings, or those of any player (login or ID) if you're an admin with the new chat_settings ability – the settings are: the /cps and /dedicps values, and on TMF also the /style, /admin panel, /donpanel, /recpanel and /votepanel values (if the pertaining plugins are enabled) - -
  • new /plugins command to display the list of currently active plugins - -
  • new /admin delrec command to delete the specified record (1 - $maxrecs) for the current track, delete the corresponding 'rs_times' entries and, if necessary, move the next best time in as the new last record - -
  • the number of times a track can be replayed via /replay votes is now configurable via new votes.config.php setting $replays_limit (default 0 = unlimited) - -
  • admins with the new chat_jukebox ability can now use the /jukebox command even if $feature_jukebox in rasp.settings.php is false - -
  • on TMF the /recs output can optionally include the players' logins, configurable via new config.xml setting <show_rec_logins> (default: True) - -
  • on TMF the /dedirecs output can optionally include the players' logins, configurable via new dedimania.xml setting <show_rec_logins> (default: True) - -
  • new /dedistats command to display a few Dedimania statistics for the current track and, only on TMF, a link to the track's Dedimania page with all its records - -
  • for TMF global blacklists that contain optional <account> fields with values 'United' or 'Nations', the merge feature can now process only the United logins, configurable via new rasp.settings.php setting $globalbl_united (default: false) - -
  • a log message now indicates when a Dedimania record is ignored because the pertaining login is banned from Dedimania - -
  • on TMF the Dedimania plugin now verifies that the configured server login is the same as the actual server login - -
  • the /xlist command can now search TMX for track names using multiple words (thanks bmandk) - -
  • added new common Rounds points system on TMF: -
      -
    • superstars: Superstars – 20,15,12,10,8,6,4,3,2,1,... -
    - -
  • reduced the MySQL reconnect polling frequency at an empty server to once every second - -
  • optimized the MySQL queries for the /list nofinish and /list norank commands (thanks Pesky) - -
  • the 'GameData/Tracks/Challenges/TMX/' and '.../TMXtmp' directories are now created automatically if they don't exist when starting XASECO for the first time (this also simplifies the installation procedure by one step) - -
  • arguments to /admin clearhist are clipped at $buffersize - -
  • usage of the dedicated server's built-in commands /version and (TMF-only) /serverlogin is now logged as such - -
  • new donate panel BelowCPListRM and record panel RightBottomRM, courtesy of sebik1992 (RoundMania 90k) -
- -

Bug fixes in the v1.10 release of XASECO:

- -
    -
  • restarting XASECO triggers required checkpoints warnings for the ongoing race – fixed, the warnings now get enabled after the end (or a restart) of the current track - -
  • after merging a global blacklist, the local blacklist is saved without the newly blacklisted logins – fixed - -
  • blacklisted logins of players that never visited the server (e.g. from a merged global blacklist) can't be unblacklisted – fixed -
- -
- -

New features and other changes in the v1.09 release of XASECO:

- -
    -
  • new support for a global blacklist on TMN and (especially) TMF, because at start-up the TMF server now properly loads the global blacklist specified in dedicated_cfg.txt entry <blacklist_url> and merges it with the local blacklist: -
      -
    • new /admin mergegbl command to merge a (possibly updated) global blacklist with the server's blacklist at other times than just start-up -
    • the default blacklist is configurable via rasp.settings.php setting $globalbl_url, and should be the same as <blacklist_url> -
    • the /admin mergegbl command optionally accepts a http[s] URL to load another global blacklist, and access to the command is defined in adminops.xml as usual -
    • when a MasterAdmin connects, the default global blacklist can automatically be merged, configurable via rasp.settings.php setting $globalbl_merge (default: false) -
    • after merging, log and chat messages indicate the number of newly blacklisted logins, and the local blacklist is saved just like after a manual /admin black command -
    -
    - -
  • new support for player access control by nation (TMN) or zone (TMF) becomes available when plugin.access.php is enabled (uncommented) in plugins.xml, offering the following features: -
      -
    • the control mechanism is inspired by Apache's mod_access and is configured via access.xml, containing the following directives: -
        -
      • the Order section, defining the order in which the other two sections are processed -
      • the Allow section, containing one or more From fields to specify nations/zones that are allowed access -
      • the Deny section, containing one or more From fields to specify nations/zones that are denied access -
      • the From values are case-sensitive, upper- and lowercase characters must match exactly with the players' nations/zones you want to control -
      • the catch-all value for either the Allow or Deny section is 'all' (lowercase), and in that case this section cannot contain other From fields -
      • for more information and examples on these directives, see the comments in access.xml and the aforementioned mod_access page -
      -
    • on TMF, a match on a specified zone will also match its subzones, e.g. a match on "Netherlands" will include all Dutch provinces too -
    • denied players are kicked with appropriate log and chat messages, and on TMF the kick dialog also contains a suitable message, which can be configured in access.xml -
    • the new /admin access command offers the following options: -
        -
      • help: Displays help information -
      • list: Displays current access control settings -
      • reload: Reloads updated access control settings -
      -
    • player access control is disabled upon a configuration error in (re)loading access.xml -
    -
    - -
  • new /cpsspec command (TMF-only) to relay the checkpoints panel of the player you're spectating to your own screen: -
      -
    • this feature may have some performance impact on busy servers, so it's your choice to enable it via new config.xml setting <enable_cpsspec> (default: False) -
    • the /cpsspec command toggles tracking the checkpoints of a spectated player -
    • if tracking is enabled and you're spectating another player in Follow or Replay camera mode, the checkpoints panel of that player is also shown on your screen, but only if the spectated player has enabled checkpoints tracking him/herself -
    • the /cpsspec status is intentionally not saved among a player's personal settings, so it is off upon reconnects -
    -
    - -
  • updates for the music server plugin (TMF-only): -
      -
    • the song list can now be automatically shuffled at start-up and upon the /music reload command (configurable via new musicserver.xml setting <auto_shuffle>) -
    • new /music autoshuffle command to change the automatic shuffle setting; the /music settings output also includes the AutoShuffle value -
    - -
  • on TMF, released the player slot when changing a player into spectator in /admin forcespec, /afk and action 1 (Set to Spectator) for a detected cheater - -
  • in Laps mode, each lap time is now stored in the 'rs_times' table (if $feature_stats in rasp.settings.php is true, of course) - -
  • UTF-8 encoding the welcome message, necessary on some systems to display accented characters correctly, can now be configured via new config.xml setting <welcome_utf8encode> (default: True) - -
  • on TMF when rank limiting by the full Jfreu plugin is in effect, the kick dialog echoes the explanatory chat message sent to a connecting player who is kicked due to a bad rank - -
  • on TMF when using the /bootme command, the kick dialog can optionally display a 'goodbye' message if new rasp.xml message <bootme_dialog> exists - -
  • in the XASECO start-up sequence the onSync event is now issued before the onPlayerConnect events for all current players so that plugins are initialized in a more logical order - -
  • during XASECO start-up global voting explanations ($global_explain = 2 in votes.config.xml) are temporarily inhibited to prevent a message flood on busy servers - -
  • updated the stripColors function to strip out incomplete color codes before the end of the string, and add an option so that log output doesn't show '$$' for '$' - -
  • in the local database the 'Name' column in table 'challenges' and the 'NickName' column in table 'players' have been increased to 100 characters – for new XASECO installations the updated columns are defined in the localdb/aseco.sql file as usual, and for existing installs they will be modified automatically the first time this release is run - -
  • minor improvements in the GBXChallengeFetcher, GBXReplayFetcher and ReplayParser classes - -
  • this release requires the latest server build 2009-05-25 -
- -

Bug fixes in the v1.09 release of XASECO:

- -
    -
  • some players with numeric logins still confuse the player ID mechanism – now really fixed - -
  • in Laps mode, improved laps aren't always checked for new Dedimania records – fixed - -
  • the global voting explanation upon a player join doesn't appear in the TMF message window when $vote_in_window = true – fixed - -
  • on some OSes a repetitive but harmless warning about "Invalid CRT parameters" in GbxRemote can occur – fixed -
- -
- -

New features and other changes in the v1.08 release of XASECO:

- -
    -
  • improved checkpoints & finish processing, anti-cheat testing and Dedimania records handling (if plugin.checkpoints.php is enabled in plugins.xml) - -
  • on TMF, local and Dedimania records that, for any reason, don't have the correct number of checkpoints are ignored with a logged warning (if plugin.checkpoints.php is enabled) - -
  • on TMF, used the new TMF BeginChallenge and EndChallenge callbacks (instead of BeginRace and EndRace) to trigger the onNewChallenge and onEndRace events, respectively - -
  • on TMF, the onNewChallenge and onEndRace events are no longer issued when restarting a track; this prevents, for example, losing the next track from the jukebox and the needless repetition of all messages sent at the start and end of a track – instead a new onRestartChallenge event is issued - -
  • added the onPlayerIncoherence, onTunnelDataReceived and onManualFlowControlTransition events for handling the existing TMF PlayerIncoherence, TunnelDataReceived and ManualFlowControlTransition callbacks, respectively - -
  • checkpoints in Dedimania records are now sent as a comma-separated string to the central Dedimania system in order to reduce data traffic - -
  • on TMF, a new $specPlayFirst configuration variable (default: false) in mistral.idlekick.php can be used to first set an idle player into spectator mode (free camera) instead of kicking him/her right away - -
  • removed the 1 second delay to set free camera mode in /admin forcespec, /afk and action 1 (Set to Spectator) for a detected cheater - -
  • challenge names with embedded newlines no longer cause malformed messages and such (due to use of a new stripNewlines function) - -
  • a new stripSizes function that strips only the size-related tags ($n, $w and $o) from strings, is available as alternative to stripColors - -
  • added the new ReplayParser class for future use with the Dedimania system - -
  • updated the GbxRemote modules to the latest versions from Nadeo - -
  • minor code tweaks in many parts of the system - -
  • this release requires the latest server build 2009-05-04 -
- -

Bug fixes in the v1.08 release of XASECO:

- -
    -
  • an ampersand character (&) in any .xml file crashes the XML parser – fixed this long-standing issue (thanks Assembler Maniac) - -
  • non-ASCII characters in the <welcome> message in config.xml cause the entire message not to be displayed – fixed (thanks dragu) - -
  • using /style # stores the number instead of the style name in the database as the player's preference – fixed (thanks nouseforname) - -
  • players with numeric logins confuse the player ID mechanism – fixed (thanks n4p1) -
- -
- -

New features and other changes in the v1.06 release of XASECO:

- -
    -
  • new support for local checkpoints storage: -
      -
    • a new 'Checkpoints' column has been added to the local database tables 'records' and 'rs_times', and checkpoints data will be stored for all newly driven records and finish times -
    • for new XASECO installations the new columns are defined in the localdb/aseco.sql and rasp.sql files as usual, and for existing installs they will be added automatically the first time this release is run -
    • the Repair records script has been updated to handle the extra columns -
    - -
  • functionality of the /cps command is changed analogous to /dedicps: -
      -
    • checkpoint references are stored for all players (according to their individual preferences) only at the start of a track, not when entering the /cps command or when the player (re)connects -
    • with no parameter (/cps) the player's own local record on each track is selected or, if none is set, the last record on the pertaining track -
    • /cps {1-maxrecs} selects local record 1-maxrecs on each track or, if unavailable, again the last record -
    • /cps off disables checkpoints tracking altogether -
    • if the checkpoints for the chosen local record are invalid (empty, or the last checkpoint doesn't match the finish time) then no record is used as initial reference during that track for the pertaining player(s) -
    • checkpoint differences to the chosen reference are now shown from the first run through the track, rather than the second -
    • if /dedicps is not set to off, local record tracking is superseded by Dedimania record tracking as per that setting -
    • the default setting for <auto_enable_dedicps> in config.xml is now False -
    • as usual, once a player gains a better finish time than the chosen reference, the checkpoints panel (TMF) or pop-up (TMN) will use those corresponding checkpoints for further updates during the current track -
    • explanations why the TMF checkpoints panel doesn't appear immediately when /cps is activated (Stunts mode, spectating, and warm-up phase) are no longer shown -
    -
    - -
  • new support for storing donations and personal settings: -
      -
    • a new 'players_extra' table has been added to the local database to store donation totals (on TMUF servers) and setting preferences for each player -
    • stored settings are: the /cps and /dedicps values, and on TMF also the /style, /admin panel, /donpanel, /recpanel and /votepanel values -
    • for new XASECO installations the new table is defined in the localdb/extra.sql file, and for existing installs it will be added automatically the first time this release is run -
    • when a new player first connects, an entry with the server's default values is created in this table, but changed settings are subsequently stored and reloaded each time the player revisits -
    • the 'donations' column is updated every time a player donates coppers to the server -
    • on TMUF servers, the /stats output now includes the player's donation total -
    • new /topdons command to display the top-100 of players that donated the most coppers -
    -
    - -
  • new support for player stats panels during the scoreboard on TMF: -
      -
    • on TMF, this feature is configured via new config.xml setting <sb_stats_panels> (default: False) -
    • enabling the player stats panels during the scoreboard automatically disables the rank chat messages shown at the same time -
    • the panel is displayed in the lower-left corner of the scoreboard screen, and is defined by the panels/StatsNations.xml (for TMNF servers) and StatsUnited.xml (for TMUF) templates -
    • the panel contains each player's server rank, record average, records total, wins total, session play time, and donation total (on TMUF servers) -
    • computing the record totals can be a bit time consuming depending on the number of challenges, players and records in the database, so displaying all scoreboard panels may be slightly delayed -
    -
    - -
  • when a player leaves, the chat message (if either Jfreu plugin is enabled) and the log message now include the amount of time played in that session - -
  • during XASECO start-up, player join messages are temporarily inhibited to prevent a message flood on busy servers - -
  • on TMF, the /server and /admin server output now include the server's ladder points limits - -
  • on TMUF, donate panels are temporarily disabled during the scoreboard at the end of a track, as they aren't immediately responsive anyway - -
  • on TMF in Rounds mode, Dedimania records on lap tracks are now ignored if RoundForcedLaps is not set to the default value 0 - -
  • on TMF in Cup mode, commands that restart the current track or switch to the next or previous one now preserve the current match scores - -
  • new /n1 command to send a Nice One message; also, the /gr message no longer includes the word 'All' by default - -
  • added the onEcho event for handling the TMF Echo callback -
- -

Bug fixes in the v1.06 release of XASECO:

- -
    -
  • using /dedicps to track checkpoints of existing Dedimania records results in new/improved/secured records not registering in some situations – fixed - -
  • when a player joins during a Laps mode race and completes a lap, an invalid record is registered – fixed (thanks princemichi) - -
  • in Laps mode the finish times of complete races are stored in the 'rs_times' table – fixed, no times are stored in Laps mode - -
  • if <display> in localdatabase.xml is False, other records related commands and features don't work either – fixed - -
  • when using a local path for the music server, and with some http servers, the .OGG comments aren't read from the song files – fixed (thanks HaveDerf_TMu) -
- -
- -

New features and other changes in the v1.05b release of XASECO:

- -
    -
  • updated the Dedimania system to randomly choose between the existing :8006, :8007, :8011, :8012 and :8013 and new :8016 through :8021 communication ports (remember to open those ports on your firewall/router as well) in order to distribute the load across more Dedimania servers - -
  • the number of lines in the TMF system message window can now be easily modified with the $winlen variable at the start of plugin.msglog.php - -
  • refined UTF-8 string validation function (thanks Slig) -
- -

Bug fixes in the v1.05b release of XASECO:

- -
    -
  • Dedimania records aren't reliably registered due to the new anti-cheat tests – fixed - -
  • Dedimania records on tracks that don't have the full <max_recs> entries aren't registered unless they're better than the existing last record – fixed (thanks lille79) - -
  • in case of Dedimania connection problems persisting across track changes, records data from the previous track is still used on the next one – fixed (thanks schmidi) - -
  • restarting a track causes no further Dedimania records to be registered on that track – fixed - -
  • restarting XASECO triggers the anti-cheat tests for the ongoing race – fixed, the tests now get enabled after the end of the current track - -
  • with the music server configured for local files, /music current and /music settings don't show the song's info – fixed - -
  • on Windows, the /music stripdirs option doesn't work correctly on local files – fixed -
- -
- -

New features and other changes in the v1.05 release of XASECO:

- -
    -
  • new checkpoint sanity checks to prevent cheated records (if plugin.checkpoints.php is enabled in plugins.xml): -
      -
    • all checkpoint times must be larger than zero -
    • each checkpoint time within a player's run must be larger than the preceding one -
    • all checkpoint indexes must be zero or larger -
    • each checkpoint index within a player's run must be 1 larger than the preceding one -
    • the last checkpoint time must be equal to the finish time -
    • when any of these requirements isn't met, one of the following actions is taken on the pertaining player, configurable via new config.xml setting <cheater_action> (default: 0):
      -0: Ignore; 1: Set to Spectator (only on TMF, falls back to 0 on TMN); 2: Kick; 3: Ban (implies Kick); 4: BlackList & Kick; 5: Blacklist & Ban -
    • new/updated local and Dedimania records that fail one or more of the aforementioned checks are not stored (even if the above action is 0) -
    - -
  • new Dedimania sanity checks: -
      -
    • keeping any of the three <masterserver_account> fields in dedimania.xml at a default or empty value results in a fatal error -
    • records are no longer processed if they are less than 6 seconds -
    • records are no longer processed for tracks with an author time of less than 8 seconds -
    - -
  • updates for the music server plugin (TMF-only): -
      -
    • song files can now be stored locally on the TM server in a path under its GameData/ directory, e.g. Music/, without the need for a remote http server -
    • if <auto_nextsong> is disabled a jukeboxed song will no longer be repeated on all subsequent tracks until another song is jukeboxed -
    - -
  • a new config.xml setting <display_checkpoints> (default: True) can be set to False to permanently disable checkpoint pop-ups (TMN) or panels (TMF) for all players while still tracking the checkpoints data internally – this allows you to keep plugin.checkpoints.php enabled for anti-cheat checks and the Dedimania system when high player counts cause performance/crash problems - -
  • a global congratulations message can be displayed instead of the regular personal one at a player's win of a track, if that win count reaches a multiple configured via new config.xml setting <global_win_multiple> (default: 50) - -
  • if plugin.rpoints.php is enabled on TMF, it's now possible to initialize the TM server with a default Rounds points system at start-up via new config.xml setting <default_rpoints> – the value can be a custom system with specified values (X,Y,...,Z) or a common system by name (f1gp, motogp, etc), just like the parameters to the /admin rpoints command; if empty, the standard points system is initialized - -
  • on TMF, automatically setting a player that uses the /afk command into free spectator mode can now be configured via new config.xml setting <afk_force_spec> (default: True) - -
  • automatically adding the current IP address for newly added admins and operators can now be configured via new config.xml setting <auto_admin_addip> (default: True) - -
  • new /admin autotime command to enable or disable the Auto TimeLimit feature dynamically (assuming plugin.autotime.php is enabled in plugins.xml) - -
  • when restarting a track via a /ladder vote (with votes.config.php setting $ladder_fast_restart = true) or /admin restartmap, the Auto TimeLimit is no longer set to next track's value - -
  • successful /donate and /admin pay copper transfers are now logged - -
  • when adminops.xml is written out without any admin or operator entries, bannedips.xml without IPs, jfreu.bans.xml without temporary bans, or jfreu.vips.xml without VIP logins or teams, then a comment block to show the entry syntax is included instead - -
  • if the GD/JPEG libraries are installed in PHP, the GBXChallengeFetcher class now flips the upside-down thumbnail image in a challenge into its upright format - -
  • a message is logged when plugins.xml is being loaded at start-up - -
  • this release requires the latest server build 2008-12-05 -
- -

Bug fixes in the v1.05 release of XASECO:

- -
    -
  • /players <string> displays an erroneous window if the search string matches no players – fixed, a warning message is shown - -
  • on TMF if plugin.panels.php is not enabled, TMX /add and chat-based votes result in crashes – fixed - -
  • on TMN, running /admin helpall as an Admin (not MasterAdmin or Operator) locks up the client after the second page – fixed (thanks B.A.S.S.) - -
  • a time-out on the version server at the up-to-date check gives an incorrect message – fixed - -
  • some PHP constructs still cause silent notices during strict run-time checks – fixed more of them - -
  • debug logging in the Dedimania system doesn't consistently use CRLF on Windows – fixed -
- -
- -

New features and other changes in the v1.04 release of XASECO:

- -
    -
  • new support for custom Rounds points on TMF with the following features: -
      -
    • custom points in Rounds mode become available when plugin.rpoints.php is enabled (uncommented) in plugins.xml -
    • new /admin rpoints command to list and set common points systems, as well as custom distributions, with the following options: -
        -
      • help: Displays help information -
      • list: Displays available points systems -
      • show: Shows current points system -
      • <xxx>: Sets points system labelled <xxx> -
      • X,Y,...,Z: Sets custom points system with specified values; X,Y,...,Z must be decreasing integers and there must be at least two values with no spaces -
      • off: Disables custom points system -
      -
    • the following common points systems are included: -
        -
      • f1gp: Formula 1 GP – 10,8,6,5,4,3,2,1,... -
      • motogp: MotoGP – 25,20,16,13,11,10,9,8,7,6,5,4,3,2,1,... -
      • motogp5: MotoGP + 5 points – 30,25,21,18,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,... -
      • fet1: Formula ET Season 1 – 12,10,9,8,7,6,5,4,4,3,3,3,2,2,2,1,... -
      • fet2: Formula ET Season 2 – 15,12,11,10,9,8,7,6,6,5,5,4,4,3,3,3,2,2,2,1,... -
      • fet3: Formula ET Season 3 – 15,12,11,10,9,8,7,6,6,5,5,4,4,3,3,3,2,2,2,2,1,... -
      • champcar: Champ Car World Series – 31,27,25,23,21,19,17,15,13,11,10,9,8,7,6,5,4,3,2,1,... -
      • simple5: Simple 5 – 5,4,3,2,1,... -
      • simple10: Simple 10 – 10,9,8,7,6,5,4,3,2,1,... -
      -
    • note that any players finishing beyond the last points entry get the same number of points (typically 1) as that last entry -
    • new /rpoints command to show the current points system -
    • additional points systems can be added to $rounds_points at the start of the plugin -
    -
    - -
  • an updated and optimized Auto TimeLimit plugin (plugin.autotime.php) by cyrus is now part of the standard distribution: -
      -
    • this plugin works only in TimeAttack mode, and defines the timelimit for each track dynamically based on the track's author time -
    • the new autotime.xml file contains the plugin's settings to determine the new timelimit (multiplication factor, minimum, maximum and default); this file is loaded only once at start-up, rather than at every track switch -
    • as before, a multiplicator of zero will disable the plugin -
    • the new display setting controls whether to show a timelimit message in the TMF system message window, the chat window, or not at all; a similar message is always logged -
    • the type of timelimit (new, min, max, default) is included in these messages -
    • the timelimit is rounded down to seconds in the chat and log messages -
    • when there are no active players, the default timelimit is applied because of the way the TM server instantly switches tracks with no opportunity to set a custom limit -
    - -
  • updates for the music server plugin (TMF-only): -
      -
    • the song filename extensions can now be excluded in any command that shows songs (configurable via new musicserver.xml setting <strip_exts>) -
    • new /music stripexts command to change the strip extensions setting; the /music settings output also includes the StripExts value -
    • new /music sort command to sort the song list alphabetically by song paths -
    • the cache file for .OGG comments can be defined as read-only (configurable via new musicserver.xml setting <cache_readonly>) -
    - -
  • in the <ipaddress> fields in config.xml and adminops.xml, it is now possible: to specify class B (e.g. 12.34.*.*) and class C (e.g. 12.34.56.*) wildcards to allow for partial IP checks on admins with dynamics IPs; and to specify multiple comma-separated IPs and/or wildcards (e.g. 12.34.56.78,98.76.54.*) - -
  • note to plugin authors: while the isMasterAdmin, isAdmin, isOperator and isAnyAdmin functions require a player object and check the IP address, new isMasterAdminL, isAdminL, isOperatorL and isAnyAdminL functions are now available that accept a login string (like before v1.03) and don't check the IP address - -
  • significantly optimized calculation of all players' server ranks after each track - -
  • if the Nation field in the player join/leave messages is 15 or more characters, it's abbreviated to the 3-letter country code - -
  • changed the ManiaLink processing to (hopefully) reduce the risk of crashing at high player counts - -
  • a new <logina> color is defined in config.xml and used for the admin titles in player join and other relevant messages, as well as admin logins in the /players, /admin players and /jfreu players output - -
  • setting empty player, spectator and referee passwords produces the appropriate log and chat messages about disabling them - -
  • the /admin setrefmode command without parameter now shows the current Referee mode - -
  • the /admin forceshowall command has been renamed to /admin forceshowopp and its 'on' option to 'all'; further, the command now accepts a number (>= 2) to force showing that minimum number of opponents - -
  • the /admin shutdown command will now shut down only XASECO (in order to permit restarts via the external start-up script), while the new /admin shutdownall command terminates both XASECO and the TM server - -
  • the output of /admin players is limited to at most 5000 entries, and the memory limit for the XASECO PHP process is increased to 50 MB, in order to further reduce the chances of memory overruns - -
  • if the Xdebug extension for PHP is installed, a fatal error will automatically log a stack trace - -
  • the /music, /admin and /jfreu commands are now able to handle multiple spaces between their parameters -
- -

Bug fixes in the v1.04 release of XASECO:

- -
    -
  • players that instantly reconnect can have their win counts cleared – fixed - -
  • /admin removeadmin and /admin removeop don't work when the <ipaddress> field for the pertaining login is set – fixed - -
  • the /jfreu infomessages command is still boolean – updated the command to recognize the 2/1/0 values of the underlying $infomessages variable - -
  • the Auto TimeLimit plugin doesn't work correctly when all connected players are spectating – fixed - -
  • the Auto TimeLimit plugin doesn't work properly on servers with more than 500 tracks – fixed - -
  • when a player doesn't have a Dedimania record but his/her local personal best is better than the last Dedimania record, /dedinext computes the wrong difference – fixed - -
  • /admin setrefmode {0/1} doesn't work correctly – fixed - -
  • /admin forceteam, /admin forcespec and /admin specfree are allowed on offline logins – fixed - -
  • /admin forcespec doesn't always set the spectator into free camera – fixed - -
  • in the music server plugin, messages referring to songs without .OGG comments are not formatted correctly – fixed - -
  • on TMF, a newly connected player's ladder rank in the internal players list can be temporarily erased until he/she becomes official – fixed - -
  • numerous PHP constructs cause silent notices during strict run-time checks – fixed most of them -
- -
- -

Older release notes

- -
-
-Copyright © 2007-2013 – Frans P. de Vries <tm@gamers.org> -            -Last updated 26-Jul-2013 -
- - diff --git a/xaseco/DOCS/ListCallbacksForever.html b/xaseco/DOCS/ListCallbacksForever.html deleted file mode 100644 index 973767f..0000000 --- a/xaseco/DOCS/ListCallbacksForever.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - -TrackMania Forever callbacks - - -

Available callbacks:

-
    - -
  • - TrackMania.PlayerConnect(string Login, bool IsSpectator); -
  • - -
  • - TrackMania.PlayerDisconnect(string Login); -
  • - -
  • - TrackMania.PlayerChat(int PlayerUid, string Login, string Text, bool IsRegistredCmd); -
  • - -
  • - TrackMania.PlayerManialinkPageAnswer(int PlayerUid, string Login, int Answer);
    - difference with previous TM: this is not called if the player doesn't answer, and thus '0' is also a valid answer. -
  • - -
  • - TrackMania.Echo(string Internal, string Public); -
  • - -
  • - TrackMania.ServerStart(); -
  • - -
  • - TrackMania.ServerStop(); -
  • - -
  • - TrackMania.BeginRace(SChallengeInfo Challenge); -
    -	struct SChallengeInfo
    -	{
    -		string Uid;
    -		string Name;
    -		string FileName;
    -		string Author;
    -		string Environnement;
    -		string Mood;
    -		int BronzeTime;
    -		int SilverTime;
    -		int GoldTime;
    -		int AuthorTime;
    -		int CopperPrice;
    -		bool LapRace;
    -		int NbLaps;
    -		int NbCheckpoints;
    -	}
    -
  • - -
  • - TrackMania.EndRace(SPlayerRanking Rankings[], SChallengeInfo Challenge); -
    -	struct SPlayerRanking {
    -		string Login;
    -		string NickName;
    -		int PlayerId;
    -		int Rank;
    -		int BestTime;
    -		int[] BestCheckpoints;
    -		int Score;
    -		int NbrLapsFinished;
    -		double LadderScore;
    -	}
    -	
    -
  • - -
  • - TrackMania.BeginChallenge(SChallengeInfo Challenge, bool WarmUp, bool MatchContinuation); -
  • - -
  • - TrackMania.EndChallenge(SPlayerRanking Rankings[], SChallengeInfo Challenge, bool WasWarmUp, bool MatchContinuesOnNextChallenge, bool RestartChallenge); -
  • - -
  • - TrackMania.BeginRound(); -
  • - -
  • - TrackMania.EndRound(); -
  • - -
  • - TrackMania.StatusChanged(int StatusCode, string StatusName); -
  • - -
  • - TrackMania.PlayerCheckpoint(int PlayerUid, string Login, int TimeOrScore, int CurLap, int CheckpointIndex); -
  • - -
  • - TrackMania.PlayerFinish(int PlayerUid, string Login, int TimeOrScore); -
  • - -
  • - TrackMania.PlayerIncoherence(int PlayerUid, string Login); -
  • - -
  • - TrackMania.BillUpdated(int BillId, int State, string StateName, int TransactionId); -
  • - -
  • - TrackMania.TunnelDataReceived(int PlayerUid, string Login, base64 Data); -
  • - -
  • - TrackMania.ChallengeListModified(int CurChallengeIndex, int NextChallengeIndex, bool IsListModified); -
  • - -
  • - TrackMania.PlayerInfoChanged(SPlayerInfo PlayerInfo); -
    -	struct SPlayerInfo {
    -		string Login;
    -		string NickName;
    -		int PlayerId;
    -		int TeamId;
    -		int SpectatorStatus;
    -		int LadderRanking;
    -		int Flags;
    -	}
    -	
    -
  • - -
  • - TrackMania.ManualFlowControlTransition(string Transition); -
  • - -
  • - TrackMania.VoteUpdated(string StateName, string Login, string CmdName, string CmdParam);
    - StateName values: NewVote, VoteCancelled, VotePassed or VoteFailed -
  • - -
- - diff --git a/xaseco/DOCS/ListCallbacksNations.html b/xaseco/DOCS/ListCallbacksNations.html deleted file mode 100644 index b6d43c6..0000000 --- a/xaseco/DOCS/ListCallbacksNations.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - -TrackMania Nations callbacks - - -

Available callbacks:

-
    - -
  • - TrackMania.PlayerConnect(string Login, bool IsSpectator); -
  • - -
  • - TrackMania.PlayerDisconnect(string Login); -
  • - -
  • - TrackMania.PlayerChat(int PlayerUid, string Login, string Text, bool IsRegistredCmd); -
  • - -
  • - TrackMania.PlayerServerMessageAnswer(int PlayerUid, string Login, int Answer);
    - Answer: 0 -> no answer (timeout...), 1 or 2 -> answer from the player -
  • - -
  • - TrackMania.ServerStart(); -
  • - -
  • - TrackMania.ServerStop(); -
  • - -
  • - TrackMania.BeginRace(SChallengeInfo Challenge);
    -
    -	struct SChallengeInfo
    -	{
    -		string Name;
    -		string FileName;
    -		string Author;
    -		string Environnement;
    -		string Mood;
    -		int BronzeTime;
    -		int SilverTime;
    -		int GoldTime;
    -		int AuthorTime;
    -		int CopperPrice;
    -		bool LapRace;
    -	}
    -
  • - -
  • - TrackMania.EndRace(SPlayerRanking Rankings[], SChallengeInfo Challenge); -
    -	struct SPlayerRanking {
    -		string Login;
    -		string NickName;
    -		int PlayerId;
    -		int Rank;
    -		int BestTime;
    -		int[] BestCheckpoints;
    -		int Score;
    -		int NbrLapsFinished;
    -		double LadderScore;
    -	}
    -	
    -
  • - -
  • - TrackMania.BeginRound(); -
  • - -
  • - TrackMania.StatusChanged(int StatusCode, string StatusName); -
  • - -
  • TrackMania.PlayerCheckpoint(int PlayerUid, string Login, int Time, int Score, int CheckpointIndex); -
  • - -
  • TrackMania.PlayerFinish(int PlayerUid, string Login, int TimeOrScore); -
  • - -
- - diff --git a/xaseco/DOCS/ListDedimania.html b/xaseco/DOCS/ListDedimania.html deleted file mode 100644 index 7e27934..0000000 --- a/xaseco/DOCS/ListDedimania.html +++ /dev/null @@ -1,189 +0,0 @@ - - - -Dedimania Server Help Page - - - -

Dedimania Server Help Page

- -

Site & Statistics:

- - -

Usage:

- -
Where to send queries ? -
    -
  • The initial script query should be done on the main url http://dedimania.net/RPC4/server.php, because if the account was not authenticated successfully then on http://dedimania.net:80xx/ ones it could make timeouts, slowing the other scripts/clients requests responses. -

    - -
  • Next script queries (once successfully authenticated!) should be made on the http://dedimania.net:80xx/ urls, because these ones use far less server resources than the main one. You have to contact me at Slig or on the Dedimania forum for scripts devs, to both tell me that you are going to use Dedimania for your script, and to know what url you should use. -
-Note that later the main url will reply only to dedimania.CheckConnection, dedimania.GetVersion, dedimania.Authenticate and dedimania.ValidateAccount methods ! -

- - -
How to send a query ? -
    -
  • The query is a standard http text/xml call, with the xml message as the http body. This form is the standard xmlrpc calling form, and the reply will have that form too. This method is the right xmlrpc one, and should be preferred ! -

    - -
  • The query can also be a standard http text/html call, with the attribute 'xmlrpc'. This was added to permit easy requests using a standard html GET or POST query: -
      -
    • The value for the 'xmlrpc' attribute is just a xmlrpc text, urlsafe base64 encoded. -
    • The method can be either GET or POST. -
    • The reply is just plain xmlrpc. -
    -
    -A real (GET) call would look like this: -
    http://dedimania.net/RPC4/server.php?xmlrpc=<urlsafe_base64_encoded_xmlrpc_text>
    - In the case of a GET query, the xmlrpc value has to be base64 encoded, but be cautious: it has to use the url-safe base64 RFC 3548 ('+' replaced by '-' and '/' replaced by '_'), and the URI size can be limited. -

    - -
  • In all cases, the query and the reply can be compressed (gzip or deflate), using the standard http headers. After test and debug the compression should be used ! -
  • The client should try to support http keepalive (with long timeout, ie. 4 minutes or more): it will save some Dedimania server resources, and avoid to have connection timeouts on client side for each request ! -

    -
- - - -
-
The form of xmlrpc queries and replies -
    -
  • The xmlrpc query and result fully respect the xml-rpc specification. -
  • It is better if client support cookies and keep-alive connections, avoiding to make a new connection for each request, which is better both for server and client. The server support long keep-alive (more than 5min), so the client really should support it too, to help reduce the server load ! In fact your script should support long http keep-alive and http compression ! -
- - - -
-
What kind of records does Dedimania support ? -
    -
  • General: the low limit for records is now 6s : no records under that limit will be stored. Scripts should not send records for challenges with author time under 8s ! -
  • Rounds: round final time. And for a multilap challenge only if the number of laps is the main number of laps of the map, and not a different number forced using RoundForcedLaps ! -
  • Time attack: attempt final time. -
  • Laps: lap time (ie best lap time). Is merged with Time Attack records. -
  • Stunts: attempt final score (actually not supported). -
  • Cup: round final time. Is merged with Rounds records. -
- - - -
-
What kind of records does Dedimania not support ? -
    -
  • General: times under 6s are refused. -
  • Rounds: multilap challenge with the number of laps different than the main number of laps of the map (mainly using RoundForcedLaps) is not supported ! (eventually the script can send best lap as a Laps record) -
  • Laps: more than one lap is not supported. -
  • Stunts: actually not supported. -
- - - -
-
Sending xmlrpc request methods -
    -
  • It is mandatory to put the dedimania.Authenticate in your query before any other method which is indicated as 'Only if authenticated.'. Because of this, you have to use system.multicall in most cases. Yes, it is needed in each request (well, it can be omitted in next requests if you client support http keepalive correctly and stay connected, but it is easier to send the Authenticate each time).
    -You should probably always use dedimania.WarningsAndTTR as last method in your queries, so you will get warnings and not definitive errors of the previous methods in the query. -

    -
  • In dedimania.Authenticate you can send the community code of the server login, instead of the password. After the first successful authentication with Nadeo masters, an MD5 of the password is stored, to make further authentications faster. The password and/or community code are not stored in the Dedimania database. -

    -
  • Mainly, the standard usage of requests methods for a script using Dedimania records is: -
    dedimania.CurrentChallenge at beginning of map (to indicate the current map, players, and get current records), -
    dedimania.UpdateServerPlayers every 4 minutes after the beginning of the map (to keep up to date server and players infos), -
    dedimania.ChallengeRaceTimes at the end of map (sending all players best time -new records or not-, and get the new records list), -
    dedimania.PlayerArrive when a player connects (can be skipped), -
    dedimania.PlayerLeave when a player disconnects (not really needed, will only speedup to show the player offline on tmstats). -

    -
  • dedimania.CheckConnection and dedimania.GetVersion can be used to verify that your script requests are received correctly and understood by the Dedimania server, and that your script receives and understands the reply (most times only dedimania.Authenticate is used). -

    -
  • dedimania.WarningsAndTTR should be used as last method of the request, to get warnings/errors (and some other infos) about previous methods. -
- - - -
-
Raw query/reply examples (without http compression of course) - - - - -
-Note1:
-If you make a client script support, please notify it to Slig. First because I may open a new url port for your script, because the port 80 url use more resources, also because you should not use url used by other without telling it, and finally because i want to know what scripts are using Dedimania resources and on what port(s). -

-Note2:
-The url http://dedimania.net/RPC4/server.php must never be used, except eventually for testing or rescue (for users who have a server which can't use 80xx ports). To know what url you have to use, see note 1 ! -

-Note3: MaxRank
-The MaxRank work at several levels. Except special case where the player MaxRank is 0 because he is banned, a player -can always make a record on a server and challenge at the max rank determined by the max of ServerMaxRecords, his own MaxRank, and the MaxRank stored for his current record if he has one. -
- ServerMaxRecords is the server MaxRank, any player connected and not banned can make a record at least up to this value. -
- dedimania.PlayerArrive/MaxRank is the player MaxRank, the player can make a record up to this value also if ServerMaxRecords is smaller. -
- records MaxRank is the max of current player record and his general MaxRank, the record remain valid if not above that value (or ServerMaxRecords value if bigger). -

- -

Available methods:

-
  • dedimania.CheckConnection
  • boolean - dedimania.CheckConnection()
    -Just reply true.
    -
  • dedimania.GetVersion
  • struct - dedimania.GetVersion()
    -Reply a struct {'Version': int, 'MaxRecords': int}.
    -
  • dedimania.Authenticate
  • boolean - dedimania.Authenticate(struct)
    -Allow user authentication by specifying a struct {'Game': string, 'Login': string, 'Password': string, 'Tool': string, 'Version': string, [Optionals: 'Packmask': string, 'Nation': string, 'ServerIP': string, 'ServerPort': int, 'XmlrpcPort': int, 'PlayersGame': boolean]}. Game can be 'TMF', 'TMUF', 'TMNF', 'TMU', 'TMO', 'TMS' or 'TMN' (for TMUF/TMNF servers, please send TMF or TMUF or TMNF, and the Packmask !). Packmask should be the value returned by GetServerPackMask. Nation can be 3 letters nation or TMF Path. If PlayersGame is set to true then a few methods will return more infos, mainly the game associated to the player login in records, and the player max rank (see general notes). Note: in case of error it returns a string with the error description.
    -
  • dedimania.ValidateAccount
  • struct - dedimania.ValidateAccount()
    -Reply a struct {'Status': boolean, 'Messages': array of struct {'Date': string, 'Text': string} }. Status is always true, else you get a not authenticated error.
    -Only if authenticated.

    -
  • dedimania.PlayerArrive
  • struct - dedimania.PlayerArrive(string, string, string, string, string, int, boolean, boolean)
    -Announce that a new player has arrived. Arguments are (Game, Login, Nickname, Nation, TeamName, LadderRanking, IsSpec, IsOff). Game can be 'TMF', 'TMUF', 'TMNF', 'TMU', 'TMO', 'TMS' or 'TMN'.
    -Reply a struct {['Game': string,] 'Login': string, 'TeamName': string, 'Nation': string, ['MaxRank': int, 'Status': int,] 'Options': array of struct {'Option': string, 'Value': string, 'Tool': string}, 'Aliases': array of struct {'Alias': string, 'Text': string, 'Tool': string} }. Game, MaxRank and Status are added only if PlayersGame is set in authenticate, Status bit 0 is 1 if banned (is which case MaxRank is 0). Note that MaxRank is the real max rank for the player (see general notes).
    -Only if authenticated.

    -
  • dedimania.PlayerLeave
  • struct - dedimania.PlayerLeave(string, string)
    -Announce that a player has left. Arguments are (Game,Login). Game can be 'TMF', 'TMUF', 'TMNF', 'TMU', 'TMO', 'TMS' or 'TMN'.
    -Reply a struct {'Login': string}.
    -Only if authenticated.

    -
  • dedimania.CurrentChallenge
  • struct - dedimania.CurrentChallenge(string, string, string, string, string, int, struct, int, array)
    -Set current challenge info and get records. Arguments are (Uid, Name, Environment, Author, Game, Mode, SrvInfo, MaxGetTimes, Players). Game is currently 'TMF', 'TMUF', 'TMNF', 'TMU', 'TMO', 'TMS' or 'TMN'. SrvInfo is a struct {'SrvName': string, 'Comment': string, 'Private': boolean, 'SrvIP': string, 'SrvPort': int, 'XmlrpcPort': int, 'NumPlayers': int, 'MaxPlayers': int, 'NumSpecs': int, 'MaxSpecs': int, 'LadderMode': int, 'NextFiveUID': string of next five uid separated with '/'}. Players is an array of struct {'Login': string, 'Nation': string, 'TeamName': string, 'TeamId': int, 'IsSpec': boolean, 'Ranking': int, 'IsOff': boolean}.
    -Reply a struct {'Uid': string, 'TotalRaces': int, 'TotalPlayers': int, 'TimeAttackRaces': int, 'TimeAttackPlayers': int, 'NumberOfChecks': int, 'ServerMaxRecords': int, 'Records': array of struct {['Game': string,] 'Login': string, 'NickName': string, 'Best': int, 'Rank': int, ['MaxRank': int,] 'Checks': array of int, 'Vote': int} }, NumberOfChecks per lap is 0 if unknown, Checks are the bestchecks of the associated record, Vote is 0 to 100 value, or -1 if player did not vote for the map. Game and MaxRank are added only if PlayersGame is set in authenticate (see general notes).
    -Only if authenticated.

    -
  • dedimania.ChallengeRaceTimes
  • struct - dedimania.ChallengeRaceTimes(string, string, string, string, string, int, int, int, array)
    -Set current challenge info and players' best times, and get the updated records. Arguments are (Uid, Name, Environment, Author, Game, Mode, NumberOfChecks, MaxGetTimes, Times). Game is currently 'TMF', 'TMUF', 'TMNF', 'TMU', 'TMO', 'TMS' or 'TMN'. Times is a sorted (by 'Best') array of struct {'Login': string, 'Best': int, 'Checks': array of int}. Checks are BestCheckpoints array of the best time of player (can also be sent as a comma separated list of int in a string, which is far smaller in xmlrpc). In case of time equality the order in the Times array is used.
    -Reply a struct {'Uid': string, 'TotalRaces': int, 'TotalPlayers': int, 'TimeAttackRaces': int, 'TimeAttackPlayers': int, 'NumberOfChecks': int, 'ServerMaxRecords': int, 'Records': array of struct {['Game': string,] 'Login': string, 'NickName': string, 'Best': int, 'Rank': int, ['MaxRank': int,] 'Checks': array of int, 'NewBest': boolean} }, NumberOfChecks per lap is 0 if unknown. Game and MaxRank are added only if PlayersGame is set in authenticate (see general notes).
    -Only if authenticated.

    -
  • dedimania.UpdateServerPlayers
  • boolean - dedimania.UpdateServerPlayers(string, int, struct, array)
    -Set current challenge and players info. Arguments are (Game, Mode, SrvInfo, Players). Game is currently 'TMF', 'TMUF', 'TMNF', 'TMU', 'TMO', 'TMS' or 'TMN'. SrvInfo is a struct {'SrvName': string, 'Comment': string, 'Private': boolean, 'SrvIP': string, 'SrvPort': int, 'XmlrpcPort': int, 'NumPlayers': int, 'MaxPlayers': int, 'NumSpecs': int, 'MaxSpecs': int, 'LadderMode': boolean, 'NextFiveUID': string of next five uid separated with '/'}. Players is an array of struct {'Login': string, 'Nation': string, 'TeamName': string, 'TeamId': int, 'IsSpec': boolean, 'Ranking': int, 'IsOff': boolean}.
    -Reply true.
    -Should be used every 4 minutes if no 'dedimania.CurrentChallenge' or 'dedimania.ChallengeRaceTimes' has been called, to keep the server and players 'On'.
    -Only if authenticated.

    -
  • dedimania.WarningsAndTTR
  • struct - dedimania.WarningsAndTTR()
    -Get warnings messages and TimeToRespond for all previous methods.
    -Reply a struct {'globalTTR': int, 'methods': array of struct {'methodName': string, 'errors': string, 'TTR': int}}.

    -
  • system.listMethods
  • array - system.listMethods()
    -This method lists all the methods that the XML-RPC server knows how to dispatch.
    -
  • system.methodHelp
  • string - system.methodHelp(string)
    -Returns help text if defined for the method passed, otherwise returns an empty string.
    -
  • system.methodSignature
  • array - system.methodSignature(string)
    -Returns an array of known signatures (an array of arrays) for the method name passed. If no signatures are known, returns a none-array (test for type != array to detect missing signature).
    -
  • system.multicall
  • array - system.multicall(array)
    -Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 and raw examples for details. Each array entry is a struct {'methodCall':string, 'params':array}.
    -
    - - diff --git a/xaseco/DOCS/ListMethodsForever.html b/xaseco/DOCS/ListMethodsForever.html deleted file mode 100644 index d900c03..0000000 --- a/xaseco/DOCS/ListMethodsForever.html +++ /dev/null @@ -1,717 +0,0 @@ - - - - -TrackMania Forever methods - - -

    Available methods:

    -
      -
    • system.listMethods
      -array system.listMethods()
      -Return an array of all available XML-RPC methods on this server.
    • -
    • system.methodSignature
      -array system.methodSignature(string)
      -Given the name of a method, return an array of legal signatures. Each signature is an array of strings. The first item of each signature is the return type, and any others items are parameter types.
    • -
    • system.methodHelp
      -string system.methodHelp(string)
      -Given the name of a method, return a help string.
    • -
    • system.multicall
      -array system.multicall(array)
      -Process an array of calls, and return an array of results. Calls should be structs of the form {'methodName': string, 'params': array}. Each result will either be a single-item array containing the result value, or a struct of the form {'faultCode': int, 'faultString': string}. This is useful when you need to make lots of small calls without lots of round trips.
    • -
    • Authenticate
      -boolean Authenticate(string, string)
      -Allow user authentication by specifying a login and a password, to gain access to the set of functionalities corresponding to this authorization level.
    • -
    • ChangeAuthPassword
      -boolean ChangeAuthPassword(string, string)
      -Change the password for the specified login/user. Only available to SuperAdmin.
    • -
    • EnableCallbacks
      -boolean EnableCallbacks(boolean)
      -Allow the GameServer to call you back.
    • -
    • GetVersion
      -struct GetVersion()
      -Returns a struct with the Name, Version and Build of the application remotely controled.
    • -
    • CallVote
      -boolean CallVote(string)
      -Call a vote for a cmd. The command is a XML string corresponding to an XmlRpc request. Only available to Admin.
    • -
    • CallVoteEx
      -boolean CallVoteEx(string, double, int, int)
      -Extended call vote. Same as CallVote, but you can additionally supply specific parameters for this vote: a ratio, a time out and who is voting. Special timeout values: a timeout of '0' means default, '1' means indefinite; a ratio of '-1' means default; Voters values: '0' means only active players, '1' means any player, '2' is for everybody, pure spectators included. Only available to Admin.
    • -
    • InternalCallVote
      -boolean InternalCallVote()
      -Used internally by game.
    • -
    • CancelVote
      -boolean CancelVote()
      -Cancel the current vote. Only available to Admin.
    • -
    • GetCurrentCallVote
      -struct GetCurrentCallVote()
      -Returns the vote currently in progress. The returned structure is { CallerLogin, CmdName, CmdParam }.
    • -
    • SetCallVoteTimeOut
      -boolean SetCallVoteTimeOut(int)
      -Set a new timeout for waiting for votes. A zero value disables callvote. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetCallVoteTimeOut
      -struct GetCallVoteTimeOut()
      -Get the current and next timeout for waiting for votes. The struct returned contains two fields 'CurrentValue' and 'NextValue'.
    • -
    • SetCallVoteRatio
      -boolean SetCallVoteRatio(double)
      -Set a new default ratio for passing a vote. Must lie between 0 and 1. Only available to Admin.
    • -
    • GetCallVoteRatio
      -double GetCallVoteRatio()
      -Get the current default ratio for passing a vote. This value lies between 0 and 1.
    • -
    • SetCallVoteRatios
      -boolean SetCallVoteRatios(array)
      -Set new ratios for passing specific votes. The parameter is an array of structs {string Command, double Ratio}, ratio is in [0,1] or -1 for vote disabled. Only available to Admin.
    • -
    • GetCallVoteRatios
      -array GetCallVoteRatios()
      -Get the current ratios for passing votes.
    • -
    • ChatSendServerMessage
      -boolean ChatSendServerMessage(string)
      -Send a text message to all clients without the server login. Only available to Admin.
    • -
    • ChatSendServerMessageToLanguage
      -boolean ChatSendServerMessageToLanguage(array, string)
      -Send a localised text message to all clients without the server login, or optionally to a Login (which can be a single login or a list of comma-separated logins). The parameter is an array of structures {Lang='??', Text='...'}. If no matching language is found, the last text in the array is used. Only available to Admin.
    • -
    • ChatSendServerMessageToId
      -boolean ChatSendServerMessageToId(string, int)
      -Send a text message without the server login to the client with the specified PlayerId. Only available to Admin.
    • -
    • ChatSendServerMessageToLogin
      -boolean ChatSendServerMessageToLogin(string, string)
      -Send a text message without the server login to the client with the specified login. Login can be a single login or a list of comma-separated logins. Only available to Admin.
    • -
    • ChatSend
      -boolean ChatSend(string)
      -Send a text message to all clients. Only available to Admin.
    • -
    • ChatSendToLanguage
      -boolean ChatSendToLanguage(array, string)
      -Send a localised text message to all clients, or optionally to a Login (which can be a single login or a list of comma-separated logins). The parameter is an array of structures {Lang='??', Text='...'}. If no matching language is found, the last text in the array is used. Only available to Admin.
    • -
    • ChatSendToLogin
      -boolean ChatSendToLogin(string, string)
      -Send a text message to the client with the specified login. Login can be a single login or a list of comma-separated logins. Only available to Admin.
    • -
    • ChatSendToId
      -boolean ChatSendToId(string, int)
      -Send a text message to the client with the specified PlayerId. Only available to Admin.
    • -
    • GetChatLines
      -array GetChatLines()
      -Returns the last chat lines. Maximum of 40 lines. Only available to Admin.
    • -
    • ChatEnableManualRouting
      -boolean ChatEnableManualRouting(boolean, boolean)
      -The chat messages are no longer dispatched to the players, they only go to the rpc callback and the controller has to manually forward them. The second (optional) parameter allows all messages from the server to be automatically forwarded. Only available to Admin.
    • -
    • ChatForwardToLogin
      -boolean ChatForwardToLogin(string, string, string)
      -(Text, SenderLogin, DestLogin) Send a text message to the specified DestLogin (or everybody if empty) on behalf of SenderLogin. DestLogin can be a single login or a list of comma-separated logins. Only available if manual routing is enabled. Only available to Admin.
    • -
    • SendNotice
      -boolean SendNotice(string, string, int)
      -Display a notice on all clients. The parameters are the text message to display, and the login of the avatar to display next to it (or '' for no avatar), and an optional 'max duration' in seconds (default: 3). Only available to Admin.
    • -
    • SendNoticeToId
      -boolean SendNoticeToId(int, string, int, int)
      -Display a notice on the client with the specified UId. The parameters are the Uid of the client to whom the notice is sent, the text message to display, and the UId of the avatar to display next to it (or '255' for no avatar), and an optional 'max duration' in seconds (default: 3). Only available to Admin.
    • -
    • SendNoticeToLogin
      -boolean SendNoticeToLogin(string, string, string, int)
      -Display a notice on the client with the specified login. The parameters are the login of the client to whom the notice is sent, the text message to display, and the login of the avatar to display next to it (or '' for no avatar), and an optional 'max duration' in seconds (default: 3). Login can be a single login or a list of comma-separated logins. Only available to Admin.
    • -
    • SendDisplayManialinkPage
      -boolean SendDisplayManialinkPage(string, int, boolean)
      -Display a manialink page on all clients. The parameters are the xml description of the page to display, a timeout to autohide it (0 = permanent), and a boolean to indicate whether the page must be hidden as soon as the user clicks on a page option. Only available to Admin.
    • -
    • SendDisplayManialinkPageToId
      -boolean SendDisplayManialinkPageToId(int, string, int, boolean)
      -Display a manialink page on the client with the specified UId. The first parameter is the UId of the player, the other are identical to 'SendDisplayManialinkPage'. Only available to Admin.
    • -
    • SendDisplayManialinkPageToLogin
      -boolean SendDisplayManialinkPageToLogin(string, string, int, boolean)
      -Display a manialink page on the client with the specified login. The first parameter is the login of the player, the other are identical to 'SendDisplayManialinkPage'. Login can be a single login or a list of comma-separated logins. Only available to Admin.
    • -
    • SendHideManialinkPage
      -boolean SendHideManialinkPage()
      -Hide the displayed manialink page on all clients. Only available to Admin.
    • -
    • SendHideManialinkPageToId
      -boolean SendHideManialinkPageToId(int)
      -Hide the displayed manialink page on the client with the specified UId. Only available to Admin.
    • -
    • SendHideManialinkPageToLogin
      -boolean SendHideManialinkPageToLogin(string)
      -Hide the displayed manialink page on the client with the specified login. Login can be a single login or a list of comma-separated logins. Only available to Admin.
    • -
    • GetManialinkPageAnswers
      -array GetManialinkPageAnswers()
      -Returns the latest results from the current manialink page, as an array of structs {string Login, int PlayerId, int Result} Result==0 -> no answer, Result>0.... -> answer from the player.
    • -
    • Kick
      -boolean Kick(string, string)
      -Kick the player with the specified login, with an optional message. Only available to Admin.
    • -
    • KickId
      -boolean KickId(int, string)
      -Kick the player with the specified PlayerId, with an optional message. Only available to Admin.
    • -
    • Ban
      -boolean Ban(string, string)
      -Ban the player with the specified login, with an optional message. Only available to Admin.
    • -
    • BanAndBlackList
      -boolean BanAndBlackList(string, string, boolean)
      -Ban the player with the specified login, with a message. Add it to the black list, and optionally save the new list. Only available to Admin.
    • -
    • BanId
      -boolean BanId(int, string)
      -Ban the player with the specified PlayerId, with an optional message. Only available to Admin.
    • -
    • UnBan
      -boolean UnBan(string)
      -Unban the player with the specified login. Only available to Admin.
    • -
    • CleanBanList
      -boolean CleanBanList()
      -Clean the ban list of the server. Only available to Admin.
    • -
    • GetBanList
      -array GetBanList(int, int)
      -Returns the list of banned players. This method takes two parameters. The first parameter specifies the maximum number of infos to be returned, and the second one the starting index in the list. The list is an array of structures. Each structure contains the following fields : Login, ClientName and IPAddress.
    • -
    • BlackList
      -boolean BlackList(string)
      -Blacklist the player with the specified login. Only available to SuperAdmin.
    • -
    • BlackListId
      -boolean BlackListId(int)
      -Blacklist the player with the specified PlayerId. Only available to SuperAdmin.
    • -
    • UnBlackList
      -boolean UnBlackList(string)
      -UnBlackList the player with the specified login. Only available to SuperAdmin.
    • -
    • CleanBlackList
      -boolean CleanBlackList()
      -Clean the blacklist of the server. Only available to SuperAdmin.
    • -
    • GetBlackList
      -array GetBlackList(int, int)
      -Returns the list of blacklisted players. This method takes two parameters. The first parameter specifies the maximum number of infos to be returned, and the second one the starting index in the list. The list is an array of structures. Each structure contains the following fields : Login.
    • -
    • LoadBlackList
      -boolean LoadBlackList(string)
      -Load the black list file with the specified file name. Only available to Admin.
    • -
    • SaveBlackList
      -boolean SaveBlackList(string)
      -Save the black list in the file with specified file name. Only available to Admin.
    • -
    • AddGuest
      -boolean AddGuest(string)
      -Add the player with the specified login on the guest list. Only available to Admin.
    • -
    • AddGuestId
      -boolean AddGuestId(int)
      -Add the player with the specified PlayerId on the guest list. Only available to Admin.
    • -
    • RemoveGuest
      -boolean RemoveGuest(string)
      -Remove the player with the specified login from the guest list. Only available to Admin.
    • -
    • RemoveGuestId
      -boolean RemoveGuestId(int)
      -Remove the player with the specified PlayerId from the guest list. Only available to Admin.
    • -
    • CleanGuestList
      -boolean CleanGuestList()
      -Clean the guest list of the server. Only available to Admin.
    • -
    • GetGuestList
      -array GetGuestList(int, int)
      -Returns the list of players on the guest list. This method takes two parameters. The first parameter specifies the maximum number of infos to be returned, and the second one the starting index in the list. The list is an array of structures. Each structure contains the following fields : Login.
    • -
    • LoadGuestList
      -boolean LoadGuestList(string)
      -Load the guest list file with the specified file name. Only available to Admin.
    • -
    • SaveGuestList
      -boolean SaveGuestList(string)
      -Save the guest list in the file with specified file name. Only available to Admin.
    • -
    • SetBuddyNotification
      -boolean SetBuddyNotification(string, boolean)
      -Sets whether buddy notifications should be sent in the chat. login is the login of the player, or '' for global setting, and enabled is the value. Only available to Admin.
    • -
    • GetBuddyNotification
      -boolean GetBuddyNotification(string)
      -Gets whether buddy notifications are enabled for login, or '' to get the global setting.
    • -
    • WriteFile
      -boolean WriteFile(string, base64)
      -Write the data to the specified file. The filename is relative to the Tracks path. Only available to Admin.
    • -
    • TunnelSendDataToId
      -boolean TunnelSendDataToId(int, base64)
      -Send the data to the specified player. Only available to Admin.
    • -
    • TunnelSendDataToLogin
      -boolean TunnelSendDataToLogin(string, base64)
      -Send the data to the specified player. Login can be a single login or a list of comma-separated logins. Only available to Admin.
    • -
    • Echo
      -boolean Echo(string, string)
      -Just log the parameters and invoke a callback. Can be used to talk to other xmlrpc clients connected, or to make custom votes. If used in a callvote, the first parameter will be used as the vote message on the clients. Only available to Admin.
    • -
    • Ignore
      -boolean Ignore(string)
      -Ignore the player with the specified login. Only available to Admin.
    • -
    • IgnoreId
      -boolean IgnoreId(int)
      -Ignore the player with the specified PlayerId. Only available to Admin.
    • -
    • UnIgnore
      -boolean UnIgnore(string)
      -Unignore the player with the specified login. Only available to Admin.
    • -
    • UnIgnoreId
      -boolean UnIgnoreId(int)
      -Unignore the player with the specified PlayerId. Only available to Admin.
    • -
    • CleanIgnoreList
      -boolean CleanIgnoreList()
      -Clean the ignore list of the server. Only available to Admin.
    • -
    • GetIgnoreList
      -array GetIgnoreList(int, int)
      -Returns the list of ignored players. This method takes two parameters. The first parameter specifies the maximum number of infos to be returned, and the second one the starting index in the list. The list is an array of structures. Each structure contains the following fields : Login.
    • -
    • Pay
      -int Pay(string, int, string)
      -Pay coppers from the server account to a player, returns the BillId. This method takes three parameters: Login of the payee, Coppers to pay and a Label to send with the payment. The creation of the transaction itself may cost coppers, so you need to have coppers on the server account. Only available to Admin.
    • -
    • SendBill
      -int SendBill(string, int, string, string)
      -Create a bill, send it to a player, and return the BillId. This method takes four parameters: LoginFrom of the payer, Coppers the player has to pay, Label of the transaction and an optional LoginTo of the payee (if empty string, then the server account is used). The creation of the transaction itself may cost coppers, so you need to have coppers on the server account. Only available to Admin.
    • -
    • GetBillState
      -struct GetBillState(int)
      -Returns the current state of a bill. This method takes one parameter, the BillId. Returns a struct containing State, StateName and TransactionId. Possible enum values are: CreatingTransaction, Issued, ValidatingPayement, Payed, Refused, Error.
    • -
    • GetServerCoppers
      -int GetServerCoppers()
      -Returns the current number of coppers on the server account.
    • -
    • GetSystemInfo
      -struct GetSystemInfo()
      -Get some system infos, including connection rates (in kbps).
    • -
    • SetConnectionRates
      -boolean SetConnectionRates(int, int)
      -Set the download and upload rates (in kbps).
    • -
    • SetServerName
      -boolean SetServerName(string)
      -Set a new server name in utf8 format. Only available to Admin.
    • -
    • GetServerName
      -string GetServerName()
      -Get the server name in utf8 format.
    • -
    • SetServerComment
      -boolean SetServerComment(string)
      -Set a new server comment in utf8 format. Only available to Admin.
    • -
    • GetServerComment
      -string GetServerComment()
      -Get the server comment in utf8 format.
    • -
    • SetHideServer
      -boolean SetHideServer(int)
      -Set whether the server should be hidden from the public server list (0 = visible, 1 = always hidden, 2 = hidden from nations). Only available to Admin.
    • -
    • GetHideServer
      -int GetHideServer()
      -Get whether the server wants to be hidden from the public server list.
    • -
    • IsRelayServer
      -boolean IsRelayServer()
      -Returns true if this is a relay server.
    • -
    • SetServerPassword
      -boolean SetServerPassword(string)
      -Set a new password for the server. Only available to Admin.
    • -
    • GetServerPassword
      -string GetServerPassword()
      -Get the server password if called as Admin or Super Admin, else returns if a password is needed or not.
    • -
    • SetServerPasswordForSpectator
      -boolean SetServerPasswordForSpectator(string)
      -Set a new password for the spectator mode. Only available to Admin.
    • -
    • GetServerPasswordForSpectator
      -string GetServerPasswordForSpectator()
      -Get the password for spectator mode if called as Admin or Super Admin, else returns if a password is needed or not.
    • -
    • SetMaxPlayers
      -boolean SetMaxPlayers(int)
      -Set a new maximum number of players. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetMaxPlayers
      -struct GetMaxPlayers()
      -Get the current and next maximum number of players allowed on server. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetMaxSpectators
      -boolean SetMaxSpectators(int)
      -Set a new maximum number of Spectators. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetMaxSpectators
      -struct GetMaxSpectators()
      -Get the current and next maximum number of Spectators allowed on server. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • EnableP2PUpload
      -boolean EnableP2PUpload(boolean)
      -Enable or disable peer-to-peer upload from server. Only available to Admin.
    • -
    • IsP2PUpload
      -boolean IsP2PUpload()
      -Returns if the peer-to-peer upload from server is enabled.
    • -
    • EnableP2PDownload
      -boolean EnableP2PDownload(boolean)
      -Enable or disable peer-to-peer download for server. Only available to Admin.
    • -
    • IsP2PDownload
      -boolean IsP2PDownload()
      -Returns if the peer-to-peer download for server is enabled.
    • -
    • AllowChallengeDownload
      -boolean AllowChallengeDownload(boolean)
      -Allow clients to download challenges from the server. Only available to Admin.
    • -
    • IsChallengeDownloadAllowed
      -boolean IsChallengeDownloadAllowed()
      -Returns if clients can download challenges from the server.
    • -
    • AutoSaveReplays
      -boolean AutoSaveReplays(boolean)
      -Enable the autosaving of all replays (vizualisable replays with all players, but not validable) on the server. Only available to SuperAdmin.
    • -
    • AutoSaveValidationReplays
      -boolean AutoSaveValidationReplays(boolean)
      -Enable the autosaving on the server of validation replays, every time a player makes a new time. Only available to SuperAdmin.
    • -
    • IsAutoSaveReplaysEnabled
      -boolean IsAutoSaveReplaysEnabled()
      -Returns if autosaving of all replays is enabled on the server.
    • -
    • IsAutoSaveValidationReplaysEnabled
      -boolean IsAutoSaveValidationReplaysEnabled()
      -Returns if autosaving of validation replays is enabled on the server.
    • -
    • SaveCurrentReplay
      -boolean SaveCurrentReplay(string)
      -Saves the current replay (vizualisable replays with all players, but not validable). Pass a filename, or '' for an automatic filename. Only available to Admin.
    • -
    • SaveBestGhostsReplay
      -boolean SaveBestGhostsReplay(string, string)
      -Saves a replay with the ghost of all the players' best race. First parameter is the login of the player (or '' for all players), Second parameter is the filename, or '' for an automatic filename. Only available to Admin.
    • -
    • GetValidationReplay
      -base64 GetValidationReplay(string)
      -Returns a replay containing the data needed to validate the current best time of the player. The parameter is the login of the player.
    • -
    • SetLadderMode
      -boolean SetLadderMode(int)
      -Set a new ladder mode between ladder disabled (0) and forced (1). Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetLadderMode
      -struct GetLadderMode()
      -Get the current and next ladder mode on server. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • GetLadderServerLimits
      -struct GetLadderServerLimits()
      -Get the ladder points limit for the players allowed on this server. The struct returned contains two fields LadderServerLimitMin and LadderServerLimitMax.
    • -
    • SetVehicleNetQuality
      -boolean SetVehicleNetQuality(int)
      -Set the network vehicle quality to Fast (0) or High (1). Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetVehicleNetQuality
      -struct GetVehicleNetQuality()
      -Get the current and next network vehicle quality on server. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetServerOptions
      -boolean SetServerOptions(struct)
      -Set new server options using the struct passed as parameters. This struct must contain the following fields : Name, Comment, Password, PasswordForSpectator, NextMaxPlayers, NextMaxSpectators, IsP2PUpload, IsP2PDownload, NextLadderMode, NextVehicleNetQuality, NextCallVoteTimeOut, CallVoteRatio, AllowChallengeDownload, AutoSaveReplays, and optionally for forever: RefereePassword, RefereeMode, AutoSaveValidationReplays, HideServer, UseChangingValidationSeed. Only available to Admin. A change of NextMaxPlayers, NextMaxSpectators, NextLadderMode, NextVehicleNetQuality, NextCallVoteTimeOut or UseChangingValidationSeed requires a challenge restart to be taken into account.
    • -
    • GetServerOptions
      -struct GetServerOptions(int)
      -Optional parameter for compatibility: struct version (0 = united, 1 = forever). Returns a struct containing the server options: Name, Comment, Password, PasswordForSpectator, CurrentMaxPlayers, NextMaxPlayers, CurrentMaxSpectators, NextMaxSpectators, IsP2PUpload, IsP2PDownload, CurrentLadderMode, NextLadderMode, CurrentVehicleNetQuality, NextVehicleNetQuality, CurrentCallVoteTimeOut, NextCallVoteTimeOut, CallVoteRatio, AllowChallengeDownload and AutoSaveReplays, and additionally for forever: RefereePassword, RefereeMode, AutoSaveValidationReplays, HideServer, CurrentUseChangingValidationSeed, NextUseChangingValidationSeed.
    • -
    • SetServerPackMask
      -boolean SetServerPackMask(string)
      -Defines the packmask of the server. Can be 'United', 'Nations', 'Sunrise', 'Original', or any of the environment names. (Only challenges matching the packmask will be allowed on the server, so that player connecting to it know what to expect.) Only available when the server is stopped. Only available to Admin.
    • -
    • GetServerPackMask
      -string GetServerPackMask()
      -Get the packmask of the server.
    • -
    • SetForcedMods
      -boolean SetForcedMods(boolean, array)
      -Set the mods to apply on the clients. Parameters: Override, if true even the challenges with a mod will be overridden by the server setting; and Mods, an array of structures [{EnvName, Url}, ...]. Requires a challenge restart to be taken into account. Only available to Admin.
    • -
    • GetForcedMods
      -struct GetForcedMods()
      -Get the mods settings.
    • -
    • SetForcedMusic
      -boolean SetForcedMusic(boolean, string)
      -Set the music to play on the clients. Parameters: Override, if true even the challenges with a custom music will be overridden by the server setting, and a UrlOrFileName for the music. Requires a challenge restart to be taken into account. Only available to Admin.
    • -
    • GetForcedMusic
      -struct GetForcedMusic()
      -Get the music setting.
    • -
    • SetForcedSkins
      -boolean SetForcedSkins(array)
      -Defines a list of remappings for player skins. It expects a list of structs Orig, Name, Checksum, Url. Orig is the name of the skin to remap, or '*' for any other. Name, Checksum, Url define the skin to use. (They are optional, you may set value '' for any of those. All 3 null means same as Orig). Will only affect players connecting after the value is set. Only available to Admin.
    • -
    • GetForcedSkins
      -array GetForcedSkins()
      -Get the current forced skins.
    • -
    • GetLastConnectionErrorMessage
      -string GetLastConnectionErrorMessage()
      -Returns the last error message for an internet connection. Only available to Admin.
    • -
    • SetRefereePassword
      -boolean SetRefereePassword(string)
      -Set a new password for the referee mode. Only available to Admin.
    • -
    • GetRefereePassword
      -string GetRefereePassword()
      -Get the password for referee mode if called as Admin or Super Admin, else returns if a password is needed or not.
    • -
    • SetRefereeMode
      -boolean SetRefereeMode(int)
      -Set the referee validation mode. 0 = validate the top3 players, 1 = validate all players. Only available to Admin.
    • -
    • GetRefereeMode
      -int GetRefereeMode()
      -Get the referee validation mode.
    • -
    • SetUseChangingValidationSeed
      -boolean SetUseChangingValidationSeed(boolean)
      -Set whether the game should use a variable validation seed or not. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetUseChangingValidationSeed
      -struct GetUseChangingValidationSeed()
      -Get the current and next value of UseChangingValidationSeed. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetWarmUp
      -boolean SetWarmUp(boolean)
      -Sets whether the server is in warm-up phase or not. Only available to Admin.
    • -
    • GetWarmUp
      -boolean GetWarmUp()
      -Returns whether the server is in warm-up phase.
    • -
    • ChallengeRestart
      -boolean ChallengeRestart()
      -Restarts the challenge, with an optional boolean parameter DontClearCupScores (only available in cup mode). Only available to Admin.
    • -
    • RestartChallenge
      -boolean RestartChallenge()
      -Restarts the challenge, with an optional boolean parameter DontClearCupScores (only available in cup mode). Only available to Admin.
    • -
    • NextChallenge
      -boolean NextChallenge()
      -Switch to next challenge, with an optional boolean parameter DontClearCupScores (only available in cup mode). Only available to Admin.
    • -
    • StopServer
      -boolean StopServer()
      -Stop the server. Only available to SuperAdmin.
    • -
    • ForceEndRound
      -boolean ForceEndRound()
      -In Rounds or Laps mode, force the end of round without waiting for all players to giveup/finish. Only available to Admin.
    • -
    • SetGameInfos
      -boolean SetGameInfos(struct)
      -Set new game settings using the struct passed as parameters. This struct must contain the following fields : GameMode, ChatTime, RoundsPointsLimit, RoundsUseNewRules, RoundsForcedLaps, TimeAttackLimit, TimeAttackSynchStartPeriod, TeamPointsLimit, TeamMaxPoints, TeamUseNewRules, LapsNbLaps, LapsTimeLimit, FinishTimeout, and optionally: AllWarmUpDuration, DisableRespawn, ForceShowAllOpponents, RoundsPointsLimitNewRules, TeamPointsLimitNewRules, CupPointsLimit, CupRoundsPerChallenge, CupNbWinners, CupWarmUpDuration. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetCurrentGameInfo
      -struct GetCurrentGameInfo(int)
      -Optional parameter for compatibility: struct version (0 = united, 1 = forever). Returns a struct containing the current game settings, ie: GameMode, ChatTime, NbChallenge, RoundsPointsLimit, RoundsUseNewRules, RoundsForcedLaps, TimeAttackLimit, TimeAttackSynchStartPeriod, TeamPointsLimit, TeamMaxPoints, TeamUseNewRules, LapsNbLaps, LapsTimeLimit, FinishTimeout, and additionally for version 1: AllWarmUpDuration, DisableRespawn, ForceShowAllOpponents, RoundsPointsLimitNewRules, TeamPointsLimitNewRules, CupPointsLimit, CupRoundsPerChallenge, CupNbWinners, CupWarmUpDuration.
    • -
    • GetNextGameInfo
      -struct GetNextGameInfo(int)
      -Optional parameter for compatibility: struct version (0 = united, 1 = forever). Returns a struct containing the game settings for the next challenge, ie: GameMode, ChatTime, NbChallenge, RoundsPointsLimit, RoundsUseNewRules, RoundsForcedLaps, TimeAttackLimit, TimeAttackSynchStartPeriod, TeamPointsLimit, TeamMaxPoints, TeamUseNewRules, LapsNbLaps, LapsTimeLimit, FinishTimeout, and additionally for version 1: AllWarmUpDuration, DisableRespawn, ForceShowAllOpponents, RoundsPointsLimitNewRules, TeamPointsLimitNewRules, CupPointsLimit, CupRoundsPerChallenge, CupNbWinners, CupWarmUpDuration.
    • -
    • GetGameInfos
      -struct GetGameInfos(int)
      -Optional parameter for compatibility: struct version (0 = united, 1 = forever). Returns a struct containing two other structures, the first containing the current game settings and the second the game settings for next challenge. The first structure is named CurrentGameInfos and the second NextGameInfos.
    • -
    • SetGameMode
      -boolean SetGameMode(int)
      -Set a new game mode between Rounds (0), TimeAttack (1), Team (2), Laps (3), Stunts (4) and Cup (5). Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetGameMode
      -int GetGameMode()
      -Get the current game mode.
    • -
    • SetChatTime
      -boolean SetChatTime(int)
      -Set a new chat time value in milliseconds (actually 'chat time' is the duration of the end race podium, 0 means no podium displayed.). Only available to Admin.
    • -
    • GetChatTime
      -struct GetChatTime()
      -Get the current and next chat time. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetFinishTimeout
      -boolean SetFinishTimeout(int)
      -Set a new finish timeout (for rounds/laps mode) value in milliseconds. 0 means default. 1 means adaptative to the duration of the challenge. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetFinishTimeout
      -struct GetFinishTimeout()
      -Get the current and next FinishTimeout. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetAllWarmUpDuration
      -boolean SetAllWarmUpDuration(int)
      -Set whether to enable the automatic warm-up phase in all modes. 0 = no, otherwise it's the duration of the phase, expressed in number of rounds (in rounds/team mode), or in number of times the gold medal time (other modes). Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetAllWarmUpDuration
      -struct GetAllWarmUpDuration()
      -Get whether the automatic warm-up phase is enabled in all modes. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetDisableRespawn
      -boolean SetDisableRespawn(boolean)
      -Set whether to disallow players to respawn. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetDisableRespawn
      -struct GetDisableRespawn()
      -Get whether players are disallowed to respawn. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetForceShowAllOpponents
      -boolean SetForceShowAllOpponents(int)
      -Set whether to override the players preferences and always display all opponents (0=no override, 1=show all, other value=minimum number of opponents). Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetForceShowAllOpponents
      -struct GetForceShowAllOpponents()
      -Get whether players are forced to show all opponents. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetTimeAttackLimit
      -boolean SetTimeAttackLimit(int)
      -Set a new time limit for time attack mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetTimeAttackLimit
      -struct GetTimeAttackLimit()
      -Get the current and next time limit for time attack mode. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetTimeAttackSynchStartPeriod
      -boolean SetTimeAttackSynchStartPeriod(int)
      -Set a new synchronized start period for time attack mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetTimeAttackSynchStartPeriod
      -struct GetTimeAttackSynchStartPeriod()
      -Get the current and synchronized start period for time attack mode. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetLapsTimeLimit
      -boolean SetLapsTimeLimit(int)
      -Set a new time limit for laps mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetLapsTimeLimit
      -struct GetLapsTimeLimit()
      -Get the current and next time limit for laps mode. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetNbLaps
      -boolean SetNbLaps(int)
      -Set a new number of laps for laps mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetNbLaps
      -struct GetNbLaps()
      -Get the current and next number of laps for laps mode. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetRoundForcedLaps
      -boolean SetRoundForcedLaps(int)
      -Set a new number of laps for rounds mode (0 = default, use the number of laps from the challenges, otherwise forces the number of rounds for multilaps challenges). Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetRoundForcedLaps
      -struct GetRoundForcedLaps()
      -Get the current and next number of laps for rounds mode. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetRoundPointsLimit
      -boolean SetRoundPointsLimit(int)
      -Set a new points limit for rounds mode (value set depends on UseNewRulesRound). Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetRoundPointsLimit
      -struct GetRoundPointsLimit()
      -Get the current and next points limit for rounds mode (values returned depend on UseNewRulesRound). The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetRoundCustomPoints
      -boolean SetRoundCustomPoints(array, boolean)
      -Set the points used for the scores in rounds mode. Points is an array of decreasing integers for the players from the first to last. And you can add an optional boolean to relax the constraint checking on the scores. Only available to Admin.
    • -
    • GetRoundCustomPoints
      -array GetRoundCustomPoints()
      -Gets the points used for the scores in rounds mode.
    • -
    • SetUseNewRulesRound
      -boolean SetUseNewRulesRound(boolean)
      -Set if new rules are used for rounds mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetUseNewRulesRound
      -struct GetUseNewRulesRound()
      -Get if the new rules are used for rounds mode (Current and next values). The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetTeamPointsLimit
      -boolean SetTeamPointsLimit(int)
      -Set a new points limit for team mode (value set depends on UseNewRulesTeam). Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetTeamPointsLimit
      -struct GetTeamPointsLimit()
      -Get the current and next points limit for team mode (values returned depend on UseNewRulesTeam). The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetMaxPointsTeam
      -boolean SetMaxPointsTeam(int)
      -Set a new number of maximum points per round for team mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetMaxPointsTeam
      -struct GetMaxPointsTeam()
      -Get the current and next number of maximum points per round for team mode. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetUseNewRulesTeam
      -boolean SetUseNewRulesTeam(boolean)
      -Set if new rules are used for team mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetUseNewRulesTeam
      -struct GetUseNewRulesTeam()
      -Get if the new rules are used for team mode (Current and next values). The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetCupPointsLimit
      -boolean SetCupPointsLimit(int)
      -Set the points needed for victory in Cup mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetCupPointsLimit
      -struct GetCupPointsLimit()
      -Get the points needed for victory in Cup mode. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetCupRoundsPerChallenge
      -boolean SetCupRoundsPerChallenge(int)
      -Sets the number of rounds before going to next challenge in Cup mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetCupRoundsPerChallenge
      -struct GetCupRoundsPerChallenge()
      -Get the number of rounds before going to next challenge in Cup mode. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetCupWarmUpDuration
      -boolean SetCupWarmUpDuration(int)
      -Set whether to enable the automatic warm-up phase in Cup mode. 0 = no, otherwise it's the duration of the phase, expressed in number of rounds. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetCupWarmUpDuration
      -struct GetCupWarmUpDuration()
      -Get whether the automatic warm-up phase is enabled in Cup mode. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetCupNbWinners
      -boolean SetCupNbWinners(int)
      -Set the number of winners to determine before the match is considered over. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetCupNbWinners
      -struct GetCupNbWinners()
      -Get the number of winners to determine before the match is considered over. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • GetCurrentChallengeIndex
      -int GetCurrentChallengeIndex()
      -Returns the current challenge index in the selection, or -1 if the challenge is no longer in the selection.
    • -
    • GetNextChallengeIndex
      -int GetNextChallengeIndex()
      -Returns the challenge index in the selection that will be played next (unless the current one is restarted...)
    • -
    • SetNextChallengeIndex
      -boolean SetNextChallengeIndex(int)
      -Sets the challenge index in the selection that will be played next (unless the current one is restarted...)
    • -
    • GetCurrentChallengeInfo
      -struct GetCurrentChallengeInfo()
      -Returns a struct containing the infos for the current challenge. The struct contains the following fields : Name, UId, FileName, Author, Environnement, Mood, BronzeTime, SilverTime, GoldTime, AuthorTime, CopperPrice, LapRace, NbLaps and NbCheckpoints.
    • -
    • GetNextChallengeInfo
      -struct GetNextChallengeInfo()
      -Returns a struct containing the infos for the next challenge. The struct contains the following fields : Name, UId, FileName, Author, Environnement, Mood, BronzeTime, SilverTime, GoldTime, AuthorTime, CopperPrice and LapRace. (NbLaps and NbCheckpoints are also present but always set to -1)
    • -
    • GetChallengeInfo
      -struct GetChallengeInfo(string)
      -Returns a struct containing the infos for the challenge with the specified filename. The struct contains the following fields : Name, UId, FileName, Author, Environnement, Mood, BronzeTime, SilverTime, GoldTime, AuthorTime, CopperPrice and LapRace. (NbLaps and NbCheckpoints are also present but always set to -1)
    • -
    • CheckChallengeForCurrentServerParams
      -boolean CheckChallengeForCurrentServerParams(string)
      -Returns a boolean if the challenge with the specified filename matches the current server settings.
    • -
    • GetChallengeList
      -array GetChallengeList(int, int)
      -Returns a list of challenges among the current selection of the server. This method take two parameters. The first parameter specifies the maximum number of infos to be returned, and the second one the starting index in the selection. The list is an array of structures. Each structure contains the following fields : Name, UId, FileName, Environnement, Author, GoldTime and CopperPrice.
    • -
    • AddChallenge
      -boolean AddChallenge(string)
      -Add the challenge with the specified filename at the end of the current selection. Only available to Admin.
    • -
    • AddChallengeList
      -int AddChallengeList(array)
      -Add the list of challenges with the specified filenames at the end of the current selection. The list of challenges to add is an array of strings. Only available to Admin.
    • -
    • RemoveChallenge
      -boolean RemoveChallenge(string)
      -Remove the challenge with the specified filename from the current selection. Only available to Admin.
    • -
    • RemoveChallengeList
      -int RemoveChallengeList(array)
      -Remove the list of challenges with the specified filenames from the current selection. The list of challenges to remove is an array of strings. Only available to Admin.
    • -
    • InsertChallenge
      -boolean InsertChallenge(string)
      -Insert the challenge with the specified filename after the current challenge. Only available to Admin.
    • -
    • InsertChallengeList
      -int InsertChallengeList(array)
      -Insert the list of challenges with the specified filenames after the current challenge. The list of challenges to insert is an array of strings. Only available to Admin.
    • -
    • ChooseNextChallenge
      -boolean ChooseNextChallenge(string)
      -Set as next challenge the one with the specified filename, if it is present in the selection. Only available to Admin.
    • -
    • ChooseNextChallengeList
      -int ChooseNextChallengeList(array)
      -Set as next challenges the list of challenges with the specified filenames, if they are present in the selection. The list of challenges to choose is an array of strings. Only available to Admin.
    • -
    • LoadMatchSettings
      -int LoadMatchSettings(string)
      -Set a list of challenges defined in the playlist with the specified filename as the current selection of the server, and load the gameinfos from the same file. Only available to Admin.
    • -
    • AppendPlaylistFromMatchSettings
      -int AppendPlaylistFromMatchSettings(string)
      -Add a list of challenges defined in the playlist with the specified filename at the end of the current selection. Only available to Admin.
    • -
    • SaveMatchSettings
      -int SaveMatchSettings(string)
      -Save the current selection of challenge in the playlist with the specified filename, as well as the current gameinfos. Only available to Admin.
    • -
    • InsertPlaylistFromMatchSettings
      -int InsertPlaylistFromMatchSettings(string)
      -Insert a list of challenges defined in the playlist with the specified filename after the current challenge. Only available to Admin.
    • -
    • GetPlayerList
      -array GetPlayerList(int, int, int)
      -Returns the list of players on the server. This method take two parameters. The first parameter specifies the maximum number of infos to be returned, and the second one the starting index in the list, an optional 3rd parameter is used for compatibility: struct version (0 = united, 1 = forever, 2 = forever, including the servers). The list is an array of PlayerInfo structures. Forever PlayerInfo struct is: Login, NickName, PlayerId, TeamId, SpectatorStatus, LadderRanking, and Flags.
      -LadderRanking is 0 when not in official mode,
      -Flags = ForceSpectator(0,1,2) + IsReferee * 10 + IsPodiumReady * 100 + IsUsingStereoscopy * 1000 + IsManagedByAnOtherServer * 10000 + IsServer * 100000 + HasPlayerSlot * 1000000
      -SpectatorStatus = Spectator + TemporarySpectator * 10 + PureSpectator * 100 + AutoTarget * 1000 + CurrentTargetId * 10000
    • -
    • GetPlayerInfo
      -struct GetPlayerInfo(string, int)
      -Returns a struct containing the infos on the player with the specified login, with an optional parameter for compatibility: struct version (0 = united, 1 = forever). The structure is identical to the ones from GetPlayerList. Forever PlayerInfo struct is: Login, NickName, PlayerId, TeamId, SpectatorStatus, LadderRanking, and Flags.
      -LadderRanking is 0 when not in official mode,
      -Flags = ForceSpectator(0,1,2) + IsReferee * 10 + IsPodiumReady * 100 + IsUsingStereoscopy * 1000 + IsManagedByAnOtherServer * 10000 + IsServer * 100000 + HasPlayerSlot * 1000000
      -SpectatorStatus = Spectator + TemporarySpectator * 10 + PureSpectator * 100 + AutoTarget * 1000 + CurrentTargetId * 10000
    • -
    • GetDetailedPlayerInfo
      -struct GetDetailedPlayerInfo(string)
      -Returns a struct containing the infos on the player with the specified login. The structure contains the following fields : Login, NickName, PlayerId, TeamId, IPAddress, DownloadRate, UploadRate, Language, IsSpectator, IsInOfficialMode, a structure named Avatar, an array of structures named Skins, a structure named LadderStats, HoursSinceZoneInscription and OnlineRights (0: nations account, 3: united account). Each structure of the array Skins contains two fields Environnement and a struct PackDesc. Each structure PackDesc, as well as the struct Avatar, contains two fields FileName and Checksum.
    • -
    • GetMainServerPlayerInfo
      -struct GetMainServerPlayerInfo(int)
      -Returns a struct containing the player infos of the game server (ie: in case of a basic server, itself; in case of a relay server, the main server), with an optional parameter for compatibility: struct version (0 = united, 1 = forever). The structure is identical to the ones from GetPlayerList. Forever PlayerInfo struct is: Login, NickName, PlayerId, TeamId, SpectatorStatus, LadderRanking, and Flags.
      -LadderRanking is 0 when not in official mode,
      -Flags = ForceSpectator(0,1,2) + IsReferee * 10 + IsPodiumReady * 100 + IsUsingStereoscopy * 1000 + IsManagedByAnOtherServer * 10000 + IsServer * 100000 + HasPlayerSlot * 1000000
      -SpectatorStatus = Spectator + TemporarySpectator * 10 + PureSpectator * 100 + AutoTarget * 1000 + CurrentTargetId * 10000
    • -
    • GetCurrentRanking
      -array GetCurrentRanking(int, int)
      -Returns the current rankings for the race in progress. (in team mode, the scores for the two teams are returned. In other modes, it's the individual players' scores) This method take two parameters. The first parameter specifies the maximum number of infos to be returned, and the second one the starting index in the ranking. The ranking returned is a list of structures. Each structure contains the following fields : Login, NickName, PlayerId, Rank, BestTime, Score, NbrLapsFinished and LadderScore. It also contains an array BestCheckpoints that contains the checkpoint times for the best race.
    • -
    • GetCurrentRankingForLogin
      -array GetCurrentRankingForLogin(string)
      -Returns the current ranking for the race in progressof the player with the specified login (or list of comma-separated logins). The ranking returned is a list of structures, that contains the following fields : Login, NickName, PlayerId, Rank, BestTime, Score, NbrLapsFinished and LadderScore. It also contains an array BestCheckpoints that contains the checkpoint times for the best race.
    • -
    • ForceScores
      -boolean ForceScores(array, boolean)
      -Force the scores of the current game. Only available in rounds and team mode. You have to pass an array of structs {int PlayerId, int Score}. And a boolean SilentMode - if true, the scores are silently updated (only available for SuperAdmin), allowing an external controller to do its custom counting... Only available to Admin/SuperAdmin.
    • -
    • ForcePlayerTeam
      -boolean ForcePlayerTeam(string, int)
      -Force the team of the player. Only available in team mode. You have to pass the login and the team number (0 or 1). Only available to Admin.
    • -
    • ForcePlayerTeamId
      -boolean ForcePlayerTeamId(int, int)
      -Force the team of the player. Only available in team mode. You have to pass the playerid and the team number (0 or 1). Only available to Admin.
    • -
    • ForceSpectator
      -boolean ForceSpectator(string, int)
      -Force the spectating status of the player. You have to pass the login and the spectator mode (0: user selectable, 1: spectator, 2: player). Only available to Admin.
    • -
    • ForceSpectatorId
      -boolean ForceSpectatorId(int, int)
      -Force the spectating status of the player. You have to pass the playerid and the spectator mode (0: user selectable, 1: spectator, 2: player). Only available to Admin.
    • -
    • ForceSpectatorTarget
      -boolean ForceSpectatorTarget(string, string, int)
      -Force spectators to look at a specific player. You have to pass the login of the spectator (or '' for all) and the login of the target (or '' for automatic), and an integer for the camera type to use (-1 = leave unchanged, 0 = replay, 1 = follow, 2 = free). Only available to Admin.
    • -
    • ForceSpectatorTargetId
      -boolean ForceSpectatorTargetId(int, int, int)
      -Force spectators to look at a specific player. You have to pass the id of the spectator (or -1 for all) and the id of the target (or -1 for automatic), and an integer for the camera type to use (-1 = leave unchanged, 0 = replay, 1 = follow, 2 = free). Only available to Admin.
    • -
    • SpectatorReleasePlayerSlot
      -boolean SpectatorReleasePlayerSlot(string)
      -Pass the login of the spectator. A spectator that once was a player keeps his player slot, so that he can go back to race mode. Calling this function frees this slot for another player to connect. Only available to Admin.
    • -
    • SpectatorReleasePlayerSlotId
      -boolean SpectatorReleasePlayerSlotId(int)
      -Pass the playerid of the spectator. A spectator that once was a player keeps his player slot, so that he can go back to race mode. Calling this function frees this slot for another player to connect. Only available to Admin.
    • -
    • ManualFlowControlEnable
      -boolean ManualFlowControlEnable(boolean)
      -Enable control of the game flow: the game will wait for the caller to validate state transitions. Only available to Admin.
    • -
    • ManualFlowControlProceed
      -boolean ManualFlowControlProceed()
      -Allows the game to proceed. Only available to Admin.
    • -
    • ManualFlowControlIsEnabled
      -int ManualFlowControlIsEnabled()
      -Returns whether the manual control of the game flow is enabled. 0 = no, 1 = yes by the xml-rpc client making the call, 2 = yes, by some other xml-rpc client. Only available to Admin.
    • -
    • ManualFlowControlGetCurTransition
      -string ManualFlowControlGetCurTransition()
      -Returns the transition that is currently blocked, or '' if none. (That's exactly the value last received by the callback.) Only available to Admin.
    • -
    • CheckEndMatchCondition
      -string CheckEndMatchCondition()
      -Returns the current match ending condition. Return values are: 'Playing', 'ChangeMap' or 'Finished'.
    • -
    • GetNetworkStats
      -struct GetNetworkStats()
      -Returns a struct containing the networks stats of the server. The structure contains the following fields : Uptime, NbrConnection, MeanConnectionTime, MeanNbrPlayer, RecvNetRate, SendNetRate, TotalReceivingSize, TotalSendingSize and an array of structures named PlayerNetInfos. Each structure of the array PlayerNetInfos contains the following fields : Login, IPAddress, LastTransferTime, DeltaBetweenTwoLastNetState, PacketLossRate. Only available to SuperAdmin.
    • -
    • StartServerLan
      -boolean StartServerLan()
      -Start a server on lan, using the current configuration. Only available to SuperAdmin.
    • -
    • StartServerInternet
      -boolean StartServerInternet(struct)
      -Start a server on internet using the 'Login' and 'Password' specified in the struct passed as parameters. Only available to SuperAdmin.
    • -
    • GetStatus
      -struct GetStatus()
      -Returns the current status of the server.
    • -
    • QuitGame
      -boolean QuitGame()
      -Quit the application. Only available to SuperAdmin.
    • -
    • GameDataDirectory
      -string GameDataDirectory()
      -Returns the path of the game datas directory. Only available to Admin.
    • -
    • GetTracksDirectory
      -string GetTracksDirectory()
      -Returns the path of the tracks directory. Only available to Admin.
    • -
    • GetSkinsDirectory
      -string GetSkinsDirectory()
      -Returns the path of the skins directory. Only available to Admin.
    • -
    - - diff --git a/xaseco/DOCS/ListMethodsNations.html b/xaseco/DOCS/ListMethodsNations.html deleted file mode 100644 index eb95e8d..0000000 --- a/xaseco/DOCS/ListMethodsNations.html +++ /dev/null @@ -1,432 +0,0 @@ - - - - -TrackMania Nations methods - - -

    Available methods:

    -
      -
    • system.listMethods
      -array system.listMethods()
      -Return an array of all available XML-RPC methods on this server.
    • -
    • system.methodSignature
      -array system.methodSignature(string)
      -Given the name of a method, return an array of legal signatures. Each signature is an array of strings. The first item of each signature is the return type, and any others items are parameter types.
    • -
    • system.methodHelp
      -string system.methodHelp(string)
      -Given the name of a method, return a help string.
    • -
    • system.multicall
      -array system.multicall(array)
      -Process an array of calls, and return an array of results. Calls should be structs of the form {'methodName': string, 'params': array}. Each result will either be a single-item array containg the result value, or a struct of the form {'faultCode': int, 'faultString': string}. This is useful when you need to make lots of small calls without lots of round trips.
    • -
    • Authenticate
      -boolean Authenticate(string, string)
      -Allow user authentication by specifying a login and a password, to gain access to the set of functionalities corresponding to this authorization level
    • -
    • ChangeAuthPassword
      -boolean ChangeAuthPassword(string, string)
      -Change the password for the specified login/user. Only available to SuperAdmin.
    • -
    • EnableCallbacks
      -boolean EnableCallbacks(boolean)
      -Allow the GameServer to call you back.
    • -
    • GetVersion
      -struct GetVersion()
      -Returns a struct with the Name, Version and Build of the application remotely controled.
    • -
    • CallVote
      -boolean CallVote(string)
      -Call vote for cmd. The command is a XML string corresponding to an XmlRpc request. Only available to Admin.
    • -
    • InternalCallVote
      -boolean InternalCallVote()
      -Used internally by game.
    • -
    • CancelVote
      -boolean CancelVote()
      -Cancel the current vote. Only available to Admin.
    • -
    • AddChatCommand
      -boolean AddChatCommand(string)
      -Add a new command 'cmd' for the chat, i.e. a chat message that begins with '/cmd ' will not be transmitted to clients. Only available to Admin.
    • -
    • RemoveChatCommand
      -boolean RemoveChatCommand(string)
      -Remove a chat command. Only available to Admin.
    • -
    • CleanChatCommand
      -boolean CleanChatCommand()
      -Remove all chat commands. Only available to Admin.
    • -
    • GetChatCommandList
      -array GetChatCommandList(int, int)
      -Returns the list of chat commands. This method takes two parameters. The first parameter specifies the maximum number of infos to be returned, and the second one the starting index in the list. The list is an array of structures. Each structure contains the following fields : Name.
    • -
    • ChatSend
      -boolean ChatSend(string)
      -Send a text message to all clients. Only available to Admin.
    • -
    • ChatSendServerMessage
      -boolean ChatSendServerMessage(string)
      -Send a text message to all clients without the server login. Only available to Admin.
    • -
    • ChatSendServerMessageToId
      -boolean ChatSendServerMessageToId(string, int)
      -Send a text message to the client with the specified PlayerId without the server login. Only available to Admin.
    • -
    • ChatSendServerMessageToLogin
      -boolean ChatSendServerMessageToLogin(string, string)
      -Send a text message to the client with the specified login without the server login. Only available to Admin.
    • -
    • ChatSendToLogin
      -boolean ChatSendToLogin(string, string)
      -Send a text message to the client with the specified login. Only available to Admin.
    • -
    • ChatSendToId
      -boolean ChatSendToId(string, int)
      -Send a text message to the client with the specified PlayerId. Only available to Admin.
    • -
    • GetChatLines
      -array GetChatLines()
      -Returns the last chat lines. Maximum of 20 lines. Only available to Admin.
    • -
    • SendDisplayServerMessage
      -boolean SendDisplayServerMessage(string, string, string, int)
      -Display a text message on all clients. The parameters are the text message to display, the label of the first button, the label of the second button, a timeout for the message box. This message is displayed in a message box with one or two buttons, if the label are not empty. The message box will disappear after the timeout if it's non-zero. Only available to Admin.
    • -
    • SendDisplayServerMessageToId
      -boolean SendDisplayServerMessageToId(int, string, string, string, int)
      -Display a text message on the client with the specified UId. The parameters are the UId of the player, the text message to display, the label of the first button, the label of the second button, and a timeout for the message box. This message is displayed in a message box with one or two buttons, if the corresponding label are not empty. The message box will disappear after the timeout if it's non-zero. Only available to Admin.
    • -
    • SendDisplayServerMessageToLogin
      -boolean SendDisplayServerMessageToLogin(string, string, string, string, int)
      -Display a text message on the client with the specified login. The parameters are the login of the player, the text message to display, the label of the first button, the label of the second button, and a timeout for the message box. This message is displayed in a message box with one or two buttons, if the corresponding label are not empty. The message box will disappear after the timeout if it's non-zero. Only available to Admin.
    • -
    • SendHideServerMessage
      -boolean SendHideServerMessage()
      -Hide the displayed message on all clients. Only available to Admin.
    • -
    • SendHideServerMessageToId
      -boolean SendHideServerMessageToId(int)
      -Hide the displayed message on the client with the specified UId. Only available to Admin.
    • -
    • SendHideServerMessageToLogin
      -boolean SendHideServerMessageToLogin(string)
      -Hide the displayed message on the client with the specified login. Only available to Admin.
    • -
    • GetServerMessageResults - CRASHES SERVER WHEN CALLED FROM COMMAND LINE
      -array GetServerMessageResults()
      -Returns the results of the message boxes, as an array of structs {login, playerid, result} result==0 -> no answer, result==1 or 2 -> answer from the player. Only available to Admin.
    • -
    • Kick
      -boolean Kick(string)
      -Kick the player with the specified login. Only available to Admin.
    • -
    • KickId
      -boolean KickId(int)
      -Kick the player with the specified PlayerId. Only available to Admin.
    • -
    • Ban
      -boolean Ban(string)
      -Ban the player with the specified login. Only available to Admin.
    • -
    • BanId
      -boolean BanId(int)
      -Ban the player with the specified PlayerId. Only available to Admin.
    • -
    • UnBan
      -boolean UnBan(string)
      -Unban the player with the specified login. Only available to Admin.
    • -
    • CleanBanList
      -boolean CleanBanList()
      -Clean the ban list of the server. Only available to Admin.
    • -
    • GetBanList
      -array GetBanList(int, int)
      -Returns the list of players banned. This method takes two parameters. The first parameter specifies the maximum number of infos to be returned, and the second one the starting index in the list. The list is an array of structures. Each structure contains the following fields : Login, ClientName and IPAddress.
    • -
    • BlackList
      -boolean BlackList(string)
      -Blacklist the player with the specified login. Only available to SuperAdmin.
    • -
    • UnBlackList
      -boolean UnBlackList(string)
      -UnBlackList the player with the specified login. Only available to SuperAdmin.
    • -
    • CleanBlackList
      -boolean CleanBlackList()
      -Clean the blacklist of the server. Only available to SuperAdmin.
    • -
    • GetBlackList
      -array GetBlackList(int, int)
      -Returns the list of players blacklisted. This method takes two parameters. The first parameter specifies the maximum number of infos to be returned, and the second one the starting index in the list. The list is an array of structures. Each structure contains the following fields : Login.
    • -
    • AddGuest
      -boolean AddGuest(string)
      -Add the player with the specified login on the guest list. Only available to Admin.
    • -
    • RemoveGuest
      -boolean RemoveGuest(string)
      -Remove the player with the specified login from the guest list. Only available to Admin.
    • -
    • CleanGuestList
      -boolean CleanGuestList()
      -Clean the guest list of the server. Only available to Admin.
    • -
    • GetGuestList
      -array GetGuestList(int, int)
      -Returns the list of players on the guest list. This method takes two parameters. The first parameter specifies the maximum number of infos to be returned, and the second one the starting index in the list. The list is an array of structures. Each structure contains the following fields : Login.
    • -
    • WriteFile
      -boolean WriteFile(string, base64)
      -Write the data to the specified file. The filename is relative to the Tracks path. Only available to Admin.
    • -
    • SetServerName
      -boolean SetServerName(string)
      -Set a new server name in utf8 format. Only available to Admin. Max. length: 26 chars (incl. colors & tags)
    • -
    • GetServerName
      -string GetServerName()
      -Get the server name in utf8 format.
    • -
    • SetServerComment
      -boolean SetServerComment(string)
      -Set a new server comment in utf8 format. Only available to Admin.
    • -
    • GetServerComment
      -string GetServerComment()
      -Get the server comment in utf8 format.
    • -
    • SetServerPassword
      -boolean SetServerPassword(string)
      -Set a new password for the server. Only available to Admin.
    • -
    • GetServerPassword
      -string GetServerPassword()
      -Get the server password if called as Admin or Super Admin, else returns if a password is needed or not.
    • -
    • SetServerPasswordForSpectator
      -boolean SetServerPasswordForSpectator(string)
      -Set a new password for the spectator mode. Only available to Admin.
    • -
    • GetServerPasswordForSpectator
      -string GetServerPasswordForSpectator()
      -Get the password for spectator mode if called as Admin or Super Admin, else returns if a password is needed or not.
    • -
    • SetMaxPlayers
      -boolean SetMaxPlayers(int)
      -Set a new maximum number of players. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetMaxPlayers
      -struct GetMaxPlayers()
      -Get the current and next maximum number of players allowed on server. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetMaxSpectators
      -boolean SetMaxSpectators(int)
      -Set a new maximum number of Spectators. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetMaxSpectators
      -struct GetMaxSpectators()
      -Get the current and next maximum number of Spectators allowed on server. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • EnableP2PUpload
      -boolean EnableP2PUpload(boolean)
      -Enable or disable peer-to-peer upload from server. Only available to Admin.
    • -
    • IsP2PUpload
      -boolean IsP2PUpload()
      -Returns if the peer-to-peer upload from server is enabled.
    • -
    • EnableP2PDownload
      -boolean EnableP2PDownload(boolean)
      -Enable or disable peer-to-peer download for server. Only available to Admin.
    • -
    • IsP2PDownload
      -boolean IsP2PDownload()
      -Returns if the peer-to-peer download for server is enabled.
    • -
    • AllowChallengeDownload
      -boolean AllowChallengeDownload(boolean)
      -Allow clients to download challenges from the server. Only available to Admin.
    • -
    • IsChallengeDownloadAllowed
      -boolean IsChallengeDownloadAllowed()
      -Returns if clients can download challenges from the server.
    • -
    • SetLadderMode
      -boolean SetLadderMode(int)
      -Set a new ladder mode between ladder inactive (0), normal mode (1), and forced ladder (2). Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetLadderMode
      -struct GetLadderMode()
      -Get the current and next ladder mode on server. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetVehicleNetQuality
      -boolean SetVehicleNetQuality(int)
      -Set the network vehicle quality to Fast (0) or High (1). Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetVehicleNetQuality
      -struct GetVehicleNetQuality()
      -Get the current and next network vehicle quality on server. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetCallVoteTimeOut
      -boolean SetCallVoteTimeOut(int)
      -Set a new timeout for waiting for votes. A zero value disables callvote. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetCallVoteTimeOut
      -struct GetCallVoteTimeOut()
      -Get the current and next timeout for waiting for votes. The struct returned contains two fields 'CurrentValue' and 'NextValue'.
    • -
    • SetCallVoteRatio
      -boolean SetCallVoteRatio(double)
      -Set a new ratio for accepting callvote. Must lie between 0 and 1. Only available to Admin.
    • -
    • GetCallVoteRatio
      -double GetCallVoteRatio()
      -Get the current ratio for accepting callvote. This value lies between 0 and 1.
    • -
    • SetServerOptions
      -boolean SetServerOptions(struct)
      -Set new server options using the struct passed as parameters. This struct must contain the following fields : Name, Comment, Password, PasswordForSpectator, NextMaxPlayers, NextMaxSpectators, IsP2PUpload, IsP2PDownload, NextLadderMode, NextVehicleNetQuality, NextCallVoteTimeOut, CallVoteRatio and AllowChallengeDownload. Only available to Admin. A change of NextMaxPlayers, NextMaxSpectators, NextLadderMode, NextVehicleNetQuality or NextCallVoteTimeOut requires a challenge restart to be taken into account.
    • -
    • GetServerOptions
      -struct GetServerOptions()
      -Returns a struct containing the server options i.e. Name, Comment, Password, PasswordForSpectator, CurrentMaxPlayers, NextMaxPlayers, CurrentMaxSpectators, NextMaxSpectators, IsP2PUpload, IsP2PDownload, CurrentLadderMode, NextLadderMode, CurrentVehicleNetQuality, NextVehicleNetQuality, CurrentCallVoteTimeOut, NextCallVoteTimeOut, CallVoteRatio and AllowChallengeDownload.
    • -
    • LoadBlackList
      -boolean LoadBlackList(string)
      -Load the black list file with the specified file name. Only available to SuperAdmin.
    • -
    • SaveBlackList
      -boolean SaveBlackList(string)
      -Save the black list in the file with specified file name. Only available to SuperAdmin.
    • -
    • LoadGuestList
      -boolean LoadGuestList(string)
      -Load the guest list file with the specified file name. Only available to Admin.
    • -
    • SaveGuestList
      -boolean SaveGuestList(string)
      -Save the guest list in the file with specified file name. Only available to SuperAdmin.
    • -
    • GetLastConnectionErrorMessage
      -string GetLastConnectionErrorMessage()
      -Returns the last error message for an internet connection. Only available to SuperAdmin.
    • -
    • ChallengeRestart
      -boolean ChallengeRestart()
      -Restarts the challenge. Only available to Admin.
    • -
    • NextChallenge
      -boolean NextChallenge()
      -Switch to next challenge. Only available to Admin.
    • -
    • StopServer
      -boolean StopServer()
      -Stop the server. Only available to SuperAdmin.
    • -
    • ForceEndRound
      -boolean ForceEndRound()
      -In Rounds or Laps mode, force the end of round without waiting for all players to giveup/finish. Only available to Admin.
    • -
    • SetGameInfos
      -boolean SetGameInfos(struct)
      -Set new game settings using the struct passed as parameters. This struct must contain the following fields : GameMode, RoundsPointsLimit, RoundsUseNewRules, TimeAttackLimit, TimeAttackSynchStartPeriod, TeamPointsLimit, TeamMaxPoints, TeamUseNewRules, LapsNbLaps, LapsTimeLimit and ChatTime. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetCurrentGameInfo
      -struct GetCurrentGameInfo()
      -Returns a struct containing the current game settings i.e. GameMode, RoundsPointsLimit, RoundsUseNewRules, TimeAttackLimit, TimeAttackSynchStartPeriod, TeamPointsLimit, TeamMaxPoints, TeamUseNewRules, LapsNbLaps, LapsTimeLimit, ChatTime and NbChallenge.
    • -
    • GetNextGameInfo
      -struct GetNextGameInfo()
      -Returns a struct containing the game settings for the next challenge i.e. GameMode, RoundsPointsLimit, RoundsUseNewRules, TimeAttackLimit, TimeAttackSynchStartPeriod, TeamPointsLimit, TeamMaxPoints, TeamUseNewRules, LapsNbLaps, LapsTimeLimit, ChatTime and NbChallenge.
    • -
    • GetGameInfos
      -struct GetGameInfos()
      -Returns a struct containing two other structures, the first containing the current game settings and the second the game settings for next challenge. The first structure is named CurrentGameInfos and the second NextGameInfos.
    • -
    • SetChatTime
      -boolean SetChatTime(int)
      -Set a new chat time value in milliseconds. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetChatTime
      -struct GetChatTime()
      -Get the current and next chat time. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetFinishTimeout
      -boolean SetFinishTimeout(int)
      -Set a new finish timeout (for rounds/laps mode) value in milliseconds. 0 means default. 1 means adaptative to the duration of the challenge. Not available in 'nations'. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetFinishTimeout
      -struct GetFinishTimeout()
      -Get the current and next FinishTimeout. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetGameMode
      -boolean SetGameMode(int)
      -Set a new game mode between Rounds (0), TimeAttack (1), Team (2), Laps (3) and Stunts (4). Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetGameMode
      -int GetGameMode()
      -Get the current game mode.
    • -
    • SetTimeAttackLimit
      -boolean SetTimeAttackLimit(int)
      -Set a new time limit for time attack mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetTimeAttackLimit
      -struct GetTimeAttackLimit()
      -Get the current and next time limit for time attack mode. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetTimeAttackSynchStartPeriod
      -boolean SetTimeAttackSynchStartPeriod(int)
      -Set a new synchronised start period for time attack mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetTimeAttackSynchStartPeriod
      -struct GetTimeAttackSynchStartPeriod()
      -Get the current and synchronised start period for time attack mode. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetLapsTimeLimit
      -boolean SetLapsTimeLimit(int)
      -Set a new time limit for laps mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetLapsTimeLimit
      -struct GetLapsTimeLimit()
      -Get the current and next time limit for laps mode. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetNbLaps
      -boolean SetNbLaps(int)
      -Set a new number of laps for laps mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetNbLaps
      -struct GetNbLaps()
      -Get the current and next number of laps for laps mode. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetRoundPointsLimit
      -boolean SetRoundPointsLimit(int)
      -Set a new points limit for rounds mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetRoundPointsLimit
      -struct GetRoundPointsLimit()
      -Get the current and next points limit for rounds mode. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetUseNewRulesRound
      -boolean SetUseNewRulesRound(int)
      -Set if new rules are used for rounds mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetUseNewRulesRound
      -struct GetUseNewRulesRound()
      -Get if the new rules are used for rounds mode (Current and next values). The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetTeamPointsLimit
      -boolean SetTeamPointsLimit(int)
      -Set a new points limit for team mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetTeamPointsLimit
      -struct GetTeamPointsLimit()
      -Get the current and next points limit for team mode. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetMaxPointsTeam
      -boolean SetMaxPointsTeam(int)
      -Set a new number of maximum points per round for team mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetMaxPointsTeam
      -struct GetMaxPointsTeam()
      -Get the current and next number of maximum points per round for team mode. The struct returned contains two fields CurrentValue and NextValue.
    • -
    • SetUseNewRulesTeam
      -boolean SetUseNewRulesTeam(int)
      -Set if new rules are used for team mode. Only available to Admin. Requires a challenge restart to be taken into account.
    • -
    • GetUseNewRulesTeam
      -struct GetUseNewRulesTeam()
      -Get if the new rules are used for team mode (Current and next values). The struct returned contains two fields CurrentValue and NextValue.
    • -
    • GetCurrentChallengeIndex
      -int GetCurrentChallengeIndex()
      -Returns the current challenge index in the selection.
    • -
    • GetCurrentChallengeInfo
      -struct GetCurrentChallengeInfo()
      -Returns a struct containing the infos for the current challenge. The struct contains the following fields : Name, UId, FileName, Author, Environnement, Mood, BronzeTime, SilverTime, GoldTime, AuthorTime, CopperPrice and LapRace.
    • -
    • GetChallengeInfo
      -struct GetChallengeInfo(string)
      -Returns a struct containing the infos for the challenge with the specified filename. The struct contains the following fields : Name, UId, FileName, Author, Environnement, Mood, BronzeTime, SilverTime, GoldTime, AuthorTime, CopperPrice and LapRace.
    • -
    • GetChallengeList
      -array GetChallengeList(int, int)
      -Returns a list of challenges among the current selection of the server. This method takes two parameters. The first parameter specifies the maximum number of infos to be returned, and the second one the starting index in the selection. The list is an array of structures. Each structure contains the following field : Name, UId, FileName, Environnement, Mood, GoldTime and CopperPrice.
    • -
    • AddChallenge
      -boolean AddChallenge(string)
      -Add the challenge with the specified filename at the end of the current selection. Only available to Admin.
    • -
    • AddChallengeList
      -int AddChallengeList(array)
      -Add the list of challenges with the specified filename at the end of the current selection. Only available to Admin.
    • -
    • RemoveChallenge
      -boolean RemoveChallenge(string)
      -Remove the challenge with the specified filename from the current selection. Only available to Admin.
    • -
    • RemoveChallengeList
      -int RemoveChallengeList(array)
      -Remove the list of challenges with the specified filenames from the current selection. The list of challenges to remove is an array of strings. Only available to Admin.
    • -
    • InsertChallenge
      -boolean InsertChallenge(string)
      -Insert the challenge with the specified filename after the current challenge. Only available to Admin.
    • -
    • InsertChallengeList
      -int InsertChallengeList(array)
      -Insert the list challenges with the specified filename after the current challenge. The list of challenges to insert is an array of strings. Only available to Admin.
    • -
    • ChooseNextChallenge
      -boolean ChooseNextChallenge(string)
      -Set as next challenge the one with the specified filename, if it is present in the selection. Only available to Admin.
    • -
    • ChooseNextChallengeList
      -int ChooseNextChallengeList(array)
      -Set as next challenge the list of challenges with the specified filenames, if they are present in the selection. Only available to Admin.
    • -
    • LoadMatchSettings
      -int LoadMatchSettings(string)
      -Set a list of challenges defined in the playlist with the specified filename as the current selection of the server, and load the gameinfos from the same file. Only available to Admin.
    • -
    • AppendPlaylistFromMatchSettings
      -int AppendPlaylistFromMatchSettings(string)
      -Add a list of challenges defined in the playlist with the specified filename at the end of the current selection. Only available to Admin.
    • -
    • SaveMatchSettings
      -int SaveMatchSettings(string)
      -Save the current selection of challenges in the playlist with the specified filename, as well as the current gameinfos. Only available to Admin.
    • -
    • InsertPlaylistFromMatchSettings
      -int InsertPlaylistFromMatchSettings(string)
      -Insert a list of challenges defined in the playlist with the specified filename after the current challenge. Only available to Admin.
    • -
    • GetPlayerList
      -array GetPlayerList(int, int)
      -Returns the list of players on the server. This method takes two parameters. The first parameter specifies the maximum number of infos to be returned, and the second one the starting index in the list. The list is an array of structures. Each structure contains the following field : Login, NickName, PlayerId, TeamId, IsSpectator, IsInOfficialMode and LadderRanking.
    • -
    • GetPlayerInfo
      -struct GetPlayerInfo(string)
      -Returns a struct containing the infos on the player with the specified login. The structure contains the following fields : Login, NickName, IPAddress, ConnectionType, PlayerId, IsSpectator, IsInOfficialMode, HasJoinMatch, a structure named Avatar, an array of structures named Skins and a structure named LadderStats. The structure Avatar and each structure of the array Skins contains two fields FileName and Checksum. The structure LadderStats contains the following fields : Ranking, Score, LastMatchScore, NbrMatchWins, NbrMatchDraws, NbrMatchLosses, NbrTotalPlayers, TeamName, TeamRanking, NbrTotalTeams.
    • -
    • GetCurrentRanking
      -array GetCurrentRanking(int, int)
      -Returns the current ranking for the race in progress. This method takes two parameters. The first parameter specifies the maximum number of infos to be returned, and the second one the starting index in the ranking. The ranking returned is a list of structures. Each structure contains the following fields : Login, NickName, PlayerId, Rank, BestTime, Score, NbrLapsFinished and LadderScore. It also contains an array BestCheckpoints that contains the checkpoints times for the best race.
    • -
    • ForceScores
      -boolean ForceScores(array)
      -Force the scores of the current game. Only available in rounds and team mode. You have to pass an array of structs with two fields: PlayerId and Score. Only available to Admin.
    • -
    • GetNetworkStats
      -struct GetNetworkStats()
      -Returns a struct containing the networks stats of the server. The structure contains the following fields : Uptime, NbrConnection, MeanConnectionTime, MeanNbrPlayer, RecvNetRate, SendNetRate, TotalReceivingSize, TotalSendingSize and an array of structures named PlayerNetInfos. Each structure of the array PlayerNetInfos contains the following fields : Login, IPAddress, LastTransferTime, DeltaBetweenTwoLastNetState, PacketLossRate. Only available to SuperAdmin.
    • -
    • GetValidationReplay
      -base64 GetValidationReplay(string)
      -Returns a replay containing the data needed to validate the current best time of the player.
    • -
    • StartServerLan
      -boolean StartServerLan()
      -Start a server on lan, using the current configuration. Only available to SuperAdmin.
    • -
    • StartServerInternet
      -boolean StartServerInternet(struct)
      -Start a server on internet using the 'Login' and 'Password' specified in the struct passed as parameters. Only available to SuperAdmin.
    • -
    • GetStatus
      -struct GetStatus()
      -Returns the current status of the server.
    • -
    • QuitGame
      -boolean QuitGame()
      -Quit the application. Only available to SuperAdmin.
    • -
    • GetBaseDirectory
      -string GetBaseDirectory()
      -Returns the path of the dedicated server application. Only available to Admin.
    • -
    • GameDataDirectory
      -string GameDataDirectory()
      -Returns the path of the game datas directory. Only available to Admin.
    • -
    • GetTracksDirectory
      -string GetTracksDirectory()
      -Returns the path of the tracks directory. Only available to Admin.
    • -
    • GetSkinsDirectory
      -string GetSkinsDirectory()
      -Returns the path of the skins directory. Only available to Admin.
    • -
    - - diff --git a/xaseco/DOCS/OLD/Jfreu install.txt b/xaseco/DOCS/OLD/Jfreu install.txt deleted file mode 100644 index 677cf5a..0000000 --- a/xaseco/DOCS/OLD/Jfreu install.txt +++ /dev/null @@ -1,24 +0,0 @@ -0/ Save "aseco/plugins.xml" somewhere - -1/ Copy "jfreu.config.php", "jfreu.plugin.php" , "chat.jfreu.php" - the "jfreu" directory in your aseco plugins directory (aseco/plugins/) - -2/ Add "jfreu.plugin.php" before "" - in "plugins.xml" in your aseco directory (aseco/plugins.xml) - -3/ Edit "jfreu.config.php" in your aseco plugins directory (aseco/plugins/) - change the server name, ranklimit,...(customise the plugin ^^) - -4/ Edit "jfreu/jfreu.lists.xml" and add VIP, team_vip & admins - -5/ Run Aseco and enjoy. ("/help" & "/jfreu help" for new commands avilable) - New admin commands are : "/jfreu [command]" (ex: "/jfreu novote ON") - - -Any problem or suggestion -> http://jfreu.servegame.com (forum) - - -?/ To uninstall the plugin delete "jfreu.config.php", "jfreu.plugin.php", - "chat.jfreu.php" and the "jfreu" directory in your aseco plugins directory - (aseco/plugins/) and overwrite modified file with your saved one - ("aseco/plugins.xml") or remove the "jfreu.plugin.php" line. diff --git a/xaseco/DOCS/OLD/Jfreu's plugin.txt b/xaseco/DOCS/OLD/Jfreu's plugin.txt deleted file mode 100644 index 2326af8..0000000 --- a/xaseco/DOCS/OLD/Jfreu's plugin.txt +++ /dev/null @@ -1,121 +0,0 @@ -~~[ v0.13d ]~~ - -- Add : "/jfreu banfor X login" to ban a player for X min -- Fix : banFor X min ban for X min (not for X min - 30 sec) -- Fix : bug with non-included chat & settings files -- Upg : unspec command cancel the player's vote to unspec him. -- Upg : "unspec vote" ban the player for 5 min if vote result is NO. -- Upg : badwordsban ban the player for 10 min. -- Upg : in "unspec vote" +50% yes -> yes | +49% no -> no. - -~~[ v0.13c ]~~ - -- Fix : "/jfreu kickHiRank ON/OFF" fixed : kick worst ranked players - when the server is "full". -- Fix : "/jfreu kickWorst X" fixed : kick X wost players. - -~~[ v0.13b ]~~ - -- Add : the word between the server's name and the limit - (usually "Top") can be changed. -- Add : autoChangeName can be turned OFF - (server's name when the limit changes) -- Add : TEAM_VIP all team members are vip. -- Add : "/uptodate" command to know if the version of the - plugin is up to date. -- Add : NewPlayer message when a new player joins and ranklimit - is OFF. -- Add : "/jfreu removevip" & "/jfreu removeteamvip" to remove - a vip & a team_vip ingame (by an admin). -- Upg : Admins, VIP and TEAM_VIP added ingame are saved in a - xml file ("jfreu/jfreu.lists.xlm") -- Upg : All the plugin's admin commands are "jfreu commands" now, - to avoid the bug with "/admin help", now it is "/jfreu help" - the plugin's admin commands are "/jfreu [command]" -- Upg : Cancel custom vote ('unspec') when the player disconects. - -~~[ v0.13a ]~~ - -- Add : Admin command "addadmin {login}" to add an admin. -- Add : Admin private message to login : - /admin messagetologin {login} {message} -- Add : Hardlimit : players, VIP, specs over the hardlimit - are kicked (without message in the chat) -- Add : Admins can change the hardlimit ingame. -- Add : SpecOnly command unspec to launch an unspec vote. -- Add : If a player leaves the game his vote is canceled. -- Fix : novote can be turned OFF. -- Fix : fix bug in "clean_nick" function. -- Fix : No more timeOut crash -- Fix : PM fixed (no more server's login at the begining) -- Upg : badWord bot more powerfull. - ( "$zS$wHh$00FIII$wiiI$nT" is a badword ) -- Upg : Colors in the plugin are aseco's colors. -- Upg : Ranklimit & Info commands send a PM to the player. -- Upg : Unspec and VIP not in server average if autorankVIP ON. - - -~~[ v0.12d ]~~ - -- Add : Admin command "unspec" to allow a "SpecOnly" to join - the race. -- Add : custom maxBadWords -- Add : BadWordsBan (ON/OFF) ban when 2x maxBadWords -- Add : Admin command KickWorst to kick worst ranked players - except vip. -- Add : admin cancelvote to cancel current vote - (kick/ban/nextmap/restartmap) -- Add : novote (ON/OFF) to autocancel votes -- Upg : kickhirank function use KickWorst function. - -~~[ v0.12c ]~~ - -- Add : SpecOnly are kicked if they join the race -- Fix : bug with "admin help" -- Fix : bug with "autorankvip" -- Fix : bug with "autorankvip" default state - -~~[ v0.12b ]~~ - -- Add : "BadWords" -> 3 "badwords" = kicked (on/off) -- Add : random information message at the end of the race - (editable) (on/off) -- Add : players over ranklimit can join the game as spec -- Add : kick hiRank players if server is "full" - (custom maxplayers) (on/off) -- Fix : problem with "chat.admin" overwritten - -~~[ v0.11a ]~~ - -- Add : autoRankMinPlayer : autorank disabled when not enough - players then limit = Ranklimit. - autoRankMinPlayer value can be changed ingame (by admins) - Autorank limit = Ranklimit when not enough players -- Add : admin can add VIP ingame (deleted when aseco restarts) -- Add : admin can force Autorank limit (until a new player comes) - -~~[ v0.10b ]~~ - -- Add : admin command : /admin player {login} {message} -- Fix : bug with AutoRank Offset. -- Fix : bug timeOut crash. - -~~[ v0.09e ]~~ - -- Add : RankLimit can be turned on/off ingame (by an admin) -- Add : AutoRank offset value can be changed ingame (by an admin) -- Add : AutoRankLimit is changed when a player left the game - -~~[ v0.08 ]~~ - -- Rank limit (new player with a rank over the limit is kicked) -- AutoRankLimit (DynaRank) : server's rank + offset = ranklimit -- Change the server's name when the limit is changed : - [ServerName]Top[Limit] -- Server's messages in chat : "[ServerName] your message" -- fake rec : ">> XXX took the 1. rank with a time of... ! -- informations on account : IP/port/login/nick/score/rank -- AutoRankLimit can be turned on/off ingame (by an admin) -- Rank limit value can be changed ingame (by an admin) -- ... - diff --git a/xaseco/DOCS/OLD/README-autotime.txt b/xaseco/DOCS/OLD/README-autotime.txt deleted file mode 100644 index a9168b4..0000000 --- a/xaseco/DOCS/OLD/README-autotime.txt +++ /dev/null @@ -1,23 +0,0 @@ -//////////////////////////////////// -//Plugin Auto TimeLimit -//Changes Timelimit for TimeAttack dynamically depending on the next -// track's author time -// -//Written by ck|cyrus -//martin@die-webber.com -//www.chaoskrieger.com - -//////////////// -// Installation - -- Copy file "plugin.autotime.php" into your XAseco plugin directory -- Copy file "autotime.xml" into your XAseco main directory -- Add plugin.autotime.php at the END of your "plugins.xml" -- Open file "autotime.xml" and edit the variables to your liking -- Have fun and enjoy playing with nice timings! - -//////////////// -// SUPPORT: - -Visit: http://www.tm-forum.com/viewtopic.php?t=6563 -ICQ: 85020221 diff --git a/xaseco/DOCS/OLD/README.txt b/xaseco/DOCS/OLD/README.txt deleted file mode 100644 index ccb5934..0000000 --- a/xaseco/DOCS/OLD/README.txt +++ /dev/null @@ -1,144 +0,0 @@ -BEFORE ATTEMPTING TO INSTALL RASP, YOU MUST HAVE A FULLY WORKING VERSION OF ASECO 0.6.1b RUNNING ON A LOCAL DATABASE - - -Introduction ------------- -Rasp is a plugin pack for the aseco server script. The core rasp plugin is the ranks and stats system which heavily relies on aseco's records system. The other plugins are extra features which you can add/remove as you please and are completely independent of the core plugin. - -Features --------- -Here is a list of the features: - -- In-built rank system which assigns a rank based on all records a player has made. -- Stats calculation which shows a player their personal best and average time on a track. -- Jukebox system which allows a player to select their favorite tracks to be played. -- Direct adding of tracks from http://nations.tm-exchange.com (Nations only) -- Karma system which works as a simple but effective replacement of the voting system. -- Built in IRC bot which links your server to a channel on an IRC server. -- /nextmap command which shows the track that will be played next on the server. -- /hi, /bye, /gg, /lol commands from FAST. -- /msg command which allows players to communicate privately via private message. -- Team match scoring & output -- New admin commands - - -Minimum Requirements --------------------- --Php5 or above (haven't tested with php4 but feel free to try) --MySQL 5.1 or above --Stable installation of Aseco 0.6.1b - - -Installation ------------- - ---CORE PLUGIN-- - -Before installing the core plugin you MUST ensure Aseco 0.6.1b is running stable on your server. Resolve any issues with aseco before attempting to install rasp. - -1. BACK UP your working ASECO files!!!!!!!!! All of them, including files in subdirectories!!!!! - -2. Un-rar/un-zip the rasp files and upload the following to the appropriate directory on your server. All files must be within the aseco root folder and in their correct directories. - - all but five of the files are in their correct directories in the current zip file; use winzip, or similar, and extract with folder/subdirectory names to the main aseco folder - - overwrite existing files with all of the ones that are in the zip file - - newinstall/rasp.settings.php has default values in it for a new install. Move it to the includes folder only if this is a brand new install of RASP and you don't yet have an includes/rasp.settings.php file. - - newinstall/*.xml goes in the main ASECO directory. If you are already running ASECO/RASP, you can leave these alone. - -3. Run the rasp.sql file on your aseco database, either with PhpMyAdmin or with console access to mysql. - -4, 5, & 6 apply to new installs only. Existing installs can skip these steps. - -4. Edit includes/rasp.settings.php as required. - -5. Edit plugins.xml to display the line plugin.rasp.php or copy the one out of newinstall and edit as needed. - -6. Edit rasp.xml, localdatabase.xml, publicdatabase.xml in the main ASECO directory as needed. - -7. If you had an existing RASP installation (prior to 1.1), open newinstall/localdatabase.xml and copy over the section to your existing localdatabase.xml. Do the same for publicdatabase.xml. - -8. If you had an existing RASP installation prior to 1.1, and are going to use the matchsave feature, edit matchsave.xml and make appropriate changes as necessary. If you don't want a continuous output file (append, instead of overwrite), you should be able to use nul: for Windows, and /dev/null for linux. If you don't want matchsave, don't add it to plugins.xml. - -9. Restart Aseco! - -Any problems go to http://www.tm-forum.com/viewtopic.php?t=3356 - - ---ADDITIONAL PLUGINS-- - -The extra plugins can be used independently or alongside the core plugin. If -used independently you must ensure rasp.settings.php has been copied over to -the includes directory. - -1. Place the plugin file inside the plugins folder for your aseco installation. - -2. Insert the line plugin.rasp_*plugin-name*.php in plugins.xml - -3. Modify includes/rasp.settings.php as required. - -3. Restart aseco. - - -plugin.rasp_jukebox.php ------------------------ -This plugin adds the jukebox function to your server, as well as the jukebox extension for requesting tracks directly from TM-Exchange. There are several variables inside rasp.settings.php which correspond to this plugin. - -To add a track to the jukebox type: -/jukebox - -To check what a track's ID is use /list. If you do /list xxx, you'll get a list of all tracks with "xxx" in their name, or author's name. - -To find out the best & worst rated tracks, use /list karma +# or -#. You'll get back a list of all tracks with # or higher value for +, and all tracks with # or lower for -. - -If an admin wants to clear the jukebox use /admin clearjukebox (requires the rasp version of chat.admin.php) - -To add a track from TM-Exchange use: -/add -The tmx_id is the number on the end of the external link which is at the top of a track's page in TMX. -Using the /add command successfuly will start a vote. You can specify the pass ratio in rasp.settings.php -If a player wants to vote for the track they must use /y. -If the vote is successful the track will be added to the jukebox. Once the track has been played it is removed from the map rotation. To add a track permenantly use /admin add (requires the rasp version of chat.admin.php) - - -plugin.rasp_chat.php --------------------- -This plugin adds various chat commands to your server which were originally seen in FAST. These commands include /hi, /bye, /gg, /lol, /lool, /msg - -To use the hi/bye/gg commands you must use the following syntax (with /hi used as an example) - -/hi --displays the message "Hello all !" -/hi player_name --displays "Hello player_name !" - -The /msg command is used for sending private messages. -/msg player_login *some text* --will send *some text* to the player whose login name is player_login. - -/lol & /lool don't require any arguments. - -plugin.rasp_karma.php ---------------------- -This plugin adds a replacement to the built-in voting system for aseco. It is much easier to use and seems to get more usage than the original /vote command. Players type "/++" if they like a track and "/--" if they don't. The /karma command shows the track's current karma. - -plugin.rasp_irc.php -------------------- -If this plugin is added it will link your server to an IRC channel. You MUST specify the IRC variables in rasp.settings.php before adding this plugin. If successful you will see a bot in your chosen channel which will relay all chat from your server. It also parses the messages typed by other users in the channel and relays them to the TM-server. It is recommended you use a seperate channel for this bot, as putting it in an existing channel will make it virtually un-usable for anything else if you have a busy server. - -plugin.rasp_nextmap.php ------------------------ -This plugin is fairly simple. It adds one command to your server, /nextmap which shows the map to be played next on the server. - ---------------------------------------------- -Released under the GNU General Public License -Copyright (C) 2006 Iain Surgey - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/xaseco/DOCS/OLD/ReadMe.pdf b/xaseco/DOCS/OLD/ReadMe.pdf deleted file mode 100644 index 008f8a2d952100d80646418ef2745e58e8660fc4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 215757 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

    vTV&SWSTm_!i~g{qqyoD8>$ir+ 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%0AMqyMD?G!i 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%0DVn6!%s546S*UYD4~31s zBF+5z2$OuxP<(vB_}qiEz;t};V1D{XH?K&&4u(LzC=fJ!IVoelEinHonDs(!dzXT$ zsMMuo?+2ib77Ne(?00}$fc((7i8Jtxx4qycLR8t|&A1ARRNduSYnmqtpo>ptRqSzg zu@GE~*HQ;6Hwz^o>g#{ovIntw0nrxnrrLrPu!G2F0+meqDlQQm`kt9}XM??e7sBsm zts4l{^hKRfFfy0*MXw-Nc4OGE>JF%%&FQCFwo$>4{Q(c1jqKMbIt}_jgIL6 zdn2$CMZ!E(t2yXog>IHItqjEoiaG%*D4tp1c1SgRFYQm_Ydbz}wBZAPL{0y6Rlnp^ zpA>X_6gBf*nHY1?vKOTA1ri_L2_2$0d>Dbhe5{i?nu@3=z+gG~^|%ln2*LQ+0k){U zY!KwLBsw=oH&&kM(_>FiP}i5O&=nO@Dc0smbED*J6Errnpu7Ay#U)W0LHNGIPWbjX zicC(%{@np*^KoeSnnBl4KTEIGWre9o^HIrFpbnTEtnw4QRh3G63tSJk7y4WsmfZoC-30c!6v&JVp^2a%V3}y1 z^45F<@i=*{6uRDb(}SQ}=#$9wj_CM;=m8;%Ssz~0%mDfByGzPKC2to88*N z>>%v?fQDMo?NG2C@4FFWmF~Tv#s1Tc8SF5Lmhm1#bINg;{*?KUdDpQ$<`o6sOE(Vx z#?)^bNL^DbtAsm!2W1~)+R{Yljk(7<=p7;PegNK3g){H{l=D~z-VlvD4~IQ}$yI#8 zS^R)KPi;r|iCFZwm9eTIeSsrwRi|*~V9vtU;3RF86N9yX8Ax}Vj(*kO47rCHQP#|X z)*$!++d5DU#hF#sVftBoQglcR+GN_g)n4GgKeb=ic3_AlqvMcb>4Q=C-gIA

    =5W)w<}`m! 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 - $command['params'] = explode(' ', preg_replace('/ +/', ' ', $command['params'])); - - // check for optional Track/SM ID parameter - $id = $aseco->server->challenge->uid; - $name = $aseco->server->challenge->name; - if ($command['params'][0] != '') { - if (is_numeric($command['params'][0]) && $command['params'][0] > 0) { - $tid = ltrim($command['params'][0], '0'); - // check for possible track ID - if ($tid <= count($player->tracklist)) { - // find UID by given track ID - $tid--; - $id = $player->tracklist[$tid]['uid']; - $name = $player->tracklist[$tid]['name']; - } else { - // consider it an SM ID - $id = $tid; - $name = ''; - } - } else { - $message = '{#server}> {#highlite}' . $tid . '{#error} is not a valid Track/SM ID!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - return; - } - } - - // obtain SM info - $data = new SMInfoFetcher($id); - if (!$data->name) { - $message = '{#server}> {#highlite}' . ($name != '' ? stripColors($name) : $id) . - '{#error} is not a known SM track, or ShareMania is down!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - return; - } - $data->name = stripNewlines($data->name); - - // compile & send message - if ($aseco->server->getGame() == 'TMN') { - $stats = 'SM Info for: {#black}' . $data->name . '$z' . LF . LF; - $stats .= '$gSM ID : {#black}' . $data->id . LF; - $stats .= '$gUID : {#black}$n' . $data->uid . '$m' . LF; - $stats .= '$gAuthor : {#black}' . $data->author . LF; - $stats .= '$gUploaded : {#black}' . preg_replace('/^\d\d\d\d/', '\$n$0\$m', strftime('%Y-%m-%d %H:%M', $data->uploaded)) . LF; - if ($data->type == 'Stunts') - $stats .= '$gAuthorSc : {#black}' . $data->authorsc . LF; - else - $stats .= '$gAuthorTm : {#black}' . formatTime($data->authortm) . LF; - $stats .= '$gGame : {#black}' . $data->game . LF; - $stats .= '$gType : {#black}' . $data->type . LF; - $stats .= '$gEnviron : {#black}' . $data->envir . LF; - $stats .= '$gMood : {#black}' . $data->mood . LF; - $stats .= '$gNumLaps : {#black}' . $data->nblaps . LF; - $stats .= '$gCoppers : {#black}' . $data->coppers . LF; - $stats .= '$gRating : {#black}' . $data->rating . LF; - $stats .= '$gVotes : {#black}' . $data->votes . LF; - $stats .= '$gDownloads: {#black}' . $data->dnloads; - - // display popup message - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $aseco->formatColors($stats), 'OK', '', 0); - - } elseif ($aseco->server->getGame() == 'TMF') { - $header = 'SM Info for: {#black}' . $data->name; - $links = array($data->imageurl, true, - '$l[' . $data->pageurl . ']Visit SM Page', - '$l[' . $data->dloadurl . ']Download Track'); - $stats = array(); - $stats[] = array('SM ID', '{#black}' . $data->id, - 'Game', '{#black}' . $data->game); - $stats[] = array('UID', '{#black}$n' . $data->uid, - 'Type', '{#black}' . $data->type); - $stats[] = array('Author', '{#black}' . $data->author, - 'Environ', '{#black}' . $data->envir); - $stats[] = array('Uploaded', '{#black}' . strftime('%Y-%m-%d %H:%M', $data->uploaded), - 'Mood', '{#black}' . $data->mood); - if ($data->type == 'Stunts') - $stats[] = array('AuthorSc', '{#black}' . $data->authorsc, - 'NumLaps', '{#black}' . $data->nblaps); - else - $stats[] = array('AuthorTm', '{#black}' . formatTime($data->authortm), - 'NumLaps', '{#black}' . $data->nblaps); - $stats[] = array('Rating', '{#black}' . $data->rating, - 'Coppers', '{#black}' . $data->coppers); - $stats[] = array('Votes', '{#black}' . $data->votes, - 'Downloads', '{#black}' . $data->dnloads); - - // display custom ManiaLink message - display_manialink_track($login, $header, array('Icons64x64_1', 'Maximize', -0.01), $links, $stats, array(1.15, 0.2, 0.45, 0.2, 0.3), 'OK'); - - } else { // TMS/TMO - $stats = '{#server}ShareMania Info for: {#highlite}' . $data->name . '$z' . LF; - $stats .= '{#server}SM ID : {#highlite}' . $data->id . LF; - $stats .= '{#server}UID : {#highlite}' . $data->uid . LF; - $stats .= '{#server}Author : {#highlite}' . $data->author . LF; - $stats .= '{#server}Rating : {#highlite}' . $data->rating; - - // show chat message - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($stats), $login); - } -} // chat_sminfo -?> diff --git a/xaseco/DOCS/OLD/sminfofetcher.inc.php b/xaseco/DOCS/OLD/sminfofetcher.inc.php deleted file mode 100644 index 0e73a95..0000000 --- a/xaseco/DOCS/OLD/sminfofetcher.inc.php +++ /dev/null @@ -1,149 +0,0 @@ - - * Based on TMXInfoFetcher & http://www.sharemania.eu/api_tutorial.php - * - * v1.2: Added magic __set_state function to support var_export() - * v1.1: Optimized get_file URL parsing - * v1.0: Initial release - */ -class SMInfoFetcher { - - public $uid, $id, - $name, $stname, $author, - $game, $type, $envir, $mood, $nblaps, $coppers, - $bronzetm, $silvertm, $goldtm, $authortm, $authorsc, - $rating, $votes, $dnloads, $uploaded, - $pageurl, $imageurl, $dloadurl; - - /** - * Fetches a hell of a lot of data about a SM track - * - * @param String $id - * The challenge UID to search for (if a 26/27-char alphanum string), - * otherwise the SM ID to search for (if a number) - * @return SMInfoFetcher - * If $name is empty, track was not found - */ - public function SMInfoFetcher($id) { - - // check for UID string - if (preg_match('/^\w{26,27}$/', $id)) { - $this->uid = $id; - $this->getData(true); - // check for SM ID - } elseif (is_numeric($id) && $id > 0) { - $this->id = floor($id); - $this->getData(false); - } - } // SMInfoFetcher - - public function __set_state($import) { - - $sm = new SMInfoFetcher(0); - - $sm->uid = $import['uid']; - $sm->id = $import['id']; - $sm->name = $import['name']; - $sm->stname = $import['stname']; - $sm->author = $import['author']; - $sm->game = $import['game']; - $sm->type = $import['type']; - $sm->envir = $import['envir']; - $sm->mood = $import['mood']; - $sm->nblaps = $import['nblaps']; - $sm->coppers = $import['coppers']; - $sm->bronzetm = $import['bronzetm']; - $sm->silvertm = $import['silvertm']; - $sm->goldtm = $import['goldtm']; - $sm->authortm = $import['authortm']; - $sm->authorsc = $import['authorsc']; - $sm->rating = $import['rating']; - $sm->votes = $import['votes']; - $sm->dnloads = $import['dnloads']; - $sm->uploaded = $import['uploaded']; - $sm->pageurl = $import['pageurl']; - $sm->imageurl = $import['imageurl']; - $sm->dloadurl = $import['dloadurl']; - - return $sm; - } // __set_state - - private function getData($isuid) { - - // get all track info - $file = $this->get_file('http://www.sharemania.eu/api.php?i&u&n&sn&a&gv&e&m&ty&nbl&c&t&p&pa&id=' . ($isuid ? $this->uid : $this->id)); - if ($file === false || $file == -1) - return false; - - // parse XML info - if (!$xml = @simplexml_load_string($file)) - return false; - - // extract all track info - if ($isuid) - $this->id = (string) $xml->header->i; - else - $this->uid = (string) $xml->header->u; - - $this->name = (string) $xml->header->n; - $this->stname = (string) $xml->header->sn; - $this->author = (string) $xml->header->a; - $this->type = (string) $xml->header->ty; - $this->game = (string) $xml->header->gv; - $this->envir = (string) $xml->header->e; - $this->mood = (string) $xml->header->m; - $this->nblaps = (string) $xml->header->nbl; - $this->coppers = (string) $xml->header->c; - $this->bronzetm = (string) $xml->times->b; - $this->silvertm = (string) $xml->times->s; - $this->goldtm = (string) $xml->times->g; - $this->authortm = (string) $xml->times->at; - $this->authorsc = (string) $xml->times->as; - $this->rating = (string) $xml->infos->r; - $this->votes = (string) $xml->infos->v; - $this->dnloads = (string) $xml->infos->d; - $this->uploaded = (string) $xml->infos->ud; - - $this->imageurl = (string) $xml->pic; - $this->pageurl = 'http://www.sharemania.eu/track.php?id=' . $this->id; - $this->dloadurl = 'http://www.sharemania.eu/download.php?id=' . $this->id; - } // getData - - // Simple HTTP Get function with timeout - // ok: return string || error: return false || timeout: return -1 - private function get_file($url) { - - $url = parse_url($url); - $port = isset($url['port']) ? $url['port'] : 80; - $query = isset($url['query']) ? "?" . $url['query'] : ""; - - $fp = @fsockopen($url['host'], $port, $errno, $errstr, 4); - if (!$fp) - return false; - - fwrite($fp, 'GET ' . $url['path'] . $query . " HTTP/1.0\r\n" . - 'Host: ' . $url['host'] . "\r\n\r\n"); - stream_set_timeout($fp, 2); - $res = ''; - $info['timed_out'] = false; - while (!feof($fp) && !$info['timed_out']) { - $res .= fread($fp, 512); - $info = stream_get_meta_data($fp); - } - fclose($fp); - - if ($info['timed_out']) { - return -1; - } else { - if (substr($res, 9, 3) != '200') - return false; - $page = explode("\r\n\r\n", $res, 2); - return trim($page[1]); - } - } // get_file -} // class SMInfoFetcher -?> diff --git a/xaseco/DOCS/admin_abilities.html b/xaseco/DOCS/admin_abilities.html deleted file mode 100644 index 1606fe8..0000000 --- a/xaseco/DOCS/admin_abilities.html +++ /dev/null @@ -1,194 +0,0 @@ - - - -TrackMania Nations - XASECO administrator abilities - - - - - - - - - - -

    -TrackMania Nations -


    - -

    XASECO Administrator abilities:

    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    AbilityDescriptionMasterAdminAdminOperator
    helpuse /admin helpXXX
    helpalluse /admin helpallXXX
    setservernameuse /admin setservernameX  
    setcommentuse /admin setcommentX  
    setpwduse /admin setpwdXX 
    setspecpwduse /admin setspecpwdXX 
    setrefpwd (TMF)use /admin setrefpwdXX 
    setmaxplayersuse /admin setmaxplayersX  
    setmaxspecsuse /admin setmaxspecsX  
    setgamemodeuse /admin setgamemodeXX 
    setrefmode (TMF)use /admin setrefmodeXX 
    nextmapuse /admin nextmapXXX
    nextuse /admin nextXXX
    skipmapuse /admin skipmapXXX
    skipuse /admin skipXXX
    previoususe /admin previousXXX
    prevuse /admin prevXXX
    nextenv (TMUF)use /admin nextenvXXX
    restartmapuse /admin restartmapXXX
    resuse /admin resXXX
    replaymapuse /admin replaymapXXX
    replayuse /admin replayXXX
    dropjukeboxuse /admin dropjukeboxXXX
    djbuse /admin djbXXX
    clearjukeboxuse /admin clearjukeboxXXX
    clearhistuse /admin clearhistXX 
    cjbuse /admin cjbXXX
    adduse /admin addXX 
    addthisuse /admin addthisXX 
    addlocaluse /admin addlocalXX 
    warnuse /admin warnXXX
    kickuse /admin kickXXX
    kickghostuse /admin kickghostXXX
    banuse /admin banXX 
    unbanuse /admin unbanXX 
    banipuse /admin banipXX 
    unbanipuse /admin unbanipXX 
    blackuse /admin blackXX 
    unblackuse /admin unblackXX 
    addguestuse /admin addguestXXX
    removeguestuse /admin removeguestXXX
    passuse /admin passXXX
    canceluse /admin cancelXXX
    canuse /admin canXXX
    endrounduse /admin endroundXXX
    eruse /admin erXXX
    playersuse /admin playersXXX
    showbanlistuse /admin showbanlistXXX
    listbansuse /admin listbansXXX
    showiplistuse /admin showiplistXXX
    listipsuse /admin listipsXXX
    showblacklistuse /admin showblacklistXXX
    listblacksuse /admin listblacksXXX
    showguestlistuse /admin showguestlistXXX
    listguestsuse /admin listguestsXXX
    writeiplistuse /admin writeiplistXX 
    readiplistuse /admin readiplistXX 
    writeblacklistuse /admin writeblacklistXX 
    readblacklistuse /admin readblacklistXX 
    writeguestlistuse /admin writeguestlistXX 
    readguestlistuse /admin readguestlistXX 
    cleanbanlistuse /admin cleanbanlistX  
    cleaniplistuse /admin cleaniplistX  
    cleanblacklistuse /admin cleanblacklistX  
    cleanguestlistuse /admin cleanguestlistX  
    mergegbluse /admin mergegblX  
    accessuse /admin accessX  
    writetracklistuse /admin writetracklistXX 
    readtracklistuse /admin readtracklistXX 
    shuffleuse /admin shuffleXX 
    shufflemapsuse /admin shufflemapsXX 
    listdupesuse /admin listdupesXX 
    removeuse /admin removeXX 
    eraseuse /admin eraseXX 
    removethisuse /admin removethisXX 
    erasethisuse /admin erasethisXX 
    muteuse /admin muteXXX
    ignoreuse /admin ignoreXXX
    unmuteuse /admin unmuteXXX
    unignoreuse /admin unignoreXXX
    mutelistuse /admin mutelistXXX
    ignorelistuse /admin ignorelistXXX
    listmutesuse /admin listmutesXXX
    listignoresuse /admin listignoresXXX
    cleanmutesuse /admin cleanmutesX  
    cleanignoresuse /admin cleanignoresX  
    addadminuse /admin addadminX  
    removeadminuse /admin removeadminX  
    addopuse /admin addopXX 
    removeopuse /admin removeopXX 
    listmastersuse /admin listmastersXXX
    listadminsuse /admin listadminsXXX
    listopsuse /admin listopsXXX
    adminabilityuse /admin adminabilityX  
    opabilityuse /admin opabilityX  
    listabilitiesuse /admin listabilitiesXXX
    writeabilitiesuse /admin writeabilitiesXX 
    readabilitiesuse /admin readabilitiesXX 
    walluse /admin wallXXX
    mtause /admin mtaXXX
    delrecuse /admin delrecX  
    prunerecsuse /admin prunerecsX  
    rpoints (TMF)use /admin rpointsXX 
    matchuse /admin matchXX 
    acdluse /admin acdlX  
    autotimeuse /admin autotimeXX 
    disablerespawn (TMF)use /admin disablerespawnXX 
    forceshowopp (TMF)use /admin forceshowoppXX 
    scorepanel (TMF)use /admin scorepanelXXX
    roundsfinish (TMF)use /admin roundsfinishXXX
    forceteam (TMF)use /admin forceteamXXX
    forcespec (TMF)use /admin forcespecXXX
    specfree (TMF)use /admin specfreeXXX
    panel (TMF)use /admin panelXXX
    style (TMF)use /admin styleX  
    admpanel (TMF)use /admin admpanelX  
    donpanel (TMUF)use /admin donpanelX  
    recpanel (TMF)use /admin recpanelX  
    votepanel (TMF)use /admin votepanelX  
    coppers (TMUF)use /admin coppersXX 
    pay (TMUF)use /admin payX  
    relays (TMF)use /admin relaysXXX
    serveruse /admin serverXX 
    pmuse /admin pmXX 
    pmloguse /admin pmlogXX 
    calluse /admin callX  
    unlockuse /admin unlockXXX
    debuguse /admin debugX  
    shutdownuse /admin shutdownX  
    shutdownalluse /admin shutdownallX  
    uptodateuse /admin uptodateXX 
    chat_pmause /pma to send a PM to player & adminsXX 
    chat_bestworst/best & /worst accept login/Player_IDXX 
    chat_statsip/stats includes IP addressXX 
    chat_summary/summary accepts login/Player_IDXX 
    chat_jukeboxuse /jukebox even if $feature_jukebox is falseXX 
    chat_jb_multi/jukebox adds more than one trackXX 
    chat_jb_recent/jukebox adds recently played trackXX 
    chat_add_trefuse /add trackref to write TMX trackref fileX  
    chat_matchuse /match to allow match controlX  
    chat_tc_listen/tc will copy team chat to adminsX  
    chat_jfreuuse all /jfreu commandsXX 
    chat_musicadmin (TMF)use /music admin commandsXX 
    noidlekick_playno idlekick when admin is playerX  
    noidlekick_specno idlekick when admin is spectatorXXX
    server_coppers (TMUF)view coppers amount in /serverXX 
    -
    -

    - -
    -
    -Copyright © 2007-2013 – Frans P. de Vries <tm@gamers.org> -            -Last updated 08-Aug-2011 -
    - - diff --git a/xaseco/DOCS/aseco_commands.doc b/xaseco/DOCS/aseco_commands.doc deleted file mode 100644 index 3c350db1b54d49045b53741397cfae4532ed2301..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69120 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 - - -TrackMania Nations - XASECO commands - - - - - - - - - - -

    -TrackMania Nations -


    - - -

    Important XASECO admin commands:

    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    /admin help | helpall
    /jfreu help | helpall
     
    /players
    /players <string>finds online player(s)
    /admin players
    /admin players <string>finds offline player(s)
    /admin players livelists online player(s)
     
    /pm <login/id> <message>
    /pma <login/id> <message>
    /pmlog
    /chatlog
    /msglog
     
    /admin pm <message>
    /admin pmlog
    /admin wall <message>
     
    /admin endround/admin er
    /admin resmap/admin res
    /admin nextmap/admin next
    /admin replaymap/admin replay
     
    /admin pass
    /admin cancel/admin can
    /admin dropjukebox <#>/admin djb <#>
    /admin clearjukebox/admin cjb
     
    /admin warn <login/id>
    /admin kick <login/id>
    /admin kickghost <login>
    /admin black <login/id>
    /admin ban <login/id>
    /admin banip <IP>
     
    /jfreu badword <login/id>
    /jfreu banfor <time> <login/id><time> = <xx> mins or <x>H hours
    /jfreu players
    /jfreu players <string>finds offline player(s)
    /jfreu players livelists online player(s)
    -
    - - -

    List of available user commands:

    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    /adminProvides admin commands (see: /admin help)
    /helpShows all available commands
    /helpallDisplays help for available commands
    /recsDisplays all records on current track
    /recs help Displays this help information
    /recs pb Shows your personal best on current track
    /recs new Shows newly driven records
    /recs live Shows records of online players
    /recs first Shows first ranked record on current track
    /recs last Shows last ranked record on current track
    /recs next Shows next better ranked record to beat
    /recs diff Shows your difference to first ranked record
    /recs range Shows difference first to last ranked record
    /newrecsShows newly driven records
    /liverecsShows records of online players
    /bestDisplays your best records
    /worstDisplays your worst records
    /summaryShows summary of all your records
    /topsumsDisplays top 100 of top-3 record holders
    /toprecsDisplays top 100 ranked records holders
    /firstrecShows first ranked record on current track
    /lastrecShows last ranked record on current track
    /nextrecShows next better ranked record to beat
    /diffrecShows your difference to first ranked record
    /recrangeShows difference first to last ranked record
    /helpdedi | /dedihelpDisplays info about the Dedimania records system
    /dedirecsDisplays all Dedimania records on current track
    /dedirecs help Displays this help information
    /dedirecs pb Shows your personal best on current track
    /dedirecs new Shows newly driven records
    /dedirecs live Shows records of online players
    /dedirecs first Shows first ranked record on current track
    /dedirecs last Shows last ranked record on current track
    /dedirecs next Shows next better ranked record to beat
    /dedirecs diff Shows your difference to first ranked record
    /dedirecs range Shows difference first to last ranked record
    /dedinewShows newly driven Dedimania records
    /dediliveShows Dedimania records of online players
    /dedipbShows your Dedimania personal best on current track
    /dedifirstShows first Dedimania record on current track
    /dedilastShows last Dedimania record on current track
    /dedinextShows next better Dedimania record to beat
    /dedidiffShows your difference to first Dedimania record
    /dedirangeShows difference first to last Dedimania record
    /dedicpsSets Dedimania record checkpoints tracking
    /dedistatsDisplays Dedimania track statistics
    /dedicptmsDisplays all Dedimania records' checkpoints times
    /dedisectmsDisplays all Dedimania records' sector times
    /playersDisplays current list of nicks/logins
    /ranksDisplays list of online ranks/nicks
    /clansDisplays list of online clans/nicks
    /topclansDisplays top 10 best ranked clans
    /winsShows wins for current player
    /lastonShows when a player was last online
    /lastwinRe-opens the last closed multi-page window
    /statsDisplays statistics of current player
    /statsall (TMN)Displays world statistics of a player
    /settingsDisplays your personal settings
    /serverDisplays info about this server
    /xasecoDisplays info about this XASECO
    /pluginsDisplays list of active plugins
    /nationsDisplays top 10 most visiting nations
    /songShows filename of current track's song
    /modShows (file)name of current track's mod
    /meCan be used to express emotions
    /muteMute another player's chat messages
    /unmuteUnMute another player's chat messages
    /mutelistDisplay list of muted players
    /refreshRefresh chat window
    /tmxinfoDisplays TMX info {Track_ID/TMX_ID} {sec}
    /tmxrecsDisplays TMX records {Track_ID/TMX_ID} {sec}
    /trackShows info about the current track
    /playtimeShows time current track has been playing
    /timeShows current server time & date
    /cpsSets local record checkpoints tracking
    /cpsspecShows checkpoints of spectated player
    /cptmsDisplays all local records' checkpoint times
    /sectmsDisplays all local records' sector times
    /pbShows your personal best on current track
    /rankShows your current server rank
    /top10Displays top 10 best ranked players
    /top100Displays top 100 best ranked players
    /topwinsDisplays top 100 victorious players
    /activeDisplays top 100 most active players
    /listLists tracks currently on the server (see: /list help)
    /list help Displays this help information
    /list nofinish Tracks you don't have a rank on
    /list norank Tracks you haven't completed
    /list nogold Tracks you didn't beat gold time on
    /list noauthor Tracks you didn't beat author time on
    /list norecent Tracks you didn't play recently
    /list best | worst Tracks with your best / worst records
    /list longest | shortest The longest / shortest tracks
    /list newest | oldest <#> The newest / oldest # tracks (default: 50)
    /list <xxx> Where <xxx> is part of a track or author name
    /list env:<zzz> (TMUF) Where <zzz> is an environment from: stadium,
    bay,coast,island,snow/alpine,desert/speed,rally
    /list <xxx> env:<zzz> Combines the name and environment searches
    /list novote Tracks you didn't karma vote for
    /list karma <#> Display all tracks with karma >= or <= given value
    /jukeboxSets a track to be played next (see: /jukebox help)
    /jukebox help Displays this help information
    /jukebox list Shows upcoming tracks
    /jukebox display Displays upcoming tracks and requesters
    /jukebox drop Drops your currently added track
    /jukebox <#> Adds a track where <#> is track number from /list
    /autojukeJukeboxes track from /list (see: /autojuke help)
    /autojuke help Displays this help information
    /autojuke nofinish Tracks you don't have a rank on
    /autojuke norank Tracks you haven't completed
    /autojuke nogold Tracks you didn't beat gold time on
    /autojuke noauthor Tracks you didn't beat author time on
    /autojuke norecent Tracks you didn't play recently
    /autojuke longest | shortest The longest / shortest tracks
    /autojuke newest | oldest The newest / oldest # tracks
    /autojuke novote Tracks you didn't karma vote for
    /addAdds a track directly from TMX (<ID> {sec})
    /add <ID> <sec> (TMF)Adds a track from a specific TMX section
    /yVotes Yes for a TMX track or chat-based vote
    /historyShows the 10 most recently played tracks
    /xlistLists tracks on TMX (see: /xlist help)
    /xlist help Displays this help information
    /xlist recent Lists the 10 most recent tracks
    /xlist <xxx> Lists tracks matching (partial) name
    /xlist auth:<yyy> Lists tracks matching (partial) author
    /xlist env:<zzz> Where <zzz> is an environment from: stadium,
    bay,coast,island,snow/alpine,desert/speed,rally
    /xlist <xxx> auth:<yyy> env:<zzz> Combines the name, author and/or env searches
    /xlist <tmx> Where <tmx> is a TMX section from:
    TMO,TMS,TMN,TMNF,TMU
    Can be appended to any of the above searches
    /pmSends a private message to login or Player_ID
    /pmaSends a private message to player & admins (admin-only)
    /pmlogDisplays log of your recent private messages
    /hiSends a Hi message to everyone
    /byeSends a Bye message to everyone
    /thxSends a Thanks message to everyone
    /lolSends a Lol message to everyone
    /loolSends a Lool message to everyone
    /brbSends a Be Right Back message to everyone
    /afkSends an Away From Keyboard message to everyone
    /ggSends a Good Game message to everyone
    /grSends a Good Race message to everyone
    /n1Sends a Nice One message to everyone
    /bgmSends a Bad Game message to everyone
    /officialShows a helpful message ;-)
    /bootmeBoot yourself from the server
    /karmaShows karma for the current track
    /++Increases karma for the current track
    /--Decreases karma for the current track
    /nextmapShows name of the next challenge
    /nextrankShows the next better ranked player
    /helpvote | /votehelpDisplays info about the chat-based votes
    /endroundStarts a vote to end current round
    /ladderStarts a vote to restart track for ladder
    /replayStarts a vote to replay this track
    /skipStarts a vote to skip this track
    /ignoreStarts a vote to ignore a player
    /kickStarts a vote to kick a player
    /cancelCancels your current vote
    /chatlogDisplays log of recent chat messages
    /msglog (TMF)Displays log of recent system messages
    /style (TMF)Selects window style (see: /style help)
    /style help Displays this help information
    /style list Displays available styles
    /style default Resets style to server default
    /style off Disables TMF window style
    /style <xxx> Selects window style <xxx>
    /donpanel (TMUF)Selects donate panel (see: /donpanel help)
    /donpanel help Displays this help information
    /donpanel list Displays available panels
    /donpanel default Resets panel to server default
    /donpanel off Disables donate panel
    /donpanel <xxx> Selects donate panel <xxx>
    /recpanel (TMF)Selects records panel (see: /recpanel help)
    /recpanel help Displays this help information
    /recpanel list Displays available panels
    /recpanel default Resets panel to server default
    /recpanel off Disables records panel
    /recpanel <xxx> Selects records panel <xxx>
    /votepanel (TMF)Selects records panel (see: /votepanel help)
    /votepanel help Displays this help information
    /votepanel list Displays available panels
    /votepanel default Resets panel to server default
    /votepanel off Disables vote panel
    /votepanel <xxx> Selects vote panel <xxx>
    /donate (TMUF)Donates coppers to server
    /topdons (TMUF)Displays top 100 highest donators
    /music (TMF)Handles server music (see: /music help)
    /music help Displays this help information
    /music settings Displays current music settings
    /music list Displays all available songs
    /music list <xxx> Searches song names/tags for <xxx>
    /music current Shows the current song
    /music reload (admin) Reloads musicserver.xml config file
    /music next (admin) Skips to next song (upon next track)
    /music sort (admin) Sorts the song list
    /music shuffle (admin) Randomizes the song list
    /music override (admin) Changes track override setting
    /music autonext (admin) Changes automatic next song setting
    /music allowjb (admin) Changes allow jukebox setting
    /music stripdirs (admin) Changes strip subdirs setting
    /music stripexts (admin) Changes strip extensions setting
    /music off (admin) Disables music, auto next & jukebox
    /music jukebox Displays upcoming songs in jukebox
    /music drop Drops your currently added song
    /music <#> Adds a song to jukebox where <#> is song number
    /rpoints (TMF)Shows current Rounds points system
    /ranklimitShows the current rank limit
    /passwordShows server's player/spectator password
    /yesVotes Yes for unSpec
    /noVotes No for unSpec
    /unspecLaunches an unSpec vote
    /messageShows random informational message
    /jfreuJfreu admin commands (see: /jfreu help)
    /versionShows server version (dedicated built-in command)
    /serverlogin (TMF)Shows server login (dedicated built-in command)
    -
    - - -

    List of available /admin commands:

    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    helpShows all available /admin commands
    helpallDisplays help for available /admin commands
    setservernameChanges the name of the server
    setcommentChanges the server comment
    setpwdChanges the player password
    setspecpwdChanges the spectator password
    setrefpwd (TMF)Changes the referee password
    setmaxplayersSets a new maximum of players
    setmaxspecsSets a new maximum of spectators
    setgamemodeSets next mode {ta,rounds,team,laps,stunts,cup}
    setrefmode (TMF)Sets referee mode {0=top3,1=all}
    nextmap | nextForces server to load next track
    skipmap | skipForces server to load next track
    previous | prevForces server to load previous track
    nextenv (TMUF)Loads next track in same environment
    restartmap | resRestarts currently running track
    replaymap | replayReplays current track (via jukebox)
    dropjukebox | djbDrops a track from the jukebox
    clearjukebox | cjbClears the entire jukebox
    clearhistClears (part of) track history
    addAdds tracks directly from TMX (<ID>... {sec})
    add <ID> <sec> (TMF)Adds a track from a specific TMX section
    addthisAdds current /add-ed track permanently
    addlocalAdds a local track (<filename>)
    warnSends a kick/ban warning to a player
    kickKicks a player from server
    kickghostKicks a ghost player from server
    banBans a player from server
    unbanUnBans a player from server
    banipBans an IP address from server
    unbanipUnBans an IP address from server
    blackBlacklists a player from server
    unblackUnBlacklists a player from server
    addguestAdds a guest player to server
    removeguestRemoves a guest player from server
    passPasses a chat-based or TMX /add vote
    cancel | canCancels any running vote
    endround | erForces end of current round
    playersDisplays list of known players {string}
    players liveDisplays list of online players
    showbanlist | listbansDisplays current ban list
    showiplist | listipsDisplays current banned IPs list
    showblacklist | listblacksDisplays current black list
    showguestlist | listguestsDisplays current guest list
    writeiplistSaves current banned IPs list (def: bannedips.xml)
    readiplistLoads current banned IPs list (def: bannedips.xml)
    writeblacklistSaves current black list (def: blacklist.txt)
    readblacklistLoads current black list (def: blacklist.txt)
    writeguestlistSaves current guest list (def: guestlist.txt)
    readguestlistLoads current guest list (def: guestlist.txt)
    cleanbanlistCleans current ban list
    cleaniplistCleans current banned IPs list
    cleanblacklistCleans current black list
    cleanguestlistCleans current guest list
    mergegblMerges a global black list {URL}
    accessHandles player access control (see: /admin acess help)
    access help Displays this help information
    access list Displays current access control settings
    access reload Reloads updated access control settings
    writetracklistSaves current track list (def: tracklist.txt)
    readtracklistLoads current track list (def: tracklist.txt)
    shuffle | shufflemapsRandomizes current track list
    listdupesDisplays list of duplicate tracks
    removeRemoves a track from rotation
    eraseRemoves a track from rotation & delete track file
    removethisRemoves this track from rotation
    erasethisRemoves this track from rotation & delete track file
    mute | ignoreAdds a player to global mute/ignore list
    unmute | unignoreRemoves a player from global mute/ignore list
    mutelist | listmutesDisplays global mute/ignore list
    ignorelist | listignoresDisplays global mute/ignore list
    cleanmutes | cleanignoresCleans global mute/ignore list
    addadminAdds a new admin
    removeadminRemoves an admin
    addopAdds a new operator
    removeopRemoves an operator
    listmastersDisplays current masteradmin list
    listadminsDisplays current admin list
    listopsDisplays current operator list
    adminabilityShows/changes admin ability {ON/OFF}
    opabilityShows/changes operator ability {ON/OFF}
    listabilitiesDisplays current abilities list
    writeabilitiesSaves current abilities list (def: adminops.xml)
    readabilitiesLoads current abilities list (def: adminops.xml)
    wall | mtaDisplays popup message to all players
    delrecDeletes specific record on current track
    prunerecsDeletes records for specified track
    rpoints (TMF)Sets custom Rounds points (see: /admin rpoints help)
    rpoints help Displays this help information
    rpoints list Displays available points systems
    rpoints show Shows current points system
    rpoints <xxx> Sets custom points system labelled <xxx>
    rpoints X,Y,...,Z Sets custom points system with specified values
    rpoints off Disables custom points system
    rpoints f1old Sets Formula 1 GP old points
    rpoints f1new Sets Formula 1 GP new points
    rpoints motogp Sets MotoGP points
    rpoints motogp5 Sets MotoGP + 5 points
    rpoints fet1 Sets Formula ET Season 1 points
    rpoints fet2 Sets Formula ET Season 2 points
    rpoints fet3 Sets Formula ET Season 3 points
    rpoints champcar Sets Champ Car World Series points
    rpoints superstars Sets Superstars points
    rpoints simple5 Sets Simple 5 points
    rpoints simple10 Sets Simple 10 points
    match{begin/end} to start/stop match tracking
    acdlSets AllowChallengeDownload {ON/OFF}
    autotimeSets Auto TimeLimit {ON/OFF}
    disablerespawn (TMF)Disable respawns at CPs {ON/OFF}
    forceshowopp (TMF)Forces to show opponents {##/ALL/OFF}
    scorepanel (TMF)Shows automatic scorepanel {ON/OFF}
    roundsfinish (TMF)Shows rounds panel upon first finish {ON/OFF}
    forceteam (TMF)Forces player into Blue or Red team
    forcespec (TMF)Forces player into free spectator
    specfree (TMF)Forces spectator into free mode
    panel (TMF)Selects admin panel (see: /admin panel help)
    panel help Displays this help information
    panel list Displays available panels
    panel default Resets panel to server default
    panel off Disables admin panel
    panel <xxx> Selects admin panel <xxx>
    style (TMF)Selects default window style
    admpanel (TMF)Selects default admin panel
    donpanel (TMUF)Selects default donate panel
    recpanel (TMF)Selects default records panel
    votepanel (TMF)Selects default vote panel
    coppers (TMUF)Shows server's coppers amount
    pay (TMUF)Pays server coppers to login
    relays (TMF)Displays relays list or shows relay master
    serverDisplays server's detailed settings
    pmSends private message to all available admins
    pmlogDisplays log of recent private admin messages
    callExecutes direct server call (see: /admin call help)
    unlock <pwd>Unlocks admin commands & features
    debugToggles debugging output
    shutdownShuts down XASECO
    shutdownallShuts down Server & XASECO
    uptodateChecks current version of XASECO
    -
    - - -

    List of available /jfreu commands:

    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    helpShows Jfreu commands
    helpallDisplays help for Jfreu commands
    autochangenameAuto change servername {ON/OFF}
    setrankSets ranklimiting {ON/OFF}
    setlimitSets ranklimit value
    autorankSets autoranking {ON/OFF}
    offsetSets autorank offset (-999 - 999)
    hardlimitSets hardlimit value
    autorankminplayersSets min players for autorank {0-50}
    autorankvipInclude VIPs in autorank {ON/OFF}
    maxplayersSets maxplayers for Kick HiRank
    kickhirankKick HiRank when server full {ON/OFF}
    listlimitsDisplays rank limit settings
    kickworstKicks worst players {count}
    playersDisplays list of known players {string}
    players liveDisplays list of online players
    unspecUnSpecs player {login/ID} (clear SpecOnly)
    addvipAdds a VIP {login/ID}
    removevipRemoves a VIP {login/ID}
    addvipteamAdds a VIP_Team {team}
    removevipteamRemoves VIP_Team {team}
    listvipsDisplays VIPs list
    listvipteamsDisplays VIP_Teams list
    writelistsSaves VIP/VIP_Team lists (def: jfreu.vips.xml)
    readlistsLoads VIP/VIP_Team lists (def: jfreu.vips.xml)
    badwordsSets badwords bot {ON/OFF}
    badwordsbanSets badwords ban {ON/OFF}
    badwordsnumSets badwords limit {count}
    badwordstimeSets banning period {mins}
    badwordGives extra badword warning {login/ID}
    banforBans player {mins/hoursH} {login/ID}
    unbanUnBans temporarily banned player
    listbansDisplays temporarily banned players
    messageFakes message from server {msg}
    playerFakes message from player {login/ID} {msg}
    nopfkickSets NoPfKick {map/time/OFF}
    cancelCancels current vote (kick/ban/nextmap/restart)
    novoteAuto-cancel CallVotes {ON/OFF}
    unspecvoteAllow /unspec votes {ON/OFF}
    infomessagesSets info messages {ON/OFF}
    writeconfigSaves Jfreu config (def: jfreu.config.xml)
    readconfigLoads Jfreu config (def: jfreu.config.xml)
    -
    -
    - -
    -
    -Copyright © 2007-2013 – Frans P. de Vries <tm@gamers.org> -            -Last updated 02-Apr-2013 -
    - - diff --git a/xaseco/DOCS/index.html b/xaseco/DOCS/index.html deleted file mode 100644 index 029582d..0000000 --- a/xaseco/DOCS/index.html +++ /dev/null @@ -1,421 +0,0 @@ - - - -TrackMania Nations - XASECO - - - - - - - - - - -Welcome to this humble page for: - -

    -TrackMania Nations -


    - -

    Introduction:

    - -
    -
    -This is a simple hub page for TrackMania Nations ESWC. It aims to collect in one place useful information and references to official and community sites for this classic racing game by Nadeo. Here are relevant excerpts from the TMN ESWC press release: -

    -"For the first time in the history of eSport, a video game has been specially developed for the Electronic Sports World Cup and is being offered free of charge to the players of the entire planet." -

    -"This special version features a brand new environment, the Stadium, and a revolutionary new gameplay designed for Nadeo's own custom cars. Nadeo has pulled the technological rabbit from the hat with their own brand game engine, featuring the series' best graphics yet, finely tuned for the latest generation graphics cards, not forgetting simpler machines so that no one round the world has a technical freeze out on the race track. With the online in-game ladder, players can customise their cars and avatars and race in their nation's colours on the world's servers. Many Trackmania and Trackmania Sunrise players will already have access to the following exclusive features: peer 2 peer data exchange and skin and avatar customisation." -

    -
    - -

    XASECO:

    - -
    -
    -This page is also the initial home to XASECO, which stands -for Xymph's ASECO and is the new name for the system formerly -known as ASECO/RASP for classic TMN. It now supports TrackMania -Forever as well. ASECO is an abbreviation of "Automatic -SErver COntrol", a system for TrackMania Original, -Sunrise and (especially) Nations and Forever servers -to keep track of player records and provide useful player and admin -commands. RASP is a sizable set of plugins for ASECO that adds -server ranks, a track jukebox, a karma voting system, and lots more. - -

    -As evidenced by the many TrackMania (Nations) servers running it, -the ASECO/RASP system for TMO/TMS/TMN remained a very popular package -to keep track of records and offer players various useful commands -and features. But its original authors have moved on to TM United, -so I have been working at the system on my own since May 2007 to -improve and expand it. The number of changes is so large that I -decided (somewhat presumptuously perhaps) to call this system XASECO -version 0.8, subsequently updated to v1.16, many major -releases since ASECO 0.61b and RASP 1.5 that together combined into -version 0.7. TrackMania Forever is also supported since XASECO v0.96. - -

    -I don't believe there's a point in running a barebones (X)ASECO system -without at least the RASP plugins because those add so many basic -and useful features and commands which players enjoy, so they are -not released separately like before but as one combined system. The -remaining plugins included in this v1.16 release (Mistral's idle-kick, -and Jfreu's plugin) are however optional, as are the series of new -plugins I developed myself. My overall goal remained to stick to -'core functionality' as much as possible, rather than include into -the base system any of the variety of 'niche' plugins that exist out -there, while still providing a complete and integrated solution for -server control. - -

    -For a high-level outline of all of XASECO's features and plugins, -see the Overview page. For screenshots -of many of the TMF interface features, see this preview -thread. - -

    -For a comprehensive overview of the new stuff, see the v0.8 initial release notes, v0.81 - v0.95 oldest release notes, v0.96 - v1.03 older release notes, and -v1.04 - v1.16 current release notes. - -

    -And here is a complete overview of all available -commands in HTML and Word. - -

    -
    - - -

    Content:

    - - - -

    Installation:

    - -
      -
    • New installation of v1.16:

      -See the XASECO Installation page on XAseco.org, -or go directly to the classic TMN & XASECO quick start guide or TMF & XASECO quick start guide. -

      - -For historical info, you can consult the old ASECO v0.61b readme.pdf -and corresponding Tutorial, as well as the old RASP v1.5 readme.txt, -in Server Resources below. -

      - - -
    • Upgrading from v1.15b to v1.16:

      -See the XASECO Upgrade page on XAseco.org for general instructions. -

      - -The following files were updated in v1.16: aseco.php, dedimania.xml, -includes/GbxRemote.bem.php, GbxRemote.inc.php, basic.inc.php, -gbxdatafetcher.inc.php, rasp.funcs.php, rasp.settings.php, -tmxinfosearcher.inc.php, types.inc.php, jfreu.config.php, -plugins/chat.dedimania.php, plugin.checkpoints.php, -plugin.dedimania.php, plugin.localdatabase.php, -plugin.rasp_nextmap.php. - -

      - -Important: -
        -
      • To register your server with the central Dedimania -database, you must copy the login and password values in the -<masterserver_account> section from your -server's dedicated.cfg (TMN) or dedicated_cfg.txt (TMF) file into -the corresponding section of the dedimania.xml file, and add -the 3-character nation abbreviation. Instead of the password -you can also use the community code for your server by using -the server login/password on the official -site for your game (TMO/TMS/TMN) or on this page for -TMF. -
      • Open port 8002 on your firewall/router for communication -with the central Dedimania server. -
      • In the zip file, all *.XML and *.PHP config files are located -inside the newinstall/ directory. This means that you can (and -have to) unzip the download and replace all the PHP code files, without -worrying about overwriting your customized config files. However, for -every XML/PHP config file that was updated (see above), you must -replace your version with the one from the newinstall/ directory, or -compare them and add any new/changed configuration settings -to your version to insure the system remains working correctly. -
      • For a new installation, go into the newinstall/ directory and move -all *.XML files into the main directory (next to aseco.php), all *.PHP -files into the includes/ directory, and Aseco.bat|Aseco.sh|AsecoF.sh -also into the main directory. -
      -
      - -
    • Upgrading from v0.7 to v0.8+ and from v0.8-v1.14 to v1.15b:

      - -See the archived Upgrade notes. -
    - - -

    Configuration options:

    - - - - -

    Version Notes:

    - - - - -

    Download:

    - - - - -

    Feedback/questions:

    - - - -

    Thanks:

    - - - -

    Links:

    - - - -
    -
    -
    -Copyright © 2007-2013 – Frans P. de Vries <tm@gamers.org> -            -Last updated 26-Jul-2013 -
    - - diff --git a/xaseco/DOCS/overview.html b/xaseco/DOCS/overview.html deleted file mode 100644 index 3459aae..0000000 --- a/xaseco/DOCS/overview.html +++ /dev/null @@ -1,331 +0,0 @@ - - - -TrackMania Nations - XASECO overview - - - - - - - - - - -

    -TrackMania Nations -


    - -

    XASECO Overview

    - -
      -
    1. Introduction:

      -XASECO is a server controller for TMF (both Nations and United) and TMN -written in PHP using a MySQL database. It should also still support -TMO and TMS but I'm unable to test that. The main XASECO program -aseco.php is merely a framework to handle communication with -the dedicated server, handle events, provide logging, and offer support -functionality. All user features and commands are provided by plugins. - -
    2. Plugins:

      -XASECO runs fine with an empty plugins.xml list, it just -won't do very much. To provide user functionality, add files from -plugins/ directory to the list. Some plugins are essential, such as -plugin.localdatabase.php for communication with the database, while -many others are optional. However, some plugins depend on others being -present (sometimes in the correct order in plugins.xml), so it's best -to preserve as much of the default plugins.xml list as needed. - -
    3. Configs:

      -Configuration files consist of .XML files in the main directory, as -well as some of the .PHP files in the includes/ directory. The most -important one is config.xml, containing many of the basic -settings, messages, and server and admin definitions. Other config -files are typically related to one specific plugin or set of plugins -(like RASP with rasp.xml and rasp.settings.php). - -
    4. Database:

      -Using the MySQL database requires the plugin.localdatabase.php -plugin configured via localdatabase.xml. It stores information -about tracks, players, local records, all finishes, karma (or old -style) votes, player ranks, and more. - -
    5. Help:

      -The main /help command provided by chat.help.php lists -all available chat commands in a chat message, while /helpall -generates a window that includes descriptions. Other plugins -sometimes include a help command (/helpdedi for Dedimania, -/helpvote for chat-based votes) and some commands include a -help option (e.g. /admin helpall, /list help, etc). - -
    6. Players:

      -Player connects and disconnects are handled by the main aseco.php -program and relayed to plugins via the onPlayerConnect and -onPlayerDisconnect events. The /players command is provided -by the chat.players.php plugin, while chat.players2.php -provides a couple of related commands. - -
    7. Local Records:

      -Local records are determined and stored by the -plugin.localdatabase.php plugin, and can be displayed -by the /recs command from the chat.records.php -plugin. Additional record rankings and listings are provided by -the chat.records2.php plugin, while chat.recrels.php -offers record relation commands. The maximum number of records -per track is defined in rasp.settings.php. - -
    8. Dedimania Records:

      -Dedimania records are determined and stored on the central Dedimania site -by the plugin.dedimania.php plugin, and can be displayed by the -/dedirecs command from the chat.dedimania.php plugin. -Additional record rankings and listings, as well as record relations, -are provided by the same plugin. All related configuration options -and messages are provided in dedimania.xml (the maximum number -of records per track should be kept at 30). Detailed information can -be found in the v0.95 release notes. - -
    9. Checkpoints:

      -Players can keep track of their relative performance at -each checkpoint with the /cps command, provided by the -plugin.checkpoints.php plugin. Normally it uses the player's -own (or any other) local record as reference, if checkpoints are -available for that record, and shows whether each crossing is faster -or slower in the CPS panel above the main racing time (TMF) or in -a brief pop-up window (TMN). If the Dedimania system is enabled, -the /dedicps command can be used to compare the times with the -player's own (or any other) Dedimania record instead. Both settings -are stored for all players, and reloaded upon every visit. More -information can be found in the v0.93, -v0.98 and v1.06 release notes. - -
    10. Anti-cheat checks:

      -When the plugin.checkpoints.php plugin is active, it also checks -checkpoints and finish times in several ways to reduce the chances of -cheated races resulting in local and Dedimania records. See the v1.05 release notes for more information. - -
    11. Rounds:

      -When the plugin.rounds.php plugin is active in Rounds, Team and -Cup (TMF) mode, a report with each player's finish time is shown after -each run. It serves no purpose in TimeAttack, Laps and Stunts mode. - -
    12. Track Lists & Jukebox:

      -The plugin.rasp_jukebox.php plugin provides the /list -command to list, search for and select from the available tracks -with a wide variety of options. Players can pick any track from the -list that hasn't been played recently, and add it to a jukebox of -tracks that's given precedence over tracks from the regular server -rotation. This is controlled by a number of configuration settings -in rasp.settings.php. - -
    13. TMX

      -Specific tracks can be downloaded and added directly from TMX with the -/add command (in plugin.rasp_jukebox.php) and /admin -add command (in chat.admin.php). Searching on TMX can be -done with /xlist, while the plugin.tmxinfo.php plugin -offers commands to display track information. More information -can be found in the v0.93 and v1.01 release notes. - -
    14. Karma:

      -Karma votes are an easy way for players to indicate via /++ -or /-- commands whether they like or don't like a track, -provided by the plugin.rasp_karma.php plugin and configured -by settings in rasp.settings.php. Some details can be found -in the v0.89 release notes. - -
    15. Ranks:

      -A player obtains a server rank by racing a minimum number (default 3, -configured in rasp.settings.php) of local ("ranked") records. -The average of a player's records over all server tracks determines -the ranking, managed by the plugin.rasp.php plugin with -plugin.rasp_nextrank.php providing a related command. - -
    16. Stats:

      -Player stats are displayed by the /stats command from -the chat.stats.php plugin, while server information is -provided by /server, /xaseco and /plugins from -chat.server.php. Related plugins chat.laston.php, -chat.songmod.php, chat.wins.php and -plugin.track.php offer associated commands. - -
    17. Tiered Admins:

      -The tiered admin system defines three ability levels: MasterAdmins -(specified in config.xml) who can run any admin command, and -Admins and Operators for whom the logins and abilities are specified -in adminops.xml. Typically, Operators are allowed to use -the fewest admin commands, and the abilities list extends beyond -the /admin command to special admin features of user commands -(such as jukeboxing tracks that were played too recently). Admins and -operators can be added and removed dynamically, or their abilities -updated, and adminops.xml will be automatically kept in sync. -See this table for the default list -of abilities, and detailed information can be found in the v0.88 release notes. - -
    18. Chat-based Votes:

      -Chat-based votes allow players to vote on actions that -regular CallVotes don't support, such as ending a round or -replaying a track. When enabled in rasp.settings.php, the -plugin.rasp_votes.php plugin disables CallVotes completely and -offers several voting commands that can be voted on with the /y -command (there is no /n counterpart), and on TMF also via the -vote panel provided by plugin.panels.php. Vote configuration -is done in votes.config.php, and detailed information can be -found in the v0.84 release notes. - -
    19. Public/Private Messages:

      -The plugin.rasp_chat.php and chat.me.php plugins -offer a wide variety of commands to quickly shout out a message to -other players. Players' regular chat lines are logged and displayed -by the plugin.chatlog.php plugin. Private messages from one -player to another can be send with the /pm command, from an -admin to a player (Cc-ing all other admins) with /pma, and -from one admin to all other admins with /admin pm. - -
    20. Muting/Ignoring:

      -Muting (the TMN term) or ignoring (TMF) implies that chat messages -from a specified player are suppressed and aren't readable for other -players in the chat window. Admins can maintain a global list of -muted/ignored players, which on TMF uses the built-in Ignore features -and on TMN a simulated suppression system. The latter system is -also used when an individual player mutes another player using the -commands provided in the plugin.muting.php plugin. See the v0.90 release notes for more information. - -
    21. Ban, Black- & Guestlist:

      -A number of /admin commands from chat.admin.php can be -used to warn (with a pop-up message) or kick players, and to punish -repeat offenders with a ban or blacklist. Bans work by IP address and -are tracked only by the dedicated server, and therefore get purged -by a server restart. Blacklists work by login and are stored in -the blacklist.txt file, and therefore persist across server -restarts. Logins on the guestlist can enter the server even when -the maximum number of players or spectators is reached or when the -server is passworded, and are preserved in the guestlist.txt -file. In addition to these built-in mechanisms, XASECO provides -a list of banned IP addresses in the bannedips.xml file -that also transcends server restarts, with more info in the v1.03 release notes. - -
    22. Player access

      -Besides the above local server mechanisms, player access can -also be managed with the global blacklist, and with -access control by nation (TMN) or zone (TMF). See the v1.09 release notes for details. Lastly, -the full Jfreu plugin (below) allows player limiting by rank. - -
    23. Admin Commands:

      -Other /admin commands control the game flow (ending a round, -skipping to the next or previous track, restarting or replaying -a track), affect the server settings (player/spectator limits, -TMF panels, etc), manage tracks and the jukebox, and much more. -Security of the admin commands and features against unauthorized -use can be warranted with a lock password and by specifying -authorized IP addresses for each admin login. See the v1.03 release notes for more information. - -
    24. Idlekick:

      -Inactive players and, optionally, spectators can be kicked -automatically at the end of each track after a specified number of -tracks by the mistral.idlekick.php plugin. Configuration -settings are included in the plugin itself. Some details can be -found in the v0.90 release notes. - -
    25. Auto TimeLimit:

      -With the plugin.autotime.php plugin enabled in TimeAttack mode, -the timelimit for each track is defined dynamically, based on the -track's author time and settings in autotime.xml. See the v1.04 release notes for details. - -
    26. Jfreu:

      -The Jfreu plugins jfreu.plugin.php and jfreu.chat.php -provide a variety of features, including player connect/disconnect -messages, informational messages, rank limiting (with VIP -exceptions), bad word filtering, and more. The first two features -are also available separately in the jfreu.lite.php -plugin in case the rest isn't needed. They are configured via -jfreu.config.php and changed options are automatically stored in -the jfreu/jfreu.config.xml file. More information is available -in the v0.91 release notes. - -
    27. TMF Music:

      -The plugin.musicserver.php plugin offers support for playing -server controlled music instead of the default game music and, -optionally, even overriding a track's own song. The /music -command includes a simple jukebox feature as well as admin options, -and .OGG comments (ID3 tags) can also be displayed. Configuration -is done in musicserver.xml, and more information can -be found in the v0.99, -v1.02 and v1.04 release notes. - -
    28. TMF Custom Rounds Points:

      -With the plugin.rpoints.php plugin enabled, admins can select -alternative points systems in Rounds mode instead of the standard -10,6,4,3,2,1. Various common systems (like Formula 1 GP and MotoGP) -are included, and completely custom systems can be defined as well. -See the v1.04 release notes for -details. - -
    29. TMF Styles & Panels:

      -The main output window can be dynamically customized with a large -number of style templates from the styles/ subdirectory, managed -by the plugin.style.php plugin. The plugin.panels.php -plugin provides four panels: the admin panel to easily activate a -handful of the most commonly used admin commands; the donate panel -to easily initiate a donation with a variety of copper amounts; the -records panel to display a track's relevant records; and a temporary -vote panel to easily respond to chat-based votes. Each panel -can be dynamically customized with a number of templates from the -panels/ subdirectory. All players' style and panel preferences -are stored, and reloaded upon every visit. Detailed information -can be found in the v1.00, -v1.01 and v1.06 release notes. - -
    30. TMF Stats Panels:

      -Instead of the rank chat messages at the end of each track, on TMF -a personal stats panel can be displayed for each player during the -scoreboard. The stats are: server rank, record average, records total, -wins total, session play time, and donation total (on TMUF servers). -Details are included in the v1.06 release -notes. - -
    31. TMF Message Window:

      -When enabled via plugin.msglog.php, the system message window -near the top of the screen can be used to temporarily display a wide -variety of global messages that normally flood the chat window. -These include new/improved/equalled records, record reports -before and after each track, rounds reports, Jfreu Info messages -and several more, all individually configured in their pertaining -config files. See the v1.02 and v1.03 release notes for more information. - -
    32. TMUF Coppers:

      -Copper transactions are managed by the plugin.donate.php -plugin, allowing players to donate coppers to the server, and -admins (with the appropriate ability) to pay coppers to any -other login (including another server). More information is -available in the v0.99 and v1.01 release notes. - -
    - -
    -
    -
    -Copyright © 2007-2013 – Frans P. de Vries <tm@gamers.org> -            -Last updated 08-Aug-2010 -
    - - diff --git a/xaseco/DOCS/quickstart_tmf.html b/xaseco/DOCS/quickstart_tmf.html deleted file mode 100644 index a9a7423..0000000 --- a/xaseco/DOCS/quickstart_tmf.html +++ /dev/null @@ -1,254 +0,0 @@ - - - -TrackMania Forever - TMF & XASECO quick start guide - - - - - - - - - - -

    -TrackMania Forever -


    - -

    Quick Start Guide:

    - -This is step-by-step guide to setting up a TrackMania Forever (TMF) dedicated server with XASECO for those lost in or confused by the available readme's and tutorials, and those just too lazy to read them. ;-) - -

    -It's written for Linux but should largely apply to Windows too, and your system needs to have a working MySQL 4.x or 5.x and PHP 5.x setup. To manage the database, enter the MySQL commands in the mysql command prompt, PhpMyAdmin or another tool of your choice. You need to run the TMF server and XASECO on the same machine, running them on separate machines is beyond the scope of this guide. - -

    -First, the dedicated server: - -

      - -
    1. Ideally, the TMF server and XASECO run under a user account rather than root, so create a user e.g. "tmf" with home directory "/home/tmf". - -
    2. Login as (or switch to) user "tmf" so that all files created down the road receive the correct ownership and permissions. - -
    3. The TMF server zip doesn't have a top-level directory, so create one first to keep all the server files together, e.g.:
      -
      -    mkdir TMF
      -    cd TMF
      -
      - -
    4. Download and unzip the latest TMF server (TrackmaniaServer_2011-02-21.zip) into this TMF/ directory. - -
    5. Create a dedicated login: -
        -
      1. for TM Nations Forever, use your game client to create a new dedicated server login (note that this server won't be able to use coppers). -
      2. for TM United Forever, log into the TMF Player Page using your player login and password, and create a dedicated server login and password (different from your player password) after entering your full game key. -
      - -
    6. Edit TMF/GameData/Config/dedicated_cfg.txt: -
        -
      1. In the <authorization_levels> section, change all three passwords, but do not change the names (SuperAdmin, Admin, User). -
      2. In the <masterserver_account> section, enter the server <login> obtained above with its own <password>, and optionally the last three characters of your player key in <validation_key> if you want your TMUF server to be able to use coppers. -
      3. In the <server_options> section, give your server a <name> and configure the other options to your liking. -
      4. In the <system_config> section, set <connection_uploadrate> and <connection_downloadrate> to values closest to your connection's speed (in Kbps), and remember the three port numbers (default: 2350 for the server, 3450 for P2P and 5000 for XMLRPC; these can be changed to other free port numbers, but it's recommended to keep the defaults at least until your server comes online successfully). -
      5. For a TMNF server, <packmask> must be set to 'nations' or 'stadium'. For TMUF it can be empty or set to 'united', or to one environment like 'desert', 'island', etc, or to the three environments of the older games with 'original' or 'sunrise'. -
      - -
    7. Choose and edit a match settings file, e.g. TMF/GameData/Tracks/MatchSettings/Nations/NationsBlue.txt, to your liking (see the server readme for details). - -
    8. On your firewall/router, open the server port 2350 and the P2P port 3450 for both UDP and TCP traffic, but not the XMLRPC port 5000. For the Dedimania system in XASECO port 8002 needs to be open as well. For more information on this, PortForward.com may be useful. - -
    9. A standard startup script is no longer included in the zip, so create one looking like this in RunTrackmaniaNations.sh (and make it executable with chmod +x RunTrackmaniaNations.sh):
      -
      -    ./TrackmaniaServer /game_settings=MatchSettings/Nations/NationsBlue.txt /dedicated_cfg=dedicated_cfg.txt
      -
      - or in RunTrackmaniaNations.bat:
      -
      -    TrackmaniaServer.exe /game_settings=MatchSettings/Nations/NationsBlue.txt /dedicated_cfg=dedicated_cfg.txt
      -
      - Options /internet and /autoquit are now default, and the /game=... option (from TMN) is no longer needed due to the <packmask> setting. - -
    10. Start the server:
      -
      -    cd ~/TMF/
      -    ./RunTrackmaniaNations.sh (or RunTrackmaniaNations.bat)
      -
      - You should see output like the following:
      -
      -Starting TmForever v2011-02-21...
      -Initializing...
      -Configuration file : dedicated_cfg.txt
      -Loading system configuration...
      -...system configuration loaded
      -Loading cache...
      -...OK
      -Listening for xml-rpc commands on port 5000.
      -Trackmania server daemon started with pid=13512 (parent=13483).
      -
      - If you get a Segmentation Fault here, the server cannot create files/directories due to ownership/permission problems (perhaps you forgot step 2?). - -
    11. The first time the server starts, it creates a Default.SystemConfig.Gbx file as well as the blacklist.txt and guestlist.txt files in the GameData/Config/ directory, and a number of directories like Logs/, GameData/Cache/, GameData/Profiles/, GameData/Scores/, GameData/Tracks/Campaigns/, GameData/Tracks/Replays/, GameData/Tracks/Challenges/Downloaded/ and GameData/Tracks/Challenges/My Challenges/. The files in the Logs/ directory are useful to monitor your server's activity. - -
    12. Start your TMF client and check in the Internet server browser that the server is running in your zone with your chosen server name and the Nadeo Advanced tracks. - -
    13. Join your server, and have a friend join from elsewhere on the Internet, to verify that the server is accessible. - -
    14. Ignore the private network warning that is always logged in ConsoleLog.*.txt. - -
    15. To start & stop your TMF server on Linux more easily, you can use this start-up script. - -
    16. Collect your own tracks in GameData/Tracks/Challenges/My Challenges/, copy and edit a new match settings file in GameData/Tracks/MatchSettings/ that lists those tracks, use that file in RunTrackmaniaNations.sh (or RunTrackmaniaNations.bat), and restart your server. It should now be running your track selection. Congratulations. :-) - -
    - - -Secondly, the XASECO system: - -
      - -
    1. Create the XASECO database in MySQL (default "aseco" but any other name is okay too):
      -
      -    CREATE DATABASE aseco;
      -
      - Also create a separate user (e.g. "tmf") in MySQL with a password, and grant this user all rights to the "aseco" database. The basic MySQL commands are:
      -
      -    CREATE USER 'tmf'@'localhost';
      -    SET PASSWORD FOR 'tmf'@'localhost' = password('password');
      -    GRANT all ON aseco.* TO 'tmf'@'localhost'; -
      - -
    2. Login as (or switch to) user "tmf" so that all files created down the road receive the correct ownership and permissions. - -
    3. Download and unzip XASECO (latest version) into the "/home/tmf/" directory too, the default path will be xaseco/ so that future releases can be unpacked into the same directory tree.
      - In the zip file, all *.XML and config files are located inside the newinstall/ directory. Go into the newinstall/ directory and move all *.XML files into the main directory (next to aseco.php), and move all *.PHP files into the includes/ directory.
      - Also, move AsecoF.sh (on Linux) or Aseco.bat (on Windows) into the main directory and adjust it to your situation if necessary. - -
    4. Setting up the database tables in MySQL is done automatically the first time XASECO runs, but if you prefer you can do it manually in advance:
      -
      -    USE aseco;
      -    SOURCE /home/tmf/xaseco/localdb/aseco.sql;
      -    SOURCE /home/tmf/xaseco/localdb/rasp.sql;
      -    SOURCE /home/tmf/xaseco/localdb/extra.sql; -
      - -
    5. Edit xaseco/localdatabase.xml: -
        -
      1. Replace YOUR_MYSQL_LOGIN with the MySQL user you created above, e.g. tmf. -
      2. Replace YOUR_MYSQL_PASSWORD with the MySQL password you set above. -
      3. Use the same database name as you created above, e.g. aseco. -
      4. localhost is your own machine, so the server option is okay. -
      - -
    6. Edit xaseco/config.xml: -
        -
      1. In the <masteradmins> section, uncomment and replace YOUR_MASTERADMIN_LOGIN with your player login, and add further logins for other players you want to grant all XASECO admin rights. -
      2. In the <tmserver> section, replace YOUR_SUPERADMIN_PASSWORD with the password you chose for SuperAdmin in dedicated_cfg.txt (TMF step 6a above) but do not change the SuperAdmin login itself. -
      3. The <port> field should contain the same XMLRPC port number you chose in dedicated_cfg.txt (TMF step 6d above), default 5000. -
      4. IP 127.0.0.1 is your own machine again, so that option is okay too. -
      - -
    7. Edit xaseco/adminops.xml: -
        -
      1. In the <admins> section, uncomment and replace YOUR_ADMIN_LOGIN with an admin's login, and add further logins for other players you want to grant partial XASECO admin rights. Or leave the <tmlogin> entry commented out if there are none. -
      2. In the <operators> section, uncomment and replace YOUR_OPERATOR_LOGIN with an operator's login, and add further logins for other players you want to grant XASECO operator rights. Or leave the <tmlogin> entry commented out if there are none. -
      - -
    8. Edit xaseco/dedimania.xml if you want to use the Dedimania world records system: -
        -
      1. In the <masterserver_account> section, replace YOUR_SERVER_LOGIN and YOUR_SERVER_PASSWORD with the <login> and <password> values from your dedicated_cfg.txt file. -
      2. Also, replace YOUR_SERVER_NATION with your 3-character nation abbreviation. -
      3. Instead of the password you can also enter the community code for your server which can be obtained by using the server login/password on this page for TMF. -
      4. To disable Dedimania support, remove or comment out the chat.dedimania.php and plugin.dedimania.php entries in plugins.xml. -
      - -
    9. Start the XASECO system:
      -
      -    cd ~/xaseco/
      -    ./Aseco.sh (or Aseco.bat)
      -
      - You won't see output, but logfile.txt should contain something like the following:
      -
      -[XAseco] PHP Version is 5.3.x on Linux
      -[XAseco] Load settings [config.xml]
      -[XAseco] Load admin/ops lists [adminops.xml]
      -[XAseco] Load banned IPs list [bannedips.xml]
      -[XAseco] Load plugins list [plugins.xml]
      -[XAseco] Load plugin [plugin.localdatabase.php]
      -[XAseco] Load plugin [plugin.rounds.php]
      -[...snip plugins...]
      -[XAseco] Load plugin [jfreu.plugin.php]
      -[XAseco] Load plugin [mistral.idlekick.php]
      -[XAseco] Try to connect to TM dedicated server on 127.0.0.1:5000 timeout 180s
      -[XAseco] Try to authenticate with username 'SuperAdmin' and password 'PASSWORD'
      -[XAseco] Connection established successfully!
      -[Local DB] Load settings file
      -[Local DB] Try to connect to MySQL server on 'localhost' with database 'aseco'
      -[Local DB] MySQL Server Version is 5.1.56-log
      -[RASP] Cleaning up unused data
      -*-*-*-*-*-* RASP is running! *-*-*-*-*-*
      -|...Loading Settings
      -|...Loaded!
      -|...Checking database structure
      -|...Structure OK!
      -|...Calculating ranks
      -|...Done!
      -[04/xx,xx:11:17] Load default style [styles/DarkBlur.xml]
      -[04/xx,xx:11:17] Load default admin panel [panels/AdminBelowChat.xml]
      -[04/xx,xx:11:17] Load default donate panel [panels/DonateBelowCPList.xml]
      -[04/xx,xx:11:17] Load default records panel [panels/RecordsRightBottom.xml]
      -[04/xx,xx:11:17] Load default vote panel [panels/VoteBelowChat.xml]
      -[04/xx,xx:11:17] ************* (Dedimania) *************
      -[04/xx,xx:11:17] * Dataserver connection on Dedimania ...
      -[04/xx,xx:11:17] * Try connection on http://dedimania.net:8002/Dedimania ...
      -[04/xx,xx:11:18] Webaccess (dedimania.net:80): send: deflate, receive: gzip
      -[04/xx,xx:11:18] * Connection and status ok! :)
      -[04/xx,xx:11:18] * NEWS (Dedimania, 08/05): news
      -[04/xx,xx:11:18] ------------- (Dedimania) -------------
      -[04/xx,xx:11:18] Load stats panel [panels/StatsNations.xml]
      -[04/xx,xx:11:18] Load auto timelimit config [autotime.xml]
      -###############################################################################
      -  XASECO v1.1x running on 127.0.0.1:5000
      -  Name   : YOUR SERVER NAME - YOUR_SERVER_LOGIN
      -  Game   : TmForever United - Stadium - TimeAttack
      -  Version: 2.11.26 / 2011-02-21
      -  Authors: Florian Schnell & Assembler Maniac
      -  Re-Authored: Xymph
      -###############################################################################
      -Begin Race
      -[04/xx,xx:11:18] track changed [none] >> [Pro - 38~74~75~89~93]
      -[04/xx,xx:11:18] currently no record on Pro - 38~74~75~89~93
      -
      - If you get an RPC Permission Error here, there is an XMLRPC port mismatch or the dedicated server isn't running (anymore). - -
    10. To start & stop your XASECO on Linux more easily, you can use this start-up script. - -
    11. Edit the configuration options to your liking, and restart XASECO. It should now be ready to manage tracks, receive players, and record... er... records. Congratulations. :-) -
    - -Finally, to run another server on the same machine: - -
      - -
    1. Basically follow the same steps above, but use a second user account (e.g. "tmf2"), another new server login/password in dedicated_cfg.txt, a separate server name, a second set of ports (e.g. 2351, 3451 and 5001), a new database (e.g. "aseco2"), optionally a second MySQL account (e.g. "tmf2"), and the corresponding updates in localdatabase.xml and config.xml. - -
    2. Don't use symbolic links in the GameData/ directory tree (e.g. to symlink the tracks from the first server to the second one), as the TMF server will crash without an error message. - -
    - -And for the last time, read the readme's and tutorials completely for a more thorough understanding of the entire setup. -

    - -
    -
    -Copyright © 2007-2013 – Frans P. de Vries <tm@gamers.org> -            -Last updated 08-May-2013 -
    - - diff --git a/xaseco/DOCS/quickstart_tmn.html b/xaseco/DOCS/quickstart_tmn.html deleted file mode 100644 index 9fb4bde..0000000 --- a/xaseco/DOCS/quickstart_tmn.html +++ /dev/null @@ -1,226 +0,0 @@ - - - -TrackMania Nations - TMN & XASECO quick start guide - - - - - - - - - - -

    -TrackMania Nations -


    - -

    Quick Start Guide:

    - -This is step-by-step guide to setting up a classic TrackMania Nations (TMN) dedicated server with XASECO for those lost in or confused by the available readme's and tutorials, and those just too lazy to read them. ;-) - -

    -It's written for Linux but should largely apply to Windows too, and your system needs to have a working MySQL 4.x or 5.x and PHP 5.x setup. To manage the database, enter the MySQL commands in the mysql command prompt, PhpMyAdmin or another tool of your choice. You need to run the TMN server and XASECO on the same machine, running them on separate machines is beyond the scope of this guide. - -

    -First, the dedicated server: - -

      - -
    1. Ideally, the TMN server and XASECO run under a user account rather than root, so create a user e.g. "tmn" with home directory "/home/tmn". - -
    2. Login as (or switch to) user "tmn" so that all files created down the road receive the correct ownership and permissions. - -
    3. Download and unzip the final TMN server (TmDedicatedServer_2006-05-30.zip) into this home directory, the default path will be TmDedicatedServer/. - -
    4. Edit TmDedicatedServer/dedicated.cfg: -
        -
      1. In the <authorization_levels> section, change all three passwords, but do not change the names (SuperAdmin, Admin, User). -
      2. In the <masterserver_account> section, enter a new server <login> (different from your player login) with its own <password> (again different) and an appropriate and valid three-letter <nation>. If you're not sure your chosen login is new, use your TMN client to test it first, in both cases the account will be registered automatically with the masterserver. -
      3. In the <server_options> section, give your server a <name> and configure the other options to your liking. -
      4. In the <system_config> section, set <connection_type> to a value closest to your connection's speed (see the server readme for correct values), and remember the three port numbers (default: 2350 for the server, 3450 for P2P and 5000 for XMLRPC; these can be changed to other free port numbers, but it's recommended to keep the defaults at least until your server comes online successfully). -
      - -
    5. Edit the default match settings file TmDedicatedServer/GameData/Tracks/MatchSettings/Internet/AdvancedTraining.txt to your liking (again, see the server readme for details). - -
    6. On your firewall/router, open the server port 2350 and the P2P port 3450 for both UDP and TCP traffic, but not the XMLRPC port 5000. For the Dedimania system in XASECO port 8002 needs to be open as well. For more information on this, PortForward.com may be useful. - -
    7. Start the server:
      -
      -    cd ~/TmDedicatedServer/
      -    ./RunTrackmaniaNations.sh (or RunTrackmaniaNations.bat)
      -
      - You should see output like the following:
      -
      -Starting TmNationsESWC v2006-05-30...
      -Unable to open '/home/tmn/TmDedicatedServer/Default.SystemConfig.GbxInitializing...
      -Configuration file : dedicated.cfg
      -Loading system configuration...
      -...system configuration loaded
      -Loading cache...
      -...OK
      -Listening for xml-rpc commands on port 5000.
      -Trackmania server daemon started with pid=28936 (parent=28935).
      -
      - If you get a Segmentation Fault here, the server cannot create files/directories due to ownership/permission problems (perhaps you forgot step 2?). - -
    8. The first time the server starts, it creates that Default.SystemConfig.Gbx file, as well as the blacklist.txt and guestlist.txt files and a number of directories like Logs/, GameData/Cache/, GameData/Profiles/, GameData/Scores/, GameData/Tracks/Campaigns/, GameData/Tracks/Replays/, GameData/Tracks/Challenges/Downloaded/ and GameData/Tracks/Challenges/My Challenges/. The files in the Logs/ directory are useful to monitor your server's activity. - -
    9. Start your TMN client and check in the Internet server browser that the server is running in your Nation with your chosen server name and the Nadeo Advanced tracks. - -
    10. Join your server, and have a friend join from elsewhere on the Internet, to verify that the server is accessible. - -
    11. Ignore the private network warning that is always logged in ConsoleLog.*.txt. - -
    12. To start & stop your TMN server on Linux more easily, you can use this start-up script. - -
    13. Collect your own tracks in GameData/Tracks/Challenges/My Challenges/, copy and edit a new match settings file in GameData/Tracks/MatchSettings/Internet/ that lists those tracks, use that file in RunTrackmaniaNations.sh (or RunTrackmaniaNations.bat), and restart your server. It should now be running your track selection. Congratulations. :-) - -
    - - -Secondly, the XASECO system: - -
      - -
    1. Create the XASECO database in MySQL (default "aseco" but any other name is okay too):
      -
      -    CREATE DATABASE aseco;
      -
      - Also create a separate user (e.g. "tmn") in MySQL with a password, and grant this user all rights to the "aseco" database. The basic MySQL commands are:
      -
      -    CREATE USER 'tmn'@'localhost';
      -    SET PASSWORD FOR 'tmn'@'localhost' = password('password');
      -    GRANT all ON aseco.* TO 'tmn'@'localhost'; -
      - -
    2. Login as (or switch to) user "tmn" so that all files created down the road receive the correct ownership and permissions. - -
    3. Download and unzip XASECO (latest version) into the "/home/tmn/" directory too, the default path will be xaseco/ so that future releases can be unpacked into the same directory tree.
      - In the zip file, all *.XML and config files are located inside the newinstall/ directory. Go into the newinstall/ directory and move all *.XML files into the main directory (next to aseco.php), and move all *.PHP files into the includes/ directory.
      - Also, move Aseco.sh (on Linux) or Aseco.bat (on Windows) into the main directory and adjust it to your situation if necessary. - -
    4. Setting up the database tables in MySQL is done automatically the first time XASECO runs, but if you prefer you can do it manually in advance:
      -
      -    USE aseco;
      -    SOURCE /home/tmn/xaseco/localdb/aseco.sql;
      -    SOURCE /home/tmn/xaseco/localdb/rasp.sql;
      -    SOURCE /home/tmn/xaseco/localdb/extra.sql; -
      - -
    5. Edit xaseco/localdatabase.xml: -
        -
      1. Replace YOUR_MYSQL_LOGIN with the MySQL user you created above, e.g. tmn. -
      2. Replace YOUR_MYSQL_PASSWORD with the MySQL password you set above. -
      3. Use the same database name as you created above, e.g. aseco. -
      4. localhost is your own machine, so the server option is okay. -
      - -
    6. Edit xaseco/config.xml: -
        -
      1. In the <masteradmins> section, uncomment and replace YOUR_MASTERADMIN_LOGIN with your player login, and add further logins for other players you want to grant all XASECO admin rights. -
      2. In the <tmserver> section, replace YOUR_SUPERADMIN_PASSWORD with the password you chose for SuperAdmin in dedicated.cfg (TMN step 4a above) but do not change the SuperAdmin login itself. -
      3. The <port> field should contain the same XMLRPC port number you chose in dedicated.cfg (TMN step 4d above), default 5000. -
      4. IP 127.0.0.1 is your own machine again, so that option is okay too. -
      - -
    7. Edit xaseco/adminops.xml: -
        -
      1. In the <admins> section, uncomment and replace YOUR_ADMIN_LOGIN with an admin's login, and add further logins for other players you want to grant partial XASECO admin rights. Or leave the <tmlogin> entry commented out if there are none. -
      2. In the <operators> section, uncomment and replace YOUR_OPERATOR_LOGIN with an operator's login, and add further logins for other players you want to grant XASECO operator rights. Or leave the <tmlogin> entry commented out if there are none. -
      - -
    8. Edit xaseco/dedimania.xml if you want to use the Dedimania world records system: -
        -
      1. In the <masterserver_account> section, replace YOUR_SERVER_LOGIN and YOUR_SERVER_PASSWORD with the <login> and <password> values from -your dedicated.cfg file. -
      2. Also, replace YOUR_SERVER_NATION with the <nation> value. -
      3. Instead of the password you can also enter the community code for your server which can be obtained by using the server login/password on the official site for your game (TMO/TMS/TMN). -
      4. To disable Dedimania support, remove or comment out the chat.dedimania.php and plugin.dedimania.php entries in plugins.xml. -
      - -
    9. Start the XASECO system:
      -
      -    cd ~/xaseco/
      -    ./Aseco.sh (or Aseco.bat)
      -
      - You won't see output, but logfile.txt should contain something like the following:
      -
      -[XAseco] PHP Version is 5.3.x on Linux
      -[XAseco] Load settings [config.xml]
      -[XAseco] Load admin/ops lists [adminops.xml]
      -[XAseco] Load banned IPs list [bannedips.xml]
      -[XAseco] Load plugins list [plugins.xml]
      -[XAseco] Load plugin [plugin.localdatabase.php]
      -[XAseco] Load plugin [plugin.rounds.php]
      -[...snip plugins...]
      -[XAseco] Load plugin [jfreu.plugin.php]
      -[XAseco] Load plugin [mistral.idlekick.php]
      -[XAseco] Try to connect to TM dedicated server on 127.0.0.1:5000 timeout 180s
      -[XAseco] Try to authenticate with username 'SuperAdmin' and password 'PASSWORD'
      -[XAseco] Connection established successfully!
      -[Local DB] Load settings file
      -[Local DB] Try to connect to MySQL server on 'localhost' with database 'aseco'
      -[Local DB] MySQL Server Version is 5.1.56-log
      -[RASP] Cleaning up unused data
      -*-*-*-*-*-* RASP is running! *-*-*-*-*-*
      -|...Loading Settings
      -|...Loaded!
      -|...Checking database structure
      -|...Structure OK!
      -|...Calculating ranks
      -|...Done!
      -[12/xx,xx:11:17] ************* (Dedimania) *************
      -[12/xx,xx:11:17] * Dataserver connection on Dedimania ...
      -[12/xx,xx:11:17] * Try connection on http://dedimania.net:8002/Dedimania ...
      -[12/xx,xx:11:18] Webaccess (dedimania.net:80): send: deflate, receive: gzip
      -[12/xx,xx:11:18] * Connection and status ok! :)
      -[12/xx,xx:11:18] * NEWS (Dedimania, 08/05): news
      -[12/xx,xx:11:18] ------------- (Dedimania) -------------
      -[12/xx,xx:11:18] Load auto timelimit config [autotime.xml]
      -###############################################################################
      -  XASECO v1.1x running on 127.0.0.1:5000
      -  Name   : YOUR SERVER NAME
      -  Game   : TmNationsESWC - TimeAttack
      -  Version: 0.1.7.4 / 2006-05-30
      -  Authors: Florian Schnell & Assembler Maniac
      -  Re-Authored: Xymph
      -###############################################################################
      -Begin Race
      -[12/xx,xx:11:18] track changed [none] >> [Pro - 38~74~75~89~93]
      -[12/xx,xx:11:18] currently no record on Pro - 38~74~75~89~93
      -
      - If you get an RPC Permission Error here, there is an XMLRPC port mismatch or the dedicated server isn't running (anymore). - -
    10. To start & stop your XASECO on Linux more easily, you can use this start-up script. - -
    11. Edit the configuration options to your liking, and restart XASECO. It should now be ready to manage tracks, receive players, and record... er... records. Congratulations. :-) -
    - -Finally, to run another server on the same machine: - -
      - -
    1. Basically follow the same steps above, but use a second user account (e.g. "tmn2"), another new server login/password in dedicated.cfg, a separate server name, a second set of ports (e.g. 2351, 3451 and 5001), a new database (e.g. "aseco2"), optionally a second MySQL account (e.g. "tmn2"), and the corresponding updates in localdatabase.xml and config.xml. - -
    2. Don't use symbolic links in the GameData/ directory tree (e.g. to symlink the tracks from the first server to the second one), as the TMN server will crash without an error message. - -
    - -And for the last time, read the readme's and tutorials completely for a more thorough understanding of the entire setup. -

    - -
    -
    -Copyright © 2007-2013 – Frans P. de Vries <tm@gamers.org> -            -Last updated 08-May-2013 -
    - - diff --git a/xaseco/DOCS/repairnations.php b/xaseco/DOCS/repairnations.php deleted file mode 100644 index ac95b26..0000000 --- a/xaseco/DOCS/repairnations.php +++ /dev/null @@ -1,432 +0,0 @@ -#!/usr/bin/php -q - - - if (!mysql_connect('localhost','YOUR_MYSQL_LOGIN','YOUR_MYSQL_PASSWORD')) { - echo "could not connect\n"; - exit; - } - if (!mysql_select_db('aseco')) { - echo "could not select\n"; - exit; - } - - // get all unique Nation strings - $query = 'SELECT Nation FROM players'; - $resply = mysql_query($query); - - $nations = array(); - if (mysql_num_rows($resply) > 0) { - while ($rowply = mysql_fetch_object($resply)) { - $nations[] = $rowply->Nation; - } - mysql_free_result($resply); - } else { - echo "no players!\n"; - } - $uniques = array_unique($nations); - - echo 'Unique nations: ' . count($uniques) . "\n\n"; - - $count = 0; - foreach ($uniques as $oldnation) { - if (strlen($oldnation) == 0) - $newnation = 'OTH'; // default OTH(ER) for empty nations - // check for full, capitalized country name - elseif (strlen($oldnation) > 3) - $newnation = mapCountry($oldnation); - // check for trunctated, capitalized country name - elseif ($oldnation != strtoupper($oldnation)) - $newnation = mapAbbrev($oldnation); - else // already all-caps abbreviation - continue; - - // update Nation with 3-letter abbreviation - $query = 'UPDATE players - SET Nation="' . $newnation . '" - WHERE Nation="' . $oldnation . '"'; - $result = mysql_query($query); - $count++; - } - echo 'Updated nations: ' . $count . "\n"; - -/** - * Map country names to 3-letter Nation abbreviations - * Created by Xymph - * Based on http://en.wikipedia.org/wiki/List_of_IOC_country_codes - */ -function mapCountry($country) { - - $nations = array( - 'Afghanistan' => 'AFG', - 'Albania' => 'ALB', - 'Algeria' => 'ALG', - 'Andorra' => 'AND', - 'Angola' => 'ANG', - 'Argentina' => 'ARG', - 'Armenia' => 'ARM', - 'Aruba' => 'ARU', - 'Australia' => 'AUS', - 'Austria' => 'AUT', - 'Azerbaijan' => 'AZE', - 'Bahamas' => 'BAH', - 'Bahrain' => 'BRN', - 'Bangladesh' => 'BAN', - 'Barbados' => 'BAR', - 'Belarus' => 'BLR', - 'Belgium' => 'BEL', - 'Belize' => 'BIZ', - 'Benin' => 'BEN', - 'Bermuda' => 'BER', - 'Bhutan' => 'BHU', - 'Bolivia' => 'BOL', - 'Bosnia&Herzegovina' => 'BIH', - 'Botswana' => 'BOT', - 'Brazil' => 'BRA', - 'Brunei' => 'BRU', - 'Bulgaria' => 'BUL', - 'Burkina Faso' => 'BUR', - 'Burundi' => 'BDI', - 'Cambodia' => 'CAM', - 'Cameroon' => 'CAR', // actually CMR - 'Canada' => 'CAN', - 'Cape Verde' => 'CPV', - 'Central African Republic' => 'CAF', - 'Chad' => 'CHA', - 'Chile' => 'CHI', - 'China' => 'CHN', - 'Chinese Taipei' => 'TPE', - 'Colombia' => 'COL', - 'Congo' => 'CGO', - 'Costa Rica' => 'CRC', - 'Croatia' => 'CRO', - 'Cuba' => 'CUB', - 'Cyprus' => 'CYP', - 'Czech Republic' => 'CZE', - 'Czech republic' => 'CZE', - 'DR Congo' => 'COD', - 'Denmark' => 'DEN', - 'Djibouti' => 'DJI', - 'Dominica' => 'DMA', - 'Dominican Republic' => 'DOM', - 'Ecuador' => 'ECU', - 'Egypt' => 'EGY', - 'El Salvador' => 'ESA', - 'Eritrea' => 'ERI', - 'Estonia' => 'EST', - 'Ethiopia' => 'ETH', - 'Fiji' => 'FIJ', - 'Finland' => 'FIN', - 'France' => 'FRA', - 'Gabon' => 'GAB', - 'Gambia' => 'GAM', - 'Georgia' => 'GEO', - 'Germany' => 'GER', - 'Ghana' => 'GHA', - 'Greece' => 'GRE', - 'Grenada' => 'GRN', - 'Guam' => 'GUM', - 'Guatemala' => 'GUA', - 'Guinea' => 'GUI', - 'Guinea-Bissau' => 'GBS', - 'Guyana' => 'GUY', - 'Haiti' => 'HAI', - 'Honduras' => 'HON', - 'Hong Kong' => 'HKG', - 'Hungary' => 'HUN', - 'Iceland' => 'ISL', - 'India' => 'IND', - 'Indonesia' => 'INA', - 'Iran' => 'IRI', - 'Iraq' => 'IRQ', - 'Ireland' => 'IRL', - 'Israel' => 'ISR', - 'Italy' => 'ITA', - 'Ivory Coast' => 'CIV', - 'Jamaica' => 'JAM', - 'Japan' => 'JPN', - 'Jordan' => 'JOR', - 'Kazakhstan' => 'KAZ', - 'Kenya' => 'KEN', - 'Kiribati' => 'KIR', - 'Korea' => 'KOR', - 'Kuwait' => 'KUW', - 'Kyrgyzstan' => 'KGZ', - 'Laos' => 'LAO', - 'Latvia' => 'LAT', - 'Lebanon' => 'LIB', - 'Lesotho' => 'LES', - 'Liberia' => 'LBR', - 'Libya' => 'LBA', - 'Liechtenstein' => 'LIE', - 'Lithuania' => 'LTU', - 'Luxembourg' => 'LUX', - 'Macedonia' => 'MKD', - 'Malawi' => 'MAW', - 'Malaysia' => 'MAS', - 'Mali' => 'MLI', - 'Malta' => 'MLT', - 'Mauritania' => 'MTN', - 'Mauritius' => 'MRI', - 'Mexico' => 'MEX', - 'Moldova' => 'MDA', - 'Monaco' => 'MON', - 'Mongolia' => 'MGL', - 'Montenegro' => 'MNE', - 'Morocco' => 'MAR', - 'Mozambique' => 'MOZ', - 'Myanmar' => 'MYA', - 'Namibia' => 'NAM', - 'Nauru' => 'NRU', - 'Nepal' => 'NEP', - 'Netherlands' => 'NED', - 'New Zealand' => 'NZL', - 'Nicaragua' => 'NCA', - 'Niger' => 'NIG', - 'Nigeria' => 'NGR', - 'Norway' => 'NOR', - 'Oman' => 'OMA', - 'Other Countries' => 'OTH', - 'Pakistan' => 'PAK', - 'Palau' => 'PLW', - 'Palestine' => 'PLE', - 'Panama' => 'PAN', - 'Paraguay' => 'PAR', - 'Peru' => 'PER', - 'Philippines' => 'PHI', - 'Poland' => 'POL', - 'Portugal' => 'POR', - 'Puerto Rico' => 'PUR', - 'Qatar' => 'QAT', - 'Romania' => 'ROM', // actually ROU - 'Russia' => 'RUS', - 'Rwanda' => 'RWA', - 'Samoa' => 'SAM', - 'San Marino' => 'SMR', - 'Saudi Arabia' => 'KSA', - 'Senegal' => 'SEN', - 'Serbia' => 'SCG', // actually SRB - 'Sierra Leone' => 'SLE', - 'Singapore' => 'SIN', - 'Slovakia' => 'SVK', - 'Slovenia' => 'SLO', - 'Somalia' => 'SOM', - 'South Africa' => 'RSA', - 'Spain' => 'ESP', - 'Sri Lanka' => 'SRI', - 'Sudan' => 'SUD', - 'Suriname' => 'SUR', - 'Swaziland' => 'SWZ', - 'Sweden' => 'SWE', - 'Switzerland' => 'SUI', - 'Syria' => 'SYR', - 'Taiwan' => 'TWN', - 'Tajikistan' => 'TJK', - 'Tanzania' => 'TAN', - 'Thailand' => 'THA', - 'Togo' => 'TOG', - 'Tonga' => 'TGA', - 'Trinidad and Tobago' => 'TRI', - 'Tunisia' => 'TUN', - 'Turkey' => 'TUR', - 'Turkmenistan' => 'TKM', - 'Tuvalu' => 'TUV', - 'Uganda' => 'UGA', - 'Ukraine' => 'UKR', - 'United Arab Emirates' => 'UAE', - 'United Kingdom' => 'GBR', - 'United States of America' => 'USA', - 'Uruguay' => 'URU', - 'Uzbekistan' => 'UZB', - 'Vanuatu' => 'VAN', - 'Venezuela' => 'VEN', - 'Vietnam' => 'VIE', - 'Yemen' => 'YEM', - 'Zambia' => 'ZAM', - 'Zimbabwe' => 'ZIM', - ); - - if (array_key_exists($country, $nations)) { - $nation = $nations[$country]; - } else { - $nation = "OTH"; - } - return $nation; -} // end mapCountry - -/* - * Map truncated country names to 3-letter Nation abbreviations - */ -function mapAbbrev($abbrev) { - - $nations = array( - 'Afg' => 'AFG', - 'Alb' => 'ALB', - 'Alg' => 'ALG', - 'And' => 'AND', - 'Ang' => 'ANG', - 'Arg' => 'ARG', - 'Arm' => 'ARM', - 'Aru' => 'ARU', - 'Aus' => 'AUT', - 'Aze' => 'AZE', - 'Bah' => 'BAH', - 'Ban' => 'BAN', - 'Bar' => 'BAR', - 'Bel' => 'BEL', - 'Ben' => 'BEN', - 'Ber' => 'BER', - 'Bhu' => 'BHU', - 'Bol' => 'BOL', - 'Bos' => 'BIH', - 'Bot' => 'BOT', - 'Bra' => 'BRA', - 'Bru' => 'BRU', - 'Bul' => 'BUL', - 'Bur' => 'BDI', - 'Cam' => 'CAM', - 'Can' => 'CAN', - 'Cap' => 'CPV', - 'Cen' => 'CAF', - 'Cha' => 'CHA', - 'Chi' => 'CHN', - 'Col' => 'COL', - 'Con' => 'CGO', - 'Cos' => 'CRC', - 'Cro' => 'CRO', - 'Cub' => 'CUB', - 'Cyp' => 'CYP', - 'Cze' => 'CZE', - 'DR ' => 'COD', - 'Den' => 'DEN', - 'Dji' => 'DJI', - 'Dom' => 'DOM', - 'Ecu' => 'ECU', - 'Egy' => 'EGY', - 'El ' => 'ESA', - 'Eri' => 'ERI', - 'Est' => 'EST', - 'Eth' => 'ETH', - 'Fij' => 'FIJ', - 'Fin' => 'FIN', - 'Fra' => 'FRA', - 'Gab' => 'GAB', - 'Gam' => 'GAM', - 'Geo' => 'GEO', - 'Ger' => 'GER', - 'Gha' => 'GHA', - 'Gre' => 'GRE', - 'Gua' => 'GUA', - 'Gui' => 'GUI', - 'Guy' => 'GUY', - 'Hai' => 'HAI', - 'Hon' => 'HKG', - 'Hun' => 'HUN', - 'Ice' => 'ISL', - 'Ind' => 'IND', - 'Ira' => 'IRI', - 'Ire' => 'IRL', - 'Isr' => 'ISR', - 'Ita' => 'ITA', - 'Ivo' => 'CIV', - 'Jam' => 'JAM', - 'Jap' => 'JPN', - 'Jor' => 'JOR', - 'Kaz' => 'KAZ', - 'Ken' => 'KEN', - 'Kir' => 'KIR', - 'Kor' => 'KOR', - 'Kuw' => 'KUW', - 'Kyr' => 'KGZ', - 'Lao' => 'LAO', - 'Lat' => 'LAT', - 'Leb' => 'LIB', - 'Les' => 'LES', - 'Lib' => 'LBA', - 'Lie' => 'LIE', - 'Lit' => 'LTU', - 'Lux' => 'LUX', - 'Mac' => 'MKD', - 'Mal' => 'MAS', - 'Mau' => 'MTN', - 'Mex' => 'MEX', - 'Mol' => 'MDA', - 'Mon' => 'MNE', - 'Mor' => 'MAR', - 'Moz' => 'MOZ', - 'Mya' => 'MYA', - 'Nam' => 'NAM', - 'Nau' => 'NRU', - 'Nep' => 'NEP', - 'Net' => 'NED', - 'New' => 'NZL', - 'Nic' => 'NCA', - 'Nig' => 'NGR', - 'Nor' => 'NOR', - 'Oma' => 'OMA', - 'Oth' => 'OTH', - 'Pak' => 'PAK', - 'Pal' => 'PLE', - 'Pan' => 'PAN', - 'Par' => 'PAR', - 'Per' => 'PER', - 'Phi' => 'PHI', - 'Pol' => 'POL', - 'Por' => 'POR', - 'Pue' => 'PUR', - 'Qat' => 'QAT', - 'Rom' => 'ROM', - 'Rus' => 'RUS', - 'Rwa' => 'RWA', - 'Sam' => 'SAM', - 'San' => 'SMR', - 'Sau' => 'KSA', - 'Sen' => 'SEN', - 'Ser' => 'SCG', - 'Sie' => 'SLE', - 'Sin' => 'SIN', - 'Slo' => 'SVK', - 'Som' => 'SOM', - 'Sou' => 'RSA', - 'Spa' => 'ESP', - 'Sri' => 'SRI', - 'Sud' => 'SUD', - 'Sur' => 'SUR', - 'Swa' => 'SWZ', - 'Swe' => 'SWE', - 'Swi' => 'SUI', - 'Syr' => 'SYR', - 'Tai' => 'TWN', - 'Taj' => 'TJK', - 'Tan' => 'TAN', - 'Tha' => 'THA', - 'Tog' => 'TOG', - 'Ton' => 'TGA', - 'Tri' => 'TRI', - 'Tun' => 'TUN', - 'Tur' => 'TUR', - 'Tuv' => 'TUV', - 'Uga' => 'UGA', - 'Ukr' => 'UKR', - 'Uni' => 'USA', - 'Uru' => 'URU', - 'Uzb' => 'UZB', - 'Van' => 'VAN', - 'Ven' => 'VEN', - 'Vie' => 'VIE', - 'Yem' => 'YEM', - 'Zam' => 'ZAM', - 'Zim' => 'ZIM', - ); - - if (array_key_exists($abbrev, $nations)) { - $nation = $nations[$abbrev]; - } else { - $nation = "OTH"; - } - return $nation; -} // end mapAbbrev -?> diff --git a/xaseco/DOCS/repairrecs.php b/xaseco/DOCS/repairrecs.php deleted file mode 100644 index 84a2a3f..0000000 --- a/xaseco/DOCS/repairrecs.php +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/php -q - -// Updated Apr 2012 by Xymph - - function stripColors($str) { - return - str_replace("\0", '$$', - preg_replace( - '/\\$(?:[0-9a-f]..|[g-z]|$)/iu', '', - str_replace('$$', "\0", $str) - ) - ) - ; - } - - date_default_timezone_set(@date_default_timezone_get()); - $maxrecs = 50; - - if (!mysql_connect('localhost','YOUR_MYSQL_LOGIN','YOUR_MYSQL_PASSWORD')) { - echo "could not connect\n"; - exit; - } - if (!mysql_select_db('aseco')) { - echo "could not select\n"; - exit; - } - - $query = 'SELECT id,name FROM challenges ORDER BY id'; - $reschl = mysql_query($query); - - if (mysql_num_rows($reschl) > 0) { - echo 'Selected challenges: ' . mysql_num_rows($reschl) . "\n\n"; - - $tracks = 0; - $trackid = 0; - $add = 0; - $upd = 0; - $list = array(); - while ($rowchl = mysql_fetch_object($reschl)) { - - $query = 'SELECT playerid,score,date FROM records WHERE ChallengeID=' . $rowchl->id . - ' ORDER BY score,date LIMIT ' . $maxrecs; - $resrec = mysql_query($query); - - $query = 'SELECT DISTINCT playerid,score FROM rs_times t1 WHERE challengeid=' . $rowchl->id . - ' AND score=(SELECT MIN(t2.score) FROM rs_times t2 WHERE challengeid=' . $rowchl->id . - ' AND t1.playerid=t2.playerid) ORDER BY score,date LIMIT ' . $maxrecs; - $restms = mysql_query($query); - - if (mysql_num_rows($resrec) > 0) { - $n = 1; - while ($rowrec = mysql_fetch_object($resrec)) { - $rowtms = mysql_fetch_object($restms); - if ($rowtms === false) { - printf("%3d : %32s\t-> rec %3d: no more rs_times entries - consistency error!\n", $rowchl->id, stripColors($rowchl->name), $n); - break; - } - - // consistency check - if ($rowrec->playerid != $rowtms->playerid || - $rowrec->score != $rowtms->score) { - // fetch corresponding date/time & checkpoints - $query = 'SELECT date,checkpoints FROM rs_times WHERE challengeid=' . $rowchl->id . - ' AND playerid=' . $rowtms->playerid . ' ORDER BY score,date LIMIT 1'; - $resdat = mysql_query($query); - $rowdat = mysql_fetch_object($resdat); - mysql_free_result($resdat); - $newdat = date('Y-m-d H:i:s', $rowdat->date); - -// printf("%3d : %32s\t-> rec %3d: %5d/%5d differs from %d/%5d\n", $rowchl->id, stripColors($rowchl->name), $n, $rowrec->playerid, $rowrec->score, $rowtms->playerid, $rowtms->score); - - $query = "INSERT INTO records - (ChallengeId, PlayerId, Score, Date, Checkpoints) - VALUES - (" . $rowchl->id . ", " . $rowtms->playerid . ", " . - $rowtms->score . ", '" . $newdat . "', '" . - $rowdat->checkpoints . "')"; - $result = mysql_query($query); - - // couldn't be inserted? then player had a record already - if (mysql_affected_rows() != 1) { - - $query = "UPDATE records - SET Score=" . $rowtms->score . ", Checkpoints='" . $rowdat->checkpoints . "', Date='" . $newdat . "' - WHERE ChallengeId=" . $rowchl->id . " AND PlayerId=" . $rowtms->playerid; - $result = mysql_query($query); - - // couldn't be updated? then something's going wrong - if (mysql_affected_rows() == -1) { - echo mysql_errno() . ': ' . mysql_error() . "\n"; - exit; - } elseif (mysql_affected_rows() == 0) { -// printf("%3d : %32s\t-> rec %3d: skipped %d5/%5d %s\n", $rowchl->id, stripColors($rowchl->name), $n, $rowtms->playerid, $rowtms->score, $newdat); - } else { // mysql_affected_rows() == 1 - printf("%3d : %32s\t-> rec %3d: updated %5d/%5d %s\n", $rowchl->id, stripColors($rowchl->name), $n, $rowtms->playerid, $rowtms->score, $newdat); - $upd++; - if ($trackid != $rowchl->id) { - $trackid = $rowchl->id; - $tracks++; - } - } - } else { // mysql_affected_rows() == 1 - printf("%3d : %32s\t-> rec %3d: added %5d/%5d %s\n", $rowchl->id, stripColors($rowchl->name), $n, $rowtms->playerid, $rowtms->score, $newdat); - $add++; - if ($trackid != $rowchl->id) { - $trackid = $rowchl->id; - $tracks++; - } - } - } - $n++; - } - } - - mysql_free_result($resrec); - mysql_free_result($restms); - } - - echo "\n" . $add . ' added & ' . $upd . ' updated records on ' . $tracks . " tracks\n"; - mysql_free_result($reschl); - } else { - echo "no challenges!\n"; - } -?> diff --git a/xaseco/DOCS/sm_hub.html b/xaseco/DOCS/sm_hub.html deleted file mode 100644 index b365a88..0000000 --- a/xaseco/DOCS/sm_hub.html +++ /dev/null @@ -1,112 +0,0 @@ - - - -ShootMania Storm - - - - - - - - - - -Welcome to this humble page for: - -

    -ShootMania Storm -


    - -

    Introduction:

    - -
    -
    -This is a simple hub page for ShootMania Storm. It aims to collect in one place useful information and references to official and community sites for this new multiplayer shooter game by Nadeo. Here are relevant excerpts from the Storm press releases: -

    -"ShootMania Storm is the second offering from ManiaPlanet, following the successful launch of TrackMania 2 in 2011. For the first time in its history, Nadeo have decided to bring to FPS what made TrackMania such a phenomenon: its unique competitive spirit and a sense of fun." -

    -"In conjunction with the online competitive multiplayer, ShootMania Storm will also feature extensive map editing capabilities, allowing players to customise, design and share their creations across the community, using the soon to be released, upgraded ManiaPlanet 2.0 gaming system." - -

    -
    - - -

    Content:

    - - - - - -

    Links:

    - - - -
    -
    -
    -Copyright © 2007-2013 – Frans P. de Vries <tm@gamers.org> -            -Last updated 02-Mar-2013 -
    - - diff --git a/xaseco/DOCS/tm2_hub.html b/xaseco/DOCS/tm2_hub.html deleted file mode 100644 index 3941cce..0000000 --- a/xaseco/DOCS/tm2_hub.html +++ /dev/null @@ -1,296 +0,0 @@ - - - -TrackMania² - XASECO2 - - - - - - - - - - -Welcome to this humble page for: - -

    -TrackMania² -


    - -

    Introduction:

    - -
    -
    -This is a simple hub page for TrackMania² Canyon, Stadium and Valley. It aims to collect in one place useful information and references to official and community sites for these racing games by Nadeo. Here are relevant excerpts from their announcements: -

    -"TrackMania² Canyon will give players an adrenaline-filled experience, with insane jumps, controlled drifting and extreme speed. Driving in the Canyon is like running on a tightrope, only the fearless will succeed. It goes far beyond traditional driving games with a fully customisable world of tracks generated by the players themselves! With loops, wall rides, ramps and a multitude of other stunts, you're in for the ride of your life!" -

    -"With over 12 million players, Stadium is the all time favorite racer on PC. A classic needs to stay true to its roots. The acclaimed gameplay of Stadium remains identical: same speed, same controls, same blocks!
    -This core experience is improved by ManiaPlanet: better graphics and more tools for the community. Lots of competitions, new maps and game modes. The most creative community on PC can now benefit from ManiaPlanet cutting edge technologies." -

    -"The third game of the TrackMania² series is taking place in a lush valley with a rally gameplay. Reaching a level of detail never achieved on a TrackMania game, the valley environment will blow you away at first glance. The challenging gameplay will offer you hours and hours of fun, trying to shave milliseconds off every tight corner. Precision and control are key on this playground. Drifting is a last resort and will make you lose time." - -

    -
    - -

    XASECO2:

    - -
    -
    -This page is also the initial home to XASECO2, a port for -ManiaPlanet / TM² of the popular server controller for TM Forever -and previous TM games. - -

    -For a high-level outline of all of XASECO2's features and plugins, see -the Overview page. For a comprehensive -overview of the changes, see the v0.90 -initial release notes and the v0.93 - -v1.03 current release notes. - -

    -And here is a complete overview of all available commands -in HTML and Word. - -

    -
    - - -

    Content:

    - - - -

    TM² Downloads:

    - - - -

    TM² Versions:

    - - - -

    Installation:

    -
      -
    • New installation of v1.03:

      -See the XASECO2 Installation page on XAseco.org, -or go directly to the TM² & XASECO2 quick start guide. -

      - - -
    • Upgrading from v1.02 to v1.03:

      -See the XASECO2 Upgrade page on XAseco.org for general instructions. -

      - -The following files were updated in v1.03: xaseco2.php, dedimania.xml, -includes/gbxdatafetcher.inc.php, mxinfosearcher.inc.php, -rasp.funcs.php, rasp.settings.php, plugins/chat.dedimania.php, -plugin.checkpoints.php, plugin.dedimania.php, plugin.rasp.php. -

      - -Important: -
        -
      • To configure your server for the central Dedimania -database, you must copy the login value in the -<masterserver_account> section from -your server's dedicated_cfg.txt file into the corresponding -section of the dedimania.xml file. Further, register your -server with the Dedimania system, generate a DedimaniaCode, -and add that in dedimania.xml as well. -
      • Open port 8082 on your firewall/router for communication -with the central Dedimania server. -
      • In the zip file, all *.XML and *.PHP config files are located -inside the newinstall/ directory. This means that you can (and -have to) unzip the download and replace all the PHP code files, without -worrying about overwriting your customized config files. However, for -every XML/PHP config file that was updated (see above), you must -replace your version with the one from the newinstall/ directory, or -compare them and add any new/changed configuration settings -to your version to insure the system remains working correctly. -
      • For a new installation, go into the newinstall/ directory and move -all *.XML files into the main directory (next to xaseco2.php), all -*.PHP files into the includes/ directory, and XAseco2.bat|XAseco2.sh -also into the main directory. -
      -
      - -
    • Upgrading from v0.90 to v1.02:

      - -See the archived Upgrade notes. -
    - - -

    Configuration options:

    - - - -

    Versions:

    - - - -

    Download:

    - - - -

    Feedback/questions:

    - - - - -

    Links:

    - - - -
    -
    -
    -Copyright © 2007-2013 – Frans P. de Vries <tm@gamers.org> -            -Last updated 29-Jun-2013 -
    - - diff --git a/xaseco/DOCS/tmf_hub.html b/xaseco/DOCS/tmf_hub.html deleted file mode 100644 index 34e5b4a..0000000 --- a/xaseco/DOCS/tmf_hub.html +++ /dev/null @@ -1,127 +0,0 @@ - - - -TrackMania Forever - - - - - - - - - - -Welcome to this humble page for: - -

    -TrackMania Forever -


    - -

    Introduction:

    - -
    -
    -This is a simple hub page for TrackMania Forever, both Nations and United. It aims to collect in one place useful information and references to official and community sites for these upgraded racing games by Nadeo. Here are relevant excerpts from the Forever press releases: -

    -"TrackMania Nations Forever offers a new complete 'Forever' version of the Stadium environment, a complete solo mode and 65 brand new, progressively difficult tracks. TrackMania Nations Forever will unite an even larger number of players thanks to its engaging multiplayer modes, innovative online functions and revolutionary interactivity between players." -

    -"The release of TrackMania Nations Forever will also allow the convergence of the huge TrackMania community. For the first time, players of the free versions will be able to play online with players from the retail version of TrackMania United on servers dedicated to the Stadium environment that is common to both games. Players of the TrackMania United retail version also gain a brilliant free extension named 'United Forever'. In addition to being compatible with Nations Forever, this extension offers many bonuses - including tracks that have never been seen before, new design blocks for the game environments and a spectacular graphics update for the three historic environments Desert, Snow and Rally." -

    -"The TrackMania Forever add-on also features a new 3D function that enhances the gameplay experience by optionally displaying the game in three dimensions when players wear special 3D glasses. Glasses will be provided in every new retail version of TrackMania United Forever. In order to offer even more interactivity, TrackMania United Forever lets player create and manage groups of friends in-game, much in the same way they would in Facebook or Myspace." -

    -
    - - -

    Content:

    - - - -

    TMF Downloads:

    - - - -

    TMF Versions:

    - - - -

    Links:

    - - - -
    -
    -
    -Copyright © 2007-2013 – Frans P. de Vries <tm@gamers.org> -            -Last updated 26-Jul-2013 -
    - - diff --git a/xaseco/DOCS/updatepanels.php b/xaseco/DOCS/updatepanels.php deleted file mode 100644 index 1426299..0000000 --- a/xaseco/DOCS/updatepanels.php +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/php -q - - - $panelpath = '/home/tmf/aseco/panels'; - - if (!isset($argv[1]) || !isset($argv[2])) { - echo 'usage: ' . basename($argv[0]) . ' {admin|donate|records|vote} PanelName' . "\n"; - exit; - } - if (!file_exists($panelpath)) { - echo "Panel path '$panelpath' not found\n"; - exit; - } - if ($argv[1] != 'admin' && $argv[1] != 'donate' && - $argv[1] != 'records' && $argv[1] != 'vote') { - echo "unknown panel type\n"; - exit; - } - $panelpath = rtrim($panelpath, '/'); - if (!file_exists($panelpath . '/' . ucfirst($argv[1]) . $argv[2] . '.xml')) { - echo "unknown panel name\n"; - exit; - } - - if (!mysql_connect('localhost','YOUR_MYSQL_LOGIN','YOUR_MYSQL_PASSWORD')) { - echo "could not connect\n"; - exit; - } - if (!mysql_select_db('aseco')) { - echo "could not select\n"; - exit; - } - - $query = 'SELECT PlayerID,Panels FROM players_extra ORDER BY PlayerID'; - $resply = mysql_query($query); - - if (mysql_num_rows($resply) > 0) { - echo 'Updating players_extra entries: ' . mysql_num_rows($resply) . " ...\n"; - - while ($rowply = mysql_fetch_object($resply)) { - $panels = explode('/', $rowply->Panels); - switch ($argv[1]) { - case 'admin': - $panels[0] = ucfirst($argv[1]) . $argv[2]; - break; - case 'donate': - $panels[1] = ucfirst($argv[1]) . $argv[2]; - break; - case 'records': - $panels[2] = ucfirst($argv[1]) . $argv[2]; - break; - case 'vote': - $panels[3] = ucfirst($argv[1]) . $argv[2]; - break; - } - - $query = "UPDATE players_extra SET Panels = '" . implode('/', $panels) . "' WHERE PlayerID = " . $rowply->PlayerID; - $result = mysql_query($query); - if (mysql_affected_rows() == -1) { - mysql_free_result($resply); - echo "couldn't update panels for player ID " . $rowply->PlayerID . ":\n"; - echo mysql_error() . "\n"; - exit; - } - } - echo "Done\n"; - - mysql_free_result($resply); - } else { - echo "no players_extra!\n"; - } -?> diff --git a/xaseco/DOCS/upgrades.html b/xaseco/DOCS/upgrades.html deleted file mode 100644 index a3abf5c..0000000 --- a/xaseco/DOCS/upgrades.html +++ /dev/null @@ -1,1128 +0,0 @@ - - - -TrackMania Nations - XASECO upgrade history - - - - - - - - - - -

    -TrackMania Nations -


    - -

    Earlier upgrades:

    - -
      -
    • Upgrading from v1.15 to v1.15b:

      - -The following files were updated in v1.15b: aseco.php, -includes/gbxdatafetcher.inc.php, rasp.funcs.php, -tmxinfofetcher.inc.php, tmxinfosearcher.inc.php, -plugins/chat.songmod.php, plugin.autotime.php, plugin.track.php, -plugin.tmxinfo.php. -

      - -Important: -
        -
      • See the installation notes for upgrading to v1.15. -
      -
      - -
    • Upgrading from v1.14 to v1.15:

      - -The following files were updated in v1.15: aseco.php, config.xml, -dedimania.xml, rasp.xml, includes/GbxRemote.bem.php, GbxRemote.inc.php, -basic.inc.php, gbxdatafetcher.inc.php, jfreu.config.php, -manialinks.inc.php, ogg_comments.inc.php, rasp.funcs.php, -rasp.settings.php, replayparser.inc.php, tmndatafetcher.inc.php, -tmxinfofetcher.inc.php, tmxinfosearcher.inc.php, types.inc.php, -votes.config.php, web_access.inc.php, xmlparser.inc.php, -xmlrpc_db.inc.php, panels/VoteBottomCenterTransp.xml, -plugins/chat.admin.php, chat.dedimania.php, chat.players.php, -chat.players2.php, chat.records.php, chat.records2.php, -chat.recrels.php, chat.server.php, chat.stats.php, -plugin.checkpoints.php, plugin.dedimania.php, plugin.donate.php, -plugin.localdatabase.php, plugin.matchsave.php, plugin.panels.php, -plugin.rasp.php, plugin.rasp_chat.php, plugin.rasp_jukebox.php, -plugin.rasp_votes.php, plugin.rounds.php, plugin.rpoints.php, -plugin.style.php, plugin.tmxinfo.php, plugin.track.php. -

      - -Important: -
        -
      • To register your server with the central Dedimania -database, you must copy the login and password values in the -<masterserver_account> section from your -server's dedicated.cfg (TMN) or dedicated_cfg.txt (TMF) file into -the corresponding section of the dedimania.xml file, and add -the 3-character nation abbreviation. Instead of the password -you can also use the community code for your server by using -the server login/password on the official -site for your game (TMO/TMS/TMN) or on this page for -TMF. -
      • Open port 8002 on your firewall/router for communication -with the central Dedimania server. -
      • In the zip file, all *.XML and *.PHP config files are located -inside the newinstall/ directory. This means that you can (and -have to) unzip the download and replace all the PHP code files, without -worrying about overwriting your customized config files. However, for -every XML/PHP config file that was updated (see above), you must -replace your version with the one from the newinstall/ directory, or -compare them and add any new/changed configuration settings -to your version to insure the system remains working correctly. -
      • For a new installation, go into the newinstall/ directory and move -all *.XML files into the main directory (next to aseco.php), all *.PHP -files into the includes/ directory, and Aseco.bat|Aseco.sh|AsecoF.sh -also into the main directory. -
      -
      - -
    • Upgrading from v1.13 to v1.14:

      - -The following files were updated in v1.14: aseco.php, adminops.xml, -config.xml, dedimania.xml, includes/GbxRemote.bem.php, -GbxRemote.inc.php, manialinks.inc.php, rasp.funcs.php, -tmndatafetcher.inc.php, types.inc.php, xmlparser.inc.php, -xmlrpc_db.inc.php, plugins/chat.admin.php, chat.dedimania.php, -chat.records.php, chat.records2.php, chat.recrels.php, chat.server.php, -plugin.checkpoints.php, plugin.dedimania.php, plugin.localdatabase.php, -plugin.matchsave.php, plugin.rasp.php, plugin.rasp_chat.php, -plugin.rasp_irc.php, plugin.rasp_jukebox.php, plugin.rasp_karma.php, -plugin.rasp_nextmap.php, plugin.rasp_nextrank.php, plugin.rpoints.php, -plugin.track.php, plugin.uptodate.php, jfreu.chat.php, -mistral.idlekick.php. -

      - -Important: -
        -
      • See the installation notes for upgrading to v1.11. -
      -
      - -
    • Upgrading from v1.12 to v1.13:

      - -The following file was added in v1.13: chat.lastwin.php. -

      - -The following files were updated in v1.13: aseco.php, config.xml, -dedimania.xml, plugins.xml, rasp.xml, includes/GbxRemote.inc.php, -GbxRemote.bem.php, basic.inc.php, gbxdatafetcher.inc.php, -ogg_comments.inc.php, rasp.funcs.php, replayparser.inc.php, -tmndatafetcher.inc.php, tmxinfofetcher.inc.php, tmxinfosearcher.inc.php, -types.inc.php, votes.config.php, plugins/chat.admin.php, -chat.dedimania.php, chat.laston.php, chat.records2.php, chat.server.php, -chat.stats.php, plugin.checkpoints.php, plugin.localdatabase.php, -plugin.matchsave.php, plugin.musicserver.php, plugin.panels.php, -plugin.rasp.php, plugin.rasp_jukebox.php, plugin.rasp_karma.php, -plugin.rasp_votes.php, plugin.rounds.php, localdb/*.sql. -

      - -Important: -
        -
      • See the installation notes for upgrading to v1.11. -
      -
      - -
    • Upgrading from v1.11 to v1.12:

      - -The following files were updated in v1.12: aseco.php, -config.xml, dedimania.xml, rasp.xml, localdb/*.sql, -includes/GbxRemote.response.php, basic.inc.php, rasp.funcs.php, -rasp.settings.php, tmxinfofetcher.inc.php, tmxinfosearcher.inc.php, -types.inc.php, xmlrpc_db.inc.php, plugins/chat.admin.php, -chat.dedimania.php, chat.players.php, chat.players2.php, -chat.records.php, chat.records2.php, chat.recrels.php, chat.server.php, -chat.stats.php, plugin.access.php, plugin.autotime.php, -plugin.chatlog.php, plugin.checkpoints.php, plugin.dedimania.php, -plugin.donate.php, plugin.localdatabase.php, plugin.matchsave.php, -plugin.musicserver.php, plugin.muting.php, plugin.panels.php, -plugin.rasp.php, plugin.rasp_chat.php, plugin.rasp_jukebox.php, -plugin.rasp_karma.php, plugin.rasp_nextmap.php, -plugin.rasp_nextrank.php, plugin.rasp_votes.php, plugin.rpoints.php, -plugin.style.php, plugin.track.php, jfreu.chat.php. -

      - -The following files were removed from v1.12 (actually moved to the -DOCS/OLD/ directory for posterity): plugins/chat.vote.php, and the -votes.sql section of localdb/aseco.sql. -

      - -Important: -
        -
      • See the installation notes for upgrading to v1.11. -
      -
      - -
    • Upgrading from v1.10 to v1.11:

      - -The following file was renamed in v1.11: plugins/chat.song.php to -plugins/chat.songmod.php. -

      - -The following files were updated in v1.11: aseco.php, -config.xml, dedimania.xml, plugins.xml, includes/basic.inc.php, -gbxdatafetcher.inc.php, jfreu.config.php, manialinks.inc.php, -rasp.funcs.php, rasp.settings.php, tmndatafetcher.inc.php, -types.inc.php, web_access.inc.php, xmlrpc_db.inc.php, -plugins/chat.admin.php, chat.records2.php, chat.server.php, -chat.stats.php, plugin.autotime.php, plugin.chatlog.php, -plugin.checkpoints.php, plugin.dedimania.php, plugin.donate.php, -plugin.ml_howto.php, plugin.msglog.php, plugin.musicserver.php, -plugin.muting.php, plugin.panels.php, plugin.rasp.php, -plugin.rasp_chat.php, plugin.rasp_jukebox.php, plugin.rasp_karma.php, -plugin.rasp_votes.php, plugin.rpoints.php, plugin.style.php, -plugin.track.php, plugin.uptodate.php, jfreu.chat.php, jfreu.lite.php, -jfreu.plugin.php. -

      - -The following files were removed from v1.11 (actually moved to the -DOCS/OLD/ directory for posterity): includes/sminfofetcher.inc.php, -plugins/plugin.sminfo.php. -

      - -Important: -
        -
      • To register your server with the central Dedimania -database, you must copy the login and password values in the -<masterserver_account> section from your -server's dedicated.cfg (TMN) or dedicated_cfg.txt (TMF) file into -the corresponding section of the dedimania.xml file, and add -the 3-character nation abbreviation. Instead of the password -you can also use the community code for your server by using -the server login/password on the official -site for your game (TMO/TMS/TMN) or on this page for -TMF. -
      • Open port 8002 on your firewall/router for communication -with the central Dedimania server. -
      • In the zip file, all *.XML and *.PHP config files are now located -inside the newinstall/ directory. This means that you can (and -have to) unzip the download and replace all the PHP code files, without -worrying about overwriting your customized config files. However, for -every XML/PHP config file that was updated (see above), you must -replace your version with the one from the newinstall/ directory, -or compare them and add any new/changed configuration settings -to your version to insure the system remains working correctly. -
      • For a new installation, go into the newinstall/ directory and -move all *.XML files into the main directory, next to aseco.php, -and *.PHP files into the includes/ directory. -
      -
      - -
    • Upgrading from v1.09 to v1.10:

      - -The following files were added in v1.10: panels/DonateBelowCPListRM.xml, -RecordsRightBottomRM.xml. -

      - -The following files were updated in v1.10: aseco.php, adminops.xml, -config.xml, dedimania.xml, rasp.xml, includes/basic.inc.php, -rasp.funcs.php, rasp.settings.php, tmxinfofetcher.inc.php, -tmxinfosearcher.inc.php, types.inc.php, votes.config.php, -plugins/chat.admin.php, chat.dedimania.php, chat.records.php, -chat.server.php, chat.stats.php, plugin.checkpoints.php, -plugin.dedimania.php, plugin.donate.php, plugin.localdatabase.php, -plugin.panels.php, plugin.rasp.php, plugin.rasp_jukebox.php, -plugin.rasp_votes.php, plugin.rpoints.php, plugin.style.php, -plugin.tmxinfo.php, plugin.uptodate.php. -

      - -Important: -
        -
      • See the installation notes for upgrading to v1.05b. -
      -
      - -
    • Upgrading from v1.08 to v1.09:

      -The following files were added in v1.09: access.xml, -plugins/plugin.access.php. -

      - -The following files were updated in v1.09: aseco.php, -adminops.xml, config.xml, musicserver.xml, plugins.xml, -rasp.xml, includes/GbxRemote.bem.php, GbxRemote.inc.php, -basic.inc.php, gbxdatafetcher.inc.php, rasp.settings.php, -replayparser.inc.php, sminfofetcher.inc.php, tmxinfofetcher.inc.php, -types.inc.php, localdb/aseco.sql, rasp.sql, plugins/chat.admin.php, -plugin.autotime.php, plugin.checkpoints.php, plugin.dedimania.php, -plugin.localdatabase.php, plugin.musicserver.php, plugin.rasp.php, -plugin.rasp_chat.php, plugin.rasp_jukebox.php, plugin.rasp_votes.php, -plugin.rounds.php, plugin.track.php, plugin.uptodate.php, -jfreu.plugin.php, mistral.idlekick.php. -

      - -Important: -
        -
      • See the installation notes for upgrading to v1.05b. -
      -
      - -
    • Upgrading from v1.06 to v1.08:

      -The following file was added in v1.08: includes/replayparser.inc.php. -

      - -The following files were updated in v1.08: aseco.php, config.xml, -includes/GbxRemote.inc.php, GbxRemote.bem.php, basic.inc.php, -gbxdatafetcher.inc.php, ogg_comments.inc.php, rasp.funcs.php, -sminfofetcher.inc.php, tmxinfofetcher.inc.php, tmxinfosearcher.inc.php, -types.inc.php, web_access.inc.php, xmlparser.inc.php, -localdb/aseco.sql, plugins/chat.admin.php, chat.dedimania.php, -chat.players.php, chat.players2.php, chat.records.php, -chat.records2.php, chat.stats.php, plugin.autotime.php, -plugin.chatlog.php, plugin.checkpoints.php, plugin.dedimania.php, -plugin.donate.php, plugin.localdabase.php, plugin.matchsave.php, -plugin.musicserver.php, plugin.muting.php, plugin.panels.php, -plugin.rasp.php, plugin.rasp_chat.php, plugin.rasp_jukebox.php, -plugin.rasp_nextmap.php, plugin.rasp_votes.php, plugin.rounds.php, -plugin.sminfo.php, plugin.style.php, plugin.tmxinfo.php, -plugin.track.php, jfreu.chat.php, jfreu.lite.php, jfreu.plugin.php, -mistral.idlekick.php. -

      - -Important: -
        -
      • See the installation notes for upgrading to v1.05b. -
      -
      - -
    • Upgrading from v1.05b to v1.06:

      - -The following files were added in v1.06: localdb/extra.sql, -panels/StatsNations.xml, StatsUnited.xml. -

      - -The following files were updated in v1.06: aseco.php, -config.xml, includes/manialinks.inc.php, ogg_comments.inc.php, -types.inc.php, jfreu.config.php, localdb/aseco.sql, rasp.sql, -plugins/chat.admin.php, chat.dedimania.php, chat.server.php, -chat.stats.php, plugin.checkpoints.php, plugin.dedimania.php, -plugin.donate.php, plugin.localdatabase.php, plugin.matchsave.php, -plugin.musicserver.php, plugin.panels.php, plugin.rasp.php, -plugin.rasp_chat.php, plugin.rasp_jukebox.php, plugin.style.php, -jfreu.lite.php, jfreu.plugin.php. -

      - -Important: -
        -
      • See the installation notes for upgrading to v1.05b. -
      -
      - -
    • Upgrading from v1.05 to v1.05b:

      - -The following files were updated in v1.05b: aseco.php, dedimania.xml, -includes/basic.inc.php, plugins/plugin.checkpoints.php, -plugin.dedimania.php, plugin.msglog.php, plugin.musicserver.php, -jfreu.lite.php. -

      - -Important: -
        -
      • To register your server with the central Dedimania -database, you must copy the login and password values in the -<masterserver_account> section from your -server's dedicated.cfg (TMN) or dedicated_cfg.txt (TMF) file into -the corresponding section of the dedimania.xml file, and add -the 3-character nation abbreviation. Instead of the password -you can also use the community code for your server by using -the server login/password on the official -site for your game (TMO/TMS/TMN) or on this page for -TMF. -
      • Open ports 8003, 8006, 8007, 8011, 8012, 8013 and 8016 through -8021 on your firewall/router for communication with the central -Dedimania server (if that's not possible, the system falls back on -port 80). -
      • In the zip file, all *.XML and *.PHP config files are now located -inside the newinstall/ directory. This means that you can (and -have to) unzip the download and replace all the PHP code files, without -worrying about overwriting your customized config files. However, for -every XML/PHP config file that was updated (see above), you must -replace your version with the one from the newinstall/ directory, -or compare them and add any new/changed configuration settings -to your version to insure the system remains working correctly. -
      • For a new installation, go into the newinstall/ directory and -move all *.XML files into the main directory, next to aseco.php, -and *.PHP files into the includes/ directory. -
      -
      - -
    • Upgrading from v1.04 to v1.05:

      - -The following files were updated in v1.05: aseco.php, -adminops.xml, config.xml, musicserver.xml, includes/basic.inc.php, -gbxdatafetcher.inc.php, types.inc.php, votes.config.php, -xmlrpc_db.inc.php, plugins/chat.admin.php, chat.dedimania.php, -chat.players.php, plugin.autotime.php, plugin.checkpoints.php, -plugin.dedimania.php, plugin.donate.php, plugin.localdatabase.php, -plugin.musicserver.php, plugin.rasp_chat.php, plugin.rasp_jukebox.php, -plugin.rasp_votes.php, plugin.rpoints.php, plugin.uptodate.php, -jfreu.chat.php, jfreu.plugin.php. -

      - -Important: -
        -
      • See the installation notes for upgrading to v1.03. -
      -
      - -
    • Upgrading from v1.03 to v1.04:

      - -The following files were added in v1.04: autotime.xml, -plugins/plugin.autotime.php, plugin.rpoints.php. -

      - -The following files were updated in v1.04: aseco.php, -adminops.xml, config.xml, musicserver.xml, plugins.xml, rasp.xml, -includes/gbxdatafetcher.inc.php, manialinks.inc.php, rasp.funcs.php, -tmndatafetcher.inc.php, tmxinfofetcher.inc.php, types.inc.php, -votes.config.php, jfreu.config.php, plugins/chat.admin.php, -chat.dedimania.php, chat.laston.php, chat.players.php, -chat.records.php, chat.records2.php, chat.server.php, -chat.stats.php, plugin.chatlog.php, plugin.checkpoints.php, -plugin.dedimania.php, plugin.donate.php, plugin.localdatabase.php, -plugin.musicserver.php, plugin.muting.php, plugin.rasp.php, -plugin.rasp_chat.php, plugin.rasp_jukebox.php, plugin.rasp_karma.php, -plugin.rasp_nextmap.php, plugin.rasp_nextrank.php, -plugin.rasp_votes.php, plugin.rounds.php, plugin.sminfo.php, -plugin.tmxinfo.php, plugin.track.php, jfreu.lite.php, jfreu.chat.php, -jfreu.plugin.php, plugins/jfreu/jfreu.vips.xml. -

      - -Important: -
        -
      • See the installation notes for upgrading to v1.03. -
      -
      - -
    • Upgrading from v1.02 to v1.03:

      - -The following file was added in v1.03: bannedips.xml. -

      - -The following files were updated in v1.03: aseco.php, adminops.xml, -config.xml, dedimania.xml, rasp.xml, includes/basic.inc.php, -manialinks.inc.php, rasp.settings.php, votes.config.php, -types.inc.php, jfreu.config.php, plugins/chat.admin.php, -chat.players.php, chat.records2.php, chat.server.php, chat.stats.php, -plugin.dedimania.php, plugin.localdatabase.php, plugin.matchsave.php, -plugin.msglog.php, plugin.musicserver.php, plugin.muting.php, -plugin.panels.php, plugin.rasp.php, plugin.rasp_chat.php, -plugin.rasp_jukebox.php, plugin.rasp_votes.php, plugin.track.php, -plugin.uptodate.php, jfreu.chat.php, jfreu.lite.php, jfreu.plugin.php, -mistral.idlekick.php. -

      - -Important: -
        -
      • To register your server with the central Dedimania -database, you must copy the login and password values in the -<masterserver_account> section from your -server's dedicated.cfg (TMN) or dedicated_cfg.txt (TMF) file into -the corresponding section of the dedimania.xml file, and add -the 3-character nation abbreviation. Instead of the password -you can also use the community code for your server by using -the server login/password on the official -site for your game (TMO/TMS/TMN) or on this page for -TMF. -
      • Open ports 8003, 8006, 8007, 8011, 8012 and 8013 on your -firewall/router for communication with the central Dedimania server -(if that's not possible, the system falls back on port 80). -
      • In the zip file, all *.XML and *.PHP config files are now located -inside the newinstall/ directory. This means that you can (and -have to) unzip the download and replace all the PHP code files, without -worrying about overwriting your customized config files. However, for -every XML/PHP config file that was updated (see above), you must -replace your version with the one from the newinstall/ directory, -or compare them and add any new/changed configuration settings -to your version to insure the system remains working correctly. -
      • For a new installation, go into the newinstall/ directory and -move all *.XML files into the main directory, next to aseco.php, -and *.PHP files into the includes/ directory. -
      -
      - -
    • Upgrading from v1.01 to v1.02:

      - -The following files were added in v1.02: includes/ogg_comments.inc.php, -plugins/plugin.msglog.php, panels/AdminAboveSpeed2.xml, -DonateLeftSmall.xml, DonateRightSmall.xml, VoteBottomCenterTransp.xml, -styles/ProgressBar.xml. -

      - -The following files were updated in v1.02: aseco.php, -adminops.xml, config.xml, dedimania.xml, plugins.xml, rasp.xml, -includes/basic.inc.php, manialinks.inc.php, jfreu.config.php, -rasp.funcs.php, rasp.settings.php, votes.config.php, -sminfofetcher.inc.php, tmndatafetcher.inc.php, tmxinfofetcher.inc.php, -tmxinfosearcher.inc.php, plugins/chat.admin.php, chat.dedimania.php, -chat.help.php, chat.players.php, chat.players2.php, chat.records.php, -chat.records2.php, chat.recrels.php, chat.server.php, chat.stats.php, -plugin.chatlog.php, plugin.donate.php, plugin.matchsave.php, -plugin.ml_howto.php, plugin.musicserver.php, plugin.muting.php, -plugin.panels.php, plugin.rasp.php, plugin.rasp_chat.php, -plugin.rasp_irc.php, plugin.rasp_jukebox.php, plugin.rasp_votes.php, -plugin.rounds.php, plugin.sminfo.php, plugin.style.php, -plugin.tmxinfo.php, jfreu.chat.php, jfreu.lite.php, jfreu.plugin.php. -

      - -Important: -
        -
      • See the installation notes for upgrading to v1.01. -
      -
      - -
    • Upgrading from v1.00 to v1.01:

      - -The following files were added in v1.01: includes/sminfofetcher.inc.php, -tmxinfosearcher.inc.php, plugins/plugin.sminfo.php, panels/Donate*.xml. -

      - -The following files were updated in v1.01: aseco.php, -adminops.xml, config.xml, dedimania.xml, plugins.xml, -rasp.xml, includes/basic.inc.php, gbxdatafetcher.inc.php, -manialinks.inc.php, rasp.funcs.php, types.inc.php, -plugins/chat.admin.php, chat.records2.php, chat.server.php, -chat.stats.php, plugin.checkpoints.php, plugin.dedimania.php, -plugin.donate.php, plugin.localdatabase.php, plugin.musicserver.php, -plugin.muting.php, plugin.panels.php, plugin.rasp.php, -plugin.rasp_irc.php, plugin.rasp_jukebox.php, plugin.rasp_nextmap.php, -plugin.rasp_votes.php, plugin.style.php, plugin.tmxinfo.php, -jfreu.chat.php, jfreu.plugin.php, panels/00README.txt. -

      - -Important: -
        -
      • To register your server with the central Dedimania -database, you must copy the login and password values in the -<masterserver_account> section from your -server's dedicated.cfg (TMN) or dedicated_cfg.txt (TMF) file into -the corresponding section of the dedimania.xml file, and add -the 3-character nation abbreviation. Instead of the password -you can also use the community code for your server by using -the server login/password on the official -site for your game (TMO/TMS/TMN) or on this page for -TMF. -
      • Open ports 8003, 8006, 8007, 8011, 8012 and 8013 on your -firewall/router for communication with the central Dedimania server -(if that's not possible, the system falls back on port 80). -
      • In the zip file, all *.XML and *.PHP config files are now located -inside the newinstall/ directory. This means that you can (and -have to) unzip the download and replace all the PHP code files, without -worrying about overwriting your customized config files. However, for -every XML/PHP config file that was updated (see above), you must -replace your version with the one from the newinstall/ directory, -or compare them and add any new/changed configuration settings -to your version to insure the system remains working correctly. -
      • For a new installation, go into the newinstall/ directory and -move all *.XML files into the main directory, next to aseco.php, -and *.PHP files into the includes/ directory. -
      -
      - -
    • Upgrading from v0.99b to v1.00:

      - -The following directories were added in v1.00: panels/ and -styles/. -

      - -The following files were added in v1.00: panels/00README.txt, -panels/*.xml, style/00README.txt, styles/*.xml, -plugins/plugin.panels.php, plugin.style.php (TMN admins can remove -these from plugins.xml). -

      - -The following files were updated in v1.00: aseco.php, -adminops.xml, config.xml, plugins.xml, includes/basic.inc.php, -gbxdatafetcher.inc.php, jfreu.config.php, manialinks.inc.php, -rasp.funcs.php, types.inc.php, web_access.inc.php, -plugins/chat.admin.php, chat.dedimania.php, chat.players.php, -chat.records2.php, chat.recrels.php, chat.server.php, chat.song.php, -chat.stats.php, plugin.chatlog.php, plugin.checkpoints.php, -plugin.dedimania.php, plugin.localdatabase.php, plugin.musicserver.php, -plugin.muting.php, plugin.rasp.php, plugin.rasp_chat.php, -plugin.rasp_jukebox.php, plugin.rasp_karma.php, plugin.rasp_votes.php, -plugin.tmxinfo.php, plugin.track.php, plugin.uptodate.php, -jfreu.chat.php, jfreu.plugin.php, mistral.idlekick.php. -

      - -Important: -
        -
      • To register your server with the central Dedimania -database, you must copy the login and password values in the -<masterserver_account> section from your -server's dedicated.cfg (TMN) or dedicated_cfg.txt (TMF) file into -the corresponding section of the dedimania.xml file, and add -the 3-character nation abbreviation. Instead of the password -you can also use the community code for your server by using -the server login/password on the official -site for your game (TMO/TMS/TMN) or on this page for -TMF. -
      • Open ports 8003, 8006 and 8007 on your firewall/router for -communication with the central Dedimania server (if that's not -possible, the system falls back on port 80). -
      • In the zip file, all *.XML and *.PHP config files are now located -inside the newinstall/ directory. This means that you can (and -have to) unzip the download and replace all the PHP code files, without -worrying about overwriting your customized config files. However, for -every XML/PHP config file that was updated (see above), you must -replace your version with the one from the newinstall/ directory, -or compare them and add any new/changed configuration settings -to your version to insure the system remains working correctly. -
      • For a new installation, go into the newinstall/ directory and -move all *.XML files into the main directory, next to aseco.php, -and *.PHP files into the includes/ directory. -
      -
      - -
    • Upgrading from v0.99 to v0.99b:

      - -The following files were updated in v0.99b: aseco.php, adminops.xml, -config.xml, musicserver.xml, includes/manialinks.inc.php, -plugins/chat.admin.php, plugin.dedimania.php, plugin.localdatabase.php, -plugin.musicserver.php, plugin.rasp.php, plugin.uptodate.php. -

      - -Important: -
        -
      • See the installation notes for upgrading to v0.98. -
      -
      - -
    • Upgrading from v0.98 to v0.99:

      - -The following files were added in v0.99: musicserver.xml, -plugins/plugin.donate.php, plugin.musicserver.php. -

      - -The following files were updated in v0.99: aseco.php, adminops.xml, -config.xml, dedimania.xml, localdatabase.xml, plugins.xml, rasp.xml, -includes/GbxRemote.inc.php, GbxRemote.bem.php, basic.inc.php, -manialinks.inc.php, rasp.funcs.php, tmxinfofetcher.inc.php, -types.inc.php, xmlrpc_db.inc.php, plugins/chat.admin.php, -chat.dedimania.php, chat.records2.php, chat.server.php, chat.stats.php, -plugin.dedimania.php, plugin.localdatabase.php, plugin.matchsave.php, -plugin.muting.php, plugin.rasp.php, plugin.rasp_chat.php, -plugin.rasp_jukebox.php, plugin.rasp_karma.php, plugin.rasp_votes.php, -plugin.tmxinfo.php, plugin.track.php, jfreu.plugin.php. -

      - -Important: -
        -
      • See the installation notes for upgrading to v0.98. -
      -
      - -
    • Upgrading from v0.97 to v0.98:

      - -The following file was added in v0.98: plugins/plugin.ml_howto.php. -

      - -The following files were updated in v0.98: aseco.php, adminops.xml, -dedimania.xml, localdatabase.xml, includes/basic.inc.php, -manialinks.inc.php, rasp.funcs.php, types.inc.php, xmlparser.inc.php, -xmlrpc_db.inc.php, plugins/chat.admin.php, chat.dedimania.php, -chat.players.php, chat.players2.php, chat.records.php, -chat.records2.php, chat.recrels.php, chat.server.php, chat.stats.php, -plugin.chatlog.php, plugin.checkpoints.php, plugin.dedimania.php, -plugin.localdatabase.php, plugin.matchsave.php, plugin.muting.php, -plugin.rasp.php, plugin.rasp_chat.php, plugin.rasp_jukebox.php, -plugin.rasp_karma.php, plugin.track.php, plugin.uptodate.php, -jfreu.chat.php, jfreu.plugin.php. -

      - -Important: -
        -
      • To register your server with the central Dedimania -database, you must copy the login and password values in the -<masterserver_account> section from your -server's dedicated.cfg (TMN) or dedicated_cfg.txt (TMF) file into -the corresponding section of the dedimania.xml file, and add -the 3-character nation abbreviation. Instead of the password -you can also use the community code for your server by using -the server login/password on the official -site for your game (TMO/TMS/TMN) or on this page for -TMF. -
      • Open ports 8003, 8006 and 8007 on your firewall/router for -communication with the central Dedimania server (if that's not -possible, the system falls back on port 80). -
      • In the zip file, all *.XML and config files are now located inside -the newinstall/ directory. This means that you should be able -to unzip the download and replace all the code files (unless you made -changes to any) without worrying about overwriting your customized -config files. However, for every XML/config file that was updated -(see above), you must replace your version with the one from -the newinstall/ directory, or compare them and add any new/changed -configuration settings to your version to insure the system remains -working correctly. -
      • For a new installation, go into the newinstall/ directory and -move all *.XML files into the main directory, next to aseco.php, -and *.PHP files into the includes/ directory. -
      -
      - -
    • Upgrading from v0.96b to v0.97:

      - -The following files were updated in v0.97: aseco.php, adminops.xml, -config.xml, dedimania.xml, rasp.xml, includes/basic.inc.php, -manialinks.inc.php, rasp.funcs.php, types.inc.php, web_access.inc.php, -xmlrpc_db.inc.php, jfreu.config.php, plugins/chat.admin.php, -chat.dedimania.php, chat.players.php, chat.players2.php, -chat.records.php, chat.records2.php, chat.server.php, chat.stats.php, -plugin.checkpoints.php, plugin.dedimania.php, plugin.localdatabase.php, -plugin.matchsave.php, plugin.muting.php, plugin.rasp.php, -plugin.rasp_jukebox.php, plugin.rasp_votes.php, plugin.rounds.php, -plugin.tmxinfo.php, jfreu.chat.php, jfreu.plugin.php. -

      - -Important: -
        -
      • To register your server with the central Dedimania -database, you must copy the login and password values in the -<masterserver_account> section from your -server's dedicated.cfg (TMN) or dedicated_cfg.txt (TMF) file into -the corresponding section of the dedimania.xml file, and add -the 3-character nation abbreviation. Instead of the password -you can also use the community code for your server by using -the server login/password on the official -site for your game (TMO/TMS/TMN) or on this page for -TMF. -
      • Open port 8003 on your firewall/router for communication with the -central Dedimania server (if that's not possible, the system falls -back on port 80). -
      • In the zip file, all *.XML and config files are now located inside -the newinstall/ directory. This means that you should be able -to unzip the download and replace all the code files (unless you made -changes to any) without worrying about overwriting your customized -config files. However, for every XML/config file that was updated -(see above), you must replace your version with the one from -the newinstall/ directory, or compare them and add any new/changed -configuration settings to your version to insure the system remains -working correctly. -
      • For a new installation, go into the newinstall/ directory and -move all *.XML files into the main directory, next to aseco.php, -and *.PHP files into the includes/ directory. -
      -
      - -
    • Upgrading from v0.96 to v0.96b:

      - -The following files were updated in v0.96b: aseco.php, adminops.xml, -matchsave.xml, rasp.xml, includes/basic.inc.php, manialinks.inc.php, -rasp.settings.php, votes.config.php, plugins/chat.admin.php, -plugin.dedimania.php, plugin.localdatabase.php, plugin.matchsave.php, -plugin.rasp.php, plugin.rasp_jukebox.php, plugin.rasp_nextmap.php, -plugin.rasp_votes.php. -

      - -Important: -
        -
      • See the installation notes for upgrading to v0.96. -
      -
      - -
    • Upgrading from v0.95 to v0.96:

      - -The following file was added in v0.96: includes/manialinks.inc.php. -

      - -The following files were updated in v0.96: aseco.php, config.xml, -includes/basic.inc.php, rasp.funcs.php, rasp.settings.php, -tmxinfofetcher.inc.php, types.inc.php, web_access.inc.php, -jfreu.config.php, plugins/chat.admin.php, chat.dedimania.php, -chat.help.php, chat.players.php, chat.players2.php, chat.records.php, -chat.records2.php, chat.server.php, chat.stats.php, plugin.chatlog.php, -plugin.checkpoints.php, plugin.dedimania.php, plugin.localdatabase.php, -plugin.matchsave.php, plugin.muting.php, plugin.rasp.php, -plugin.rasp_chat.php plugin.rasp_jukebox.php, plugin.rasp_nextmap.php, -plugin.rasp_votes.php, plugin.rounds.php, plugin.tmxinfo.php, -plugin.track.php, plugin.uptodate.php, jfreu.chat.php. -

      - -The following files were removed from v0.96: publicdatabase.xml, -includes/dataexchanger.inc.php, plugins/plugin.publicdatabase.php -(the old public database ceased operations long ago, and has now been -superceded by Dedimania). -

      - -Important: -
        -
      • To register your server with the central Dedimania -database, you must copy the login and password values in the -<masterserver_account> section from your -server's dedicated.cfg (TMN) or dedicated_cfg.txt (TMF) file into -the corresponding section of the dedimania.xml file, and add -the 3-character nation abbreviation. Instead of the password -you can also use the community code for your server by using -the server login/password on the official -site for your game (TMO/TMS/TMN) or on this page for -TMF. -
      • Open port 8003 on your firewall/router for communication with the -central Dedimania server (if that's not possible, the system falls -back on port 80). -
      • In the zip file, all *.XML and config files are now located inside -the newinstall/ directory. This means that you should be able -to unzip the download and replace all the code files (unless you made -changes to any) without worrying about overwriting your customized -config files. However, for every XML/config file that was updated -(see above), you must replace your version with the one from -the newinstall/ directory, or compare them and add any new/changed -configuration settings to your version to insure the system remains -working correctly. -
      • For a new installation, go into the newinstall/ directory and -move all *.XML files into the main directory, next to aseco.php, -and *.PHP files into the includes/ directory. -
      -
      - -
    • Upgrading from v0.93 to v0.95:

      - -The following files were added in v0.95: dedimania.xml, -includes/GbxRemote.bem.php, GbxRemote.response.php, urlsafebase64.php, -web_access.inc.php, xmlrpc_db.inc.php, plugins/chat.dedimania.php, -plugin.dedimania.php. -

      - -The following files were updated in v0.95: aseco.php, -adminops.xml, config.xml, localdatabase.xml, plugins.xml, rasp.xml, -includes/GbxRemote.inc.php, basic.inc.php, gbxdatafetcher.inc.php, -rasp.funcs.php, tmndatafetcher.inc.php, tmxinfofetcher.inc.php, -types.inc.php, votes.config.php, xmlparser.inc.php, -plugins/chat.admin.php, chat.players2.php, chat.records2.php, -chat.server.php, chat.song.php, plugin.checkpoints.php, -plugin.localdatabase.php, plugin.rasp.php, plugin.rasp_jukebox.php, -plugin.rasp_nextrank.php, plugin.rasp_votes.php, plugin.tmxinfo.php, -plugin.track.php, jfreu.plugin.php. -

      - -Important: -
        -
      • To register your server with the central Dedimania database, you -must copy the three values in the <masterserver_account> -section from your server's dedicated.cfg file into the corresponding -section of the dedimania.xml file. Instead of the password you -can also use the community code for your server by using the server -login/password on the official site for your -game (TMO/TMS/TMN). -
      • Open port 8003 on your firewall/router for communication with the -central Dedimania server (if that's not possible, the system falls -back on port 80). -
      • In the zip file, all *.XML and config files are now located inside -the newinstall/ directory. This means that you should be able -to unzip the download and replace all the code files (unless you made -changes to any) without worrying about overwriting your customized -config files. However, for every XML/config file that was updated -(see above), you must replace your version with the one from -the newinstall/ directory, or compare them and add any new/changed -configuration settings to your version to insure the system remains -working correctly. -
      • For a new installation, go into the newinstall/ directory and -move all *.XML files into the main directory, next to aseco.php, -and *.PHP files into the includes/ directory. -
      -
      - -
    • Upgrading from v0.92b to v0.93:

      - -The following files were added in v0.93: -includes/gbxdatafetcher.inc.php, tmxinfofetcher.inc.php, -plugins/chat.song.php, plugin.checkpoints.php, plugin.tmxinfo.php. -

      - -The following file was renamed in v0.93: includes/datafetcher.inc.php -to includes/tmndatafetcher.inc.php. -

      - -The following files were updated in v0.93: aseco.php, -adminops.xml, config.xml, plugins.xml, includes/GbxRemote.inc.php, -rasp.funcs.php, jfreu.config.php, plugins/chat.admin.php, -chat.stats.php, plugin.localdatabase.php, plugin.matchsave.php, -plugin.publicdatabase.php, plugin.rasp.php, plugin.rasp_jukebox.php, -plugin.rasp_votes.php, plugin.rounds.php, jfreu.plugin.php. The -remaining code files were updated too, but only for the dependency -comments and occasional layout tweaks. -

      - -Important: -
        -
      • In the zip file, all *.XML and config files are now located inside -the newinstall/ directory. This means that you should be able -to unzip the download and replace all the code files (unless you made -changes to any) without worrying about overwriting your customized -config files. However, for every XML/config file that was updated -(see above), you must replace your version with the one from -the newinstall/ directory, or compare them and add any new/changed -configuration settings to your version to insure the system remains -working correctly. -
      • For a new installation, go into the newinstall/ directory and -move all *.XML files into the main directory, next to aseco.php, -and *.PHP files into the includes/ directory. -
      -
      - -
    • Upgrading from v0.92 to v0.92b:

      - -The following files were updated in v0.92b: aseco.php, -includes/jfreu.config.php, plugins/chat.stats.php, jfreu.chat.php, -jfreu.plugin.php. -

      - -
    • Upgrading from v0.91 to v0.92:

      - -The following file was added in v0.92: includes/datafetcher.inc.php. -

      - -The following files were updated in v0.92: aseco.php, rasp.xml, -includes/GbxRemote.inc.php, types.inc.php, rasp.funcs.php, -rasp.settings.php, jfreu.config.php, plugins/chat.admin.php, -chat.laston.php, chat.players2.php, chat.stats.php, -plugin.localdatabase.php, plugin.rasp.php, plugin.rasp_jukebox.php, -plugin.rasp_nextrank.php, plugin.uptodate.php, jfreu.chat.php, -jfreu.plugin.php. -

      - -
    • Upgrading from v0.90 to v0.91:

      - -The following file was moved in v0.91: plugins/jfreu.config.php -to includes/jfreu.config.php. -

      - -The following files were added in v0.91: includes/votes.config.php -(containing the configuration options previously embedded in -plugins/plugin.rasp_votes.php), plugins/chat.laston.php, and -plugins/jfreu/jfreu.config.xml (by default containing the same settings -as includes/jfreu.config.php). -

      - -The following file was removed in v0.91: plugins/jfreu.unspec.php -(it was integrated into plugins/jfreu.plugin.php). -

      - -The following files were updated in v0.91: aseco.php, adminops.xml, -config.xml, plugins.xml, rasp.xml, includes/jfreu.config.php, -rasp.funcs.php, rasp.settings.php, plugins/chat.admin.php, -chat.server.php, plugin.rasp.php, plugin.rasp_jukebox.php, -plugin.rasp_nextmap.php, plugin.rasp_votes.php, jfreu.chat.php, -jfreu.lite.php, jfreu.plugin.php. -

      - -Important: -
        -
      • Because jfreu.unspec.php was integrated into the main plugin, you -must remove the file from an existing installation and not include it -in plugins.xml anymore if you used it together with jfreu.plugin.php. - -
      • If you allow TMX /add votes ($feature_tmxadd -is true), you must created a new -'GameData/Tracks/Challenges/TMXtmp/' directory along the required -'GameData/Tracks/Challenges/TMX/' directory. Tracks downloaded via -/add are saved to the former directory, but if permanently -added to the server's track list via /admin addthis, they are -moved into the latter. -
      -
      - -
    • Upgrading from v0.89 to v0.90:

      - -The following file was added in v0.90 (although by default not enabled -in plugins.xml): plugins/plugin.muting.php. -

      - -All *.php files were updated in v0.90 for various reasons (if only to -add more comments), so it's strongly recommended to replace them all. -Further, adminops.xml, config.xml, localdatabase.xml, rasp.xml were -also updated. -

      - -Important: -
        -
      • If you use a blacklist or guestlist file (in the "GameData/" -directory), or a tracklist file (in "GameData/Tracks/MatchSettings/"), -rename their extensions from .xml back to .txt. -
      -
      - -
    • Upgrading from v0.88 to v0.89:

      - -The following file was renamed in v0.89: plugins/jfreu.player.php -to plugins/jfreu.lite.php. -

      - -Almost all *.php files were updated in v0.89 for various reasons, so -it's strongly recommended to replace them all. Further, config.xml, -rasp.xml and adminops.xml were also updated. -

      - -Important: -
        -
      • If you use a blacklist or guestlist file (in the "GameData/" -directory), rename its extension from .txt to .xml. - -
      • If you use a tracklist / match settings file, the default file -is now at "GameData/Tracks/MatchSettings/tracklist.xml" (previously -"GameData/Tracks/rasp-tracklist.txt"), and alternate files should -also be in the "GameData/Tracks/MatchSettings/" directory with the -.xml extension. -
      -
      - -
    • Upgrading from v0.86 to v0.88:

      - -The following file was added in v0.88: adminops.xml. -

      - -The following file was renamed in v0.88: plugins/jfreu/jfreu.lists.xml -into plugins/jfreu/jfreu.vips.xml. -

      - -The following files were updated in v0.88: aseco.php, config.xml, -rasp.xml, includes/types.inc.php, rasp.settings.php, rasp.funcs.php, -plugins/chat.admin.php, chat.players.php, chat.records2.php, -chat.server.php, chat.stats.php, plugin.localdatabase.php, -plugin.matchsave.php, plugin.rasp.php, plugin.rasp_chat.php, -plugin.rasp_irc.php, plugin.rasp_jukebox.php, plugin.rasp_karma.php, -plugin.rasp_nextmap.php, plugin.rasp_nextrank.php, -plugin.rasp_votes.php, jfreu.chat.php, jfreu.config.php, -jfreu.player.php, jfreu.plugin.php. -

      - -Important: -
        -
      • In config.xml the <admins> section has been -renamed to <masteradmins>, along with adding a -new <adminops_file> definition. If you use your -previous config.xml file, you must make these changes yourself. - -
      • The <masteradmins> section of config.xml should -contain only those logins you want to have all admin rights, -and it should also contain the server owner's LAN login (with IP and -port), if applicable. - -
      • Any other admin logins should be moved from config.xml into the -<admins> (or <operators>) -section of adminsops.xml, or they can be re-added later via the -/admin addadmin (or addop) command. - -
      • If you use Jfreu's vip/team_vip abilities, rename your existing -plugins/jfreu/jfreu.lists.xml file to plugins/jfreu/jfreu.vips.xml. - -
      • The <admin_list> section in jfreu.vips.xml -is obsolete, and its contents should also be moved into the -<admins> section of adminops.xml before -starting v0.88+, because it will no longer be written back into the -file after the next vip/team_vip change. -
      -
      - -
    • Upgrading from v0.85 to v0.86:

      - -The following file was renamed in v0.86: chat.jfreu.php into -jfreu.chat.php. -

      - -The following files were updated in v0.86: aseco.php, -config.xml, rasp.xml, includes/rasp.funcs.php, rasp.settings.php, -xmlparser.inc.php, plugins/chat.admin.php, chat.players.php, -chat.players2.php, chat.records.php, plugin.rasp.php, -plugin.rasp_jukebox.php, plugin.rasp_karma.php, plugin.rasp_votes.php, -plugin.uptodate.php, jfreu.config.php, jfreu.plugin.php, -mistral.idlekick.php. -

      - -
    • Upgrading from v0.84 to v0.85:

      - -The following file was added in v0.85: plugins/plugin.uptodate.php. -

      - -The following files were updated in v0.85: plugins.xml, -rasp.xml, localdatabase.xml, aseco.php, includes/rasp.settings.php, -rasp.funcs.php, plugins/chat.admin.php, chat.me.php, chat.records2.php, -chat.server.php, chat.stats.php, plugin.localdatabase.php, -plugin.matchsave.php, plugin.rasp.php, plugin.rasp_chat.php, -plugin.rasp_votes.php, jfreu.config.php, jfreu.plugin.php, -mistral.idlekick.php, plus layout tweaks in others. -

      - -
    • Upgrading from v0.82 to v0.84:

      - -The following file was added in v0.84: plugins/plugin.rasp_votes.php. -

      - -Once again, many other *.php files were updated for various reasons, so -it's strongly recommended to replace them all. Further, plugins.xml, -config.xml, localdatabase.xml and rasp.xml were also updated. -

      - -
    • Upgrading from v0.8 to v0.82:

      - -The following files were added in v0.81/0.82: -plugins/plugin.chatlog.php, plugin.rasp_nextrank.php, jfreu.player.php -and jfreu.unspec.php (previously known as jfreu.hack.php). -

      - -Almost all other *.php files were updated for the '&' -pass-by-reference fixes, so it's strongly recommended to replace them -all. Further, plugins.xml, config.xml and rasp.xml were also updated. -

      - -
    • Upgrading from v0.7 to v0.8+:

      -Because of the extent of the changes in v1.14, upgrading from v0.7 and -older versions is best done by renaming your ASECO directory to another -name, following the instructions for a new installation (above), -and then copying over your configuration settings into the pertaining -config files, as well as updating the new options to your liking. - -

      -Subsequently, you can port over any code changes and plugins specific -to your server, or let me know if you think -they might be of general interest, then I'll see whether they can be -incorporated in the next release. - -
    - -
    -
    -Note on upgrading from versions prior to v0.8, and v2.0 and beyond:
    -
    -This release, when first run, will automatically rename the -'trackID' column in the rs_times table in your ASECO database -to 'challengeID' for consistency with all the other tables. -This means that if you ever want to downgrade to v0.7, you'll -need to manually rename that column back by entering the following -command in your database (via PhpMyAdmin, the MySQL command prompt, -or similar):

    - -ALTER TABLE rs_times CHANGE challengeID trackID mediumint(9) NOT NULL default 0 -

    -Aseco/Rasp versions 2.0 and beyond include additional columns in some -tables, but they are ignored. -
    -
    - -
    -
    -
    -Copyright © 2007-2013 – Frans P. de Vries <tm@gamers.org> -            -Last updated 09-Mar-2013 -
    - - diff --git a/xaseco/access.xml b/xaseco/access.xml deleted file mode 100644 index 9b57d03..0000000 --- a/xaseco/access.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - Deny,Allow - - - all - - - - - - - - {#server}>> {#message}Player {#highlite}{1}$z$s{#message} denied access from {2} {#highlite}{3}{#message} [{#error}Kicked $z$s{#message}] - {#message}Access from zone {#highlite}{1} {#message}denied$z - {#server}> Player access control reloaded from {#highlite}access.xml - {#server}> {#highlite}access.xml {#error}config error, player access control disabled! - {#server}> {#error}Missing parameter, use {#highlite}$i /admin access help {#error}! - - diff --git a/xaseco/adminops.xml b/xaseco/adminops.xml deleted file mode 100644 index b68e965..0000000 --- a/xaseco/adminops.xml +++ /dev/null @@ -1,320 +0,0 @@ - - - - MasterAdmin - Admin - Operator - - - - - - - - - - - - true - true - false - false - true - true - true - false - false - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - false - false - false - false - false - false - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - false - false - false - false - true - true - true - true - true - false - false - true - true - true - true - true - false - false - true - true - false - true - true - true - true - true - true - true - true - true - - false - false - false - false - true - false - true - true - true - true - false - true - false - false - false - true - true - true - true - true - true - true - true - true - false - false - false - true - true - false - true - true - - - - true - true - false - false - false - false - false - false - false - false - false - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - false - false - false - false - true - true - true - false - false - false - false - false - false - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - true - true - true - true - true - true - true - true - false - false - false - false - false - false - true - true - true - false - false - true - false - false - false - false - false - false - false - false - false - false - false - false - true - true - true - true - true - true - - false - false - false - false - false - false - true - false - false - false - false - true - false - false - false - false - false - false - false - true - false - false - false - false - false - false - false - false - false - false - true - false - - diff --git a/xaseco/aseco.php b/xaseco/aseco.php deleted file mode 100644 index c8525b9..0000000 --- a/xaseco/aseco.php +++ /dev/null @@ -1,2562 +0,0 @@ - - * - * Re-authored & copyright May 2007 - Jul 2013 by Xymph - * - * Visit the official site at http://www.xaseco.org/ - */ - -/** - * Include required classes - */ -require_once('includes/types.inc.php'); // contains classes to store information -require_once('includes/basic.inc.php'); // contains standard functions -require_once('includes/GbxRemote.inc.php'); // needed for dedicated server connections -require_once('includes/xmlparser.inc.php'); // provides an XML parser -require_once('includes/gbxdatafetcher.inc.php'); // provides access to GBX data -require_once('includes/tmndatafetcher.inc.php'); // provides access to TMN world stats -require_once('includes/rasp.settings.php'); // specific to the RASP plugins - -/** - * Runtime configuration definitions - */ - -// add abbreviations for some chat commands? -// /admin -> /ad, /jukebox -> /jb, /autojuke -> /aj -define('ABBREV_COMMANDS', false); -// disable local & Dedi record relations commands from help lists? -define('INHIBIT_RECCMDS', false); -// separate logs by month in logs/ dir? -define('MONTHLY_LOGSDIR', false); -// keep UTF-8 encoding in config.xml? -define('CONFIG_UTF8ENCODE', false); - -/** - * System definitions - no changes below this point - */ - -// current project version -define('XASECO_VERSION', '1.16'); -define('XASECO_TMN', 'http://www.gamers.org/tmn/'); -define('XASECO_TMF', 'http://www.gamers.org/tmf/'); -define('XASECO_TM2', 'http://www.gamers.org/tm2/'); -define('XASECO_ORG', 'http://www.xaseco.org/'); - -// required official dedicated server builds -define('TMN_BUILD', '2006-05-30'); -define('TMF_BUILD', '2011-02-21'); - -// check current operating system -if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - // on Win32/NT use: - define('CRLF', "\r\n"); -} else { - // on Unix use: - define('CRLF', "\n"); -} -if (!defined('LF')) { - define('LF', "\n"); -} - -/** - * Error function - * Report errors in a regular way. - */ -set_error_handler('displayError'); -function displayError($errno, $errstr, $errfile, $errline) { - global $aseco; - - // check for error suppression - if (error_reporting() == 0) return; - - switch ($errno) { - case E_USER_ERROR: - $message = "[XASECO Fatal Error] $errstr on line $errline in file $errfile" . CRLF; - echo $message; - doLog($message); - - // throw 'shutting down' event - $aseco->releaseEvent('onShutdown', null); - // clear all ManiaLinks - $aseco->client->query('SendHideManialinkPage'); - - if (function_exists('xdebug_get_function_stack')) - doLog(print_r(xdebug_get_function_stack()), true); - die(); - break; - case E_USER_WARNING: - $message = "[XASECO Warning] $errstr" . CRLF; - echo $message; - doLog($message); - break; - case E_ERROR: - $message = "[PHP Error] $errstr on line $errline in file $errfile" . CRLF; - echo $message; - doLog($message); - break; - case E_WARNING: - $message = "[PHP Warning] $errstr on line $errline in file $errfile" . CRLF; - echo $message; - doLog($message); - break; - default: - if (strpos($errstr, 'Function call_user_method') !== false) break; - //$message = "[PHP $errno] $errstr on line $errline in file $errfile" . CRLF; - //echo $message; - //doLog($message); - // do nothing, only treat known errors - } -} // displayError - -/** - * Here XASECO actually starts. - */ -class Aseco { - - /** - * Public fields - */ - var $client; - var $xml_parser; - var $script_timeout; - var $debug; - var $server; - var $command; - var $events; - var $rpc_calls; - var $rpc_responses; - var $chat_commands; - var $chat_colors; - var $chat_messages; - var $plugins; - var $settings; - var $style; - var $panels; - var $statspanel; - var $titles; - var $masteradmin_list; - var $admin_list; - var $adm_abilities; - var $operator_list; - var $op_abilities; - var $bannedips; - var $startup_phase; // XAseco start-up phase - var $warmup_phase; // warm-up phase - var $restarting; // restarting challenge (0 = not, 1 = instant, 2 = chattime) - var $changingmode; // changing game mode - var $currstatus; // server status changes - var $prevstatus; - var $currsecond; // server time changes - var $prevsecond; - var $uptime; // XAseco start-up time - - - /** - * Initializes the server. - */ - function Aseco($debug) { - global $maxrecs; // from rasp.settings.php - - echo '# initialize XASECO ###########################################################' . CRLF; - - // log php & mysql version info - $this->console_text('[XAseco] PHP Version is ' . phpversion() . ' on ' . PHP_OS); - - // initialize - $this->uptime = time(); - $this->chat_commands = array(); - $this->debug = $debug; - $this->client = new IXR_ClientMulticall_Gbx(); - $this->xml_parser = new Examsly(); - $this->server = new Server('127.0.0.1', 5000, 'SuperAdmin', 'SuperAdmin'); - $this->server->challenge = new Challenge(); - $this->server->players = new PlayerList(); - $this->server->records = new RecordList($maxrecs); - $this->server->mutelist = array(); - $this->plugins = array(); - $this->titles = array(); - $this->masteradmin_list = array(); - $this->admin_list = array(); - $this->adm_abilities = array(); - $this->operator_list = array(); - $this->op_abilities = array(); - $this->bannedips = array(); - $this->startup_phase = true; - $this->warmup_phase = false; - $this->restarting = 0; - $this->changingmode = false; - $this->currstatus = 0; - } // Aseco - - - /** - * Load settings and apply them on the current instance. - */ - function loadSettings($config_file) { - - if ($settings = $this->xml_parser->parseXml($config_file, true, CONFIG_UTF8ENCODE)) { - // read the XML structure into an array - $aseco = $settings['SETTINGS']['ASECO'][0]; - - // read settings and apply them - $this->chat_colors = $aseco['COLORS'][0]; - $this->chat_messages = $aseco['MESSAGES'][0]; - $this->masteradmin_list = $aseco['MASTERADMINS'][0]; - if (!isset($this->masteradmin_list) || !is_array($this->masteradmin_list)) - trigger_error('No MasterAdmin(s) configured in config.xml!', E_USER_ERROR); - - // check masteradmin list consistency - if (empty($this->masteradmin_list['IPADDRESS'])) { - // fill list to same length as list - if (($cnt = count($this->masteradmin_list['TMLOGIN'])) > 0) - $this->masteradmin_list['IPADDRESS'] = array_fill(0, $cnt, ''); - } else { - if (count($this->masteradmin_list['TMLOGIN']) != count($this->masteradmin_list['IPADDRESS'])) - trigger_error("MasterAdmin mismatch between 's and 's!", E_USER_WARNING); - } - - // set admin lock password - $this->settings['lock_password'] = $aseco['LOCK_PASSWORD'][0]; - // set cheater action - $this->settings['cheater_action'] = $aseco['CHEATER_ACTION'][0]; - // set script timeout - $this->settings['script_timeout'] = $aseco['SCRIPT_TIMEOUT'][0]; - // set minimum number of records to be displayed - $this->settings['show_min_recs'] = $aseco['SHOW_MIN_RECS'][0]; - // show records before start of track? - $this->settings['show_recs_before'] = $aseco['SHOW_RECS_BEFORE'][0]; - // show records after end of track? - $this->settings['show_recs_after'] = $aseco['SHOW_RECS_AFTER'][0]; - // show TMX world record? - $this->settings['show_tmxrec'] = $aseco['SHOW_TMXREC'][0]; - // show played time at end of track? - $this->settings['show_playtime'] = $aseco['SHOW_PLAYTIME'][0]; - // show current track at start of track? - $this->settings['show_curtrack'] = $aseco['SHOW_CURTRACK'][0]; - // set default filename for readtracklist/writetracklist - $this->settings['default_tracklist'] = $aseco['DEFAULT_TRACKLIST'][0]; - // set minimum number of ranked players in a clan to be included in /topclans - $this->settings['topclans_minplayers'] = $aseco['TOPCLANS_MINPLAYERS'][0]; - // set multiple of win count to show global congrats message - $this->settings['global_win_multiple'] = ($aseco['GLOBAL_WIN_MULTIPLE'][0] > 0 ? $aseco['GLOBAL_WIN_MULTIPLE'][0] : 1); - // timeout of the TMF message window in seconds - $this->settings['window_timeout'] = $aseco['WINDOW_TIMEOUT'][0]; - // set filename of admin/operator/ability lists file - $this->settings['adminops_file'] = $aseco['ADMINOPS_FILE'][0]; - // set filename of banned IPs list file - $this->settings['bannedips_file'] = $aseco['BANNEDIPS_FILE'][0]; - // set filename of blacklist file - $this->settings['blacklist_file'] = $aseco['BLACKLIST_FILE'][0]; - // set filename of guestlist file - $this->settings['guestlist_file'] = $aseco['GUESTLIST_FILE'][0]; - // set filename of track history file - $this->settings['trackhist_file'] = $aseco['TRACKHIST_FILE'][0]; - // set minimum admin client version - $this->settings['admin_client'] = $aseco['ADMIN_CLIENT_VERSION'][0]; - // set minimum player client version - $this->settings['player_client'] = $aseco['PLAYER_CLIENT_VERSION'][0]; - // set default rounds points system - $this->settings['default_rpoints'] = $aseco['DEFAULT_RPOINTS'][0]; - // set windows style (none = old TMN style) - $this->settings['window_style'] = $aseco['WINDOW_STYLE'][0]; - // set admin panel (none = no panel) - $this->settings['admin_panel'] = $aseco['ADMIN_PANEL'][0]; - // set donate panel (none = no panel) - $this->settings['donate_panel'] = $aseco['DONATE_PANEL'][0]; - // set records panel (none = no panel) - $this->settings['records_panel'] = $aseco['RECORDS_PANEL'][0]; - // set vote panel (none = no panel) - $this->settings['vote_panel'] = $aseco['VOTE_PANEL'][0]; - - // display welcome message as window ? - if (strtoupper($aseco['WELCOME_MSG_WINDOW'][0]) == 'TRUE') { - $this->settings['welcome_msg_window'] = true; - } else { - $this->settings['welcome_msg_window'] = false; - } - - // log all chat, not just chat commands ? - if (strtoupper($aseco['LOG_ALL_CHAT'][0]) == 'TRUE') { - $this->settings['log_all_chat'] = true; - } else { - $this->settings['log_all_chat'] = false; - } - - // show timestamps in /chatlog, /pmlog & /admin pmlog ? - if (strtoupper($aseco['CHATPMLOG_TIMES'][0]) == 'TRUE') { - $this->settings['chatpmlog_times'] = true; - } else { - $this->settings['chatpmlog_times'] = false; - } - - // show records range? - if (strtoupper($aseco['SHOW_RECS_RANGE'][0]) == 'TRUE') { - $this->settings['show_recs_range'] = true; - } else { - $this->settings['show_recs_range'] = false; - } - - // show records in message window? - if (strtoupper($aseco['RECS_IN_WINDOW'][0]) == 'TRUE') { - $this->settings['recs_in_window'] = true; - } else { - $this->settings['recs_in_window'] = false; - } - - // show round reports in message window? - if (strtoupper($aseco['ROUNDS_IN_WINDOW'][0]) == 'TRUE') { - $this->settings['rounds_in_window'] = true; - } else { - $this->settings['rounds_in_window'] = false; - } - - // add random filter to /admin writetracklist output - if (strtoupper($aseco['WRITETRACKLIST_RANDOM'][0]) == 'TRUE') { - $this->settings['writetracklist_random'] = true; - } else { - $this->settings['writetracklist_random'] = false; - } - - // add explanation to /help output - if (strtoupper($aseco['HELP_EXPLANATION'][0]) == 'TRUE') { - $this->settings['help_explanation'] = true; - } else { - $this->settings['help_explanation'] = false; - } - - // color nicknames in the various /top... etc lists? - if (strtoupper($aseco['LISTS_COLORNICKS'][0]) == 'TRUE') { - $this->settings['lists_colornicks'] = true; - } else { - $this->settings['lists_colornicks'] = false; - } - - // color tracknames in the various /lists... lists? - if (strtoupper($aseco['LISTS_COLORTRACKS'][0]) == 'TRUE') { - $this->settings['lists_colortracks'] = true; - } else { - $this->settings['lists_colortracks'] = false; - } - - // display checkpoints panel (TMF) or pop-up (TMN)? - if (strtoupper($aseco['DISPLAY_CHECKPOINTS'][0]) == 'TRUE') { - $this->settings['display_checkpoints'] = true; - } else { - $this->settings['display_checkpoints'] = false; - } - - // enable /cpsspec command (TMF-only)? - if (strtoupper($aseco['ENABLE_CPSSPEC'][0]) == 'TRUE') { - $this->settings['enable_cpsspec'] = true; - } else { - $this->settings['enable_cpsspec'] = false; - } - - // automatically enable /cps for new players? - if (strtoupper($aseco['AUTO_ENABLE_CPS'][0]) == 'TRUE') { - $this->settings['auto_enable_cps'] = true; - } else { - $this->settings['auto_enable_cps'] = false; - } - - // automatically enable /dedicps for new players? - if (strtoupper($aseco['AUTO_ENABLE_DEDICPS'][0]) == 'TRUE') { - $this->settings['auto_enable_dedicps'] = true; - } else { - $this->settings['auto_enable_dedicps'] = false; - } - - // automatically add IP for new admins/operators? - if (strtoupper($aseco['AUTO_ADMIN_ADDIP'][0]) == 'TRUE') { - $this->settings['auto_admin_addip'] = true; - } else { - $this->settings['auto_admin_addip'] = false; - } - - // automatically force spectator on player using /afk ? - if (strtoupper($aseco['AFK_FORCE_SPEC'][0]) == 'TRUE') { - $this->settings['afk_force_spec'] = true; - } else { - $this->settings['afk_force_spec'] = false; - } - - // provide clickable buttons in TMF lists? - if (strtoupper($aseco['CLICKABLE_LISTS'][0]) == 'TRUE') { - $this->settings['clickable_lists'] = true; - } else { - $this->settings['clickable_lists'] = false; - } - - // show logins in /recs on TMF? - if (strtoupper($aseco['SHOW_REC_LOGINS'][0]) == 'TRUE') { - $this->settings['show_rec_logins'] = true; - } else { - $this->settings['show_rec_logins'] = false; - } - - // display individual stats panels at TMF scoreboard? - if (strtoupper($aseco['SB_STATS_PANELS'][0]) == 'TRUE') { - $this->settings['sb_stats_panels'] = true; - } else { - $this->settings['sb_stats_panels'] = false; - } - - // read the XML structure into an array - $tmserver = $settings['SETTINGS']['TMSERVER'][0]; - - // read settings and apply them - $this->server->login = $tmserver['LOGIN'][0]; - $this->server->pass = $tmserver['PASSWORD'][0]; - $this->server->port = $tmserver['PORT'][0]; - $this->server->ip = $tmserver['IP'][0]; - if (isset($tmserver['TIMEOUT'][0])) { - $this->server->timeout = (int)$tmserver['TIMEOUT'][0]; - } else { - $this->server->timeout = null; - trigger_error('Server init timeout not specified in config.xml !', E_USER_WARNING); - } - - $this->style = array(); - $this->panels = array(); - $this->panels['admin'] = ''; - $this->panels['donate'] = ''; - $this->panels['records'] = ''; - $this->panels['vote'] = ''; - - if ($this->settings['admin_client'] != '' && - preg_match('/^2\.11\.[12][0-9]$/', $this->settings['admin_client']) != 1 || - $this->settings['admin_client'] == '2.11.10') - trigger_error('Invalid admin client version : ' . $this->settings['admin_client'] . ' !', E_USER_ERROR); - if ($this->settings['player_client'] != '' && - preg_match('/^2\.11\.[12][0-9]$/', $this->settings['player_client']) != 1 || - $this->settings['player_client'] == '2.11.10') - trigger_error('Invalid player client version: ' . $this->settings['player_client'] . ' !', E_USER_ERROR); - } else { - // could not parse XML file - trigger_error('Could not read/parse config file ' . $config_file . ' !', E_USER_ERROR); - } - } // loadSettings - - - /** - * Read Admin/Operator/Ability lists and apply them on the current instance. - */ - function readLists() { - - // get lists file name - $adminops_file = $this->settings['adminops_file']; - - if ($lists = $this->xml_parser->parseXml($adminops_file, true, true)) { - // read the XML structure into arrays - $this->titles = $lists['LISTS']['TITLES'][0]; - - if (is_array($lists['LISTS']['ADMINS'][0])) { - $this->admin_list = $lists['LISTS']['ADMINS'][0]; - // check admin list consistency - if (empty($this->admin_list['IPADDRESS'])) { - // fill list to same length as list - if (($cnt = count($this->admin_list['TMLOGIN'])) > 0) - $this->admin_list['IPADDRESS'] = array_fill(0, $cnt, ''); - } else { - if (count($this->admin_list['TMLOGIN']) != count($this->admin_list['IPADDRESS'])) - trigger_error("Admin mismatch between 's and 's!", E_USER_WARNING); - } - } - - if (is_array($lists['LISTS']['OPERATORS'][0])) { - $this->operator_list = $lists['LISTS']['OPERATORS'][0]; - // check operator list consistency - if (empty($this->operator_list['IPADDRESS'])) { - // fill list to same length as list - if (($cnt = count($this->operator_list['TMLOGIN'])) > 0) - $this->operator_list['IPADDRESS'] = array_fill(0, $cnt, ''); - } else { - if (count($this->operator_list['TMLOGIN']) != count($this->operator_list['IPADDRESS'])) - trigger_error("Operators mismatch between 's and 's!", E_USER_WARNING); - } - } - - $this->adm_abilities = $lists['LISTS']['ADMIN_ABILITIES'][0]; - $this->op_abilities = $lists['LISTS']['OPERATOR_ABILITIES'][0]; - - // convert strings to booleans - foreach ($this->adm_abilities as $ability => $value) { - if (strtoupper($value[0]) == 'TRUE') { - $this->adm_abilities[$ability][0] = true; - } else { - $this->adm_abilities[$ability][0] = false; - } - } - foreach ($this->op_abilities as $ability => $value) { - if (strtoupper($value[0]) == 'TRUE') { - $this->op_abilities[$ability][0] = true; - } else { - $this->op_abilities[$ability][0] = false; - } - } - return true; - } else { - // could not parse XML file - trigger_error('Could not read/parse adminops file ' . $adminops_file . ' !', E_USER_WARNING); - return false; - } - } // readLists - - /** - * Write Admin/Operator/Ability lists to save them for future runs. - */ - function writeLists() { - - // get lists file name - $adminops_file = $this->settings['adminops_file']; - - // compile lists file contents - $lists = "" . CRLF - . "" . CRLF - . "\t" . CRLF; - foreach ($this->titles as $title => $value) { - $lists .= "\t\t<" . strtolower($title) . ">" . - $value[0] - . "" . CRLF; - } - $lists .= "\t" . CRLF - . CRLF - . "\t" . CRLF; - $empty = true; - if (isset($this->admin_list['TMLOGIN'])) { - for ($i = 0; $i < count($this->admin_list['TMLOGIN']); $i++) { - if ($this->admin_list['TMLOGIN'][$i] != '') { - $lists .= "\t\t" . $this->admin_list['TMLOGIN'][$i] . "" - . " " . $this->admin_list['IPADDRESS'][$i] . "" . CRLF; - $empty = false; - } - } - } - if ($empty) { - $lists .= "" . CRLF; - } - $lists .= "\t" . CRLF - . CRLF - . "\t" . CRLF; - $empty = true; - if (isset($this->operator_list['TMLOGIN'])) { - for ($i = 0; $i < count($this->operator_list['TMLOGIN']); $i++) { - if ($this->operator_list['TMLOGIN'][$i] != '') { - $lists .= "\t\t" . $this->operator_list['TMLOGIN'][$i] . "" - . " " . $this->operator_list['IPADDRESS'][$i] . "" . CRLF; - $empty = false; - } - } - } - if ($empty) { - $lists .= "" . CRLF; - } - $lists .= "\t" . CRLF - . CRLF - . "\t" . CRLF; - foreach ($this->adm_abilities as $ability => $value) { - $lists .= "\t\t<" . strtolower($ability) . ">" . - ($value[0] ? "true" : "false") - . "" . CRLF; - } - $lists .= "\t" . CRLF - . CRLF - . "\t" . CRLF; - foreach ($this->op_abilities as $ability => $value) { - $lists .= "\t\t<" . strtolower($ability) . ">" . - ($value[0] ? "true" : "false") - . "" . CRLF; - } - $lists .= "\t" . CRLF - . "" . CRLF; - - // write out the lists file - if (!@file_put_contents($adminops_file, $lists)) { - trigger_error('Could not write adminops file ' . $adminops_file . ' !', E_USER_WARNING); - return false; - } else { - return true; - } - } // writeLists - - - /** - * Read Banned IPs list and apply it on the current instance. - */ - function readIPs() { - - // get banned IPs file name - $bannedips_file = $this->settings['bannedips_file']; - - if ($list = $this->xml_parser->parseXml($bannedips_file)) { - // read the XML structure into variable - if (isset($list['BAN_LIST']['IPADDRESS'])) - $this->bannedips = $list['BAN_LIST']['IPADDRESS']; - else - $this->bannedips = array(); - return true; - } else { - // could not parse XML file - trigger_error('Could not read/parse banned IPs file ' . $bannedips_file . ' !', E_USER_WARNING); - return false; - } - } // readIPs - - /** - * Write Banned IPs list to save it for future runs. - */ - function writeIPs() { - - // get banned IPs file name - $bannedips_file = $this->settings['bannedips_file']; - $empty = true; - - // compile banned IPs file contents - $list = "" . CRLF - . "" . CRLF; - for ($i = 0; $i < count($this->bannedips); $i++) { - if ($this->bannedips[$i] != '') { - $list .= "\t\t" . $this->bannedips[$i] . "" . CRLF; - $empty = false; - } - } - if ($empty) { - $list .= "" . CRLF; - } - $list .= "" . CRLF; - - // write out the list file - if (!@file_put_contents($bannedips_file, $list)) { - trigger_error('Could not write banned IPs file ' . $bannedips_file . ' !', E_USER_WARNING); - return false; - } else { - return true; - } - } // writeIPs - - - /** - * Loads files in the plugins directory. - */ - function loadPlugins() { - - // load and parse the plugins file - if ($plugins = $this->xml_parser->parseXml('plugins.xml')) { - if (!empty($plugins['ASECO_PLUGINS']['PLUGIN'])) { - // take each plugin tag - foreach ($plugins['ASECO_PLUGINS']['PLUGIN'] as $plugin) { - // log plugin message - $this->console_text('[XAseco] Load plugin [' . $plugin . ']'); - // include the plugin - require_once('plugins/' . $plugin); - $this->plugins[] = $plugin; - } - } - } else { - trigger_error('Could not read/parse plugins list plugins.xml !', E_USER_ERROR); - } - } // loadPlugins - - - /** - * Runs the server. - */ - function run($config_file) { - - // load new settings, if available - $this->console_text('[XAseco] Load settings [{1}]', $config_file); - $this->loadSettings($config_file); - - // load admin/operator/ability lists, if available - $this->console_text('[XAseco] Load admin/ops lists [{1}]', $this->settings['adminops_file']); - $this->readLists(); - - // load banned IPs list, if available - $this->console_text('[XAseco] Load banned IPs list [{1}]', $this->settings['bannedips_file']); - $this->readIPs(); - - // load plugins and register chat commands - $this->console_text('[XAseco] Load plugins list [plugins.xml]'); - $this->loadPlugins(); - - // connect to Trackmania Dedicated Server - if (!$this->connect()) { - // kill program with an error - trigger_error('Connection could not be established !', E_USER_ERROR); - } - - // log status message - $this->console('Connection established successfully !'); - // log admin lock message - if ($this->settings['lock_password'] != '') - $this->console_text("[XAseco] Locked admin commands & features with password '{1}'", $this->settings['lock_password']); - - // get basic server info - $this->client->query('GetVersion'); - $response['version'] = $this->client->getResponse(); - $this->server->game = $response['version']['Name']; - $this->server->version = $response['version']['Version']; - $this->server->build = $response['version']['Build']; - - // throw 'starting up' event - $this->releaseEvent('onStartup', null); - - // synchronize information with server - $this->serverSync(); - - // register all chat commands - if ($this->server->getGame() != 'TMF') { - $this->registerChatCommands(); - // set spectator not available outside TMF - if ($this->settings['cheater_action'] == 1) - $this->settings['cheater_action'] = 0; - } - - // make a visual header - $this->sendHeader(); - - // get current game infos if server loaded a track yet - if ($this->currstatus == 100) { - $this->console_text('[XAseco] Waiting for the server to start a challenge'); - } else { - $this->beginRace(false); - } - - // main loop - $this->startup_phase = false; - while (true) { - $starttime = microtime(true); - // get callbacks from the server - $this->executeCallbacks(); - - // sends calls to the server - $this->executeCalls(); - - // throw timing events - $this->releaseEvent('onMainLoop', null); - - $this->currsecond = time(); - if ($this->prevsecond != $this->currsecond) { - $this->prevsecond = $this->currsecond; - $this->releaseEvent('onEverySecond', null); - } - - // reduce CPU usage if main loop has time left - $endtime = microtime(true); - $delay = 200000 - ($endtime - $starttime) * 1000000; - if ($delay > 0) - usleep($delay); - // make sure the script does not timeout - @set_time_limit($this->settings['script_timeout']); - } - - // close the client connection - $this->client->Terminate(); - } // run - - - /** - * Authenticates XASECO at the server. - */ - function connect() { - - // only if logins are set - if ($this->server->ip && $this->server->port && $this->server->login && $this->server->pass) { - // log console message - $this->console('Try to connect to TM dedicated server on {1}:{2} timeout {3}s', - $this->server->ip, $this->server->port, - ($this->server->timeout !== null ? $this->server->timeout : 0)); - - // connect to the server - if (!$this->client->InitWithIp($this->server->ip, $this->server->port, $this->server->timeout)) { - trigger_error('[' . $this->client->getErrorCode() . '] InitWithIp - ' . $this->client->getErrorMessage(), E_USER_WARNING); - return false; - } - - // log console message - $this->console("Try to authenticate with login '{1}' and password '{2}'", - $this->server->login, $this->server->pass); - - // check login - if ($this->server->login != 'SuperAdmin') { - trigger_error("Invalid login '" . $this->server->login . "' - must be 'SuperAdmin' in config.xml !", E_USER_WARNING); - return false; - } - // check password - if ($this->server->pass == 'SuperAdmin') { - trigger_error("Insecure password '" . $this->server->pass . "' - should be changed in dedicated config and config.xml !", E_USER_WARNING); - } - - // log into the server - if (!$this->client->query('Authenticate', $this->server->login, $this->server->pass)) { - trigger_error('[' . $this->client->getErrorCode() . '] Authenticate - ' . $this->client->getErrorMessage(), E_USER_WARNING); - return false; - } - - // enable callback system - $this->client->query('EnableCallbacks', true); - - // wait for server to be ready - $this->waitServerReady(); - - // connection established - return true; - } else { - // connection failed - return false; - } - } // connect - - - /** - * Waits for the server to be ready (status 4, 'Running - Play') - */ - function waitServerReady() { - - $this->client->query('GetStatus'); - $status = $this->client->getResponse(); - if ($status['Code'] != 4) { - $this->console("Waiting for dedicated server to reach status 'Running - Play'..."); - $this->console('Status: ' . $status['Name']); - $timeout = 0; - $laststatus = $status['Name']; - while ($status['Code'] != 4) { - sleep(1); - $this->client->query('GetStatus'); - $status = $this->client->getResponse(); - if ($laststatus != $status['Name']) { - $this->console('Status: ' . $status['Name']); - $laststatus = $status['Name']; - } - if (isset($this->server->timeout) && $timeout++ > $this->server->timeout) - trigger_error('Timed out while waiting for dedicated server!', E_USER_ERROR); - } - } - } // waitServerReady - - /** - * Initializes the server and the player list. - * Reads a list of the players who are on the server already, - * and loads all server variables. - */ - function serverSync() { - - // check server build - if (strlen($this->server->build) == 0 || - ($this->server->getGame() != 'TMF' && strcmp($this->server->build, TMN_BUILD) < 0) || - ($this->server->getGame() == 'TMF' && strcmp($this->server->build, TMF_BUILD) < 0)) { - trigger_error("Obsolete server build '" . $this->server->build . "' - must be " . - ($this->server->getGame() == 'TMF' ? "at least '" . TMF_BUILD . "' !" : "'" . TMN_BUILD . "' !"), E_USER_ERROR); - } - - // get server id, login, nickname, zone & packmask - $this->server->id = 0; // on TMN/TMO/TMS - $this->server->rights = false; - $this->server->isrelay = false; - $this->server->relaymaster = null; - $this->server->relayslist = array(); - $this->server->gamestate = Server::RACE; - $this->server->packmask = ''; - if ($this->server->getGame() == 'TMF') { - $this->client->query('GetSystemInfo'); - $response['system'] = $this->client->getResponse(); - $this->server->serverlogin = $response['system']['ServerLogin']; - - $this->client->query('GetDetailedPlayerInfo', $this->server->serverlogin); - $response['info'] = $this->client->getResponse(); - $this->server->id = $response['info']['PlayerId']; - $this->server->nickname = $response['info']['NickName']; - $this->server->zone = substr($response['info']['Path'], 6); // strip 'World|' - $this->server->rights = ($response['info']['OnlineRights'] == 3); // United = true - - $this->client->query('GetLadderServerLimits'); - $response['ladder'] = $this->client->getResponse(); - $this->server->laddermin = $response['ladder']['LadderServerLimitMin']; - $this->server->laddermax = $response['ladder']['LadderServerLimitMax']; - - $this->client->query('IsRelayServer'); - $this->server->isrelay = ($this->client->getResponse() > 0); - if ($this->server->isrelay) { - $this->client->query('GetMainServerPlayerInfo', 1); - $this->server->relaymaster = $this->client->getResponse(); - } - - // TMNF packmask = 'Stadium' for 'nations' or 'stadium' - $this->client->query('GetServerPackMask'); - $this->server->packmask = $this->client->getResponse(); - - // clear possible leftover ManiaLinks - $this->client->query('SendHideManialinkPage'); - } - - // get mode & limits - $this->client->query('GetCurrentGameInfo', ($this->server->getGame() == 'TMF' ? 1 : 0)); - $response['gameinfo'] = $this->client->getResponse(); - $this->server->gameinfo = new Gameinfo($response['gameinfo']); - - // get status - $this->client->query('GetStatus'); - $response['status'] = $this->client->getResponse(); - $this->currstatus = $response['status']['Code']; - - // get game & trackdir - $this->client->query('GameDataDirectory'); - $this->server->gamedir = $this->client->getResponse(); - $this->client->query('GetTracksDirectory'); - $this->server->trackdir = $this->client->getResponse(); - - // get server name & options - $this->getServerOptions(); - - // throw 'synchronisation' event - $this->releaseEvent('onSync', null); - - // get current players/servers on the server (hardlimited to 300) - if ($this->server->getGame() == 'TMF') - $this->client->query('GetPlayerList', 300, 0, 2); - else - $this->client->query('GetPlayerList', 300, 0); - $response['playerlist'] = $this->client->getResponse(); - - // update players/relays lists - if (!empty($response['playerlist'])) { - foreach ($response['playerlist'] as $player) { - // fake it into thinking it's a connecting player: - // it gets team & ladder info this way & will also throw an - // onPlayerConnect event for players (not relays) to all plugins - $this->playerConnect(array($player['Login'], '')); - } - } - } // serverSync - - - /** - * Sends program header to console and ingame chat. - */ - function sendHeader() { - - $this->console_text('###############################################################################'); - $this->console_text(' XASECO v' . XASECO_VERSION . ' running on {1}:{2}', $this->server->ip, $this->server->port); - if ($this->server->getGame() == 'TMF') { - $this->console_text(' Name : {1} - {2}', stripColors($this->server->name, false), $this->server->serverlogin); - if ($this->server->isrelay) - $this->console_text(' Relays : {1} - {2}', stripColors($this->server->relaymaster['NickName'], false), $this->server->relaymaster['Login']); - $this->console_text(' Game : {1} {2} - {3} - {4}', $this->server->game, - ($this->server->rights ? 'United' : 'Nations'), - $this->server->packmask, $this->server->gameinfo->getMode()); - } else { - $this->console_text(' Name : {1}', stripColors($this->server->name, false)); - $this->console_text(' Game : {1} - {2}', $this->server->game, $this->server->gameinfo->getMode()); - } - $this->console_text(' Version: {1} / {2}', $this->server->version, $this->server->build); - $this->console_text(' Authors: Florian Schnell & Assembler Maniac'); - $this->console_text(' Re-Authored: Xymph'); - $this->console_text('###############################################################################'); - - // format the text of the message - $startup_msg = formatText($this->getChatMessage('STARTUP'), - XASECO_VERSION, - $this->server->ip, $this->server->port); - // show startup message - $this->client->query('ChatSendServerMessage', $this->formatColors($startup_msg)); - } // sendHeader - - - /** - * Gets callbacks from the TM Dedicated Server and reacts on them. - */ - function executeCallbacks() { - - // receive callbacks with a timeout (default: 2 ms) - $this->client->resetError(); - $this->client->readCB(); - - // now get the responses out of the 'buffer' - $calls = $this->client->getCBResponses(); - if ($this->client->isError()) { - trigger_error('ExecCallbacks XMLRPC Error [' . $this->client->getErrorCode() . '] - ' . $this->client->getErrorMessage(), E_USER_ERROR); - } - - if (!empty($calls)) { - while ($call = array_shift($calls)) { - switch ($call[0]) { - case 'TrackMania.PlayerConnect': // [0]=Login, [1]=IsSpectator - $this->playerConnect($call[1]); - break; - - case 'TrackMania.PlayerDisconnect': // [0]=Login - $this->playerDisconnect($call[1]); - break; - - case 'TrackMania.PlayerChat': // [0]=PlayerUid, [1]=Login, [2]=Text, [3]=IsRegistredCmd - $this->playerChat($call[1]); - $this->releaseEvent('onChat', $call[1]); - break; - - case 'TrackMania.PlayerServerMessageAnswer': // [0]=PlayerUid, [1]=Login, [2]=Answer - $this->playerServerMessageAnswer($call[1]); - break; - - case 'TrackMania.PlayerCheckpoint': // TMN: [0]=PlayerUid, [1]=Login, [2]=Time, [3]=Score, [4]=CheckpointIndex; TMF: [0]=PlayerUid, [1]=Login, [2]=TimeOrScore, [3]=CurLap, [4]=CheckpointIndex - if (!$this->server->isrelay) - $this->releaseEvent('onCheckpoint', $call[1]); - break; - - case 'TrackMania.PlayerFinish': // [0]=PlayerUid, [1]=Login, [2]=TimeOrScore - $this->playerFinish($call[1]); - break; - - case 'TrackMania.BeginRace': // [0]=Challenge - if ($this->server->getGame() != 'TMF') - $this->beginRace($call[1]); - break; - - case 'TrackMania.EndRace': // [0]=Rankings[], [1]=Challenge - if ($this->server->getGame() != 'TMF') - $this->endRace($call[1]); - break; - - case 'TrackMania.BeginRound': // none - $this->beginRound(); - break; - - case 'TrackMania.StatusChanged': // [0]=StatusCode, [1]=StatusName - // update status changes - $this->prevstatus = $this->currstatus; - $this->currstatus = $call[1][0]; - // if TMF mode Sync, check WarmUp state - if ($this->server->getGame() == 'TMF') { - if ($this->currstatus == 3 || $this->currstatus == 5) { - $this->client->query('GetWarmUp'); - $this->warmup_phase = $this->client->getResponse(); - } - } else { - $this->warmup_phase = false; - } - // on TMF, use real EndRound callback - if ($this->server->getGame() != 'TMF') { - // if change from Play (4) to Sync (3) or Finish (5), - // it's the end of a round - if ($this->prevstatus == 4 && ($this->currstatus == 3 || $this->currstatus == 5)) - $this->endRound(); - } - if ($this->currstatus == 4) { // Running - Play - $this->runningPlay(); - } - $this->releaseEvent('onStatusChangeTo' . $this->currstatus, $call[1]); - break; - - // new TMF callbacks: - - case 'TrackMania.EndRound': // none - $this->endRound(); - break; - - case 'TrackMania.BeginChallenge': // [0]=Challenge, [1]=WarmUp, [2]=MatchContinuation - $this->beginRace($call[1]); - break; - - case 'TrackMania.EndChallenge': // [0]=Rankings[], [1]=Challenge, [2]=WasWarmUp, [3]=MatchContinuesOnNextChallenge, [4]=RestartChallenge - $this->endRace($call[1]); - break; - - case 'TrackMania.PlayerManialinkPageAnswer': // [0]=PlayerUid, [1]=Login, [2]=Answer - $this->releaseEvent('onPlayerManialinkPageAnswer', $call[1]); - break; - - case 'TrackMania.BillUpdated': // [0]=BillId, [1]=State, [2]=StateName, [3]=TransactionId - $this->releaseEvent('onBillUpdated', $call[1]); - break; - - case 'TrackMania.ChallengeListModified': // [0]=CurChallengeIndex, [1]=NextChallengeIndex, [2]=IsListModified - $this->releaseEvent('onChallengeListModified', $call[1]); - break; - - case 'TrackMania.PlayerInfoChanged': // [0]=PlayerInfo - $this->playerInfoChanged($call[1][0]); - break; - - case 'TrackMania.PlayerIncoherence': // [0]=PlayerUid, [1]=Login - $this->releaseEvent('onPlayerIncoherence', $call[1]); - break; - - case 'TrackMania.TunnelDataReceived': // [0]=PlayerUid, [1]=Login, [2]=Data - $this->releaseEvent('onTunnelDataReceived', $call[1]); - break; - - case 'TrackMania.Echo': // [0]=Internal, [1]=Public - $this->releaseEvent('onEcho', $call[1]); - break; - - case 'TrackMania.ManualFlowControlTransition': // [0]=Transition - $this->releaseEvent('onManualFlowControlTransition', $call[1]); - break; - - case 'TrackMania.VoteUpdated': // [0]=StateName, [1]=Login, [2]=CmdName, [3]=CmdParam - $this->releaseEvent('onVoteUpdated', $call[1]); - break; - - default: - // do nothing - } - } - return $calls; - } else { - return false; - } - } // executeCallbacks - - - /** - * Adds calls to a multiquery. - * It's possible to set a callback function which - * will be executed on incoming response. - * You can also set an ID to read response later on. - */ - function addCall($call, $params = array(), $id = 0, $callback_func = false) { - - // adds call and registers a callback if needed - $index = $this->client->addCall($call, $params); - $rpc_call = new RPCCall($id, $index, $callback_func, array($call, $params)); - $this->rpc_calls[] = $rpc_call; - } // addCall - - - /** - * Executes a multicall and gets responses. - * Saves responses in array with IDs as keys. - */ - function executeCalls() { - - // clear responses - $this->rpc_responses = array(); - - // stop if there are no rpc calls in query - if (empty($this->client->calls)) { - return true; - } - - $this->client->resetError(); - $tmpcalls = $this->client->calls; // debugging code to find UTF-8 errors - // sends multiquery to the server and gets the response - if ($this->client->multiquery()) { - if ($this->client->isError()) { - $this->console_text(print_r($tmpcalls, true)); - trigger_error('ExecCalls XMLRPC Error [' . $this->client->getErrorCode() . '] - ' . $this->client->getErrorMessage(), E_USER_ERROR); - } - - // get new response from server - $responses = $this->client->getResponse(); - - // handle server responses - foreach ($this->rpc_calls as $call) { - // display error message if needed - $err = false; - if (isset($responses[$call->index]['faultString'])) { - $this->rpcErrorResponse($responses[$call->index]); - print_r($call->call); - $err = true; - } - - // if an id was set, then save the response under the specified id - if ($call->id) { - $this->rpc_responses[$call->id] = $responses[$call->index][0]; - } - - // if a callback function has been set, then execute it - if ($call->callback && !$err) { - if (function_exists($call->callback)) { - // callback the function with the response as parameter - call_user_func($call->callback, $responses[$call->index][0]); - } - - // if a function with the name of the callback wasn't found, then - // try to execute a method with its name - elseif (method_exists($this, $call->callback)) { - // callback the method with the response as parameter - call_user_func(array($this, $call->callback), $responses[$call->index][0]); - } - } - } - } - - // clear calls - $this->rpc_calls = array(); - } // executeCalls - - - /** - * Documents RPC Errors. - */ - function rpcErrorResponse($response) { - - $this->console_text('[RPC Error ' . $response['faultCode'] . '] ' . $response['faultString']); - } // rpcErrorResponse - - - /** - * Registers functions which are called on specific events. - */ - function registerEvent($event_type, $callback_func) { - - // registers a new event - $this->events[$event_type][] = $callback_func; - } // registerEvent - - /** - * Executes the functions which were registered for specified events. - */ - function releaseEvent($event_type, $func_param) { - - // executes registered event functions - // if there are any events for that type - if (!empty($this->events[$event_type])) { - // for each registered function of this type - foreach ($this->events[$event_type] as $func_name) { - // if function for the specified player connect event can be found - if (is_callable($func_name)) { - // ... execute it! - call_user_func($func_name, $this, $func_param); - } - } - } - } // releaseEvent - - - /** - * Stores a new user command. - */ - function addChatCommand($command_name, $command_help, $command_is_admin = false) { - - $chat_command = new ChatCommand($command_name, $command_help, $command_is_admin); - $this->chat_commands[] = $chat_command; - } // addChatCommand - - /** - * Registers all chat commands with the server. - */ - function registerChatCommands() { - - // clear the current list of chat commands - $this->client->query('CleanChatCommand'); - - if (isset($this->chat_commands)) { - foreach ($this->chat_commands as $command) { - // only if it's no admin command - if (!$command->isadmin) { - // log message if debug mode is set to true - if ($this->debug) { - $this->console_text('register chat command: ' . $command->name); - } - - // register chat command at server - $this->client->query('AddChatCommand', $command->name); - } - } - } - } // registerChatCommands - - - /** - * When a round is started, signal the event. - */ - function beginRound() { - - $this->console_text('Begin Round'); - $this->releaseEvent('onBeginRound', null); - } // beginRound - - /** - * When a round is ended, signal the event. - */ - function endRound() { - - $this->console_text('End Round'); - $this->releaseEvent('onEndRound', null); - } // endRound - - - /** - * When a TMF player's info changed, signal the event. Fields: - * Login, NickName, PlayerId, TeamId, SpectatorStatus, LadderRanking, Flags - */ - function playerInfoChanged($playerinfo) { - - // on relay, check for player from master server - if ($this->server->isrelay && floor($playerinfo['Flags'] / 10000) % 10 != 0) - return; - - // check for valid player - if (!$player = $this->server->players->getPlayer($playerinfo['Login'])) - return; - - // check ladder ranking - if ($playerinfo['LadderRanking'] > 0) { - $player->ladderrank = $playerinfo['LadderRanking']; - $player->isofficial = true; - } else { - $player->isofficial = false; - } - - // check spectator status (ignoring temporary changes) - $player->prevstatus = $player->isspectator; - if (($playerinfo['SpectatorStatus'] % 10) != 0) - $player->isspectator = true; - else - $player->isspectator = false; - - $this->releaseEvent('onPlayerInfoChanged', $playerinfo); - } // playerInfoChanged - - - /** - * When a new track is started we have to get information - * about the new track and so on. - */ - function runningPlay() { - // request information about the new challenge - // ... and callback to function newChallenge() - } // runningPlay - - - /** - * When a new race is started we have to get information - * about the new track and so on. - */ - function beginRace($race) { - // request information about the new challenge - // ... and callback to function newChallenge() - - // if TMF new challenge, check WarmUp state - if ($this->server->getGame() == 'TMF' && $race) - $this->warmup_phase = $race[1]; - - if (!$race) { - $this->addCall('GetCurrentChallengeInfo', array(), '', 'newChallenge'); - } else { - $this->newChallenge($race[0]); - } - } // beginRace - - - /** - * Reacts on new challenges. - * Gets record to current challenge etc. - */ - function newChallenge($challenge) { - - // log if not a restart - $this->server->gamestate = Server::RACE; - if ($this->restarting == 0) - $this->console_text('Begin Challenge'); - - // refresh game info - $this->client->query('GetCurrentGameInfo', ($this->server->getGame() == 'TMF' ? 1 : 0)); - $gameinfo = $this->client->getResponse(); - $this->server->gameinfo = new Gameinfo($gameinfo); - - // check for TMF restarting challenge - $this->changingmode = false; - if ($this->server->getGame() == 'TMF' && $this->restarting > 0) { - // check type of restart and signal an instant one - if ($this->restarting == 2) { - $this->restarting = 0; - } else { // == 1 - $this->restarting = 0; - // throw postfix 'restart challenge' event - $this->releaseEvent('onRestartChallenge2', $challenge); - return; - } - } - // refresh server name & options - $this->getServerOptions(); - - // reset record list - $this->server->records->clear(); - // reset player votings - //$this->server->players->resetVotings(); - - // create new challenge object - $challenge_item = new Challenge($challenge); - - // in TMF Rounds/Team/Cup mode if multilap track, get forced laps - if ($this->server->getGame() == 'TMF' && $challenge_item->laprace && - ($this->server->gameinfo->mode == Gameinfo::RNDS || - $this->server->gameinfo->mode == Gameinfo::TEAM || - $this->server->gameinfo->mode == Gameinfo::CUP)) { - $challenge_item->forcedlaps = $this->server->gameinfo->forcedlaps; - } - - // obtain challenge's GBX data, TMX info & records - $challenge_item->gbx = new GBXChallMapFetcher(true); - try - { - $challenge_item->gbx->processFile($this->server->trackdir . $challenge_item->filename); - } - catch (Exception $e) - { - trigger_error($e->getMessage(), E_USER_WARNING); - } - $challenge_item->tmx = findTMXdata($challenge_item->uid, $challenge_item->environment, $challenge_item->gbx->exeVer, true); - - // throw main 'begin challenge' event - $this->releaseEvent('onNewChallenge', $challenge_item); - - // log console message - $this->console('track changed [{1}] >> [{2}]', - stripColors($this->server->challenge->name, false), - stripColors($challenge_item->name, false)); - -/* disabled in favor of RASP's karma system - Xymph - // log track's score - if ($challenge_item->score && $challenge_item->votes) { - // calculate avarage score and display - $this->console('average score of this track is {1}', - $challenge_item->score/$challenge_item->votes); - } else { - // no votings - $this->console('no votings available for this track'); - } -disabled */ - - // check for relay server - if (!$this->server->isrelay) { - // check if record exists on new track - $cur_record = $this->server->records->getRecord(0); - if ($cur_record !== false && $cur_record->score > 0) { - $score = ($this->server->gameinfo->mode == Gameinfo::STNT ? - str_pad($cur_record->score, 5, ' ', STR_PAD_LEFT) : - formatTime($cur_record->score)); - - // log console message of current record - $this->console('current record on {1} is {2} and held by {3}', - stripColors($challenge_item->name, false), - trim($score), - stripColors($cur_record->player->nickname, false)); - - // replace parameters - $message = formatText($this->getChatMessage('RECORD_CURRENT'), - stripColors($challenge_item->name), - trim($score), - stripColors($cur_record->player->nickname)); - } else { - if ($this->server->gameinfo->mode == Gameinfo::STNT) - $score = ' ---'; - else - $score = ' --.--'; - - // log console message of no record - $this->console('currently no record on {1}', - stripColors($challenge_item->name, false)); - - // replace parameters - $message = formatText($this->getChatMessage('RECORD_NONE'), - stripColors($challenge_item->name)); - } - if (function_exists('setRecordsPanel')) - setRecordsPanel('local', $score); - - // if no trackrecs, show the original record message to all players - if (($this->settings['show_recs_before'] & 1) == 1) { - if (($this->settings['show_recs_before'] & 4) == 4 && function_exists('send_window_message')) - send_window_message($this, $message, false); - else - $this->client->query('ChatSendServerMessage', $this->formatColors($message)); - } - } - - // update the field which contains current challenge - $this->server->challenge = $challenge_item; - - // throw postfix 'begin challenge' event (various) - $this->releaseEvent('onNewChallenge2', $challenge_item); - - // show top-8 & records of all online players before track - if (($this->settings['show_recs_before'] & 2) == 2 && function_exists('show_trackrecs')) { - show_trackrecs($this, false, 1, $this->settings['show_recs_before']); // from chat.records2.php - } - } // newChallenge - - - /** - * End of current race. - * Write records to database and/or display final statistics. - */ - function endRace($race) { - - // check for TMF RestartChallenge flag - if ($this->server->getGame() == 'TMF' && $race[4]) { - $this->restarting = 1; - // check whether changing game mode or any player has a time/score, - // then there will be ChatTime, otherwise it's an instant restart - if ($this->changingmode) - $this->restarting = 2; - else - foreach ($race[0] as $pl) { - if ($pl['BestTime'] > 0 || $pl['Score'] > 0) { - $this->restarting = 2; - break; - } - } - // log type of restart and signal an instant one - if ($this->restarting == 2) { - $this->console_text('Restart Challenge (with ChatTime)'); - } else { // == 1 - $this->console_text('Restart Challenge (instant)'); - // throw main 'restart challenge' event - $this->releaseEvent('onRestartChallenge', $race); - return; - } - } - // log if not a restart - $this->server->gamestate = Server::SCORE; - if ($this->restarting == 0) - $this->console_text('End Challenge'); - - // show top-8 & all new records after track - if (($this->settings['show_recs_after'] & 2) == 2 && function_exists('show_trackrecs')) { - show_trackrecs($this, false, 3, $this->settings['show_recs_after']); // from chat.records2.php - } elseif (($this->settings['show_recs_after'] & 1) == 1) { - // fall back on old top-5 - $records = ''; - - if ($this->server->records->count() == 0) { - // display a no-new-record message - $message = formatText($this->getChatMessage('RANKING_NONE'), - stripColors($this->server->challenge->name), - 'after'); - } else { - // display new records set up this round - $message = formatText($this->getChatMessage('RANKING'), - stripColors($this->server->challenge->name), - 'after'); - - // go through each record - for ($i = 0; $i < 5; $i++) { - $cur_record = $this->server->records->getRecord($i); - - // if the record is set then display it - if ($cur_record !== false && $cur_record->score > 0) { - // replace parameters - $record_msg = formatText($this->getChatMessage('RANKING_RECORD_NEW'), - $i+1, - stripColors($cur_record->player->nickname), - ($this->server->gameinfo->mode == Gameinfo::STNT ? - $cur_record->score : formatTime($cur_record->score))); - $records .= $record_msg; - } - } - } - - // append the records if any - if ($records != '') { - $records = substr($records, 0, strlen($records)-2); // strip trailing ", " - $message .= LF . $records; - } - - // show ranking message to all players - if (($this->settings['show_recs_after'] & 4) == 4 && function_exists('send_window_message')) - send_window_message($this, $message, true); - else - $this->client->query('ChatSendServerMessage', $this->formatColors($message)); - } - - // get rankings and call endRaceRanking as soon as we have them - // $this->addCall('GetCurrentRanking', array(2, 0), false, 'endRaceRanking'); - if (!$this->server->isrelay) - $this->endRaceRanking($race[0]); - - // throw prefix 'end challenge' event (chat-based votes) - $this->releaseEvent('onEndRace1', $race); - // throw main 'end challenge' event - $this->releaseEvent('onEndRace', $race); - } // endRace - - - /** - * Check out who won the current track and increment his/her wins by one. - */ - function endRaceRanking($ranking) { - - // check for online login - if (isset($ranking[0]['Login']) && - ($player = $this->server->players->getPlayer($ranking[0]['Login'])) !== false) { - // check for winner if there's more than one player - if ($ranking[0]['Rank'] == 1 && count($ranking) > 1 && - ($this->server->gameinfo->mode == Gameinfo::STNT ? - ($ranking[0]['Score'] > 0) : ($ranking[0]['BestTime'] > 0))) { - // increase the player's wins - $player->newwins++; - - // log console message - $this->console('{1} won for the {2}. time!', - $player->login, $player->getWins()); - - if ($player->getWins() % $this->settings['global_win_multiple'] == 0) { - // replace parameters - $message = formatText($this->getChatMessage('WIN_MULTI'), - stripColors($player->nickname), $player->getWins()); - - // show chat message - $this->client->query('ChatSendServerMessage', $this->formatColors($message)); - } else { - // replace parameters - $message = formatText($this->getChatMessage('WIN_NEW'), - $player->getWins()); - - // show chat message - $this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $player->login); - } - - // throw 'player wins' event - $this->releaseEvent('onPlayerWins', $player); - } - } - } // endRaceRanking - - - /** - * Handles connections of new players. - */ - function playerConnect($player) { - - // request information about the new player - // (removed callback mechanism here, as GetPlayerInfo occasionally - // returns no data and then the connecting login would be lost) - $login = $player[0]; - if ($this->server->getGame() == 'TMF') { - $this->client->query('GetDetailedPlayerInfo', $login); - $playerd = $this->client->getResponse(); - $this->client->query('GetPlayerInfo', $login, 1); - } else { // TMN/TMS/TMO - $this->client->query('GetPlayerInfo', $login); - } - $player = $this->client->getResponse(); - - // check for server - if (isset($player['Flags']) && floor($player['Flags'] / 100000) % 10 != 0) { - // register relay server - if (!$this->server->isrelay && $player['Login'] != $this->server->serverlogin) { - $this->server->relayslist[$player['Login']] = $player; - - // log console message - $this->console('<<< relay server {1} ({2}) connected', $player['Login'], - stripColors($player['NickName'], false)); - } - - // on relay, check for player from master server - } elseif ($this->server->isrelay && floor($player['Flags'] / 10000) % 10 != 0) { - ; // ignore - } else { - $ipaddr = isset($playerd['IPAddress']) ? preg_replace('/:\d+/', '', $playerd['IPAddress']) : ''; // strip port - - // if no data fetched, notify & kick the player - if (!isset($player['Login']) || $player['Login'] == '') { - $message = str_replace('{br}', LF, $this->getChatMessage('CONNECT_ERROR')); - $message = $this->formatColors($message); - $this->client->query('ChatSendServerMessageToLogin', str_replace(LF.LF, LF, $message), $login); - if ($this->server->getGame() == 'TMN') - $this->client->query('SendDisplayServerMessageToLogin', $login, $message, 'OK', '', 0); - sleep(5); // allow time to connect and see the notice - if ($this->server->getGame() == 'TMF') - $this->client->addCall('Kick', array($login, $this->formatColors($this->getChatMessage('CONNECT_DIALOG')))); - else - $this->client->addCall('Kick', array($login)); - // log console message - $this->console('GetPlayerInfo failed for ' . $login . ' -- notified & kicked'); - return; - - // if player IP in ban list, notify & kick the player - } elseif (!empty($this->bannedips) && in_array($ipaddr, $this->bannedips)) { - $message = str_replace('{br}', LF, $this->getChatMessage('BANIP_ERROR')); - $message = $this->formatColors($message); - $this->client->query('ChatSendServerMessageToLogin', str_replace(LF.LF, LF, $message), $login); - if ($this->server->getGame() == 'TMN') - $this->client->query('SendDisplayServerMessageToLogin', $login, $message, 'OK', '', 0); - sleep(5); // allow time to connect and see the notice - if ($this->server->getGame() == 'TMF') - $this->client->addCall('Ban', array($login, $this->formatColors($this->getChatMessage('BANIP_DIALOG')))); - else - $this->client->addCall('Ban', array($login)); - // log console message - $this->console('Player ' . $login . ' banned from ' . $ipaddr . ' -- notified & kicked'); - return; - - // client version checking on TMF - } elseif ($this->server->getGame() == 'TMF') { - // extract version number - $version = str_replace(')', '', preg_replace('/.*\(/', '', $playerd['ClientVersion'])); - if ($version == '') $version = '2.11.11'; - $message = str_replace('{br}', LF, $this->getChatMessage('CLIENT_ERROR')); - - // if invalid version, notify & kick the player - if ($this->settings['player_client'] != '' && - strcmp($version, $this->settings['player_client']) < 0) { - $this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $login); - sleep(5); // allow time to connect and see the notice - $this->client->addCall('Kick', array($login, $this->formatColors($this->getChatMessage('CLIENT_DIALOG')))); - // log console message - $this->console('Obsolete player client version ' . $version . ' for ' . $login . ' -- notified & kicked'); - return; - } - - // if invalid version, notify & kick the admin - if ($this->settings['admin_client'] != '' && $this->isAnyAdminL($player['Login']) && - strcmp($version, $this->settings['admin_client']) < 0) { - $this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $login); - sleep(5); // allow time to connect and see the notice - $this->client->addCall('Kick', array($login, $this->formatColors($this->getChatMessage('CLIENT_DIALOG')))); - // log console message - $this->console('Obsolete admin client version ' . $version . ' for ' . $login . ' -- notified & kicked'); - return; - } - } - - // if no TMN team, try again via world stats - if ($this->server->getGame() == 'TMN' && !isLANLogin($login) && - $player['LadderStats']['TeamName'] == '') { - $data = new TMNDataFetcher($login, false); - if ($data->teamname != '') { - $player['LadderStats']['TeamName'] = $data->teamname; - } - } - - // create player object - $player_item = new Player($this->server->getGame() == 'TMF' ? $playerd : $player); - // set default window style & panels - $player_item->style = $this->style; - $player_item->panels['admin'] = $this->panels['admin']; - $player_item->panels['donate'] = $this->panels['donate']; - $player_item->panels['records'] = $this->panels['records']; - $player_item->panels['vote'] = $this->panels['vote']; - - // adds a new player to the internal player list - $this->server->players->addPlayer($player_item); - - // log console message - $this->console('<< player {1} joined the game [{2} : {3} : {4} : {5} : {6}]', - $player_item->pid, - $player_item->login, - $player_item->nickname, - $player_item->nation, - $player_item->ladderrank, - $player_item->ip); - - // replace parameters - $message = formatText($this->getChatMessage('WELCOME'), - stripColors($player_item->nickname), - $this->server->name, XASECO_VERSION); - // hyperlink package name & version number on TMF - if ($this->server->getGame() == 'TMF') - $message = preg_replace('/XASECO.+' . XASECO_VERSION . '/', '$l[' . XASECO_TMN . ']$0$l', $message); - - // send welcome popup or chat message - if ($this->settings['welcome_msg_window']) { - if ($this->server->getGame() == 'TMF') { - $message = str_replace('{#highlite}', '{#message}', $message); - $message = preg_split('/{br}/', $this->formatColors($message)); - // repack all lines - foreach ($message as &$line) - $line = array($line); - display_manialink($player_item->login, '', - array('Icons64x64_1', 'Inbox'), $message, - array(1.2), 'OK'); - } else { // TMN - $message = str_replace('{br}', LF, $this->formatColors($message)); - $this->client->query('SendDisplayServerMessageToLogin', $player_item->login, $message, 'OK', '', 0); - } - } else { - $message = str_replace('{br}', LF, $this->formatColors($message)); - $this->client->query('ChatSendServerMessageToLogin', str_replace(LF.LF, LF, $message), $player_item->login); - } - - // if there's a record on current track - $cur_record = $this->server->records->getRecord(0); - if ($cur_record !== false && $cur_record->score > 0) { - // set message to the current record - $message = formatText($this->getChatMessage('RECORD_CURRENT'), - stripColors($this->server->challenge->name), - ($this->server->gameinfo->mode == Gameinfo::STNT ? - $cur_record->score : formatTime($cur_record->score)), - stripColors($cur_record->player->nickname)); - } else { // if there should be no record to display - // display a no-record message - $message = formatText($this->getChatMessage('RECORD_NONE'), - stripColors($this->server->challenge->name)); - } - - // show top-8 & records of all online players before track - if (($this->settings['show_recs_before'] & 2) == 2 && function_exists('show_trackrecs')) { - show_trackrecs($this, $player_item->login, 1, 0); // from chat.records2.php - } elseif (($this->settings['show_recs_before'] & 1) == 1) { - // or show original record message - $this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $player_item->login); - } - - // throw main 'player connects' event - $this->releaseEvent('onPlayerConnect', $player_item); - // throw postfix 'player connects' event (access control) - $this->releaseEvent('onPlayerConnect2', $player_item); - } - } // playerConnect - - /** - * Handles disconnections of players. - */ - function playerDisconnect($player) { - - // check for relay server - if (!$this->server->isrelay && array_key_exists($player[0], $this->server->relayslist)) { - // log console message - $this->console('>>> relay server {1} ({2}) disconnected', $player[0], - stripColors($this->server->relayslist[$player[0]]['NickName'], false)); - - unset($this->server->relayslist[$player[0]]); - return; - } - - // delete player and put him into the player item - // ignore event if disconnect fluke after player already left, - // or on relay if player from master server (which wasn't added) - if (!$player_item = $this->server->players->removePlayer($player[0])) - return; - - // log console message - $this->console('>> player {1} left the game [{2} : {3} : {4}]', - $player_item->pid, - $player_item->login, - $player_item->nickname, - formatTimeH($player_item->getTimeOnline() * 1000, false)); - - // throw 'player disconnects' event - $this->releaseEvent('onPlayerDisconnect', $player_item); - } // playerDisconnect - - - /** - * Handles clicks on server messageboxes. - */ - function playerServerMessageAnswer($answer) { - - if ($answer[2]) { - // throw TMN 'click' event - $this->releaseEvent('onPlayerServerMessageAnswer', $answer); - } - } // playerServerMessageAnswer - - - /** - * Player reaches finish. - */ - function playerFinish($finish) { - - // if no track info, or if server 'finish', bail out immediately - if ($this->server->challenge->name == '' || $finish[0] == 0) - return; - - // if relay server or not in Play status, bail out immediately - if ($this->server->isrelay || $this->currstatus != 4) - return; - - // check for valid player - if ((!$player = $this->server->players->getPlayer($finish[1])) || - $player->login == '') - return; - - // build a record object with the current finish information - $finish_item = new Record(); - $finish_item->player = $player; - $finish_item->score = $finish[2]; - $finish_item->date = strftime('%Y-%m-%d %H:%M:%S'); - $finish_item->new = false; - $finish_item->challenge = clone $this->server->challenge; - unset($finish_item->challenge->gbx); // reduce memory usage - unset($finish_item->challenge->tmx); - - // throw prefix 'player finishes' event (checkpoints) - $this->releaseEvent('onPlayerFinish1', $finish_item); - // throw main 'player finishes' event - $this->releaseEvent('onPlayerFinish', $finish_item); - } // playerFinish - - - /** - * Receives chat messages and reacts on them. - * Reactions are done by the chat plugins. - */ - function playerChat($chat) { - - // verify login - if ($chat[1] == '' || $chat[1] == '???') { - trigger_error('playerUid ' . $chat[0] . 'has login [' . $chat[1] . ']!', E_USER_WARNING); - $this->console('playerUid {1} attempted to use chat command "{2}"', - $chat[0], $chat[2]); - return; - } - - // ignore master server messages on relay - if ($this->server->isrelay && $chat[1] == $this->server->relaymaster['Login']) - return; - - // check for chat command '/' prefix - $command = $chat[2]; - if ($command != '' && $command[0] == '/') { - // remove '/' prefix - $command = substr($command, 1); - - // split strings at spaces and add them into an array - $params = explode(' ', $command, 2); - $translated_name = str_replace('+', 'plus', $params[0]); - $translated_name = str_replace('-', 'dash', $translated_name); - - // check if the function and the command exist - if (function_exists('chat_' . $translated_name)) { - // insure parameter exists & is trimmed - if (isset($params[1])) - $params[1] = trim($params[1]); - else - $params[1] = ''; - - // get & verify player object - if (($author = $this->server->players->getPlayer($chat[1])) && - $author->login != '') { - // log console message - $this->console('player {1} used chat command "/{2} {3}"', - $chat[1], $params[0], $params[1]); - - // save circumstances in array - $chat_command = array(); - $chat_command['author'] = $author; - $chat_command['params'] = $params[1]; - - // call the function which belongs to the command - call_user_func('chat_' . $translated_name, $this, $chat_command); - } else { - trigger_error('Player object for \'' . $chat[1] . '\' not found!', E_USER_WARNING); - $this->console('player {1} attempted to use chat command "/{2} {3}"', - $chat[1], $params[0], $params[1]); - } - } elseif ($params[0] == 'version' || - ($params[0] == 'serverlogin' && $this->server->getGame() == 'TMF')) { - // log built-in commands - $this->console('player {1} used built-in command "/{2}"', - $chat[1], $command); - } else { - // optionally log bogus chat commands too - if ($this->settings['log_all_chat']) { - if ($chat[0] != $this->server->id) { - $this->console('({1}) {2}', $chat[1], stripColors($chat[2], false)); - } - } - } - } else { - // optionally log all normal chat too - if ($this->settings['log_all_chat']) { - if ($chat[0] != $this->server->id && $chat[2] != '') { - $this->console('({1}) {2}', $chat[1], stripColors($chat[2], false)); - } - } - } - } // playerChat - - - /** - * Gets the specified chat message out of the settings file. - */ - function getChatMessage($name) { - - return htmlspecialchars_decode($this->chat_messages[$name][0]); - } // getChatMessage - - - /** - * Checks if an admin is allowed to perform this ability - */ - function allowAdminAbility($ability) { - - // map to uppercase before checking list - $ability = strtoupper($ability); - if (isset($this->adm_abilities[$ability])) { - return $this->adm_abilities[$ability][0]; - } else { - return false; - } - } // allowAdminAbility - - /** - * Checks if an operator is allowed to perform this ability - */ - function allowOpAbility($ability) { - - // map to uppercase before checking list - $ability = strtoupper($ability); - if (isset($this->op_abilities[$ability])) { - return $this->op_abilities[$ability][0]; - } else { - return false; - } - } // allowOpAbility - - /** - * Checks if the given player is allowed to perform this ability - */ - function allowAbility($player, $ability) { - - // check for unlocked password - if ($this->settings['lock_password'] != '' && !$player->unlocked) - return false; - - // MasterAdmins can always do everything - if ($this->isMasterAdmin($player)) - return true; - - // check Admins & their abilities - if ($this->isAdmin($player)) - return $this->allowAdminAbility($ability); - - // check Operators & their abilities - if ($this->isOperator($player)) - return $this->allowOpAbility($ability); - - return false; - } // allowAbility - - - /** - * Checks if the given player IP matches the corresponding list IP, - * allowing for class C and B wildcards, and multiple comma-separated - * IPs / wildcards. - */ - function ip_match($playerip, $listip) { - - // check for offline player (removeadmin / removeop) - if ($playerip == '') - return true; - - $match = false; - // check all comma-separated IPs/wildcards - foreach (explode(',', $listip) as $ip) { - // check for complete list IP - if (preg_match('/^\d+\.\d+\.\d+\.\d+$/', $ip)) - $match = ($playerip == $ip); - // check class B wildcard - elseif (substr($ip, -4) == '.*.*') - $match = (preg_replace('/\.\d+\.\d+$/', '', $playerip) == substr($ip, 0, -4)); - // check class C wildcard - elseif (substr($ip, -2) == '.*') - $match = (preg_replace('/\.\d+$/', '', $playerip) == substr($ip, 0, -2)); - - if ($match) return true; - } - return false; - } - - /** - * Checks if the given player is in masteradmin list with, optionally, - * an authorized IP. - */ - function isMasterAdmin($player) { - - // check for masteradmin list entry - if (isset($player->login) && $player->login != '' && isset($this->masteradmin_list['TMLOGIN'])) - if (($i = array_search($player->login, $this->masteradmin_list['TMLOGIN'])) !== false) - // check for matching IP if set - if ($this->masteradmin_list['IPADDRESS'][$i] != '') - if (!$this->ip_match($player->ip, $this->masteradmin_list['IPADDRESS'][$i])) { - trigger_error("Attempt to use MasterAdmin login '" . $player->login . "' from IP " . $player->ip . " !", E_USER_WARNING); - return false; - } else - return true; - else - return true; - else - return false; - else - return false; - } // isMasterAdmin - - /** - * Checks if the given player is in admin list with, optionally, - * an authorized IP. - */ - function isAdmin($player) { - - // check for admin list entry - if (isset($player->login) && $player->login != '' && isset($this->admin_list['TMLOGIN'])) - if (($i = array_search($player->login, $this->admin_list['TMLOGIN'])) !== false) - // check for matching IP if set - if ($this->admin_list['IPADDRESS'][$i] != '') - if (!$this->ip_match($player->ip, $this->admin_list['IPADDRESS'][$i])) { - trigger_error("Attempt to use Admin login '" . $player->login . "' from IP " . $player->ip . " !", E_USER_WARNING); - return false; - } else - return true; - else - return true; - else - return false; - else - return false; - } // isAdmin - - /** - * Checks if the given player is in operator list with, optionally, - * an authorized IP. - */ - function isOperator($player) { - - // check for operator list entry - if (isset($player->login) && $player->login != '' && isset($this->operator_list['TMLOGIN'])) - if (($i = array_search($player->login, $this->operator_list['TMLOGIN'])) !== false) - // check for matching IP if set - if ($this->operator_list['IPADDRESS'][$i] != '') - if (!$this->ip_match($player->ip, $this->operator_list['IPADDRESS'][$i])) { - trigger_error("Attempt to use Operator login '" . $player->login . "' from IP " . $player->ip . " !", E_USER_WARNING); - return false; - } else - return true; - else - return true; - else - return false; - else - return false; - } // isOperator - - /** - * Checks if the given player is in any admin tier with, optionally, - * an authorized IP. - */ - function isAnyAdmin($player) { - - return ($this->isMasterAdmin($player) || $this->isAdmin($player) || $this->isOperator($player)); - } // isAnyAdmin - - - /** - * Checks if the given player login is in masteradmin list. - */ - function isMasterAdminL($login) { - - if ($login != '' && isset($this->masteradmin_list['TMLOGIN'])) { - return in_array($login, $this->masteradmin_list['TMLOGIN']); - } else { - return false; - } - } // isMasterAdminL - - /** - * Checks if the given player login is in admin list. - */ - function isAdminL($login) { - - if ($login != '' && isset($this->admin_list['TMLOGIN'])) { - return in_array($login, $this->admin_list['TMLOGIN']); - } else { - return false; - } - } // isAdminL - - /** - * Checks if the given player login is in operator list. - */ - function isOperatorL($login) { - - // check for operator list entry - if ($login != '' && isset($this->operator_list['TMLOGIN'])) - return in_array($login, $this->operator_list['TMLOGIN']); - else - return false; - } // isOperatorL - - /** - * Checks if the given player login is in any admin tier. - */ - function isAnyAdminL($login) { - - return ($this->isMasterAdminL($login) || $this->isAdminL($login) || $this->isOperatorL($login)); - } // isAnyAdminL - - - /** - * Checks if the given player is a spectator. - */ - function isSpectator($player) { - - // get current player status - if ($this->server->getGame() != 'TMF') { - $this->client->query('GetPlayerInfo', $player->login); - $info = $this->client->getResponse(); - if (isset($info['IsSpectator'])) - $player->isspectator = $info['IsSpectator']; - else - $player->isspectator = false; - } - return $player->isspectator; - } // isSpectator - - /** - * Handles cheating player. - */ - function processCheater($login, $checkpoints, $chkpt, $finish) { - - // collect checkpoints - $cps = ''; - foreach ($checkpoints as $cp) - $cps .= formatTime($cp) . '/'; - $cps = substr($cps, 0, strlen($cps)-1); // strip trailing '/' - - // report cheat - if ($finish == -1) - trigger_error('Cheat by \'' . $login . '\' detected! CPs: ' . $cps . ' Last: ' . formatTime($chkpt[2]) . ' index: ' . $chkpt[4], E_USER_WARNING); - else - trigger_error('Cheat by \'' . $login . '\' detected! CPs: ' . $cps . ' Finish: ' . formatTime($finish), E_USER_WARNING); - - // check for valid player - if (!$player = $this->server->players->getPlayer($login)) { - trigger_error('Player object for \'' . $login . '\' not found!', E_USER_WARNING); - return; - } - - switch ($this->settings['cheater_action']) { - - case 1: // set to spec (TMF only) - $rtn = $this->client->query('ForceSpectator', $login, 1); - if (!$rtn) { - trigger_error('[' . $this->client->getErrorCode() . '] ForceSpectator - ' . $this->client->getErrorMessage(), E_USER_WARNING); - } else { - // allow spectator to switch back to player - $rtn = $this->client->query('ForceSpectator', $login, 0); - } - // force free camera mode on spectator - $this->client->addCall('ForceSpectatorTarget', array($login, '', 2)); - // free up player slot - $this->client->addCall('SpectatorReleasePlayerSlot', array($login)); - - // log console message - $this->console('Cheater [{1} : {2}] forced into free spectator!', $login, stripColors($player->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}Cheater {#highlite}{1}$z$s{#admin} forced into spectator!', - str_ireplace('$w', '', $player->nickname)); - $this->client->query('ChatSendServerMessage', $this->formatColors($message)); - break; - - case 2: // kick - // log console message - $this->console('Cheater [{1} : {2}] kicked!', $login, stripColors($player->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}Cheater {#highlite}{1}$z$s{#admin} kicked!', - str_ireplace('$w', '', $player->nickname)); - $this->client->query('ChatSendServerMessage', $this->formatColors($message)); - - // kick the cheater - $this->client->query('Kick', $login); - break; - - case 3: // ban (& kick) - // log console message - $this->console('Cheater [{1} : {2}] banned!', $login, stripColors($player->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}Cheater {#highlite}{1}$z$s{#admin} banned!', - str_ireplace('$w', '', $player->nickname)); - $this->client->query('ChatSendServerMessage', $this->formatColors($message)); - - // update banned IPs file - $this->bannedips[] = $player->ip; - $this->writeIPs(); - - // ban the cheater and also kick him - $this->client->query('Ban', $player->login); - break; - - case 4: // blacklist & kick - // log console message - $this->console('Cheater [{1} : {2}] blacklisted!', $login, stripColors($player->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}Cheater {#highlite}{1}$z$s{#admin} blacklisted!', - str_ireplace('$w', '', $player->nickname)); - $this->client->query('ChatSendServerMessage', $this->formatColors($message)); - - // blacklist the cheater and then kick him - $this->client->query('BlackList', $player->login); - $this->client->query('Kick', $player->login); - - // update blacklist file - $filename = $this->settings['blacklist_file']; - $rtn = $this->client->query('SaveBlackList', $filename); - if (!$rtn) { - trigger_error('[' . $this->client->getErrorCode() . '] SaveBlackList (kick) - ' . $this->client->getErrorMessage(), E_USER_WARNING); - } - break; - - case 5: // blacklist & ban - // log console message - $this->console('Cheater [{1} : {2}] blacklisted & banned!', $login, stripColors($player->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}Cheater {#highlite}{1}$z$s{#admin} blacklisted & banned!', - str_ireplace('$w', '', $player->nickname)); - $this->client->query('ChatSendServerMessage', $this->formatColors($message)); - - // update banned IPs file - $this->bannedips[] = $player->ip; - $this->writeIPs(); - - // blacklist & ban the cheater - $this->client->query('BlackList', $player->login); - $this->client->query('Ban', $player->login); - - // update blacklist file - $filename = $this->settings['blacklist_file']; - $rtn = $this->client->query('SaveBlackList', $filename); - if (!$rtn) { - trigger_error('[' . $this->client->getErrorCode() . '] SaveBlackList (ban) - ' . $this->client->getErrorMessage(), E_USER_WARNING); - } - break; - - default: // ignore - } - } // processCheater - - - /** - * Finds a player ID from its login. - */ - function getPlayerId($login, $forcequery = false) { - - if (isset($this->server->players->player_list[$login]) && - $this->server->players->player_list[$login]->id > 0 && !$forcequery) { - $rtn = $this->server->players->player_list[$login]->id; - } else { - $query = 'SELECT id FROM players - WHERE login=' . quotedString($login); - $result = mysql_query($query); - if (mysql_num_rows($result) > 0) { - $row = mysql_fetch_row($result); - $rtn = $row[0]; - } else { - $rtn = 0; - } - mysql_free_result($result); - } - return $rtn; - } // getPlayerId - - /** - * Finds a player Nickname from its login. - */ - function getPlayerNick($login, $forcequery = false) { - - if (isset($this->server->players->player_list[$login]) && - $this->server->players->player_list[$login]->nickname != '' && !$forcequery) { - $rtn = $this->server->players->player_list[$login]->nickname; - } else { - $query = 'SELECT nickname FROM players - WHERE login=' . quotedString($login); - $result = mysql_query($query); - if (mysql_num_rows($result) > 0) { - $row = mysql_fetch_row($result); - $rtn = $row[0]; - } else { - $rtn = ''; - } - mysql_free_result($result); - } - return $rtn; - } // getPlayerNick - - - /** - * Finds an online player object from its login or Player_ID - * If $offline = true, search player database instead - * Returns false if not found - */ - function getPlayerParam($player, $param, $offline = false) { - - // if numeric param, find Player_ID from /players list (hardlimited to 300) - if (is_numeric($param) && $param >= 0 && $param < 300) { - if (empty($player->playerlist)) { - $message = '{#server}> {#error}Use {#highlite}$i/players {#error}first (optionally {#highlite}$i/players {#error})'; - $this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $player->login); - return false; - } - $pid = ltrim($param, '0'); - $pid--; - // find player by given # - if (array_key_exists($pid, $player->playerlist)) { - $param = $player->playerlist[$pid]['login']; - // check online players list - $target = $this->server->players->getPlayer($param); - } else { - // try param as login string as yet - $target = $this->server->players->getPlayer($param); - if (!$target) { - $message = '{#server}> {#error}Player_ID not found! Type {#highlite}$i/players {#error}to see all players.'; - $this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $player->login); - return false; - } - } - } else { // otherwise login string - // check online players list - $target = $this->server->players->getPlayer($param); - } - - // not found and offline allowed? - if (!$target && $offline) { - // check offline players database - $query = 'SELECT * FROM players - WHERE login=' . quotedString($param); - $result = mysql_query($query); - if (mysql_num_rows($result) > 0) { - $row = mysql_fetch_object($result); - // create dummy player object - $target = new Player(); - $target->id = $row->Id; - $target->login = $row->Login; - $target->nickname = $row->NickName; - $target->nation = $row->Nation; - $target->teamname = $row->TeamName; - $target->wins = $row->Wins; - $target->timeplayed = $row->TimePlayed; - } - mysql_free_result($result); - } - - // found anyone anywhere? - if (!$target) { - $message = '{#server}> {#highlite}' . $param . ' {#error}is not a valid player! Use {#highlite}$i/players {#error}to find the correct login or Player_ID.'; - $this->client->query('ChatSendServerMessageToLogin', $this->formatColors($message), $player->login); - } - return $target; - } // getPlayerParam - - - /** - * Finds a challenge ID from its UID. - */ - function getChallengeId($uid) { - - $query = 'SELECT Id FROM challenges - WHERE Uid=' . quotedString($uid); - $res = mysql_query($query); - if (mysql_num_rows($res) > 0) { - $row = mysql_fetch_row($res); - $rtn = $row[0]; - } else { - $rtn = 0; - } - mysql_free_result($res); - return $rtn; - } // getChallengeId - - /** - * Gets current servername - */ - function getServerName() { - - $this->client->query('GetServerName'); - $this->server->name = $this->client->getResponse(); - return $this->server->name; - } - - /** - * Gets current server name & options - */ - function getServerOptions() { - - $this->client->query('GetServerOptions'); - $options = $this->client->getResponse(); - $this->server->name = $options['Name']; - $this->server->maxplay = $options['CurrentMaxPlayers']; - $this->server->maxspec = $options['CurrentMaxSpectators']; - $this->server->votetime = $options['CurrentCallVoteTimeOut']; - $this->server->voterate = $options['CallVoteRatio']; - } - - - /** - * Formats aseco color codes in a string, - * for example '{#server} hello' will end up as '$ff0 hello'. - * It depends on what you've set in the config file. - */ - function formatColors($text) { - - // replace all chat colors - foreach ($this->chat_colors as $key => $value) { - $text = str_replace('{#'.strtolower($key).'}', $value[0], $text); - } - return $text; - } // formatColors - - - /** - * Outputs a formatted string without datetime. - */ - function console_text() { - - $args = func_get_args(); - $message = call_user_func_array('formatText', $args) . CRLF; - echo $message; - doLog($message); - flush(); - } // console_text - - /** - * Outputs a string to console with datetime prefix. - */ - function console() { - - $args = func_get_args(); - $message = '[' . date('m/d,H:i:s') . '] ' . call_user_func_array('formatText', $args) . CRLF; - echo $message; - doLog($message); - flush(); - } // console - -} // class Aseco - -// define process settings -if (function_exists('date_default_timezone_get') && function_exists('date_default_timezone_set')) - date_default_timezone_set(@date_default_timezone_get()); -$limit = ini_get('memory_limit'); -if (shorthand2bytes($limit) < 128 * 1048576) - ini_set('memory_limit', '128M'); -setlocale(LC_NUMERIC, 'C'); - -// create an instance of XASECO and run it -$aseco = new Aseco(false); -$aseco->run('config.xml'); -?> diff --git a/xaseco/autotime.xml b/xaseco/autotime.xml deleted file mode 100644 index 1ecf066..0000000 --- a/xaseco/autotime.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - 6 - - 3.5 - - 7 - - 5 - - 1 - {#server}>> Set {1} timelimit for {#highlite}{2}{#server} : {#highlite}{3}{#server} (Author time: {#highlite}{4}{#server}) - diff --git a/xaseco/bannedips.xml b/xaseco/bannedips.xml deleted file mode 100644 index c636898..0000000 --- a/xaseco/bannedips.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/xaseco/config.xml b/xaseco/config.xml deleted file mode 100644 index 8d612db..0000000 --- a/xaseco/config.xml +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - - - - - - - $f00$i - $f00 - $ff0 - $fff - $bbb - $0f3 - $fa0 - $d80 - $39f - $ff3 - $f8f - $ff0 - $f0f - $ff0 - $000 - $888 - $00f - $0c0 - $f00 - $ff0$i - $28b - $0b3 - - - - - {#server}*** XASECO {#highlite}v{1}{#server} running on {#highlite}{2}{#server}:{#highlite}{3}{#server} *** - {#welcome}Welcome {#highlite}{1}{#welcome} to {#highlite}{2}$z$s{br}{#welcome}This server uses {#highlite}XASECO v{3}{#welcome} to manage your records. - $s{#welcome}This is an administrative warning.{br}{br}$gWhatever you wrote is against our server's{br}policy. Not respecting other players, or{br}using offensive language might result in a{br}{#welcome}kick, or ban {#message}the next time.{br}{br}$gThe server administrators. - - - {#server}>> {#message}Current record on {#highlite}{1}{#message} is {#highlite}{2}{#message} by {#highlite}{3} - {#server}>> {#message}Currently no record on {#highlite}{1}{#message} ... - {#server}>> {#error}Could not get records from database... No records this round! - - - {#server}>> {#message}Local Record rankings on {#highlite}{1}{#message} {2} this round: - {#server}>> {#message}Local Record rankings on {#highlite}{1}{#message} {2} this round (range {#highlite}{3}{#message}): - {#server}>> {#message}Local Record rankings on {#highlite}{1}{#message} {2} this round ({#highlite}{3}{#message} new): - {#server}>> {#message}Local Record rankings on {#highlite}{1}{#message} {2} this round: none new so far - {#server}>> {#message}Local Record rankings on {#highlite}{1}{#message} {2} this round: no records! - - - {#rank}{1}{#message}.$i{#highlite}{2}{#message}[{#highlite}{3}{#message}]$i, - {#rank}{1}{#message}.{#highlite}{2}{#message}[{#highlite}{3}{#message}], - {#rank}{1}{#message}.$i{#timelite}{2}{#message}[{#timelite}{3}{#message}]$i, - {#rank}{1}{#message}.{#timelite}{2}{#message}[{#timelite}{3}{#message}], - {#rank}{1}{#message}.{#timelite}{2}{#message}, - - - {#server}> {#record}The first Local record is: - {#server}> {#record}The last Local record is: - {#server}> {#record}Difference between {1}{#record} and {2}{#record} is: {#highlite}{3} - {#server}> {#highlite}{1} $z$s{#record}has {#highlite}{2}{#record} Local record{3}, the top {4} being: - {#highlite}{1} {#record}rec{2} #{#rank}{3}{#record}, - - - {#server}> {#record}You have already won {#highlite}{1}{#record} race{2} - {#server}> {#record}Congratulations, you won your {#highlite}{1}{#record}. race! - {#server}>> {#record}Congratulations, {#highlite}{1}{#record} won his/her {#highlite}{2}{#record}. race! - - - {#server}> Player {#highlite}{1}$z$s{#server} is muted! - {#server}> Player {#highlite}{1}$z$s{#server} is unmuted! - {#server}> {#highlite}{1}{#error} disabled because you are on the global mute list! - - - {#donate} Donate {#highlite}{1}{#donate} coppers to {#highlite}{2}$z - {#server}>> {#highlite}{1}$z$s{#donate} received a donation of {#highlite}{2}{#donate} coppers from {#highlite}{3}$z$s{#donate}. Thank You! - {#server}> {#donate}You made a donation of {#highlite}{1}{#donate} coppers. Thank You! - {#server}> {#error}Minimum donation amount is {#highlite}$i {1}{#error} coppers! - {#server}> {#error}Use {#highlite}$i /donate <number>{#error} to donate coppers to the server - {#donate} Send {#highlite}{1}{#donate} coppers to {#highlite}{2}$z - {#server}> {#error}Insufficient server coppers: {#highlite}$i {1}{#error}! - {#server}> {#error}Cannot pay this server itself! - {#server}> {#donate}Payment of {#highlite}{1}{#donate} coppers to {#highlite}{2}{#donate} confirmed! Remaining coppers: {#highlite}{3} - {#server}> {#donate}Payment to {#highlite}{1}{#donate} cancelled! - {#server}> {#error}Use {#highlite}$i /admin pay <login> $m<number>{#error} to send server coppers to a login - - - {#server}> Current track {#highlite}{1}{#server} has been played for {#highlite}{2} - {#server}>> Current track {#highlite}{1}{#server} finished after {#highlite}{2} - {#server}({#highlite}{1}{#server} replay{2}, total {#highlite}{3}{#server}) - {#server}> Current track {#highlite}{1} {#server}by {#highlite}{2} {#server}Author: {#highlite}{3} {#server}Gold: {#highlite}{4} {#server}Silver: {#highlite}{5} {#server}Bronze: {#highlite}{6} {#server}Cost: {#highlite}{7} - {#server}>> Current track {#highlite}{1} {#server}by {#highlite}{2} {#server}Author: {#highlite}{3} - - - {#server}> {1}Custom points system set to {#highlite}{2}{3}: {#highlite}{4},... - {#server}> {1}Custom points system set to: {#highlite}{2},... - {#server}> {1}No custom Rounds points system defined! - - - {#server}> {#error}No relay servers connected - {#server}> This server relays master server: {#highlite}{1}{#server} ({#highlite}{2}{#server}) - {#server}> {#error}Command unavailable on relay server - - - {#server}>> {#message}This XASECO version {#highlite}{1}{#message} is up to date - {#server}>> {#message}New XASECO version {#highlite}{1}{#message} available from {#highlite}{2} - - - {#welcome}Your IP was banned from this server.$z - {#welcome}Could not connect:{br}{br}Your IP was banned from this server! - {#welcome}Obsolete client version, please $l[http://www.tm-forum.com/viewtopic.php?p=139752#p139752]upgrade$l.$z - {#welcome}Obsolete client version!{br}Please upgrade to the $l[http://www.tm-forum.com/viewtopic.php?p=139752#p139752]latest version$l. - {#welcome}Connection problem, please retry.$z - {#welcome}$sThis is an administrative notice.$z{br}{br}XASECO encountered a very rare player connection{br}problem. Please re-join the server to correct it.{br}Apologies for the inconvenience.{br}{br}$sThe server administrators. - - - {#server}>> IdleKick player {#highlite}{1}$z$s{#server} after {#highlite}{2}{#server} challenge{3}! - {#server}>> IdleSpec player {#highlite}{1}$z$s{#server} after {#highlite}{2}{#server} challenge{3} - {#server}>> IdleKick spectator {#highlite}{1}$z$s{#server} after {#highlite}{2}{#server} challenge{3}! - - - {#server}> Track {#highlite}{1} {#server}plays song: {#highlite}{2} - {#server}> Track {#highlite}{1} {#server}uses mod: {#highlite}{2} {#server}({#highlite}{3}{#server}) - {#server}> Server {#highlite}{1}$z$s {#server}owns {#highlite}{2} {#server}coppers! - - {#server}>> {#record}TMX World Record: {#highlite}{1}{#record} by {#highlite}{2} - $n{#message}R{#highlite}{1}{#message}> - {#server}> {#highlite}/cpsspec{#server} is not currently enabled on this server. - {#server}> {#error}You have to be in admin list to do that! - {#server}> Press the {#highlite}C{#server} key to see the whole list, and use {#highlite}/helpall{#server} for details - {#server}> {#error}This requires a TM United Forever {1}! - {#server}> {#error}Command only available on TM Forever! - - - False - - False - True - 0 - 60 - - 8 - - - - 2 - - - - 2 - True - - 1 - - 1 - - 0 - - tracklist.txt - True - False - True - True - 2 - 50 - True - False - True - False - True - adminops.xml - bannedips.xml - blacklist.txt - guestlist.txt - trackhistory.txt - - - 2.11.19 - - - True - True - True - - False - - False - - 6 - - - - False - - - - DarkBlur - - - AdminBelowChat - DonateBelowCPList - RecordsRightBottom - VoteBelowChat - - - - SuperAdmin - --$SERVER_SA_PASSWORD-- - 127.0.0.1 - 5000 - 180 - - diff --git a/xaseco/dedimania.xml b/xaseco/dedimania.xml deleted file mode 100644 index ad07c4d..0000000 --- a/xaseco/dedimania.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - {#welcome}Welcome to the Dedimania world record system at www.dedimania.com - see {#highlite}/helpdedi - {#dedimsg}Dedimania system timed out - retrying in {#highlite}{1}{#dedimsg} minutes - Dedimania - - http://dedimania.net:8002/Dedimania - - True - - True - - - 8 - - - 1 - - - 1 - - True - - True - - True - - False - - - - 30 - - - - - - - - - - - - - - - - {#server}>> {#dedimsg}Dedimania Record rankings on {#highlite}{1}{#dedimsg} {2} this round: - {#server}>> {#dedimsg}Dedimania Record rankings on {#highlite}{1}{#dedimsg} {2} this round (range {#highlite}{3}{#dedimsg}): - {#server}>> {#dedimsg}Dedimania Record rankings on {#highlite}{1}{#dedimsg} {2} this round ({#highlite}{3}{#dedimsg} new): - {#server}>> {#dedimsg}Dedimania Record rankings on {#highlite}{1}{#dedimsg} {2} this round: none new so far - {#server}>> {#dedimsg}Dedimania Record rankings on {#highlite}{1}{#dedimsg} {2} this round: no records! - - - {#server}>> {#highlite}{1}{#dedirec} secured his/her {#rank}{2}{#dedirec}. Dedimania Record! {3}: {#highlite}{4}{#dedirec} $n({#rank}{5}{#highlite}{6}{#dedirec}) - {#server}>> {#highlite}{1}{#dedirec} equaled his/her {#rank}{2}{#dedirec}. Dedimania Record! {3}: {#highlite}{4} - {#server}>> {#highlite}{1}{#dedirec} gained the {#rank}{2}{#dedirec}. Dedimania Record! {3}: {#highlite}{4}{#dedirec} $n({#rank}{5}{#highlite}{6}{#dedirec}) - {#server}>> {#highlite}{1}{#dedirec} claimed the {#rank}{2}{#dedirec}. Dedimania Record! {3}: {#highlite}{4} - - - {#server}> {#dedirec}The first Dedimania record is: - {#server}> {#dedirec}The last Dedimania record is: - {#server}> {#dedirec}Difference between {1}{#dedirec} and {2}{#dedirec} is: {#highlite}{3} - - - {#server}> {#dedirec}Dedimania Personal Best: {#highlite}{1}{#dedirec}({#rank}{2}{#dedirec}) - {#server}> {#error}You don't have a Dedimania record on this track yet... - - - {#server}>> {#highlite}{1} {#server}({#highlite}{2}{#server}) {#error}is banned from Dedimania! - {#server}> {#error}Finish ignored by Dedimania as you are banned! - - diff --git a/xaseco/html.tpl b/xaseco/html.tpl deleted file mode 100644 index 2b9d9a3..0000000 --- a/xaseco/html.tpl +++ /dev/null @@ -1,28 +0,0 @@ - - - -Race Results for {DATE} - {TIME} on {TRACK} - -{HEADER} -

    -
    Track: {TRACK}{DATE} - {TIME}
    - - - - -
    RankNameTimeTeamPoints
    {RANK}{NICK}{TIME}{TEAM}{POINTS}

    {MATCHCELL} - -{MATCHPOINTS} - -
    TeamTotal
    {TEAM}{POINTS}

    diff --git a/xaseco/includes/GbxRemote.bem.php b/xaseco/includes/GbxRemote.bem.php deleted file mode 100644 index 3b8dafd..0000000 --- a/xaseco/includes/GbxRemote.bem.php +++ /dev/null @@ -1,891 +0,0 @@ - htmlspecialchars) - Site: http://scripts.incutio.com/xmlrpc/ - Manual: http://scripts.incutio.com/xmlrpc/manual.php - Errors: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php - Made available under the Artistic License: http://www.opensource.org/licenses/artistic-license.php - - Modified to support protocol 'GbxRemote 2' ('GbxRemote 1') - This version is for BigEndian machines. For LittleEndian (e.g. Intel PC) - machines use the original GbxRemote.inc.php instead. - - Release 2007-09-22 - Slig: - Modified to support >256KB received data (and now >2MB data produce a specific error message) - Modified readCB() to wait the initial timeout only before first read packet - Modified readCB() to return true if there is data to get with getCBResponses() - Modified to support amd64 (for $recvhandle) - Modified IXR_ClientMulticall_Gbx::addCall() to fit Aseco 0.6.1 - Added IXR_Client_Gbx::bytes_sent & bytes_received counters - Fix for a changed feature since php5.1.1 about reference parameter assignment (was used in stream_select) - Workaround for stream_select return value bug with amd64 - - Release 2008-01-20 - Slig / Xymph / Assembler Maniac: - Workaround for fread delay bug in some cases - Added IXR_Client_Gbx::resetError() method (by Xymph) - Some comments and strings code cleanup (by Xymph) - Fix stream_set_timeout($this->socket,...) (thx to CavalierDeVache) - Added a default timeout value to IXR_Client_Gbx::readCB($timeout) - Changed calls with timeout on a stream to use microseconds instead of seconds (by AM) - Removed IXR_Client_Gbx::bytes_sent & bytes_received counters - not used (by AM) - - Release 2008-02-05 - Slig: - Changed some socket read/write timeouts back to seconds to avoid 'transport error' - Changed max data received from 2MB to 4MB - - Release 2008-05-20 - Xymph: - Prevented unpack() warnings in IXR_Client_Gbx::query() when the connection dies - Changed IXR_Client_Gbx::resetError() to assign 'false' for correct isError() - Tweaked some 'transport error' messages - - Release 2009-04-08 - Gou1: - Added method IXR_Client_Gbx::queryIgnoreResult() - Added methods IXR_Client_Gbx::sendRequest() & IXR_Client_Gbx::getResult() - IXR_Client_Gbx::queryIgnoreResult checks if the request is larger than 512KB to avoid errors - If larger than 512KB and method is system.multicall, try to divide the request into - two separate requests with two separate IXR_Client_Gbx::queryIgnoreResult() calls - - Release 2009-06-03 - Xymph: - Suppress possible repetitive CRT warning at stream_select - - Release 2011-04-09 - Xymph / La beuze: - Added optional timeout mechanism to IXR_Client_Gbx::InitWithIp() - - Release 2011-05-22 - Xymph: - Added non-error (true) return status to IXR_Client_Gbx::queryIgnoreResult() - Updated status codes and messages for transport/endian errors - Prevented possible PHP warning in IXR_Client_Gbx::getErrorCode() and getErrorMessage() - - Release 2011-12-04 - Xymph: - Prevented possible PHP warning in IXR_Value::calculateType - - Release 2013-02-18 - Xymph: - Removed unnecessary breaks after returns in IXR_Value::getXml() switch -*/ - -if (!defined('LF')) { - define('LF', "\n"); -} - -class IXR_Value { - public $data; - public $type; - - function IXR_Value ($data, $type = false) { - $this->data = $data; - if (!$type) { - $type = $this->calculateType(); - } - $this->type = $type; - if ($type == 'struct') { - // Turn all the values in the array into new IXR_Value objects - foreach ($this->data as $key => $value) { - $this->data[$key] = new IXR_Value($value); - } - } - if ($type == 'array') { - for ($i = 0, $j = count($this->data); $i < $j; $i++) { - $this->data[$i] = new IXR_Value($this->data[$i]); - } - } - } - - function calculateType() { - if ($this->data === true || $this->data === false) { - return 'boolean'; - } - if (is_integer($this->data)) { - return 'int'; - } - if (is_double($this->data)) { - return 'double'; - } - // Deal with IXR object types base64 and date - if (is_object($this->data) && ($this->data instanceof IXR_Date)) { - return 'date'; - } - if (is_object($this->data) && ($this->data instanceof IXR_Base64)) { - return 'base64'; - } - // If it is a normal PHP object convert it into a struct - if (is_object($this->data)) { - $this->data = get_object_vars($this->data); - return 'struct'; - } - if (!is_array($this->data)) { - return 'string'; - } - // We have an array - is it an array or a struct? - if ($this->isStruct($this->data)) { - return 'struct'; - } else { - return 'array'; - } - } - - function getXml() { - // Return XML for this value - switch ($this->type) { - case 'boolean': - return '' . ($this->data ? '1' : '0') . ''; - case 'int': - return '' . $this->data . ''; - case 'double': - return '' . $this->data . ''; - case 'string': - return '' . htmlspecialchars($this->data) . ''; - case 'array': - $return = '' . LF; - foreach ($this->data as $item) { - $return .= ' ' . $item->getXml() . '' . LF; - } - $return .= ''; - return $return; - case 'struct': - $return = '' . LF; - foreach ($this->data as $name => $value) { - $return .= ' ' . $name . ''; - $return .= $value->getXml() . '' . LF; - } - $return .= ''; - return $return; - case 'date': - case 'base64': - return $this->data->getXml(); - } - return false; - } - - function isStruct($array) { - // Nasty function to check if an array is a struct or not - $expected = 0; - foreach ($array as $key => $value) { - if ((string)$key != (string)$expected) { - return true; - } - $expected++; - } - return false; - } -} - - -class IXR_Message { - public $message; - public $messageType; // methodCall / methodResponse / fault - public $faultCode; - public $faultString; - public $methodName; - public $params; - // Current variable stacks - protected $_arraystructs = array(); // Stack to keep track of the current array/struct - protected $_arraystructstypes = array(); // Stack to keep track of whether things are structs or array - protected $_currentStructName = array(); // A stack as well - protected $_param; - protected $_value; - protected $_currentTag; - protected $_currentTagContents; - // The XML parser - protected $_parser; - - function IXR_Message ($message) { - $this->message = $message; - } - - function parse() { - // first remove the XML declaration - $this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message); - if (trim($this->message) == '') { - return false; - } - $this->_parser = xml_parser_create(); - // Set XML parser to take the case of tags into account - xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); - // Set XML parser callback functions - xml_set_object($this->_parser, $this); - xml_set_element_handler($this->_parser, 'tag_open', 'tag_close'); - xml_set_character_data_handler($this->_parser, 'cdata'); - if (!xml_parse($this->_parser, $this->message)) { - /* die(sprintf('GbxRemote XML error: %s at line %d', - xml_error_string(xml_get_error_code($this->_parser)), - xml_get_current_line_number($this->_parser))); */ - return false; - } - xml_parser_free($this->_parser); - // Grab the error messages, if any - if ($this->messageType == 'fault') { - $this->faultCode = $this->params[0]['faultCode']; - $this->faultString = $this->params[0]['faultString']; - } - return true; - } - - function tag_open($parser, $tag, $attr) { - $this->currentTag = $tag; - switch ($tag) { - case 'methodCall': - case 'methodResponse': - case 'fault': - $this->messageType = $tag; - break; - // Deal with stacks of arrays and structs - case 'data': // data is to all intents and purposes more interesting than array - $this->_arraystructstypes[] = 'array'; - $this->_arraystructs[] = array(); - break; - case 'struct': - $this->_arraystructstypes[] = 'struct'; - $this->_arraystructs[] = array(); - break; - } - } - - function cdata($parser, $cdata) { - $this->_currentTagContents .= $cdata; - } - - function tag_close($parser, $tag) { - $valueFlag = false; - switch ($tag) { - case 'int': - case 'i4': - $value = (int)trim($this->_currentTagContents); - $this->_currentTagContents = ''; - $valueFlag = true; - break; - case 'double': - $value = (double)trim($this->_currentTagContents); - $this->_currentTagContents = ''; - $valueFlag = true; - break; - case 'string': - $value = (string)trim($this->_currentTagContents); - $this->_currentTagContents = ''; - $valueFlag = true; - break; - case 'dateTime.iso8601': - $value = new IXR_Date(trim($this->_currentTagContents)); - // $value = $iso->getTimestamp(); - $this->_currentTagContents = ''; - $valueFlag = true; - break; - case 'value': - // If no type is indicated, the type is string - if (trim($this->_currentTagContents) != '') { - $value = (string)$this->_currentTagContents; - $this->_currentTagContents = ''; - $valueFlag = true; - } - break; - case 'boolean': - $value = (boolean)trim($this->_currentTagContents); - $this->_currentTagContents = ''; - $valueFlag = true; - break; - case 'base64': - $value = base64_decode($this->_currentTagContents); - $this->_currentTagContents = ''; - $valueFlag = true; - break; - // Deal with stacks of arrays and structs - case 'data': - case 'struct': - $value = array_pop($this->_arraystructs); - array_pop($this->_arraystructstypes); - $valueFlag = true; - break; - case 'member': - array_pop($this->_currentStructName); - break; - case 'name': - $this->_currentStructName[] = trim($this->_currentTagContents); - $this->_currentTagContents = ''; - break; - case 'methodName': - $this->methodName = trim($this->_currentTagContents); - $this->_currentTagContents = ''; - break; - } - - if ($valueFlag) { - /* - if (!is_array($value) && !is_object($value)) { - $value = trim($value); - } - */ - if (count($this->_arraystructs) > 0) { - // Add value to struct or array - if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') { - // Add to struct - $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value; - } else { - // Add to array - $this->_arraystructs[count($this->_arraystructs)-1][] = $value; - } - } else { - // Just add as a paramater - $this->params[] = $value; - } - } - } -} - - -class IXR_Request { - public $method; - public $args; - public $xml; - - function IXR_Request($method, $args) { - $this->method = $method; - $this->args = $args; - $this->xml = '' . $this->method . ''; - foreach ($this->args as $arg) { - $this->xml .= ''; - $v = new IXR_Value($arg); - $this->xml .= $v->getXml(); - $this->xml .= '' . LF; - } - $this->xml .= ''; - } - - function getLength() { - return strlen($this->xml); - } - - function getXml() { - return $this->xml; - } -} - - -class IXR_Error { - public $code; - public $message; - - function IXR_Error($code, $message) { - $this->code = $code; - $this->message = $message; - } - - function getXml() { - $xml = << - - - - - faultCode - {$this->code} - - - faultString - {$this->message} - - - - - -EOD; - return $xml; - } -} - - -class IXR_Date { - public $year; - public $month; - public $day; - public $hour; - public $minute; - public $second; - - function IXR_Date($time) { - // $time can be a PHP timestamp or an ISO one - if (is_numeric($time)) { - $this->parseTimestamp($time); - } else { - $this->parseIso($time); - } - } - - function parseTimestamp($timestamp) { - $this->year = date('Y', $timestamp); - $this->month = date('Y', $timestamp); - $this->day = date('Y', $timestamp); - $this->hour = date('H', $timestamp); - $this->minute = date('i', $timestamp); - $this->second = date('s', $timestamp); - } - - function parseIso($iso) { - $this->year = substr($iso, 0, 4); - $this->month = substr($iso, 4, 2); - $this->day = substr($iso, 6, 2); - $this->hour = substr($iso, 9, 2); - $this->minute = substr($iso, 12, 2); - $this->second = substr($iso, 15, 2); - } - - function getIso() { - return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second; - } - - function getXml() { - return ''.$this->getIso().''; - } - - function getTimestamp() { - return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year); - } -} - - -class IXR_Base64 { - public $data; - - function IXR_Base64($data) { - $this->data = $data; - } - - function getXml() { - return ''.base64_encode($this->data).''; - } -} - - -////////////////////////////////////////////////////////// -// Nadeo modifications // -// (many thanks to slig for adding callback support) // -////////////////////////////////////////////////////////// -class IXR_Client_Gbx { - public $socket; - public $message = false; - public $cb_message = array(); - public $reqhandle; - public $protocol = 0; - // Storage place for an error message - public $error = false; - - function bigEndianTest() { - list($endiantest) = array_values(unpack('L1L', pack('V', 1))); - if ($endiantest == 1) { - echo "Machine reports itself as LittleEndian, float handling will work correctly with unpack\r\n"; - echo "Use original GbxRemote.inc.php instead of GbxRemote.bem.php\r\n"; - die('App Terminated'); - return false; - } - return true; - } // bigEndianTest - - function IXR_Client_Gbx() { - $this->socket = false; - $this->reqhandle = 0x80000000; - } - - function InitWithIp($ip, $port, $timeout = null) { - - if (!$this->bigEndianTest()) { - $this->error = new IXR_Error(-31999, 'endian error - script doesn\'t match machine type'); - return false; - } - - // open connection, with timeout if specified - if (!isset($timeout)) { - $this->socket = @fsockopen($ip, $port, $errno, $errstr); - } else { - $init_time = microtime(true); - $init_timeout = 5; // retry every 5s - while (true) { - $this->socket = @fsockopen($ip, $port, $errno, $errstr, $init_timeout); - if ($this->socket || (microtime(true) - $init_time >= $timeout)) - break; - } - } - if (!$this->socket) { - $this->error = new IXR_Error(-32300, "transport error - could not open socket (error: $errno, $errstr)"); - return false; - } - // handshake - $array_result = big_endian_unpack('Vsize', fread($this->socket, 4)); - $size = $array_result['size']; - if ($size > 64) { - $this->error = new IXR_Error(-32300, 'transport error - wrong lowlevel protocol header'); - return false; - } - $handshake = fread($this->socket, $size); - if ($handshake == 'GBXRemote 1') { - $this->protocol = 1; - } else if ($handshake == 'GBXRemote 2') { - $this->protocol = 2; - } else { - $this->error = new IXR_Error(-32300, 'transport error - wrong lowlevel protocol version'); - return false; - } - return true; - } - - function Init($port) { - return $this->InitWithIp('localhost', $port); - } - - function Terminate() { - if ($this->socket) { - fclose($this->socket); - $this->socket = false; - } - } - - protected function sendRequest(IXR_Request $request) { - $xml = $request->getXml(); - - @stream_set_timeout($this->socket, 20); // timeout 20s (to write the request) - // send request - $this->reqhandle++; - if ($this->protocol == 1) { - $bytes = pack('Va*', strlen($xml), $xml); - } else { - $bytes = pack('VVa*', strlen($xml), $this->reqhandle, $xml); - } - - $bytes_to_write = strlen($bytes); - while ($bytes_to_write > 0) { - $r = @fwrite($this->socket, $bytes); - if ($r === false || $r == 0) { - // connection interrupted - return false; // or die? - } - - $bytes_to_write -= $r; - if ($bytes_to_write == 0) - break; - - $bytes = substr($bytes, $r); - } - - return true; - } - - protected function getResult() { - $contents = ''; - $contents_length = 0; - do { - $size = 0; - $recvhandle = 0; - @stream_set_timeout($this->socket, 20); // timeout 20s (to read the reply header) - // Get result - if ($this->protocol == 1) { - $contents = fread($this->socket, 4); - if (strlen($contents) == 0) { - $this->error = new IXR_Error(-32300, 'transport error - cannot read size'); - return false; - } - $array_result = big_endian_unpack('Vsize', $contents); - $size = $array_result['size']; - $recvhandle = $this->reqhandle; - } else { - $contents = fread($this->socket, 8); - if (strlen($contents) == 0) { - $this->error = new IXR_Error(-32300, 'transport error - cannot read size/handle'); - return false; - } - $array_result = big_endian_unpack('Vsize/Vhandle', $contents); - $size = $array_result['size']; - $recvhandle = $array_result['handle']; - // -- amd64 support -- - $bits = sprintf('%b', $recvhandle); - if (strlen($bits) == 64) { - $recvhandle = bindec(substr($bits, 32)); - } - } - - if ($recvhandle == 0 || $size == 0) { - $this->error = new IXR_Error(-32300, 'transport error - connection interrupted!'); - return false; - } - if ($size > 4096*1024) { - $this->error = new IXR_Error(-32300, "transport error - response too large ($size)"); - return false; - } - - $contents = ''; - $contents_length = 0; - @stream_set_timeout($this->socket, 0, 10000); // timeout 10 ms (for successive reads until end) - while ($contents_length < $size) { - $contents .= fread($this->socket, $size-$contents_length); - $contents_length = strlen($contents); - } - - if (($recvhandle & 0x80000000) == 0) { - // this is a callback, not our answer! handle= $recvhandle, xml-rpc= $contents - // just add it to the message list for the user to read - $new_cb_message = new IXR_Message($contents); - if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') { - array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params)); - } - } - } while ((int)$recvhandle != (int)$this->reqhandle); - - $this->message = new IXR_Message($contents); - if (!$this->message->parse()) { - // XML error - $this->error = new IXR_Error(-32700, 'parse error. not well formed'); - return false; - } - // Is the message a fault? - if ($this->message->messageType == 'fault') { - $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString); - return false; - } - // Message must be OK - return true; - } - - - function query() { - $args = func_get_args(); - $method = array_shift($args); - - if (!$this->socket || $this->protocol == 0) { - $this->error = new IXR_Error(-32300, 'transport error - client not initialized'); - return false; - } - - $request = new IXR_Request($method, $args); - - // Check if request is larger than 512 Kbytes - if (($size = $request->getLength()) > 512*1024-8) { - $this->error = new IXR_Error(-32300, "transport error - request too large ($size)"); - return false; - } - - // Send request - $ok = $this->sendRequest($request); - if (!$ok) { - $this->error = new IXR_Error(-32300, 'transport error - connection interrupted!'); - return false; - } - - // Get result - return $this->getResult(); - } - - // Non-blocking query method: doesn't read the response - function queryIgnoreResult() { - $args = func_get_args(); - $method = array_shift($args); - - if (!$this->socket || $this->protocol == 0) { - $this->error = new IXR_Error(-32300, 'transport error - client not initialized'); - return false; - } - - $request = new IXR_Request($method, $args); - - // Check if the request is greater than 512 Kbytes to avoid errors - // If the method is system.multicall, make two calls (possibly recursively) - if (($size = $request->getLength()) > 512*1024-8) { - if ($method = 'system.multicall' && isset($args[0])) { - $count = count($args[0]); - // If count is 1, query cannot be reduced - if ($count < 2) { - $this->error = new IXR_Error(-32300, "transport error - request too large ($size)"); - return false; - } - $length = floor($count/2); - $args1 = array_slice($args[0], 0, $length); - $args2 = array_slice($args[0], $length, ($count-$length)); - - $res1 = $this->queryIgnoreResult('system.multicall', $args1); - $res2 = $this->queryIgnoreResult('system.multicall', $args2); - return ($res1 && $res2); - } - // If the method is not a multicall, just stop - else { - $this->error = new IXR_Error(-32300, "transport error - request too large ($size)"); - return false; - } - } - - // Send request - $ok = $this->sendRequest($request); - if (!$ok) { - $this->error = new IXR_Error(-32300, 'transport error - connection interrupted!'); - return false; - } - return true; - } - - function readCB($timeout = 2000) { // timeout 2 ms - if (!$this->socket || $this->protocol == 0) { - $this->error = new IXR_Error(-32300, 'transport error - client not initialized'); - return false; - } - if ($this->protocol == 1) - return false; - - $something_received = count($this->cb_message)>0; - $contents = ''; - $contents_length = 0; - - @stream_set_timeout($this->socket, 0, 10000); // timeout 10 ms (to read available data) - // (assignment in arguments is forbidden since php 5.1.1) - $read = array($this->socket); - $write = NULL; - $except = NULL; - $nb = @stream_select($read, $write, $except, 0, $timeout); - // workaround for stream_select bug with amd64 - if ($nb !== false) - $nb = count($read); - - while ($nb !== false && $nb > 0) { - $timeout = 0; // we don't want to wait for the full time again, just flush the available data - - $size = 0; - $recvhandle = 0; - // Get result - $contents = fread($this->socket, 8); - if (strlen($contents) == 0) { - $this->error = new IXR_Error(-32300, 'transport error - cannot read size/handle'); - return false; - } - $array_result = big_endian_unpack('Vsize/Vhandle', $contents); - $size = $array_result['size']; - $recvhandle = $array_result['handle']; - - if ($recvhandle == 0 || $size == 0) { - $this->error = new IXR_Error(-32300, 'transport error - connection interrupted!'); - return false; - } - if ($size > 4096*1024) { - $this->error = new IXR_Error(-32300, "transport error - response too large ($size)"); - return false; - } - - $contents = ''; - $contents_length = 0; - while ($contents_length < $size) { - $contents .= fread($this->socket, $size-$contents_length); - $contents_length = strlen($contents); - } - - if (($recvhandle & 0x80000000) == 0) { - // this is a callback. handle= $recvhandle, xml-rpc= $contents - //echo 'CALLBACK('.$contents_length.')[ '.$contents.' ]' . LF; - $new_cb_message = new IXR_Message($contents); - if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') { - array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params)); - } - $something_received = true; - } - - // (assignment in arguments is forbidden since php 5.1.1) - $read = array($this->socket); - $write = NULL; - $except = NULL; - $nb = @stream_select($read, $write, $except, 0, $timeout); - // workaround for stream_select bug with amd64 - if ($nb !== false) - $nb = count($read); - } - return $something_received; - } - - function getResponse() { - // methodResponses can only have one param - return that - return $this->message->params[0]; - } - - function getCBResponses() { - // (look at the end of basic.php for an example) - $messages = $this->cb_message; - $this->cb_message = array(); - return $messages; - } - - function isError() { - return is_object($this->error); - } - - function resetError() { - $this->error = false; - } - - function getErrorCode() { - if ($this->isError()) - return $this->error->code; - else - return 0; - } - - function getErrorMessage() { - if ($this->isError()) - return $this->error->message; - else - return ''; - } -} - - -class IXR_ClientMulticall_Gbx extends IXR_Client_Gbx { - public $calls = array(); - - function addCall($methodName, $args) { - $struct = array('methodName' => $methodName, 'params' => $args); - $this->calls[] = $struct; - - return (count($this->calls) - 1); - } - - function multiquery($ignoreResult = false) { - // Prepare multicall, then call the parent::query() (or queryIgnoreResult) method - if ($ignoreResult) { - $result = parent::queryIgnoreResult('system.multicall', $this->calls); - } else { - $result = parent::query('system.multicall', $this->calls); - } - $this->calls = array(); // reset for next calls - return $result; - } -} - -/** - * The following code is a workaround for php's unpack function which - * does not have the capability of unpacking double precision floats - * that were packed in the opposite byte order of the current machine. - */ -function big_endian_unpack($format, $data) { - $ar = unpack($format, $data); - $vals = array_values($ar); - $f = explode('/', $format); - $i = 0; - foreach ($f as $f_k => $f_v) { - $repeater = intval(substr($f_v, 1)); - if ($repeater == 0) - $repeater = 1; - if ($f_v{1} == '*') { - $repeater = count($ar) - $i; - } - if ($f_v{0} != 'd') { - $i += $repeater; - continue; - } - $j = $i + $repeater; - for ($a = $i; $a < $j; ++$a) { - $p = pack('d', $vals[$i]); - $p = strrev($p); - list($vals[$i]) = array_values(unpack('d1d', $p)); - ++$i; - } - } - $a = 0; - foreach ($ar as $ar_k => $ar_v) { - $ar[$ar_k] = $vals[$a]; - ++$a; - } - return $ar; -} -?> diff --git a/xaseco/includes/GbxRemote.inc.php b/xaseco/includes/GbxRemote.inc.php deleted file mode 100644 index dbb3247..0000000 --- a/xaseco/includes/GbxRemote.inc.php +++ /dev/null @@ -1,854 +0,0 @@ - htmlspecialchars) - Site: http://scripts.incutio.com/xmlrpc/ - Manual: http://scripts.incutio.com/xmlrpc/manual.php - Errors: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php - Made available under the Artistic License: http://www.opensource.org/licenses/artistic-license.php - - Modified to support protocol 'GbxRemote 2' ('GbxRemote 1') - This version is for LittleEndian (e.g. Intel PC) machines. For BegEndian - machines use GbxRemote.bem.php as GbxRemote.inc.php instead. - - Release 2007-09-22 - Slig: - Modified to support >256KB received data (and now >2MB data produce a specific error message) - Modified readCB() to wait the initial timeout only before first read packet - Modified readCB() to return true if there is data to get with getCBResponses() - Modified to support amd64 (for $recvhandle) - Modified IXR_ClientMulticall_Gbx::addCall() to fit Aseco 0.6.1 - Added IXR_Client_Gbx::bytes_sent & bytes_received counters - Fix for a changed feature since php5.1.1 about reference parameter assignment (was used in stream_select) - Workaround for stream_select return value bug with amd64 - - Release 2008-01-20 - Slig / Xymph / Assembler Maniac: - Workaround for fread delay bug in some cases - Added IXR_Client_Gbx::resetError() method (by Xymph) - Some comments and strings code cleanup (by Xymph) - Fix stream_set_timeout($this->socket,...) (thx to CavalierDeVache) - Added a default timeout value to IXR_Client_Gbx::readCB($timeout) - Changed calls with timeout on a stream to use microseconds instead of seconds (by AM) - Removed IXR_Client_Gbx::bytes_sent & bytes_received counters - not used (by AM) - - Release 2008-02-05 - Slig: - Changed some socket read/write timeouts back to seconds to avoid 'transport error' - Changed max data received from 2MB to 4MB - - Release 2008-05-20 - Xymph: - Prevented unpack() warnings in IXR_Client_Gbx::query() when the connection dies - Changed IXR_Client_Gbx::resetError() to assign 'false' for correct isError() - Tweaked some 'transport error' messages - - Release 2009-04-08 - Gou1: - Added method IXR_Client_Gbx::queryIgnoreResult() - Added methods IXR_Client_Gbx::sendRequest() & IXR_Client_Gbx::getResult() - IXR_Client_Gbx::queryIgnoreResult checks if the request is larger than 512KB to avoid errors - If larger than 512KB and method is system.multicall, try to divide the request into - two separate requests with two separate IXR_Client_Gbx::queryIgnoreResult() calls - - Release 2009-06-03 - Xymph: - Suppress possible repetitive CRT warning at stream_select - - Release 2011-04-09 - Xymph / La beuze: - Added optional timeout mechanism to IXR_Client_Gbx::InitWithIp() - - Release 2011-05-22 - Xymph: - Added non-error (true) return status to IXR_Client_Gbx::queryIgnoreResult() - Updated status codes and messages for transport/endian errors - Prevented possible PHP warning in IXR_Client_Gbx::getErrorCode() and getErrorMessage() - - Release 2011-12-04 - Xymph: - Prevented possible PHP warning in IXR_Value::calculateType - - Release 2013-02-18 - Xymph: - Removed unnecessary breaks after returns in IXR_Value::getXml() switch -*/ - -if (!defined('LF')) { - define('LF', "\n"); -} - -class IXR_Value { - public $data; - public $type; - - function IXR_Value ($data, $type = false) { - $this->data = $data; - if (!$type) { - $type = $this->calculateType(); - } - $this->type = $type; - if ($type == 'struct') { - // Turn all the values in the array into new IXR_Value objects - foreach ($this->data as $key => $value) { - $this->data[$key] = new IXR_Value($value); - } - } - if ($type == 'array') { - for ($i = 0, $j = count($this->data); $i < $j; $i++) { - $this->data[$i] = new IXR_Value($this->data[$i]); - } - } - } - - function calculateType() { - if ($this->data === true || $this->data === false) { - return 'boolean'; - } - if (is_integer($this->data)) { - return 'int'; - } - if (is_double($this->data)) { - return 'double'; - } - // Deal with IXR object types base64 and date - if (is_object($this->data) && ($this->data instanceof IXR_Date)) { - return 'date'; - } - if (is_object($this->data) && ($this->data instanceof IXR_Base64)) { - return 'base64'; - } - // If it is a normal PHP object convert it into a struct - if (is_object($this->data)) { - $this->data = get_object_vars($this->data); - return 'struct'; - } - if (!is_array($this->data)) { - return 'string'; - } - // We have an array - is it an array or a struct? - if ($this->isStruct($this->data)) { - return 'struct'; - } else { - return 'array'; - } - } - - function getXml() { - // Return XML for this value - switch ($this->type) { - case 'boolean': - return '' . ($this->data ? '1' : '0') . ''; - case 'int': - return '' . $this->data . ''; - case 'double': - return '' . $this->data . ''; - case 'string': - return '' . htmlspecialchars($this->data) . ''; - case 'array': - $return = '' . LF; - foreach ($this->data as $item) { - $return .= ' ' . $item->getXml() . '' . LF; - } - $return .= ''; - return $return; - case 'struct': - $return = '' . LF; - foreach ($this->data as $name => $value) { - $return .= ' ' . $name . ''; - $return .= $value->getXml() . '' . LF; - } - $return .= ''; - return $return; - case 'date': - case 'base64': - return $this->data->getXml(); - } - return false; - } - - function isStruct($array) { - // Nasty function to check if an array is a struct or not - $expected = 0; - foreach ($array as $key => $value) { - if ((string)$key != (string)$expected) { - return true; - } - $expected++; - } - return false; - } -} - - -class IXR_Message { - public $message; - public $messageType; // methodCall / methodResponse / fault - public $faultCode; - public $faultString; - public $methodName; - public $params; - // Current variable stacks - protected $_arraystructs = array(); // Stack to keep track of the current array/struct - protected $_arraystructstypes = array(); // Stack to keep track of whether things are structs or array - protected $_currentStructName = array(); // A stack as well - protected $_param; - protected $_value; - protected $_currentTag; - protected $_currentTagContents; - // The XML parser - protected $_parser; - - function IXR_Message ($message) { - $this->message = $message; - } - - function parse() { - // first remove the XML declaration - $this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message); - if (trim($this->message) == '') { - return false; - } - $this->_parser = xml_parser_create(); - // Set XML parser to take the case of tags into account - xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); - // Set XML parser callback functions - xml_set_object($this->_parser, $this); - xml_set_element_handler($this->_parser, 'tag_open', 'tag_close'); - xml_set_character_data_handler($this->_parser, 'cdata'); - if (!xml_parse($this->_parser, $this->message)) { - /* die(sprintf('GbxRemote XML error: %s at line %d', - xml_error_string(xml_get_error_code($this->_parser)), - xml_get_current_line_number($this->_parser))); */ - return false; - } - xml_parser_free($this->_parser); - // Grab the error messages, if any - if ($this->messageType == 'fault') { - $this->faultCode = $this->params[0]['faultCode']; - $this->faultString = $this->params[0]['faultString']; - } - return true; - } - - function tag_open($parser, $tag, $attr) { - $this->currentTag = $tag; - switch ($tag) { - case 'methodCall': - case 'methodResponse': - case 'fault': - $this->messageType = $tag; - break; - // Deal with stacks of arrays and structs - case 'data': // data is to all intents and purposes more interesting than array - $this->_arraystructstypes[] = 'array'; - $this->_arraystructs[] = array(); - break; - case 'struct': - $this->_arraystructstypes[] = 'struct'; - $this->_arraystructs[] = array(); - break; - } - } - - function cdata($parser, $cdata) { - $this->_currentTagContents .= $cdata; - } - - function tag_close($parser, $tag) { - $valueFlag = false; - switch ($tag) { - case 'int': - case 'i4': - $value = (int)trim($this->_currentTagContents); - $this->_currentTagContents = ''; - $valueFlag = true; - break; - case 'double': - $value = (double)trim($this->_currentTagContents); - $this->_currentTagContents = ''; - $valueFlag = true; - break; - case 'string': - $value = (string)trim($this->_currentTagContents); - $this->_currentTagContents = ''; - $valueFlag = true; - break; - case 'dateTime.iso8601': - $value = new IXR_Date(trim($this->_currentTagContents)); - // $value = $iso->getTimestamp(); - $this->_currentTagContents = ''; - $valueFlag = true; - break; - case 'value': - // If no type is indicated, the type is string - if (trim($this->_currentTagContents) != '') { - $value = (string)$this->_currentTagContents; - $this->_currentTagContents = ''; - $valueFlag = true; - } - break; - case 'boolean': - $value = (boolean)trim($this->_currentTagContents); - $this->_currentTagContents = ''; - $valueFlag = true; - break; - case 'base64': - $value = base64_decode($this->_currentTagContents); - $this->_currentTagContents = ''; - $valueFlag = true; - break; - // Deal with stacks of arrays and structs - case 'data': - case 'struct': - $value = array_pop($this->_arraystructs); - array_pop($this->_arraystructstypes); - $valueFlag = true; - break; - case 'member': - array_pop($this->_currentStructName); - break; - case 'name': - $this->_currentStructName[] = trim($this->_currentTagContents); - $this->_currentTagContents = ''; - break; - case 'methodName': - $this->methodName = trim($this->_currentTagContents); - $this->_currentTagContents = ''; - break; - } - - if ($valueFlag) { - /* - if (!is_array($value) && !is_object($value)) { - $value = trim($value); - } - */ - if (count($this->_arraystructs) > 0) { - // Add value to struct or array - if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') { - // Add to struct - $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value; - } else { - // Add to array - $this->_arraystructs[count($this->_arraystructs)-1][] = $value; - } - } else { - // Just add as a paramater - $this->params[] = $value; - } - } - } -} - - -class IXR_Request { - public $method; - public $args; - public $xml; - - function IXR_Request($method, $args) { - $this->method = $method; - $this->args = $args; - $this->xml = '' . $this->method . ''; - foreach ($this->args as $arg) { - $this->xml .= ''; - $v = new IXR_Value($arg); - $this->xml .= $v->getXml(); - $this->xml .= '' . LF; - } - $this->xml .= ''; - } - - function getLength() { - return strlen($this->xml); - } - - function getXml() { - return $this->xml; - } -} - - -class IXR_Error { - public $code; - public $message; - - function IXR_Error($code, $message) { - $this->code = $code; - $this->message = $message; - } - - function getXml() { - $xml = << - - - - - faultCode - {$this->code} - - - faultString - {$this->message} - - - - - -EOD; - return $xml; - } -} - - -class IXR_Date { - public $year; - public $month; - public $day; - public $hour; - public $minute; - public $second; - - function IXR_Date($time) { - // $time can be a PHP timestamp or an ISO one - if (is_numeric($time)) { - $this->parseTimestamp($time); - } else { - $this->parseIso($time); - } - } - - function parseTimestamp($timestamp) { - $this->year = date('Y', $timestamp); - $this->month = date('Y', $timestamp); - $this->day = date('Y', $timestamp); - $this->hour = date('H', $timestamp); - $this->minute = date('i', $timestamp); - $this->second = date('s', $timestamp); - } - - function parseIso($iso) { - $this->year = substr($iso, 0, 4); - $this->month = substr($iso, 4, 2); - $this->day = substr($iso, 6, 2); - $this->hour = substr($iso, 9, 2); - $this->minute = substr($iso, 12, 2); - $this->second = substr($iso, 15, 2); - } - - function getIso() { - return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second; - } - - function getXml() { - return ''.$this->getIso().''; - } - - function getTimestamp() { - return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year); - } -} - - -class IXR_Base64 { - public $data; - - function IXR_Base64($data) { - $this->data = $data; - } - - function getXml() { - return ''.base64_encode($this->data).''; - } -} - - -////////////////////////////////////////////////////////// -// Nadeo modifications // -// (many thanks to slig for adding callback support) // -////////////////////////////////////////////////////////// -class IXR_Client_Gbx { - public $socket; - public $message = false; - public $cb_message = array(); - public $reqhandle; - public $protocol = 0; - // Storage place for an error message - public $error = false; - - function bigEndianTest() { - list($endiantest) = array_values(unpack('L1L', pack('V', 1))); - if ($endiantest != 1) { - echo "Machine reports itself as BigEndian, float handling must be altered\r\n"; - echo "Overwrite GbxRemote.inc.php with GbxRemote.bem.php\r\n"; - die('App Terminated'); - return false; - } - return true; - } // bigEndianTest - - function IXR_Client_Gbx() { - $this->socket = false; - $this->reqhandle = 0x80000000; - } - - function InitWithIp($ip, $port, $timeout = null) { - - if (!$this->bigEndianTest()) { - $this->error = new IXR_Error(-31999, 'endian error - script doesn\'t match machine type'); - return false; - } - - // open connection, with timeout if specified - if (!isset($timeout)) { - $this->socket = @fsockopen($ip, $port, $errno, $errstr); - } else { - $init_time = microtime(true); - $init_timeout = 5; // retry every 5s - while (true) { - $this->socket = @fsockopen($ip, $port, $errno, $errstr, $init_timeout); - if ($this->socket || (microtime(true) - $init_time >= $timeout)) - break; - } - } - if (!$this->socket) { - $this->error = new IXR_Error(-32300, "transport error - could not open socket (error: $errno, $errstr)"); - return false; - } - // handshake - $array_result = unpack('Vsize', fread($this->socket, 4)); - $size = $array_result['size']; - if ($size > 64) { - $this->error = new IXR_Error(-32300, 'transport error - wrong lowlevel protocol header'); - return false; - } - $handshake = fread($this->socket, $size); - if ($handshake == 'GBXRemote 1') { - $this->protocol = 1; - } else if ($handshake == 'GBXRemote 2') { - $this->protocol = 2; - } else { - $this->error = new IXR_Error(-32300, 'transport error - wrong lowlevel protocol version'); - return false; - } - return true; - } - - function Init($port) { - return $this->InitWithIp('localhost', $port); - } - - function Terminate() { - if ($this->socket) { - fclose($this->socket); - $this->socket = false; - } - } - - protected function sendRequest(IXR_Request $request) { - $xml = $request->getXml(); - - @stream_set_timeout($this->socket, 20); // timeout 20s (to write the request) - // send request - $this->reqhandle++; - if ($this->protocol == 1) { - $bytes = pack('Va*', strlen($xml), $xml); - } else { - $bytes = pack('VVa*', strlen($xml), $this->reqhandle, $xml); - } - - $bytes_to_write = strlen($bytes); - while ($bytes_to_write > 0) { - $r = @fwrite($this->socket, $bytes); - if ($r === false || $r == 0) { - // connection interrupted - return false; // or die? - } - - $bytes_to_write -= $r; - if ($bytes_to_write == 0) - break; - - $bytes = substr($bytes, $r); - } - - return true; - } - - protected function getResult() { - $contents = ''; - $contents_length = 0; - do { - $size = 0; - $recvhandle = 0; - @stream_set_timeout($this->socket, 20); // timeout 20s (to read the reply header) - // Get result - if ($this->protocol == 1) { - $contents = fread($this->socket, 4); - if (strlen($contents) == 0) { - $this->error = new IXR_Error(-32300, 'transport error - cannot read size'); - return false; - } - $array_result = unpack('Vsize', $contents); - $size = $array_result['size']; - $recvhandle = $this->reqhandle; - } else { - $contents = fread($this->socket, 8); - if (strlen($contents) == 0) { - $this->error = new IXR_Error(-32300, 'transport error - cannot read size/handle'); - return false; - } - $array_result = unpack('Vsize/Vhandle', $contents); - $size = $array_result['size']; - $recvhandle = $array_result['handle']; - // -- amd64 support -- - $bits = sprintf('%b', $recvhandle); - if (strlen($bits) == 64) { - $recvhandle = bindec(substr($bits, 32)); - } - } - - if ($recvhandle == 0 || $size == 0) { - $this->error = new IXR_Error(-32300, 'transport error - connection interrupted!'); - return false; - } - if ($size > 4096*1024) { - $this->error = new IXR_Error(-32300, "transport error - response too large ($size)"); - return false; - } - - $contents = ''; - $contents_length = 0; - @stream_set_timeout($this->socket, 0, 10000); // timeout 10 ms (for successive reads until end) - while ($contents_length < $size) { - $contents .= fread($this->socket, $size-$contents_length); - $contents_length = strlen($contents); - } - - if (($recvhandle & 0x80000000) == 0) { - // this is a callback, not our answer! handle= $recvhandle, xml-rpc= $contents - // just add it to the message list for the user to read - $new_cb_message = new IXR_Message($contents); - if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') { - array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params)); - } - } - } while ((int)$recvhandle != (int)$this->reqhandle); - - $this->message = new IXR_Message($contents); - if (!$this->message->parse()) { - // XML error - $this->error = new IXR_Error(-32700, 'parse error. not well formed'); - return false; - } - // Is the message a fault? - if ($this->message->messageType == 'fault') { - $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString); - return false; - } - // Message must be OK - return true; - } - - - function query() { - $args = func_get_args(); - $method = array_shift($args); - - if (!$this->socket || $this->protocol == 0) { - $this->error = new IXR_Error(-32300, 'transport error - client not initialized'); - return false; - } - - $request = new IXR_Request($method, $args); - - // Check if request is larger than 512 Kbytes - if (($size = $request->getLength()) > 512*1024-8) { - $this->error = new IXR_Error(-32300, "transport error - request too large ($size)"); - return false; - } - - // Send request - $ok = $this->sendRequest($request); - if (!$ok) { - $this->error = new IXR_Error(-32300, 'transport error - connection interrupted!'); - return false; - } - - // Get result - return $this->getResult(); - } - - // Non-blocking query method: doesn't read the response - function queryIgnoreResult() { - $args = func_get_args(); - $method = array_shift($args); - - if (!$this->socket || $this->protocol == 0) { - $this->error = new IXR_Error(-32300, 'transport error - client not initialized'); - return false; - } - - $request = new IXR_Request($method, $args); - - // Check if the request is greater than 512 Kbytes to avoid errors - // If the method is system.multicall, make two calls (possibly recursively) - if (($size = $request->getLength()) > 512*1024-8) { - if ($method = 'system.multicall' && isset($args[0])) { - $count = count($args[0]); - // If count is 1, query cannot be reduced - if ($count < 2) { - $this->error = new IXR_Error(-32300, "transport error - request too large ($size)"); - return false; - } - $length = floor($count/2); - $args1 = array_slice($args[0], 0, $length); - $args2 = array_slice($args[0], $length, ($count-$length)); - - $res1 = $this->queryIgnoreResult('system.multicall', $args1); - $res2 = $this->queryIgnoreResult('system.multicall', $args2); - return ($res1 && $res2); - } - // If the method is not a multicall, just stop - else { - $this->error = new IXR_Error(-32300, "transport error - request too large ($size)"); - return false; - } - } - - // Send request - $ok = $this->sendRequest($request); - if (!$ok) { - $this->error = new IXR_Error(-32300, 'transport error - connection interrupted!'); - return false; - } - return true; - } - - function readCB($timeout = 2000) { // timeout 2 ms - if (!$this->socket || $this->protocol == 0) { - $this->error = new IXR_Error(-32300, 'transport error - client not initialized'); - return false; - } - if ($this->protocol == 1) - return false; - - $something_received = count($this->cb_message)>0; - $contents = ''; - $contents_length = 0; - - @stream_set_timeout($this->socket, 0, 10000); // timeout 10 ms (to read available data) - // (assignment in arguments is forbidden since php 5.1.1) - $read = array($this->socket); - $write = NULL; - $except = NULL; - $nb = @stream_select($read, $write, $except, 0, $timeout); - // workaround for stream_select bug with amd64 - if ($nb !== false) - $nb = count($read); - - while ($nb !== false && $nb > 0) { - $timeout = 0; // we don't want to wait for the full time again, just flush the available data - - $size = 0; - $recvhandle = 0; - // Get result - $contents = fread($this->socket, 8); - if (strlen($contents) == 0) { - $this->error = new IXR_Error(-32300, 'transport error - cannot read size/handle'); - return false; - } - $array_result = unpack('Vsize/Vhandle', $contents); - $size = $array_result['size']; - $recvhandle = $array_result['handle']; - - if ($recvhandle == 0 || $size == 0) { - $this->error = new IXR_Error(-32300, 'transport error - connection interrupted!'); - return false; - } - if ($size > 4096*1024) { - $this->error = new IXR_Error(-32300, "transport error - response too large ($size)"); - return false; - } - - $contents = ''; - $contents_length = 0; - while ($contents_length < $size) { - $contents .= fread($this->socket, $size-$contents_length); - $contents_length = strlen($contents); - } - - if (($recvhandle & 0x80000000) == 0) { - // this is a callback. handle= $recvhandle, xml-rpc= $contents - //echo 'CALLBACK('.$contents_length.')[ '.$contents.' ]' . LF; - $new_cb_message = new IXR_Message($contents); - if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') { - array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params)); - } - $something_received = true; - } - - // (assignment in arguments is forbidden since php 5.1.1) - $read = array($this->socket); - $write = NULL; - $except = NULL; - $nb = @stream_select($read, $write, $except, 0, $timeout); - // workaround for stream_select bug with amd64 - if ($nb !== false) - $nb = count($read); - } - return $something_received; - } - - function getResponse() { - // methodResponses can only have one param - return that - return $this->message->params[0]; - } - - function getCBResponses() { - // (look at the end of basic.php for an example) - $messages = $this->cb_message; - $this->cb_message = array(); - return $messages; - } - - function isError() { - return is_object($this->error); - } - - function resetError() { - $this->error = false; - } - - function getErrorCode() { - if ($this->isError()) - return $this->error->code; - else - return 0; - } - - function getErrorMessage() { - if ($this->isError()) - return $this->error->message; - else - return ''; - } -} - - -class IXR_ClientMulticall_Gbx extends IXR_Client_Gbx { - public $calls = array(); - - function addCall($methodName, $args) { - $struct = array('methodName' => $methodName, 'params' => $args); - $this->calls[] = $struct; - - return (count($this->calls) - 1); - } - - function multiquery($ignoreResult = false) { - // Prepare multicall, then call the parent::query() (or queryIgnoreResult) method - if ($ignoreResult) { - $result = parent::queryIgnoreResult('system.multicall', $this->calls); - } else { - $result = parent::query('system.multicall', $this->calls); - } - $this->calls = array(); // reset for next calls - return $result; - } -} -?> diff --git a/xaseco/includes/GbxRemote.response.php b/xaseco/includes/GbxRemote.response.php deleted file mode 100644 index b5dfdbd..0000000 --- a/xaseco/includes/GbxRemote.response.php +++ /dev/null @@ -1,218 +0,0 @@ - xml-rpc string) -// ----------------------------------------------------------------------- -// $method is the method name. -// $params should be an array containing the method parameters. -// For system.multicall, there should be one parameter, which is an array -// containing the methods structs {'methodName':string, 'params':array} -// ----------------------------------------------------------------------- - -function xmlrpc_request($method, $params = null) { - - // in case the first level array was forgotten in a multicall then add it... - if ($method == 'system.multicall' && isset($params[0]['methodName'])) - $params = array($params); - - $xml = '' - . "\n\n$method\n\n"; - - if (is_array($params)) { - foreach ($params as $param) { - $xml .= "\n"; - $v = new IXR_Value($param); - $xml .= $v->getXml(); - $xml .= "\n\n"; - } - } - $xml .= "\n"; - return $xml; -} - - -// ----------------------------------------------------------------------- -// Build a methodCall response in xml-rpc format from arrays args -// (array_args ==> xml-rpc string) -// ----------------------------------------------------------------------- -// $args should be an array containing the response -// ----------------------------------------------------------------------- - -function xmlrpc_response($args = null) { - - $xml = '' - . "\n\n\n\n"; - - if ($args !== null) { - $xml .= ''; - $v = new IXR_Value($args); - $xml .= $v->getXml(); - $xml .= "\n"; - } - $xml .= "\n\n"; - return $xml; -} - - -// ----------------------------------------------------------------------- -// Build an error response in xml-rpc format -// (error code + message ==> xml-rpc string) -// ----------------------------------------------------------------------- -// $code is the error code -// $message is the error string -// ----------------------------------------------------------------------- - -function xmlrpc_error($code, $message) { - - $xml = '' - . "\n\n\n" - . "\n" - . "faultCode$code\n\n" - . "faultString$message\n" - . "\n" - . "\n"; - return $xml; -} - - -// ----------------------------------------------------------------------- -// Build a method struct (array) usable for a method call in a multicall -// (method name + params array ==> method struct array -// ----------------------------------------------------------------------- -// $name is the method name -// $params is the params array -// ----------------------------------------------------------------------- - -function rpc_method($name, $params = null) { - - if (!is_array($params)) - $params = array(); - return array('methodName' => $name, 'params' => $params); -} - - -// ----------------------------------------------------------------------- -// Build a response struct (array) usable as a reply for a method in a multicall -// (method name + params array ==> method struct array -// ----------------------------------------------------------------------- -// $params is the methode response array -// ----------------------------------------------------------------------- - -function rpc_response($response = null) { - - if (!is_array($response)) - $response = array(); - return array($response); -} - - -// ----------------------------------------------------------------------- -// Build an error struct (array) usable as an error for a method in a multicall -// (error code + message ==> error struct array) -// ----------------------------------------------------------------------- -// $code is the error code -// $message is the error string -// ----------------------------------------------------------------------- - -function rpc_error($code, $message) { - - return array('faultCode' => $code, 'faultString' => $message); -} - - -// ----------------------------------------------------------------------- -// Build a data array from a text xml-rpc -// (xml-rpc string ==> array) -// ----------------------------------------------------------------------- -// $xml is the xml-rpc text to decode -// If there is an error then null is returned, you can then look infos -// in global $_xmlrpc_decode_obj -// ----------------------------------------------------------------------- - -function xml_decode_rpc($xml) { - global $_xmlrpc_decode_obj; - - $_xmlrpc_decode_obj = new IXR_Message($xml); - if (!$_xmlrpc_decode_obj->parse() || !isset($_xmlrpc_decode_obj->params[0])) - return null; - return $_xmlrpc_decode_obj->params[0]; -} - - -// ----------------------------------------------------------------------- -// use this class constructor to build a methodCall request -// ----------------------------------------------------------------------- - -class IXR_RequestStd { - var $method; - var $params; - var $xml; - - // see xmlrpc_request - function IXR_RequestStd($method, $params = null) { - - $this->method = $method; - // in case the first level array was forgotten in a multicall then add it... - if ($method == 'system.multicall' && isset($params[0]['methodName'])) - $this->params = array($params); - else - $this->params = $params; - - $this->xml = xmlrpc_request($this->method, $this->params); - } - - function getLength() { - - return strlen($this->xml); - } - - function getXml() { - - return $this->xml; - } -} - - -// ----------------------------------------------------------------------- -// use this class constructor to build a methodCall response -// ----------------------------------------------------------------------- -class IXR_ResponseStd { - var $args; - var $xml; - - // see xmlrpc_response - function IXR_ResponseStd($args) { - - $this->args = $args; - $this->xml = xmlrpc_response($this->args); - } - - function getLength() { - - return strlen($this->xml); - } - - function getXml() { - - return $this->xml; - } -} -?> diff --git a/xaseco/includes/basic.inc.php b/xaseco/includes/basic.inc.php deleted file mode 100644 index 2321002..0000000 --- a/xaseco/includes/basic.inc.php +++ /dev/null @@ -1,830 +0,0 @@ -= $size) { - return false; - } - - // shift values down - for ($i = $size-1; $i >= $pos; $i--) { - $array[$i+1] = $array[$i]; - } - - // now put in the new element - $array[$pos] = $value; - return true; -} // insertArrayElement - -/** - * Removes an element from a specific position in an array. - * Decreases original size by one element. - */ -function removeArrayElement(&$array, $pos) { - - // get current size - $size = count($array); - - // if position is in array range - if ($pos < 0 && $pos >= $size) { - return false; - } - - // remove specified element - unset($array[$pos]); - // shift values up - $array = array_values($array); - return true; -} // removeArrayElement - -/** - * Moves an element from one position to the other. - * All items between are shifted down or up as needed. - */ -function moveArrayElement(&$array, $from, $to) { - - // get current size - $size = count($array); - - // destination and source have to be among the array borders! - if ($from < 0 || $from >= $size || $to < 0 || $to >= $size) { - return false; - } - - // backup the element we have to move - $moving_element = $array[$from]; - - if ($from > $to) { - // shift values between downwards - for ($i = $from-1; $i >= $to; $i--) { - $array[$i+1] = $array[$i]; - } - } else { // $from < $to - // shift values between upwards - for ($i = $from; $i <= $to; $i++) { - $array[$i] = $array[$i+1]; - } - } - - // now put in the element which was to move - $array[$to] = $moving_element; - return true; -} // moveArrayElement - -/** - * Formats a string from the format sssshh0 - * into the format mmm:ss.hh (or mmm:ss if $hsec is false) - */ -function formatTime($MwTime, $hsec = true) { - - if ($MwTime == -1) { - return '???'; - } else { - $minutes = floor($MwTime/(1000*60)); - $seconds = floor(($MwTime - $minutes*60*1000)/1000); - $hseconds = substr($MwTime, strlen($MwTime)-3, 2); - if ($hsec) { - $tm = sprintf('%02d:%02d.%02d', $minutes, $seconds, $hseconds); - } else { - $tm = sprintf('%02d:%02d', $minutes, $seconds); - } - } - if ($tm[0] == '0') { - $tm = substr($tm, 1); - } - return $tm; -} // formatTime - -/** - * Formats a string from the format sssshh0 - * into the format hh:mm:ss.hh (or hh:mm:ss if $hsec is false) - */ -function formatTimeH($MwTime, $hsec = true) { - - if ($MwTime == -1) { - return '???'; - } else { - $hseconds = substr($MwTime, strlen($MwTime)-3, 2); - $MwTime = substr($MwTime, 0, strlen($MwTime)-3); - $hours = floor($MwTime / 3600); - $MwTime = $MwTime - ($hours * 3600); - $minutes = floor($MwTime / 60); - $MwTime = $MwTime - ($minutes * 60); - $seconds = floor($MwTime); - if ($hsec) { - return sprintf('%02d:%02d:%02d.%02d', $hours, $minutes, $seconds, $hseconds); - } else { - return sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds); - } - } -} // formatTimeH - -/** - * Formats a text. - * Replaces parameters in the text which are marked with {n} - */ -function formatText($text) { - - // get all function's parameters - $args = func_get_args(); - - // first parameter is the text to format - $text = array_shift($args); - - // further parameters will be replaced in the text - $i = 1; - foreach ($args as $param) - $text = str_replace('{' . $i++ . '}', $param, $text); - - // and return the modified text - return $text; -} // formatText - -/** - * Make String for SQL use that single quoted & got special chars replaced. - */ -function quotedString($input) { - - return "'" . mysql_real_escape_string($input) . "'"; -} // quotedString - -/** - * Check login string for LAN postfix (pre/post v2.11.21). - */ -function isLANLogin($login) { - - $n="(25[0-5]|2[0-4]\d|[01]?\d\d|\d)"; - return (preg_match("/(\/{$n}\\.{$n}\\.{$n}\\.{$n}:\d+)$/", $login) || - preg_match("/(_{$n}\\.{$n}\\.{$n}\\.{$n}_\d+)$/", $login)); -} // isLANLogin - -/** - * Summary: Strips all display formatting from an input string, suitable for display - * within the game ('$$' escape pairs are preserved) and for logging - * Params : $input - The input string to strip formatting from - * $for_tm - Optional flag to double up '$' into '$$' (default, for TM) or not (for logs, etc) - * Returns: The content portions of $input without formatting - * Authors: Bilge/Assembler Maniac/Xymph/Slig - * - * "$af0Brat$s$fffwurst" will become "Bratwurst". - * 2007-08-27 Xymph - replaced with Bilge/AM's code (w/o the H&L tags bit) - * http://www.tm-forum.com/viewtopic.php?p=55867#p55867 - * 2008-04-24 Xymph - extended to handle the H/L/P tags for TMF - * http://www.tm-forum.com/viewtopic.php?p=112856#p112856 - * 2009-05-16 Slig - extended to emit non-TM variant & handle incomplete colors - * http://www.tm-forum.com/viewtopic.php?p=153368#p153368 - * 2010-10-05 Slig - updated to handle incomplete colors & tags better - * http://www.tm-forum.com/viewtopic.php?p=183410#p183410 - * 2010-10-09 Xymph - updated to handle $[ and $] properly - * http://www.tm-forum.com/viewtopic.php?p=183410#p183410 - */ -function stripColors($input, $for_tm = true) { - - return - //Replace all occurrences of a null character back with a pair of dollar - //signs for displaying in TM, or a single dollar for log messages etc. - str_replace("\0", ($for_tm ? '$$' : '$'), - //Replace links (introduced in TMU) - preg_replace( - '/ - #Strip TMF H, L & P links by stripping everything between each square - #bracket pair until another $H, $L or $P sequence (or EoS) is found; - #this allows a $H to close a $L and vice versa, as does the game - \\$[hlp](.*?)(?:\\[.*?\\](.*?))*(?:\\$[hlp]|$) - /ixu', - //Keep the first and third capturing groups if present - '$1$2', - //Replace various patterns beginning with an unescaped dollar - preg_replace( - '/ - #Match a single dollar sign and any of the following: - \\$ - (?: - #Strip color codes by matching any hexadecimal character and - #any other two characters following it (except $) - [0-9a-f][^$][^$] - #Strip any incomplete color codes by matching any hexadecimal - #character followed by another character (except $) - |[0-9a-f][^$] - #Strip any single style code (including an invisible UTF8 char) - #that is not an H, L or P link or a bracket ($[ and $]) - |[^][hlp] - #Strip the dollar sign if it is followed by [ or ], but do not - #strip the brackets themselves - |(?=[][]) - #Strip the dollar sign if it is at the end of the string - |$ - ) - #Ignore alphabet case, ignore whitespace in pattern & use UTF-8 mode - /ixu', - //Replace any matches with nothing (i.e. strip matches) - '', - //Replace all occurrences of dollar sign pairs with a null character - str_replace('$$', "\0", $input) - ) - ) - ) - ; -} // stripColors - -/** - * Strips only size tags from TM strings. - * "$w$af0Brat$n$fffwurst" will become "$af0Brat$fffwurst". - * 2009-03-27 Xymph - derived from stripColors above - * http://www.tm-forum.com/viewtopic.php?f=127&t=20602 - * 2009-05-16 Slig - extended to emit non-TM variant - * http://www.tm-forum.com/viewtopic.php?p=153368#p153368 - */ -function stripSizes($input, $for_tm = true) { - - return - //Replace all occurrences of a null character back with a pair of dollar - //signs for displaying in TM, or a single dollar for log messages etc. - str_replace("\0", ($for_tm ? '$$' : '$'), - //Replace various patterns beginning with an unescaped dollar - preg_replace( - '/ - #Match a single dollar sign and any of the following: - \\$ - (?: - #Strip any size code - [nwo] - #Strip the dollar sign if it is at the end of the string - |$ - ) - #Ignore alphabet case, ignore whitespace in pattern & use UTF-8 mode - /ixu', - //Replace any matches with nothing (i.e. strip matches) - '', - //Replace all occurrences of dollar sign pairs with a null character - str_replace('$$', "\0", $input) - ) - ) - ; -} // stripSizes - -/** - * Strips only newlines from TM strings. - */ -function stripNewlines($input) { - - return str_replace(array("\n\n", "\r", "\n"), - array(' ', '', ''), $input); -} // stripNewlines - - -/** - * Univeral show help for user, admin & Jfreu commands. - * Created by Xymph - * - * $width is the width of the first column in the ManiaLink window on TMF - */ -function showHelp($player, $chat_commands, $head, - $showadmin = false, $dispall = false, $width = 0.3) { - global $aseco; - - // display full help for TMN - if ($aseco->server->getGame() == 'TMN' && $dispall) { - $head = "Currently supported $head commands:" . LF; - - if (!empty($chat_commands)) { - // define admin or non-admin padding string - $pad = ($showadmin ? '$f00... ' : '$f00/'); - $help = ''; - $lines = 0; - $player->msgs = array(); - $player->msgs[0] = 1; - // create list of chat commands - foreach ($chat_commands as $cc) { - // collect either admin or non-admin commands - if ($cc->isadmin == $showadmin) { - $help .= $pad . $cc->name . ' $000' . $cc->help . LF; - if (++$lines > 14) { - $player->msgs[] = $head . $help; - $lines = 0; - $help = ''; - } - } - } - // add if last batch exists - if ($help != '') - $player->msgs[] = $head . $help; - - // display popup message - if (count($player->msgs) == 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $player->login, $player->msgs[1], 'OK', '', 0); - } else { // > 2 - $aseco->client->query('SendDisplayServerMessageToLogin', $player->login, $player->msgs[1], 'Close', 'Next', 0); - } - } - - // display full help for TMF - } elseif ($aseco->server->getGame() == 'TMF' && $dispall) { - $head = "Currently supported $head commands:"; - - if (!empty($chat_commands)) { - // define admin or non-admin padding string - $pad = ($showadmin ? '$f00... ' : '$f00/'); - $help = array(); - $lines = 0; - $player->msgs = array(); - $player->msgs[0] = array(1, $head, array(1.3, $width, 1.3 - $width), array('Icons64x64_1', 'TrackInfo', -0.01)); - // create list of chat commands - foreach ($chat_commands as $cc) { - // collect either admin or non-admin commands - if ($cc->isadmin == $showadmin) { - $help[] = array($pad . $cc->name, $cc->help); - if (++$lines > 14) { - $player->msgs[] = $help; - $lines = 0; - $help = array(); - } - } - } - // add if last batch exists - if (!empty($help)) - $player->msgs[] = $help; - - // display ManiaLink message - display_manialink_multi($player); - } - - // show help for TMS or TMO, and plain help for TMF/TMN - } else { - $head = "Currently supported $head commands:" . LF; - $help = $aseco->formatColors('{#interact}' . $head); - foreach ($chat_commands as $cc) { - // collect either admin or non-admin commands - if ($cc->isadmin == $showadmin) { - $help .= $cc->name . ', '; - } - } - // show chat message - $help = substr($help, 0, strlen($help) - 2); // strip trailing ", " - $aseco->client->query('ChatSendToLogin', $help, $player->login); - } -} // showHelp - -/** - * Map country names to 3-letter Nation abbreviations - * Created by Xymph - * Based on http://en.wikipedia.org/wiki/List_of_IOC_country_codes - * See also http://en.wikipedia.org/wiki/Comparison_of_IOC,_FIFA,_and_ISO_3166_country_codes - */ -function mapCountry($country) { - - $nations = array( - 'Afghanistan' => 'AFG', - 'Albania' => 'ALB', - 'Algeria' => 'ALG', - 'Andorra' => 'AND', - 'Angola' => 'ANG', - 'Argentina' => 'ARG', - 'Armenia' => 'ARM', - 'Aruba' => 'ARU', - 'Australia' => 'AUS', - 'Austria' => 'AUT', - 'Azerbaijan' => 'AZE', - 'Bahamas' => 'BAH', - 'Bahrain' => 'BRN', - 'Bangladesh' => 'BAN', - 'Barbados' => 'BAR', - 'Belarus' => 'BLR', - 'Belgium' => 'BEL', - 'Belize' => 'BIZ', - 'Benin' => 'BEN', - 'Bermuda' => 'BER', - 'Bhutan' => 'BHU', - 'Bolivia' => 'BOL', - 'Bosnia&Herzegovina' => 'BIH', - 'Botswana' => 'BOT', - 'Brazil' => 'BRA', - 'Brunei' => 'BRU', - 'Bulgaria' => 'BUL', - 'Burkina Faso' => 'BUR', - 'Burundi' => 'BDI', - 'Cambodia' => 'CAM', - 'Cameroon' => 'CAR', // actually CMR - 'Canada' => 'CAN', - 'Cape Verde' => 'CPV', - 'Central African Republic' => 'CAF', - 'Chad' => 'CHA', - 'Chile' => 'CHI', - 'China' => 'CHN', - 'Chinese Taipei' => 'TPE', - 'Colombia' => 'COL', - 'Congo' => 'CGO', - 'Costa Rica' => 'CRC', - 'Croatia' => 'CRO', - 'Cuba' => 'CUB', - 'Cyprus' => 'CYP', - 'Czech Republic' => 'CZE', - 'Czech republic' => 'CZE', - 'DR Congo' => 'COD', - 'Denmark' => 'DEN', - 'Djibouti' => 'DJI', - 'Dominica' => 'DMA', - 'Dominican Republic' => 'DOM', - 'Ecuador' => 'ECU', - 'Egypt' => 'EGY', - 'El Salvador' => 'ESA', - 'Eritrea' => 'ERI', - 'Estonia' => 'EST', - 'Ethiopia' => 'ETH', - 'Fiji' => 'FIJ', - 'Finland' => 'FIN', - 'France' => 'FRA', - 'Gabon' => 'GAB', - 'Gambia' => 'GAM', - 'Georgia' => 'GEO', - 'Germany' => 'GER', - 'Ghana' => 'GHA', - 'Greece' => 'GRE', - 'Grenada' => 'GRN', - 'Guam' => 'GUM', - 'Guatemala' => 'GUA', - 'Guinea' => 'GUI', - 'Guinea-Bissau' => 'GBS', - 'Guyana' => 'GUY', - 'Haiti' => 'HAI', - 'Honduras' => 'HON', - 'Hong Kong' => 'HKG', - 'Hungary' => 'HUN', - 'Iceland' => 'ISL', - 'India' => 'IND', - 'Indonesia' => 'INA', - 'Iran' => 'IRI', - 'Iraq' => 'IRQ', - 'Ireland' => 'IRL', - 'Israel' => 'ISR', - 'Italy' => 'ITA', - 'Ivory Coast' => 'CIV', - 'Jamaica' => 'JAM', - 'Japan' => 'JPN', - 'Jordan' => 'JOR', - 'Kazakhstan' => 'KAZ', - 'Kenya' => 'KEN', - 'Kiribati' => 'KIR', - 'Korea' => 'KOR', - 'Kuwait' => 'KUW', - 'Kyrgyzstan' => 'KGZ', - 'Laos' => 'LAO', - 'Latvia' => 'LAT', - 'Lebanon' => 'LIB', - 'Lesotho' => 'LES', - 'Liberia' => 'LBR', - 'Libya' => 'LBA', - 'Liechtenstein' => 'LIE', - 'Lithuania' => 'LTU', - 'Luxembourg' => 'LUX', - 'Macedonia' => 'MKD', - 'Malawi' => 'MAW', - 'Malaysia' => 'MAS', - 'Mali' => 'MLI', - 'Malta' => 'MLT', - 'Mauritania' => 'MTN', - 'Mauritius' => 'MRI', - 'Mexico' => 'MEX', - 'Moldova' => 'MDA', - 'Monaco' => 'MON', - 'Mongolia' => 'MGL', - 'Montenegro' => 'MNE', - 'Morocco' => 'MAR', - 'Mozambique' => 'MOZ', - 'Myanmar' => 'MYA', - 'Namibia' => 'NAM', - 'Nauru' => 'NRU', - 'Nepal' => 'NEP', - 'Netherlands' => 'NED', - 'New Zealand' => 'NZL', - 'Nicaragua' => 'NCA', - 'Niger' => 'NIG', - 'Nigeria' => 'NGR', - 'Norway' => 'NOR', - 'Oman' => 'OMA', - 'Other Countries' => 'OTH', - 'Pakistan' => 'PAK', - 'Palau' => 'PLW', - 'Palestine' => 'PLE', - 'Panama' => 'PAN', - 'Paraguay' => 'PAR', - 'Peru' => 'PER', - 'Philippines' => 'PHI', - 'Poland' => 'POL', - 'Portugal' => 'POR', - 'Puerto Rico' => 'PUR', - 'Qatar' => 'QAT', - 'Romania' => 'ROM', // actually ROU - 'Russia' => 'RUS', - 'Rwanda' => 'RWA', - 'Samoa' => 'SAM', - 'San Marino' => 'SMR', - 'Saudi Arabia' => 'KSA', - 'Senegal' => 'SEN', - 'Serbia' => 'SCG', // actually SRB - 'Sierra Leone' => 'SLE', - 'Singapore' => 'SIN', - 'Slovakia' => 'SVK', - 'Slovenia' => 'SLO', - 'Somalia' => 'SOM', - 'South Africa' => 'RSA', - 'Spain' => 'ESP', - 'Sri Lanka' => 'SRI', - 'Sudan' => 'SUD', - 'Suriname' => 'SUR', - 'Swaziland' => 'SWZ', - 'Sweden' => 'SWE', - 'Switzerland' => 'SUI', - 'Syria' => 'SYR', - 'Taiwan' => 'TWN', - 'Tajikistan' => 'TJK', - 'Tanzania' => 'TAN', - 'Thailand' => 'THA', - 'Togo' => 'TOG', - 'Tonga' => 'TGA', - 'Trinidad and Tobago' => 'TRI', - 'Tunisia' => 'TUN', - 'Turkey' => 'TUR', - 'Turkmenistan' => 'TKM', - 'Tuvalu' => 'TUV', - 'Uganda' => 'UGA', - 'Ukraine' => 'UKR', - 'United Arab Emirates' => 'UAE', - 'United Kingdom' => 'GBR', - 'United States of America' => 'USA', - 'Uruguay' => 'URU', - 'Uzbekistan' => 'UZB', - 'Vanuatu' => 'VAN', - 'Venezuela' => 'VEN', - 'Vietnam' => 'VIE', - 'Yemen' => 'YEM', - 'Zambia' => 'ZAM', - 'Zimbabwe' => 'ZIM', - ); - - if (array_key_exists($country, $nations)) { - $nation = $nations[$country]; - } else { - $nation = 'OTH'; - if ($country != '') - trigger_error('Could not map country: ' . $country, E_USER_WARNING); - } - return $nation; -} // mapCountry - -/** - * Find TMX data for the given track - * Created by Xymph - */ -require_once('includes/tmxinfofetcher.inc.php'); // provides access to TMX info -function findTMXdata($uid, $envir, $exever, $records = false) { - - // determine likely search order - if ($envir == 'Stadium') { - // check for old TMN - if (strcmp($exever, '0.1.8.0') < 0) - $sections = array('TMN', 'TMNF', 'TMU'); - // check for new TMF - elseif (strcmp($exever, '2.11.0') >= 0) - $sections = array('TMNF', 'TMU'); - else - $sections = array('TMU'); // TMNF section opened after TMF beta - } elseif ($envir == 'Bay' || $envir == 'Coast' || $envir == 'Island') { - // check for old TMS - if (strcmp($exever, '0.1.5.0') <= 0) - $sections = array('TMS', 'TMU'); - else - $sections = array('TMU'); // TMS section closed after TMU release - } else { // $envir == 'Alpine' || 'Snow' || 'Desert' || 'Speed' || 'Rally' - // check for old TMO - if (strcmp($exever, '0.1.5.0') <= 0) - $sections = array('TMO', 'TMU'); - else - $sections = array('TMU'); // TMO section closed after TMU release - } - - // search TMX for track - foreach ($sections as $section) { - $tmxdata = new TMXInfoFetcher($section, $uid, $records); - if ($tmxdata->name) { - return $tmxdata; - } - } - return false; -} // findTMXdata - -/** - * Simple HTTP Get function with timeout - * ok: return string || error: return false || timeout: return -1 - * if $openonly == true, don't read data but return true upon connect - */ -function http_get_file($url, $openonly = false) { - global $aseco; - - $url = parse_url($url); - $port = isset($url['port']) ? $url['port'] : 80; - $query = isset($url['query']) ? '?' . $url['query'] : ''; - - $fp = @fsockopen($url['host'], $port, $errno, $errstr, 4); - if (!$fp) - return false; - if ($openonly) { - fclose($fp); - return true; - } - - $uri = ''; - foreach (explode('/', $url['path']) as $subpath) - $uri .= rawurlencode($subpath) . '/'; - $uri = substr($uri, 0, strlen($uri)-1); // strip trailing '/' - - fwrite($fp, 'GET ' . $uri . $query . " HTTP/1.0\r\n" . - 'Host: ' . $url['host'] . "\r\n" . - 'User-Agent: XASECO-' . XASECO_VERSION . ' (' . PHP_OS . '; ' . - $aseco->server->game . ")\r\n\r\n"); - stream_set_timeout($fp, 2); - $res = ''; - $info['timed_out'] = false; - while (!feof($fp) && !$info['timed_out']) { - $res .= fread($fp, 512); - $info = stream_get_meta_data($fp); - } - fclose($fp); - - if ($info['timed_out']) { - return -1; - } else { - if (substr($res, 9, 3) != '200') - return false; - $page = explode("\r\n\r\n", $res, 2); - return $page[1]; - } -} // http_get_file - -/** - * Return valid UTF-8 string, replacing faulty byte values with a given string - * Created by (OoR-F)~fuckfish (fish@stabb.de) - * http://www.tm-forum.com/viewtopic.php?p=117639#p117639 - * Based on the original tm_substr function by Slig (slig@free.fr) - * Updated by Xymph; More info: http://en.wikipedia.org/wiki/UTF-8 - */ -function validateUTF8String($input, $invalidRepl = '') { - - $str = (string) $input; - $len = strlen($str); // byte string length - $pos = 0; // current byte pos in string - $new = ''; - - while ($pos < $len) { - $co = ord($str[$pos]); - - // 4-6 bytes UTF8 => unsupported - if ($co >= 240) { - // bad multibyte char - $new .= $invalidRepl; - $pos++; - - // 3 bytes UTF8 => 1110bbbb 10bbbbbb 10bbbbbb - } elseif ($co >= 224) { - if (($pos+2 < $len) && - (ord($str[$pos+1]) >= 128 && ord($str[$pos+1]) < 192) && - (ord($str[$pos+2]) >= 128 && ord($str[$pos+2]) < 192)) { - // ok, it was 1 character, increase counters - $new .= substr($str, $pos, 3); - $pos += 3; - } else { - // bad multibyte char - $new .= $invalidRepl; - $pos++; - } - - // 2 bytes UTF8 => 110bbbbb 10bbbbbb - } elseif ($co >= 194) { - if (($pos+1 < $len) && - (ord($str[$pos+1]) >= 128 && ord($str[$pos+1]) < 192)) { - // ok, it was 1 character, increase counters - $new .= substr($str, $pos, 2); - $pos += 2; - } else { - // bad multibyte char - $new .= $invalidRepl; - $pos++; - } - - // 2 bytes overlong encoding => unsupported - } elseif ($co >= 192) { - // bad multibyte char 1100000b - $new .= $invalidRepl; - $pos++; - - // 1 byte ASCII => 0bbbbbbb, or invalid => 10bbbbbb or 11111bbb - } else { // $co < 192 - // erroneous middle multibyte char? - if ($co >= 128 || $co == 0) - $new .= $invalidRepl; - else - $new .= $str[$pos]; - - $pos++; - } - } - return $new; -} // validateUTF8String - -/** - * Convert php.ini memory shorthand string to integer bytes - * http://www.php.net/manual/en/function.ini-get.php#96996 - */ -function shorthand2bytes($size_str) { - - switch (substr($size_str, -1)) { - case 'M': case 'm': return (int)$size_str * 1048576; - case 'K': case 'k': return (int)$size_str * 1024; - case 'G': case 'g': return (int)$size_str * 1073741824; - default: return (int)$size_str; - } -} // return_bytes - -/** - * Convert boolean value to text string - */ -function bool2text($boolval) { - - if ($boolval) - return 'True'; - else - return 'False'; -} // bool2text -?> diff --git a/xaseco/includes/gbxchallinfo.inc.php b/xaseco/includes/gbxchallinfo.inc.php deleted file mode 100644 index 8565002..0000000 --- a/xaseco/includes/gbxchallinfo.inc.php +++ /dev/null @@ -1,131 +0,0 @@ - - * - * v1.2: Allow getting info from tracks already on the server w/o removing them - * v1.1: Fix PHP notices about redefinition of constants - * v1.0: Initial release - */ - -class GBXChallengeInfo { - - public $name, $uid, $filename, $author, $envir, $mood, - $bronzetm, $silvertm, $goldtm, $authortm, - $coppers, $laprace, $nblaps, $nbcps, $nbrcps, $error; - - /** - * Fetches current ChallengeInfo for a GBX challenge - * Loads track into server, selects it, gets info, and removes it from server - * - * @param String $filename - * The challenge filename (must be a path below .../GameData/Tracks/) - * @return GBXChallengeInfo - * If $uid is empty, GBX data couldn't be extracted and $error contains - * an error message - */ - public function GBXChallengeInfo($filename) { - - $ip = 'localhost'; - $port = 5000; - $user = 'SuperAdmin'; - $pass = 'YOUR_SUPERADMIN_PASSWORD'; - - $this->uid = ''; - $this->error = ''; - $client = new IXR_Client_Gbx; - - // connect to the server - if (!$client->InitWithIp($ip, $port)) { - $this->error = 'Connection failed - Error ' . $client->getErrorCode() . ': ' . $client->getErrorMessage(); - return false; - } - - // log into the server - if (!$client->query('Authenticate', $user, $pass)) { - $this->error = 'Login failed - Error ' . $client->getErrorCode() . ': ' . $client->getErrorMessage(); - - } else { - // add the challenge - $ret = $client->query('AddChallenge', $filename); - $already = ($client->getErrorMessage() == 'Challenge already added.'); - if (!$ret && !$already) { - $this->error = 'AddChallenge failed - Error ' . $client->getErrorCode() . ': ' . $client->getErrorMessage(); - } else { - - // select the challenge - if (!$client->query('ChooseNextChallenge', $filename)) { - $this->error = 'ChooseNextChallenge failed - Error ' . $client->getErrorCode() . ': ' . $client->getErrorMessage(); - - // switch to our challenge - } elseif (!$client->query('NextChallenge')) { - $this->error = 'NextChallenge 1 failed - Error ' . $client->getErrorCode() . ': ' . $client->getErrorMessage(); - } else { - - // allow for challenge switch but time out after 5 seconds - $retry = 5; - while (true) { - sleep(1); - - // obtain challenge details - if ($client->query('GetCurrentChallengeInfo')) { - $info = $client->getResponse(); - // check for our challenge - if ($info['FileName'] == $filename) { - break; - } - } else { - $this->error = 'GetCurrentChallengeInfo failed - Error ' . $client->getErrorCode() . ': ' . $client->getErrorMessage(); - break; - } - if ($retry-- == 0) { - $this->error = 'GetCurrentChallengeInfo timed out after 5 seconds'; - break; - } - } - - // extract our challenge details - if ($this->error == '') { - $this->name = $info['Name']; - $this->uid = $info['UId']; - $this->filename = $info['FileName']; - $this->author = $info['Author']; - $this->envir = $info['Environnement']; - $this->mood = $info['Mood']; - $this->bronzetm = $info['BronzeTime']; - $this->silvertm = $info['SilverTime']; - $this->goldtm = $info['GoldTime']; - $this->authortm = $info['AuthorTime']; - $this->coppers = $info['CopperPrice']; - $this->laprace = $info['LapRace']; - $this->nblaps = $info['NbLaps']; - $this->nbcps = $info['NbCheckpoints']; - $this->nbrcps = $info['NbCheckpoints']; - - if ($this->laprace && $this->nblaps > 1) - $this->nbrcps *= $this->nblaps; - } - } - } - - // check if challenge wasn't already there - if (!$already) { - // remove the challenge - if (!$client->query('RemoveChallenge', $filename)) { - $this->error = 'RemoveChallenge failed - Error ' . $client->getErrorCode() . ': ' . $client->getErrorMessage(); - } - // switch away from our challenge - sleep(5); - if (!$client->query('NextChallenge')) { - $this->error = 'NextChallenge 2 failed - Error ' . $client->getErrorCode() . ': ' . $client->getErrorMessage(); - } - } - } - - $client->Terminate(); - } -} -?> diff --git a/xaseco/includes/gbxdatafetcher.inc.php b/xaseco/includes/gbxdatafetcher.inc.php deleted file mode 100644 index 9d6c0dd..0000000 --- a/xaseco/includes/gbxdatafetcher.inc.php +++ /dev/null @@ -1,1431 +0,0 @@ - - * Thanks to Electron for additional input & prototyping - * Based on information at http://en.tm-wiki.org/wiki/GBX, - * http://www.tm-forum.com/viewtopic.php?p=192817#p192817 - * and http://en.tm-wiki.org/wiki/PAK#Header_versions_8.2B - * - * v2.5: Add lookback string Valley; strip optional digits from mood; fix empty - * thumbnail warning - * v2.4: Update GBXChallMapFetcher & GBXPackFetcher version-dependent processing; - * update lookback strings - * v2.3: Add UTF-8 decoding of parsed XML chunk's elements; strip UTF-8 BOM - * from various string fields - * v2.2: Add class GBXPackFetcher; limit loadGBXdata() to first 256KB - * v2.1: Add exception codes; add $thumbLen to GBXChallMapFetcher - * v2.0: Complete rewrite - */ - -/** - * @class GBXBaseFetcher - * @brief The base GBX class with all common functionality - */ -class GBXBaseFetcher -{ - public $parseXml, $xml, $xmlParsed; - - public $authorVer, $authorLogin, $authorNick, $authorZone, $authorEInfo; - - private $_gbxdata, $_gbxlen, $_gbxptr, $_debug, $_error, $_endianess, - $_lookbacks, $_parsestack; - - // supported class ID's - const GBX_CHALLENGE_TMF = 0x03043000; - const GBX_AUTOSAVE_TMF = 0x03093000; - const GBX_CHALLENGE_TM = 0x24003000; - const GBX_AUTOSAVE_TM = 0x2403F000; - const GBX_REPLAY_TM = 0x2407E000; - - const MACHINE_ENDIAN_ORDER = 0; - const LITTLE_ENDIAN_ORDER = 1; - const BIG_ENDIAN_ORDER = 2; - - const LOAD_LIMIT = 256; // KBs to read in loadGBXdata() - - // initialise new instance - public function __construct() - { - $this->parseXml = false; - $this->xml = ''; - $this->xmlParsed = array(); - $this->_debug = false; - $this->_error = ''; - - list($endiantest) = array_values(unpack('L1L', pack('V', 1))); - if ($endiantest != 1) - $this->_endianess = self::BIG_ENDIAN_ORDER; - else - $this->_endianess = self::LITTLE_ENDIAN_ORDER; - - $this->clearGBXdata(); - $this->clearLookbacks(); - $this->_parsestack = array(); - - $this->authorVer = 0; - $this->authorLogin = ''; - $this->authorNick = ''; - $this->authorZone = ''; - $this->authorEInfo = ''; - } - - // enable debug logging - protected function enableDebug() - { - $this->_debug = true; - } - - // disable debug logging - protected function disableDebug() - { - $this->_debug = false; - } - - // print message to stderr if debugging - protected function debugLog($msg) - { - if ($this->_debug) - fwrite(STDERR, $msg."\n"); - } - - // set error message prefix - protected function setError($prefix) - { - $this->_error = (string)$prefix; - } - - // exit with error exception - protected function errorOut($msg, $code = 0) - { - $this->clearGBXdata(); - throw new Exception($this->_error . $msg, $code); - } - - // read in raw GBX data - protected function loadGBXdata($filename) - { - $gbxdata = @file_get_contents($filename, false, null, 0, self::LOAD_LIMIT * 1024); - if ($gbxdata !== false) - $this->storeGBXdata($gbxdata); - else - $this->errorOut('Unable to read GBX data from ' . $filename, 1); - } - - // store raw GBX data - protected function storeGBXdata($gbxdata) - { - $this->_gbxdata = & $gbxdata; - $this->_gbxlen = strlen($gbxdata); - $this->_gbxptr = 0; - } - - // clear GBX data (to avoid print_r problems & reduce memory usage) - protected function clearGBXdata() - { - $this->storeGBXdata(''); - } - - // get GBX pointer - protected function getGBXptr() - { - return $this->_gbxptr; - } - - // set GBX pointer - protected function setGBXptr($ptr) - { - $this->_gbxptr = (int)$ptr; - } - - // move GBX pointer - protected function moveGBXptr($len) - { - $this->_gbxptr += (int)$len; - } - - // read $len bytes from GBX data - protected function readData($len) - { - if ($this->_gbxptr + $len > $this->_gbxlen) - $this->errorOut(sprintf('Insufficient data for %d bytes at pos 0x%04X', - $len, $this->_gbxptr), 2); - $data = ''; - while ($len-- > 0) - $data .= $this->_gbxdata[$this->_gbxptr++]; - return $data; - } - - // read signed byte from GBX data - protected function readInt8() - { - $data = $this->readData(1); - list(, $int8) = unpack('c*', $data); - return $int8; - } - - // read signed short from GBX data - protected function readInt16() - { - $data = $this->readData(2); - if ($this->_endianess == self::BIG_ENDIAN_ORDER) - $data = strrev($data); - list(, $int16) = unpack('s*', $data); - return $int16; - } - - // read signed long from GBX data - protected function readInt32() - { - $data = $this->readData(4); - if ($this->_endianess == self::BIG_ENDIAN_ORDER) - $data = strrev($data); - list(, $int32) = unpack('l*', $data); - return $int32; - } - - // read string from GBX data - protected function readString() - { - $gbxptr = $this->getGBXptr(); - $len = $this->readInt32(); - $len &= 0x7FFFFFFF; - if ($len <= 0 || $len >= 0x12000) { // for large XML & Data blocks - if ($len != 0) - $this->errorOut(sprintf('Invalid string length %d (0x%04X) at pos 0x%04X', - $len, $len, $gbxptr), 3); - } - $data = $this->readData($len); - return $data; - } - - // strip UTF-8 BOM from string - protected function stripBOM($str) - { - return str_replace("\xEF\xBB\xBF", '', $str); - } - - // clear lookback strings - protected function clearLookbacks() - { - $this->_lookbacks = array(); - } - - // read lookback string from GBX data - protected function readLookbackString() - { - if (empty($this->_lookbacks)) { - $version = $this->readInt32(); - if ($version != 3) - $this->errorOut('Unknown lookback strings version: ' . $version, 4); - } - - // check index - $index = $this->readInt32(); - if ($index == -1) { - // unassigned (empty) string - $str = ''; - } elseif (($index & 0xC0000000) == 0) { - // use external reference string - switch ($index) { - case 11: $str = 'Valley'; - break; - case 12: $str = 'Canyon'; - break; - case 17: $str = 'TMCommon'; - break; - case 202: $str = 'Storm'; - break; - case 299: $str = 'SMCommon'; - break; - case 10003: $str = 'Common'; - break; - default: $str = 'UNKNOWN'; - } - } elseif (($index & 0x3FFFFFFF) == 0) { - // read string & add to lookbacks - $str = $this->readString(); - $this->_lookbacks[] = $str; - } else { - // use string from lookbacks - $str = $this->_lookbacks[($index & 0x3FFFFFFF) - 1]; - } - - return $str; - } - - // XML parser functions - private function startTag($parser, $name, $attribs) - { - foreach ($attribs as $key => &$val) - $val = utf8_decode($val); - //echo 'startTag: ' . $name . "\n"; print_r($attribs); - array_push($this->_parsestack, $name); - if ($name == 'DEP') { - $this->xmlParsed['DEPS'][] = $attribs; - } elseif (count($this->_parsestack) <= 2) { - // HEADER, IDENT, DESC, TIMES, CHALLENGE/MAP, DEPS, CHECKPOINTS - $this->xmlParsed[$name] = $attribs; - } - } - - private function charData($parser, $data) - { - //echo 'charData: ' . $data . "\n"; - if (count($this->_parsestack) == 3) - $this->xmlParsed[$this->_parsestack[1]][$this->_parsestack[2]] = $data; - elseif (count($this->_parsestack) > 3) - $this->debugLog('XML chunk nested too deeply: ', print_r($this->_parsestack, true)); - } - - private function endTag($parser, $name) - { - //echo 'endTag: ' . $name . "\n"; - array_pop($this->_parsestack); - } - - protected function parseXMLstring() - { - // define a dedicated parser to handle the attributes - $xml_parser = xml_parser_create(); - xml_set_object($xml_parser, $this); - xml_set_element_handler($xml_parser, 'startTag', 'endTag'); - xml_set_character_data_handler($xml_parser, 'charData'); - - // escape '&' characters unless already a known entity - $xml = preg_replace('/&(?!(?:amp|quot|apos|lt|gt);)/', '&', $this->xml); - - if (!xml_parse($xml_parser, utf8_encode($xml), true)) - $this->errorOut(sprintf('XML chunk parse error: %s at line %d', - xml_error_string(xml_get_error_code($xml_parser)), - xml_get_current_line_number($xml_parser)), 12); - - xml_parser_free($xml_parser); - } - - /** - * Check GBX header, main class ID & header block - * @param Array $classes - * The main class IDs accepted for this GBX - * @return Size of GBX header block - */ - protected function checkHeader(array $classes) - { - // check magic header - $data = $this->readData(3); - $version = $this->readInt16(); - if ($data != 'GBX' || $version != 6) - $this->errorOut('No magic GBX header', 5); - - // check header block (un)compression - $data = $this->readData(4); - if ($data[1] != 'U') - $this->errorOut('Compressed GBX header block not supported', 6); - - // check main class ID - $mainClass = $this->readInt32(); - if (!in_array($mainClass, $classes)) - $this->errorOut(sprintf('Main class ID %08X not supported', $mainClass), 7); - $this->debugLog(sprintf('GBX main class ID: %08X', $mainClass)); - - // get header size - $headerSize = $this->readInt32(); - if ($headerSize == 0) - $this->errorOut('No GBX header block', 8); - - $this->debugLog(sprintf('GBX header block size: %d (%.1f KB)', - $headerSize, $headerSize / 1024)); - return $headerSize; - } // checkHeader - - /** - * Get list of chunks from GBX header block - * @param Int $headerSize - * Size of header block (chunks list & chunks data) - * @param array $chunks - * List of chunk IDs & names - * @return List of chunk offsets & sizes - */ - protected function getChunksList($headerSize, array $chunks) - { - // get number of chunks - $numChunks = $this->readInt32(); - if ($numChunks == 0) - $this->errorOut('No GBX header chunks', 9); - - $this->debugLog('GBX number of header chunks: ' . $numChunks); - $chunkStart = $this->getGBXptr(); - $this->debugLog(sprintf('GBX start of chunk list: 0x%04X', $chunkStart)); - $chunkOffset = $chunkStart + $numChunks * 8; - - // get list of all chunks - $chunksList = array(); - for ($i = 0; $i < $numChunks; $i++) - { - $chunkId = $this->readInt32(); - $chunkSize = $this->readInt32(); - - $chunkId &= 0x00000FFF; - $chunkSize &= 0x7FFFFFFF; - - if (array_key_exists($chunkId, $chunks)) { - $name = $chunks[$chunkId]; - $chunksList[$name] = array( - 'off' => $chunkOffset, - 'size' => $chunkSize - ); - } else { - $name = 'UNKNOWN'; - } - $this->debugLog(sprintf('GBX chunk %2d %-8s Id 0x%03X Offset 0x%06X Size %6d', - $i, $name, $chunkId, $chunkOffset, $chunkSize)); - $chunkOffset += $chunkSize; - } - - //$this->debugLog(print_r($chunksList, true)); - $totalSize = $chunkOffset - $chunkStart + 4; // numChunks - if ($headerSize != $totalSize) - $this->errorOut(sprintf('Chunk list size mismatch: %d <> %d', - $headerSize, $totalSize), 10); - - return $chunksList; - } // getChunksList - - /** - * Initialize for a new chunk - * @param int $offset - */ - protected function initChunk($offset) - { - $this->setGBXptr($offset); - $this->clearLookbacks(); - } - - /** - * Get XML chunk from GBX header block & optionally parse it - * @param array $chunksList - * List of chunk offsets & sizes - */ - protected function getXMLChunk(array $chunksList) - { - if (!isset($chunksList['XML'])) return; - - $this->initChunk($chunksList['XML']['off']); - $this->xml = $this->readString(); - - if ($chunksList['XML']['size'] != strlen($this->xml) + 4) - $this->errorOut(sprintf('XML chunk size mismatch: %d <> %d', - $chunksList['XML']['size'], strlen($this->xml) + 4), 11); - - if ($this->parseXml && $this->xml != '') - $this->parseXMLstring(); - } // getXMLChunk - - /** - * Get Author fields from GBX header block - */ - protected function getAuthorFields() - { - $this->authorVer = $this->readInt32(); - $this->authorLogin = $this->readString(); - $this->authorNick = $this->stripBOM($this->readString()); - $this->authorZone = $this->stripBOM($this->readString()); - $this->authorEInfo = $this->readString(); - } // getAuthorFields - - /** - * Get Author chunk from GBX header block - * @param array $chunksList - * List of chunk offsets & sizes - */ - protected function getAuthorChunk(array $chunksList) - { - if (!isset($chunksList['Author'])) return; - - $this->initChunk($chunksList['Author']['off']); - $version = $this->readInt32(); - $this->debugLog('GBX Author chunk version: ' . $version); - - $this->getAuthorFields(); - } // getAuthorChunk - -} // class GBXBaseFetcher - - -/** - * @class GBXChallMapFetcher - * @brief The class that fetches all GBX challenge/map info - */ -class GBXChallMapFetcher extends GBXBaseFetcher -{ - public $tnImage; - - public $headerVersn, $bronzeTime, $silverTime, $goldTime, $authorTime, - $cost, $multiLap, $type, $typeName, $authorScore, $simpleEdit, - $nbChecks, $nbLaps; - public $uid, $envir, $author, $name, $kind, $kindName, $password, - $mood, $envirBg, $authorBg, $mapType, $mapStyle, $lightmap, $titleUid; - public $xmlVer, $exeVer, $exeBld, $validated, $songFile, $songUrl, - $modName, $modFile, $modUrl; - public $thumbLen, $thumbnail, $comment; - - const IMAGE_FLIP_HORIZONTAL = 1; - const IMAGE_FLIP_VERTICAL = 2; - const IMAGE_FLIP_BOTH = 3; - - /** - * Mirror (flip) an image across horizontal, vertical or both axis - * Source: http://www.php.net/manual/en/function.imagecopy.php#85992 - * @param String $imgsrc - * Source image data - * @param Int $dir - * Flip direction from the constants above - * @return Flipped image data if successful, otherwise source image data - */ - private function imageFlip($imgsrc, $dir) - { - $width = imagesx($imgsrc); - $height = imagesy($imgsrc); - $src_x = 0; - $src_y = 0; - $src_width = $width; - $src_height = $height; - - switch ((int)$dir) { - case self::IMAGE_FLIP_HORIZONTAL: - $src_y = $height; - $src_height = -$height; - break; - case self::IMAGE_FLIP_VERTICAL: - $src_x = $width; - $src_width = -$width; - break; - case self::IMAGE_FLIP_BOTH: - $src_x = $width; - $src_y = $height; - $src_width = -$width; - $src_height = -$height; - break; - default: - return $imgsrc; - } - - $imgdest = imagecreatetruecolor($width, $height); - if (imagecopyresampled($imgdest, $imgsrc, 0, 0, $src_x, $src_y, - $width, $height, $src_width, $src_height)) { - return $imgdest; - } - return $imgsrc; - } // imageFlip - - /** - * Instantiate GBX challenge/map fetcher - * - * @param Boolean $parsexml - * If true, the fetcher also parses the XML block - * @param Boolean $tnimage - * If true, the fetcher also extracts the thumbnail image; - * if GD/JPEG libraries are present, image will be flipped upright, - * otherwise it will be in the original upside-down format - * Warning: this is binary data in JPEG format, 256x256 pixels for - * TMU/TMF or 512x512 pixels for MP - * @param Boolean $debug - * If true, the fetcher prints debug logging to stderr - * @return GBXChallMapFetcher - * If GBX data couldn't be extracted, an Exception is thrown with - * the error message & code - */ - public function __construct($parsexml = false, $tnimage = false, $debug = false) - { - parent::__construct(); - - $this->headerVersn = 0; - $this->bronzeTime = 0; - $this->silverTime = 0; - $this->goldTime = 0; - $this->authorTime = 0; - - $this->cost = 0; - $this->multiLap = false; - $this->type = 0; - $this->typeName = ''; - - $this->authorScore = 0; - $this->simpleEdit = false; - $this->nbChecks = 0; - $this->nbLaps = 0; - - $this->uid = ''; - $this->envir = ''; - $this->author = ''; - $this->name = ''; - $this->kind = 0; - $this->kindName = ''; - - $this->password = ''; - $this->mood = ''; - $this->envirBg = ''; - $this->authorBg = ''; - - $this->mapType = ''; - $this->mapStyle = ''; - $this->lightmap = 0; - $this->titleUid = ''; - - $this->xmlVer = ''; - $this->exeVer = ''; - $this->exeBld = ''; - $this->validated = false; - $this->songFile = ''; - $this->songUrl = ''; - $this->modName = ''; - $this->modFile = ''; - $this->modUrl = ''; - - $this->thumbLen = 0; - $this->thumbnail = ''; - $this->comment = ''; - - $this->parseXml = (bool)$parsexml; - $this->tnImage = (bool)$tnimage; - if ((bool)$debug) - $this->enableDebug(); - - $this->setError('GBX map error: '); - } // __construct - - /** - * Process GBX challenge/map file - * - * @param String $filename - * The challenge filename - */ - public function processFile($filename) - { - $this->loadGBXdata((string)$filename); - - $this->processGBX(); - } // processFile - - /** - * Process GBX challenge/map data - * - * @param String $gbxdata - * The challenge/map data - */ - public function processData($gbxdata) - { - $this->storeGBXdata((string)$gbxdata); - - $this->processGBX(); - } // processData - - // process GBX data - private function processGBX() - { - // supported challenge/map class IDs - $challclasses = array( - self::GBX_CHALLENGE_TMF, - self::GBX_CHALLENGE_TM, - ); - - $headerSize = $this->checkHeader($challclasses); - $headerStart = $headerEnd = $this->getGBXptr(); - - // desired challenge/map chunk IDs - $chunks = array( - 0x002 => 'Info', // TM, MP - 0x003 => 'String', // TM, MP - 0x004 => 'Version', // TM, MP - 0x005 => 'XML', // TM, MP - 0x007 => 'Thumbnl', // TM, MP - 0x008 => 'Author', // MP - ); - - $chunksList = $this->getChunksList($headerSize, $chunks); - - $this->getInfoChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - $this->getStringChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - $this->getVersionChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - $this->getXMLChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - $this->getThumbnlChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - $this->getAuthorChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - if ($headerSize != $headerEnd - $headerStart) - $this->errorOut(sprintf('Header size mismatch: %d <> %d', - $headerSize, $headerEnd - $headerStart), 16); - - if ($this->parseXml) { - if (isset($this->xmlParsed['HEADER']['VERSION'])) - $this->xmlVer = $this->xmlParsed['HEADER']['VERSION']; - if (isset($this->xmlParsed['HEADER']['EXEVER'])) - $this->exeVer = $this->xmlParsed['HEADER']['EXEVER']; - if (isset($this->xmlParsed['HEADER']['EXEBUILD'])) - $this->exeBld = $this->xmlParsed['HEADER']['EXEBUILD']; - if ($this->lightmap == 0 && isset($this->xmlParsed['HEADER']['LIGHTMAP'])) - $this->lightmap = (int)$this->xmlParsed['HEADER']['LIGHTMAP']; - if ($this->authorZone == '' && isset($this->xmlParsed['IDENT']['AUTHORZONE'])) - $this->authorZone = $this->xmlParsed['IDENT']['AUTHORZONE']; - if ($this->envir == 'UNKNOWN' && isset($this->xmlParsed['DESC']['ENVIR'])) - $this->envir = $this->xmlParsed['DESC']['ENVIR']; - if ($this->nbLaps == 0 && isset($this->xmlParsed['DESC']['NBLAPS'])) - $this->nbLaps = (int)$this->xmlParsed['DESC']['NBLAPS']; - if (isset($this->xmlParsed['DESC']['VALIDATED'])) - $this->validated = (bool)$this->xmlParsed['DESC']['VALIDATED']; - if (isset($this->xmlParsed['DESC']['MOD'])) - $this->modName = $this->xmlParsed['DESC']['MOD']; - - // extract optional song & mod filenames - if (!empty($this->xmlParsed['DEPS'])) { - for ($i = 0; $i < count($this->xmlParsed['DEPS']); $i++) { - if (preg_match('/ChallengeMusics\\\\(.+)/', $this->xmlParsed['DEPS'][$i]['FILE'], $path)) { - $this->songFile = $path[1]; - if (isset($this->xmlParsed['DEPS'][$i]['URL'])) - $this->songUrl = $this->xmlParsed['DEPS'][$i]['URL']; - } elseif (preg_match('/.+\\\\Mod\\\\.+/', $this->xmlParsed['DEPS'][$i]['FILE'], $path)) { - $this->modFile = $path[0]; - if (isset($this->xmlParsed['DEPS'][$i]['URL'])) - $this->modUrl = $this->xmlParsed['DEPS'][$i]['URL']; - } - } - } - } - - $this->clearGBXdata(); - } // processGBX - - /** - * Get Info chunk from GBX header block - * @param array $chunksList - * List of chunk offsets & sizes - */ - protected function getInfoChunk(array $chunksList) - { - if (!isset($chunksList['Info'])) return; - - $this->initChunk($chunksList['Info']['off']); - $version = $this->readInt8(); - $this->debugLog('GBX Info chunk version: ' . $version); - - if ($version < 3) { - $this->uid = $this->readLookbackString(); - - $this->envir = $this->readLookbackString(); - $this->author = $this->readLookbackString(); - - $this->name = $this->stripBOM($this->readString()); - } - $this->moveGBXptr(4); // skip bool 0 - - if ($version >= 1) { - $this->bronzeTime = $this->readInt32(); - - $this->silverTime = $this->readInt32(); - - $this->goldTime = $this->readInt32(); - - $this->authorTime = $this->readInt32(); - - if ($version == 2) - $this->moveGBXptr(1); // skip unknown byte - - if ($version >= 4) { - $this->cost = $this->readInt32(); - - if ($version >= 5) { - $this->multiLap = (bool)$this->readInt32(); - - if ($version == 6) - $this->moveGBXptr(4); // skip unknown bool - - if ($version >= 7) { - $this->type = $this->readInt32(); - switch ($this->type) { - case 0: $this->typeName = 'Race'; - break; - case 1: $this->typeName = 'Platform'; - break; - case 2: $this->typeName = 'Puzzle'; - break; - case 3: $this->typeName = 'Crazy'; - break; - case 4: $this->typeName = 'Shortcut'; - break; - case 5: $this->typeName = 'Stunts'; - break; - case 6: $this->typeName = 'Script'; - break; - default: $this->typeName = 'UNKNOWN'; - } - - if ($version >= 9) { - $this->moveGBXptr(4); // skip int32 0 - - if ($version >= 10) { - $this->authorScore = $this->readInt32(); - - if ($version >= 11) { - $this->simpleEdit = (bool)$this->readInt32(); - - if ($version >= 12) { - $this->moveGBXptr(4); // skip bool 0 - - if ($version >= 13) { - $this->nbChecks = $this->readInt32(); - - $this->nbLaps = $this->readInt32(); - } - } - } - } - } - } - } - } - } - } // getInfoChunk - - /** - * Get String chunk from GBX header block - * @param array $chunksList - * List of chunk offsets & sizes - */ - protected function getStringChunk(array $chunksList) - { - if (!isset($chunksList['String'])) return; - - $this->initChunk($chunksList['String']['off']); - $version = $this->readInt8(); - $this->debugLog('GBX String chunk version: ' . $version); - - $this->uid = $this->readLookbackString(); - - $this->envir = $this->readLookbackString(); - $this->author = $this->readLookbackString(); - - $this->name = $this->stripBOM($this->readString()); - - $this->kind = $this->readInt8(); - switch ($this->kind) { - case 0: $this->kindName = '(internal)EndMarker'; - break; - case 1: $this->kindName = '(old)Campaign'; - break; - case 2: $this->kindName = '(old)Puzzle'; - break; - case 3: $this->kindName = '(old)Retro'; - break; - case 4: $this->kindName = '(old)TimeAttack'; - break; - case 5: $this->kindName = '(old)Rounds'; - break; - case 6: $this->kindName = 'InProgress'; - break; - case 7: $this->kindName = 'Campaign'; - break; - case 8: $this->kindName = 'Multi'; - break; - case 9: $this->kindName = 'Solo'; - break; - case 10: $this->kindName = 'Site'; - break; - case 11: $this->kindName = 'SoloNadeo'; - break; - case 12: $this->kindName = 'MultiNadeo'; - break; - default: $this->kindName = 'UNKNOWN'; - } - - if ($version >= 1) { - $this->moveGBXptr(4); // skip locked - - $this->password = $this->readString(); - - if ($version >= 2) { - $this->mood = $this->readLookbackString(); - $this->mood = preg_replace('/([A-Za-z]+)\d*/', '\1', $this->mood); - - $this->envirBg = $this->readLookbackString(); - $this->authorBg = $this->readLookbackString(); - - if ($version >= 3) { - $this->moveGBXptr(8); // skip mapOrigin - - if ($version >= 4) { - $this->moveGBXptr(8); // skip mapTarget - - if ($version >= 5) { - $this->moveGBXptr(16); // skip unknown int128 - - if ($version >= 6) { - $this->mapType = $this->readString(); - $this->mapStyle = $this->readString(); - - if ($version <= 8) - $this->moveGBXptr(4); // skip unknown bool - - if ($version >= 8) { - $this->moveGBXptr(8); // skip lightmapCacheUID - - if ($version >= 9) { - $this->lightmap = $this->readInt8(); - - if ($version >= 11) { - $this->titleUid = $this->readLookbackString(); - } - } - } - } - } - } - } - } - } - } // getStringChunk - - /** - * Get Version chunk from GBX header block - * @param array $chunksList - * List of chunk offsets & sizes - */ - protected function getVersionChunk(array $chunksList) - { - if (!isset($chunksList['Version'])) return; - - $this->initChunk($chunksList['Version']['off']); - $this->headerVersn = $this->readInt32(); - } // getVersionChunk - - /** - * Get Thumbnail/Comments chunk from GBX header block - * @param array $chunksList - * List of chunk offsets & sizes - */ - protected function getThumbnlChunk(array $chunksList) - { - if (!isset($chunksList['Thumbnl'])) return; - - $this->initChunk($chunksList['Thumbnl']['off']); - $version = $this->readInt32(); - $this->debugLog('GBX Thumbnail chunk version: ' . $version); - - if ($version == 1) { - $thumbSize = $this->readInt32(); - $this->debugLog(sprintf('GBX Thumbnail size: %d (%.1f KB)', - $thumbSize, $thumbSize / 1024)); - - $this->moveGBXptr(strlen('')); - $this->thumbnail = $this->readData($thumbSize); - $this->thumbLen = strlen($this->thumbnail); - $this->moveGBXptr(strlen('')); - - $this->moveGBXptr(strlen('')); - $this->comment = $this->stripBOM($this->readString()); - $this->moveGBXptr(strlen('')); - - // return extracted thumbnail image? - if ($this->tnImage && $this->thumbLen > 0) { - // check for GD/JPEG libraries - if (function_exists('imagecreatefromjpeg') && - function_exists('imagecopyresampled')) { - // flip thumbnail via temporary file - $tmp = tempnam(sys_get_temp_dir(), 'gbxflip'); - if (@file_put_contents($tmp, $this->thumbnail) !== false) { - if ($tn = @imagecreatefromjpeg($tmp)) { - $tn = $this->imageFlip($tn, self::IMAGE_FLIP_HORIZONTAL); - if (@imagejpeg($tn, $tmp)) { - if (($tn = @file_get_contents($tmp)) !== false) { - $this->thumbnail = $tn; - } - } - } - unlink($tmp); - } - } - } else { - $this->thumbnail = ''; - } - } - } // getThumbnlChunk - -} // class GBXChallMapFetcher - - -/** - * @class GBXChallengeFetcher - * @brief Wrapper class for backwards compatibility with the old GBXChallengeFetcher - * @deprecated Do not use for new development, use GBXChallMapFetcher instead - */ -class GBXChallengeFetcher extends GBXChallMapFetcher -{ - public $authortm, $goldtm, $silvertm, $bronzetm, $ascore, $azone, $multi, $editor, - $pub, $nblaps, $parsedxml, $xmlver, $exever, $exebld, $songfile, $songurl, - $modname, $modfile, $modurl; - - /** - * Fetches a hell of a lot of data about a GBX challenge - * - * @param String $filename - * The challenge filename (must include full path) - * @param Boolean $parsexml - * If true, the script also parses the XML block - * @param Boolean $tnimage - * If true, the script also extracts the thumbnail image; if GD/JPEG - * libraries are present, image will be flipped upright, otherwise - * it will be in the original upside-down format - * Warning: this is binary data in JPEG format, 256x256 pixels for - * TMU/TMF or 512x512 pixels for MP - * @return GBXChallengeFetcher - * If $uid is empty, GBX data couldn't be extracted - */ - public function __construct($filename, $parsexml = false, $tnimage = false) - { - parent::__construct($parsexml, $tnimage, false); - - try - { - $this->processFile($filename); - - $this->authortm = $this->authorTime; - $this->goldtm = $this->goldTime; - $this->silvertm = $this->silverTime; - $this->bronzetm = $this->bronzeTime; - $this->ascore = $this->authorScore; - $this->azone = $this->authorZone; - $this->multi = $this->multiLap; - $this->editor = $this->simpleEdit; - $this->pub = $this->authorBg; - $this->nblaps = $this->nbLaps; - $this->parsedxml = $this->xmlParsed; - $this->xmlver = $this->xmlVer; - $this->exever = $this->exeVer; - $this->exebld = $this->exeBld; - $this->songfile = $this->songFile; - $this->songurl = $this->songUrl; - $this->modname = $this->modName; - $this->modfile = $this->modFile; - $this->modurl = $this->modUrl; - } - catch (Exception $e) - { - $this->uid = ''; - } - } - -} // class GBXChallengeFetcher - - -/** - * @class GBXReplayFetcher - * @brief The class that fetches all GBX replay info - * @note The interface for GBXReplayFetcher has changed compared to the old class, - * but there is no wrapper because no third-party XASECO[2] plugins used that - */ -class GBXReplayFetcher extends GBXBaseFetcher -{ - public $uid, $envir, $author, $replay, $nickname, $login, $titleUid; - public $xmlVer, $exeVer, $exeBld, $respawns, $stuntScore, $validable, - $cpsCur, $cpsLap; - - /** - * Instantiate GBX replay fetcher - * - * @param Boolean $parsexml - * If true, the fetcher also parses the XML block - * @param Boolean $debug - * If true, the fetcher prints debug logging to stderr - * @return GBXReplayFetcher - * If GBX data couldn't be extracted, an Exception is thrown with - * the error message & code - */ - public function __construct($parsexml = false, $debug = false) - { - parent::__construct(); - - $this->uid = ''; - $this->envir = ''; - $this->author = ''; - $this->replay = 0; - $this->nickname = ''; - $this->login = ''; - $this->titleUid = ''; - - $this->xmlVer = ''; - $this->exeVer = ''; - $this->exeBld = ''; - $this->respawns = 0; - $this->stuntScore = 0; - $this->validable = false; - $this->cpsCur = 0; - $this->cpsLap = 0; - - $this->parseXml = (bool)$parsexml; - if ((bool)$debug) - $this->enableDebug(); - - $this->setError('GBX replay error: '); - } // __construct - - /** - * Process GBX replay file - * - * @param String $filename - * The replay filename - */ - public function processFile($filename) - { - $this->loadGBXdata((string)$filename); - - $this->processGBX(); - } // processFile - - /** - * Process GBX replay data - * - * @param String $gbxdata - * The replay data - */ - public function processData($gbxdata) - { - $this->storeGBXdata((string)$gbxdata); - - $this->processGBX(); - } // processData - - // process GBX data - private function processGBX() - { - // supported replay class IDs - $replayclasses = array( - self::GBX_AUTOSAVE_TMF, - self::GBX_AUTOSAVE_TM, - self::GBX_REPLAY_TM, - ); - - $headerSize = $this->checkHeader($replayclasses); - $headerStart = $headerEnd = $this->getGBXptr(); - - // desired replay chunk IDs - $chunks = array( - 0x000 => 'String', // TM, MP - 0x001 => 'XML', // TM, MP - 0x002 => 'Author', // MP - ); - - $chunksList = $this->getChunksList($headerSize, $chunks); - - $this->getStringChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - $this->getXMLChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - $this->getAuthorChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - if ($headerSize != $headerEnd - $headerStart) - $this->errorOut(sprintf('Header size mismatch: %d <> %d', - $headerSize, $headerEnd - $headerStart), 20); - - if ($this->parseXml) { - if (isset($this->xmlParsed['HEADER']['VERSION'])) - $this->xmlVer = $this->xmlParsed['HEADER']['VERSION']; - if (isset($this->xmlParsed['HEADER']['EXEVER'])) - $this->exeVer = $this->xmlParsed['HEADER']['EXEVER']; - if (isset($this->xmlParsed['HEADER']['EXEBUILD'])) - $this->exeBld = $this->xmlParsed['HEADER']['EXEBUILD']; - if (isset($this->xmlParsed['TIMES']['RESPAWNS'])) - $this->respawns = (int)$this->xmlParsed['TIMES']['RESPAWNS']; - if (isset($this->xmlParsed['TIMES']['STUNTSCORE'])) - $this->stuntScore = (int)$this->xmlParsed['TIMES']['STUNTSCORE']; - if (isset($this->xmlParsed['TIMES']['VALIDABLE'])) - $this->validable = (bool)$this->xmlParsed['TIMES']['VALIDABLE']; - if (isset($this->xmlParsed['CHECKPOINTS']['CUR'])) - $this->cpsCur = (int)$this->xmlParsed['CHECKPOINTS']['CUR']; - if (isset($this->xmlParsed['CHECKPOINTS']['ONELAP'])) - $this->cpsLap = (int)$this->xmlParsed['CHECKPOINTS']['ONELAP']; - } - - $this->clearGBXdata(); - } // processGBX - - /** - * Get String chunk from GBX header block - * @param array $chunksList - * List of chunk offsets & sizes - */ - protected function getStringChunk(array $chunksList) - { - if (!isset($chunksList['String'])) return; - - $this->initChunk($chunksList['String']['off']); - $version = $this->readInt32(); - $this->debugLog('GBX String chunk version: ' . $version); - - if ($version >= 2) { - $this->uid = $this->readLookbackString(); - - $this->envir = $this->readLookbackString(); - $this->author = $this->readLookbackString(); - - $this->replay = $this->readInt32(); - - $this->nickname = $this->stripBOM($this->readString()); - - if ($version >= 6) { - $this->login = $this->readString(); - - if ($version >= 8) { - $this->moveGBXptr(1); // skip unknown byte - - $this->titleUid = $this->readLookbackString(); - } - } - } - } // getStringChunk - -} // class GBXReplayFetcher - - -/** - * @class GBXPackFetcher - * @brief The class that fetches all GBX pack info - */ -class GBXPackFetcher extends GBXBaseFetcher -{ - public $headerVersn, $flags, $infoMlUrl, $creatDate, $comment, $titleId, - $usageSubdir, $buildInfo, $authorUrl, $exeVer, $exeBld, $xmlDate; - - /** - * Read Windows FileTime and convert to Unix timestamp - * Filetime = 64-bit value with the number of 100-nsec intervals since Jan 1, 1601 (UTC) - * Based on http://www.mysqlperformanceblog.com/2007/03/27/integers-in-php-running-with-scissors-and-portability/ - * @return Unix timestamp, or -1 on error - */ - private function readFiletime() - { - // Unix epoch (1970-01-01) - Windows epoch (1601-01-01) in 100ns units - $EPOCHDIFF = '116444735995904000'; - $UINT32MAX = '4294967296'; - $USEC2SEC = 1000000; - - $lo = $this->readInt32(); - $hi = $this->readInt32(); - - // check for 64-bit platform - if (PHP_INT_SIZE >= 8) { - // use native math - if ($lo < 0) $lo += (1 << 32); - $date = ($hi << 32) + $lo; - $this->debugLog(sprintf('PAK CreationDate source: %016x', $date)); - if ($date == 0) return -1; - - // convert to Unix timestamp in usec - $stamp = ($date - (int)$EPOCHDIFF) / 10; - $this->debugLog(sprintf('PAK CreationDate 64-bit: %u.%06u', - $stamp / $USEC2SEC, $stamp % $USEC2SEC)); - return (int)($stamp / $USEC2SEC); - - // check for 32-bit platform - } elseif (PHP_INT_SIZE >= 4) { - $this->debugLog(sprintf('PAK CreationDate source: %08x%08x', $hi, $lo)); - if ($lo == 0 && $hi == 0) return -1; - - // workaround signed/unsigned braindamage on x32 - $lo = sprintf('%u', $lo); - $hi = sprintf('%u', $hi); - - // try and use GMP - if (function_exists('gmp_mul')) { - $date = gmp_add(gmp_mul($hi, $UINT32MAX), $lo); - // convert to Unix timestamp in usec - $stamp = gmp_div(gmp_sub($date, $EPOCHDIFF), 10); - $stamp = gmp_div_qr($stamp, $USEC2SEC); - $this->debugLog(sprintf('PAK CreationDate GNU MP: %u.%06u', - gmp_strval($stamp[0]), gmp_strval($stamp[1]))); - return (int)gmp_strval($stamp[0]); - } - - // try and use BC Math - if (function_exists('bcmul')) { - $date = bcadd(bcmul($hi, $UINT32MAX), $lo); - // convert to Unix timestamp in usec - $stamp = bcdiv(bcsub($date, $EPOCHDIFF), 10, 0); - $this->debugLog(sprintf('PAK CreationDate BCMath: %u.%06u', - bcdiv($stamp, $USEC2SEC), bcmod($stamp, $USEC2SEC))); - return (int)bcdiv($stamp, $USEC2SEC); - } - - // compute everything manually - $a = substr($hi, 0, -5); - $b = substr($hi, -5); - // hope that float precision is enough - $ac = $a * 42949; - $bd = $b * 67296; - $adbc = $a * 67296 + $b * 42949; - $r4 = substr($bd, -5) + substr($lo, -5); - $r3 = substr($bd, 0, -5) + substr($adbc, -5) + substr($lo, 0, -5); - $r2 = substr($adbc, 0, -5) + substr($ac, -5); - $r1 = substr($ac, 0, -5); - while ($r4 >= 100000) { $r4 -= 100000; $r3++; } - while ($r3 >= 100000) { $r3 -= 100000; $r2++; } - while ($r2 >= 100000) { $r2 -= 100000; $r1++; } - $date = ltrim(sprintf('%d%05d%05d%05d', $r1, $r2, $r3, $r4), '0'); - - // convert to Unix timestamp in usec - $r3 = substr($date, -6) - substr($EPOCHDIFF, -6); - $r2 = substr($date, -12, 6) - substr($EPOCHDIFF, -12, 6); - $r1 = substr($date, -18, 6) - substr($EPOCHDIFF, -18, 6); - if ($r3 < 0) { $r3 += 1000000; $r2--; } - if ($r2 < 0) { $r2 += 1000000; $r1--; } - $stamp = substr(sprintf('%d%06d%06d', $r1, $r2, $r3), 0, -1); - $this->debugLog(sprintf('PAK CreationDate manual: %s.%s', - substr($stamp, 0, -6), substr($stamp, -6))); - return (int)substr($stamp, 0, -6); - } else { - return -1; - } - } - - /** - * Instantiate GBX pack fetcher - * - * @param Boolean $parsexml - * If true, the fetcher also parses the XML block - * @param Boolean $debug - * If true, the fetcher prints debug logging to stderr - * @return GBXPackFetcher - * If GBX data couldn't be extracted, an Exception is thrown with - * the error message & code - */ - public function __construct($parsexml = false, $debug = false) - { - parent::__construct(); - - $this->headerVersn = 0; - $this->flags = 0; - $this->infoMlUrl = ''; - $this->creatDate = -1; - $this->comment = ''; - $this->titleId = ''; - $this->usageSubdir = ''; - $this->buildInfo = ''; - $this->authorUrl = ''; - $this->exeVer = ''; - $this->exeBld = ''; - $this->xmlDate = ''; - - $this->parseXml = (bool)$parsexml; - if ((bool)$debug) - $this->enableDebug(); - - $this->setError('GBX pack error: '); - } // __construct - - /** - * Process GBX pack file - * - * @param String $filename - * The pack filename - */ - public function processFile($filename) - { - $this->loadGBXdata((string)$filename); - - $this->processGBX(); - } // processFile - - /** - * Process GBX pack data - * - * @param String $gbxdata - * The pack data - */ - public function processData($gbxdata) - { - $this->storeGBXdata((string)$gbxdata); - - $this->processGBX(); - } // processData - - // process GBX data - private function processGBX() - { - // check magic header - $data = $this->readData(8); - if ($data != 'NadeoPak') - $this->errorOut('No magic NadeoPak header', 5); - - $this->headerVersn = $this->readInt32(); - if ($this->headerVersn < 6) - $this->errorOut(sprintf('Pack version %d not supported', $this->headerVersn), 24); - - $this->moveGBXptr(32); // skip ContentsChecksum - - $this->flags = $this->readInt32(); - - if ($this->headerVersn >= 7) { - $this->getAuthorFields(); - - if ($this->headerVersn < 9) { - $this->comment = $this->stripBOM($this->readString()); - - $this->moveGBXptr(16); // skip unused uint128 - - if ($this->headerVersn >= 8) { - $this->buildInfo = $this->readString(); - - $this->authorUrl = $this->readString(); - } - - } else { // >= 9 - $this->infoMlUrl = $this->readString(); - - $this->creatDate = $this->readFiletime(); - - $this->comment = $this->stripBOM($this->readString()); - - if ($this->headerVersn >= 12) { - $this->xml = $this->readString(); - $this->titleId = $this->readString(); - - if ($this->parseXml && $this->xml != '') { - $this->parseXMLstring(); - - if (isset($this->xmlParsed['HEADER']['EXEVER'])) - $this->exeVer = $this->xmlParsed['HEADER']['EXEVER']; - if (isset($this->xmlParsed['HEADER']['EXEBUILD'])) - $this->exeBld = $this->xmlParsed['HEADER']['EXEBUILD']; - if (isset($this->xmlParsed['IDENT']['CREATIONDATE'])) - $this->xmlDate = $this->xmlParsed['IDENT']['CREATIONDATE']; - } - } - - $this->usageSubdir = $this->readString(); - - $this->buildInfo = $this->readString(); - - $this->moveGBXptr(16); // skip unused uint128 - // if ($this->headerVersn >= 10) // skip encrypted IncludedPacks - } - } - - $this->clearGBXdata(); - } // processGBX - -} // class GBXPackFetcher -?> diff --git a/xaseco/includes/manialinks.inc.php b/xaseco/includes/manialinks.inc.php deleted file mode 100644 index dccfe8b..0000000 --- a/xaseco/includes/manialinks.inc.php +++ /dev/null @@ -1,1404 +0,0 @@ - block fields & records panel -global $ml_custom_ui, $ml_records; -$ml_custom_ui = array('global' => true, - 'notice' => true, - 'challenge_info' => true, - 'net_infos' => true, - 'chat' => true, - 'checkpoint_list' => true, - 'round_scores' => true, - 'scoretable' => true - ); -$ml_records = array('local' => ' --.--', 'dedi' => ' --.--', 'tmx' => ' --.--'); - -/** - * Displays a single ManiaLink window to a player - * - * $login : player login to send window to - * $header: string - * $icon : array( $style, $substyle {, $sizechg} ) - * $data : array( $line1=array($col1, $col2, ...), $line2=array(...) ) - * $widths: array( $overal, $col1, $col2, ...) - * $button: string - * - * A $line with one $col will occupy the full window width, - * otherwise all $line's must have the same number of columns, - * as should $widths (+1 for $overall). - * line height=".046" is required minimum to prevent alignment glitches - * due to large characters in some cells. - * If $colX is an array, it contains the string and the button's action id. - */ -function display_manialink($login, $header, $icon, $data, $widths, $button) { - global $aseco; - - $player = $aseco->server->players->getPlayer($login); - $style = $player->style; - - // check for old TMN-style window - if (empty($style)) { - - $tsp = 'B'; // 'F' = solid, '0' = invisible - $txt = '333' . $tsp; // dark grey - $bgd = 'FFF' . $tsp; // white - $spc = 'DDD' . $tsp; // light grey - - // build manialink header - $xml = '' . - '' . LF . - '' . LF; - - // add header - $xml .= ' $o' . htmlspecialchars(validateUTF8String($header)) . '' . LF; - - // add spacer - $xml .= '' . LF; - $xml .= '$' . LF; - - // add lines with optional columns - foreach ($data as $line) { - $xml .= ''; - if (!empty($line)) { - if (count($line) > 1) { - for ($i = 0; $i < count($widths)-1; $i++) { - if (is_array($line[$i])) { - $xml .= ' $o' . htmlspecialchars(validateUTF8String($line[$i][0])) . ''; - } else { - $xml .= ' $o' . htmlspecialchars(validateUTF8String($line[$i])) . ''; - } - } - } else { - $xml .= ' $o' . htmlspecialchars(validateUTF8String($line[0])) . ''; - } - } else { // spacer - $xml .= '$'; - } - $xml .= '' . LF; - } - - // add spacer, button (action "0" = close) & footer - $xml .= '$' . LF; - $xml .= '$o' . $button . ''; - - } else { // TMF-style window - $hsize = $style['HEADER'][0]['TEXTSIZE'][0]; - $bsize = $style['BODY'][0]['TEXTSIZE'][0]; - $lines = count($data); - - // build manialink header & window - $xml = '' . - '' . LF; - - // add header and optional icon - $xml .= '' . LF; - if (is_array($icon)) { - $isize = $hsize; - if (isset($icon[2])) - $isize += $icon[2]; - $xml .= '' . LF; - $xml .= ''; - $xml = str_replace('{#black}', $style['WINDOW'][0]['BLACKCOLOR'][0], $xml); - } - - //$aseco->console_text($xml); - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $aseco->formatColors($xml), 0, true)); -} // display_manialink - - -/** - * Displays custom TMX track ManiaLink window to a player - * - * $login : player login to send window to - * $header: string - * $icon : array( $style, $substyle {, $sizechg} ) - * $links : array( $image, $square, $page, $download ) - * $data : array( $line1=array($col1, $col2, ...), $line2=array(...) ) - * $widths: array( $overal, $col1, $col2, ...) - * $button: string - * - * A $line with one $col will occupy the full window width, - * otherwise all $line's must have the same number of columns, - * as should $widths (+1 for $overall). - * line height=".046" is required minimum to prevent alignment glitches - * due to large characters in some cells. - */ -function display_manialink_track($login, $header, $icon, $links, $data, $widths, $button) { - global $aseco; - - $player = $aseco->server->players->getPlayer($login); - $style = $player->style; - $square = $links[1]; - - // check for old TMN-style window - if (empty($style)) { - - $tsp = 'B'; // 'F' = solid, '0' = invisible - $txt = '333' . $tsp; // dark grey - $bgd = 'FFF' . $tsp; // white - $spc = 'DDD' . $tsp; // light grey - - // build manialink header - $xml = '' . - '' . LF . - '' . LF; - - // add header - $xml .= ' $o' . htmlspecialchars(validateUTF8String($header)) . '' . LF; - - // add spacers & image - $xml .= '' . LF; - $xml .= '$' . LF; - $xml .= '' . htmlspecialchars($links[0]) . '' . LF; - $xml .= '$' . LF; - - // add lines with optional columns - foreach ($data as $line) { - $xml .= ''; - if (!empty($line)) { - for ($i = 0; $i < count($widths)-1; $i++) { - $xml .= ' $o' . htmlspecialchars(validateUTF8String($line[$i])) . ''; - } - } else { // spacer - $xml .= '$'; - } - $xml .= '' . LF; - } - - // add spacer & links - $xml .= '$' . LF; - $xml .= '' . LF; - $xml .= '$o' . htmlspecialchars($links[2]) . '' . - '$o' . htmlspecialchars($links[3]) . '' . LF; - - // add spacer, button (action "0" = close) & footer - $xml .= '' . LF; - $xml .= '$' . LF; - $xml .= '$o' . $button . ''; - - } else { // TMF-style window - $hsize = $style['HEADER'][0]['TEXTSIZE'][0]; - $bsize = $style['BODY'][0]['TEXTSIZE'][0]; - $lines = count($data); - - // build manialink header & window - $xml = '' . - '' . LF; - - // add header - $xml .= '' . LF; - if (is_array($icon)) { - $isize = $hsize; - if (isset($icon[2])) - $isize += $icon[2]; - $xml .= '' . LF; - $xml .= ''; - $xml = str_replace('{#black}', $style['WINDOW'][0]['BLACKCOLOR'][0], $xml); - } - - //$aseco->console_text($xml); - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $aseco->formatColors($xml), 0, true)); -} // display_manialink_track - - -/** - * Displays a multipage ManiaLink window to a player - * - * $player: player object to send windows to - * ->msgs: array( array( $ptr, $header, $widths, $icon ), - * page1: array( $line1=array($col1, $col2, ...), $line2=array(...) ), - * 2: array( $line1=array($col1, $col2, ...), $line2=array(...) ), - * ... ) - * $header: string - * $widths: array( $overal, $col1, $col2, ...) - * $icon : array( $style, $substyle {, $sizechg} ) - * - * A $line with one $col will occupy the full window width, - * otherwise all $line's must have the same number of columns, - * as should $widths (+1 for $overall). - * line height=".046" is required minimum to prevent alignment glitches - * due to large characters in some cells. - * If $colX is an array, it contains the string and the button's action id. - */ -function display_manialink_multi($player) { - global $aseco; - - // fake current page event - event_manialink($aseco, array(0, $player->login, 1)); -} // display_manialink_multi - -// called @ onPlayerManialinkPageAnswer -// Handles all ManiaLink main system responses, -// as well as multi-page ManiaLink windows -// [0]=PlayerUid, [1]=Login, [2]=Answer -function event_manialink($aseco, $answer) { - global $donation_values; - - // leave actions outside -6 - 36 to other handlers - if ($answer[2] < -6 || $answer[2] > 36) - return; - - // get player - $login = $answer[1]; - $player = $aseco->server->players->getPlayer($login); - - // check player answer - switch ($answer[2]) { - case 0: - // close main pop-up window - mainwindow_off($aseco, $login); - return; - - // /stats fields - case -5: - // log clicked command - $aseco->console('player {1} clicked command "/active "', $player->login); - // /stats field Time Played - $command = array(); - $command['author'] = $player; - chat_active($aseco, $command); - return; - case -6: - // log clicked command - $aseco->console('player {1} clicked command "/top100 "', $player->login); - // /stats field Server Rank - $command = array(); - $command['author'] = $player; - chat_top100($aseco, $command); - return; - case 5: - // log clicked command - $aseco->console('player {1} clicked command "/toprecs "', $player->login); - // /stats field Records - $command = array(); - $command['author'] = $player; - chat_toprecs($aseco, $command); - return; - case 6: - // log clicked command - $aseco->console('player {1} clicked command "/topwins "', $player->login); - // /stats field Races Won - $command = array(); - $command['author'] = $player; - chat_topwins($aseco, $command); - return; - - // Records panel fields - case 7: - // log clicked command - $aseco->console('player {1} clicked command "/topsums "', $player->login); - // records panel PB field - $command = array(); - $command['author'] = $player; - chat_topsums($aseco, $command); - return; - case 8: - // log clicked command - $aseco->console('player {1} clicked command "/recs "', $player->login); - // records panel Local field - $command = array(); - $command['author'] = $player; - $command['params'] = ''; - chat_recs($aseco, $command); - return; - case 9: - // log clicked command - $aseco->console('player {1} clicked command "/dedirecs "', $player->login); - // records panel Dedi field - $command = array(); - $command['author'] = $player; - $command['params'] = ''; - if (function_exists('chat_dedirecs')) chat_dedirecs($aseco, $command); - return; - case 10: - // log clicked command - $aseco->console('player {1} clicked command "/tmxrecs "', $player->login); - // records panel TMX field - $command = array(); - $command['author'] = $player; - $command['params'] = ''; - if (function_exists('chat_tmxrecs')) chat_tmxrecs($aseco, $command); - return; - - // /list Env fields - case 11: - // close main window because /list can take a while - mainwindow_off($aseco, $login); - // log clicked command - $aseco->console('player {1} clicked command "/list env:Stadium"', $player->login); - // /list Env field Stadium - $command = array(); - $command['author'] = $player; - $command['params'] = 'env:Stadium'; - chat_list($aseco, $command); - return; - case 12: - // close main window because /list can take a while - mainwindow_off($aseco, $login); - // log clicked command - $aseco->console('player {1} clicked command "/list env:Alpine"', $player->login); - // /list Env field Alpine - $command = array(); - $command['author'] = $player; - $command['params'] = 'env:Alpine'; - chat_list($aseco, $command); - return; - case 13: - // close main window because /list can take a while - mainwindow_off($aseco, $login); - // log clicked command - $aseco->console('player {1} clicked command "/list env:Bay"', $player->login); - // /list Env field Bay - $command = array(); - $command['author'] = $player; - $command['params'] = 'env:Bay'; - chat_list($aseco, $command); - return; - case 14: - // close main window because /list can take a while - mainwindow_off($aseco, $login); - // log clicked command - $aseco->console('player {1} clicked command "/list env:Coast"', $player->login); - // /list Env field Coast - $command = array(); - $command['author'] = $player; - $command['params'] = 'env:Coast'; - chat_list($aseco, $command); - return; - case 15: - // close main window because /list can take a while - mainwindow_off($aseco, $login); - // log clicked command - $aseco->console('player {1} clicked command "/list env:Island"', $player->login); - // /list Env field Island - $command = array(); - $command['author'] = $player; - $command['params'] = 'env:Island'; - chat_list($aseco, $command); - return; - case 16: - // close main window because /list can take a while - mainwindow_off($aseco, $login); - // log clicked command - $aseco->console('player {1} clicked command "/list env:Rally"', $player->login); - // /list Env field Rally - $command = array(); - $command['author'] = $player; - $command['params'] = 'env:Rally'; - chat_list($aseco, $command); - return; - case 17: - // close main window because /list can take a while - mainwindow_off($aseco, $login); - // log clicked command - $aseco->console('player {1} clicked command "/list env:Speed"', $player->login); - // /list Env field Speed - $command = array(); - $command['author'] = $player; - $command['params'] = 'env:Speed'; - chat_list($aseco, $command); - return; - - // Vote panel buttons/keys - case 18: - // log clicked command - $aseco->console('player {1} clicked command "/y "', $player->login); - // /y on chat-based vote - $command = array(); - $command['author'] = $player; - chat_y($aseco, $command); - return; - case 19: - // log clicked command - $aseco->console('player {1} clicked command "/n " (ignored)', $player->login); - // /n on chat-based vote (ignored) - return; - - case 20: - // log clicked command - $aseco->console('player {1} clicked command "/admin clearjukebox"', $player->login); - // close main window - mainwindow_off($aseco, $login); - // /jukebox display Clear Jukebox button - $command = array(); - $command['author'] = $player; - $command['params'] = 'clearjukebox'; - chat_admin($aseco, $command); - return; - - // Admin panel buttons - case 21: - // log clicked command - $aseco->console('player {1} clicked command "/admin restartmap"', $player->login); - // admin panel ClipRewind button - $command = array(); - $command['author'] = $player; - $command['params'] = 'restartmap'; - chat_admin($aseco, $command); - return; - case 22: - // log clicked command - $aseco->console('player {1} clicked command "/admin endround"', $player->login); - // admin panel ClipPause button - $command = array(); - $command['author'] = $player; - $command['params'] = 'endround'; - chat_admin($aseco, $command); - return; - case 23: - // log clicked command - $aseco->console('player {1} clicked command "/admin nextmap"', $player->login); - // admin panel ClipPlay button - $command = array(); - $command['author'] = $player; - $command['params'] = 'nextmap'; - chat_admin($aseco, $command); - return; - case 24: - // log clicked command - $aseco->console('player {1} clicked command "/admin replaymap"', $player->login); - // admin panel Refresh button - $command = array(); - $command['author'] = $player; - $command['params'] = 'replaymap'; - chat_admin($aseco, $command); - return; - case 25: - // log clicked command - $aseco->console('player {1} clicked command "/admin pass"', $player->login); - // admin panel ArrowGreen button - $command = array(); - $command['author'] = $player; - $command['params'] = 'pass'; - chat_admin($aseco, $command); - return; - case 26: - // log clicked command - $aseco->console('player {1} clicked command "/admin cancel"', $player->login); - // admin panel ArrowRed button - $command = array(); - $command['author'] = $player; - $command['params'] = 'cancel'; - chat_admin($aseco, $command); - return; - case 27: - // log clicked command - $aseco->console('player {1} clicked command "/admin players live"', $player->login); - // admin panel Buddies button - $command = array(); - $command['author'] = $player; - $command['params'] = 'players live'; - chat_admin($aseco, $command); - return; - - // Payment dialog buttons - case 28: - // log clicked command - $aseco->console('player {1} confirmed command "/admin pay"', $player->login); - admin_pay($aseco, $player->login, true); - return; - case 29: - // log clicked command - $aseco->console('player {1} cancelled command "/admin pay"', $player->login); - admin_pay($aseco, $player->login, false); - return; - - // Donate panel buttons - case 30: - // log clicked command - $aseco->console('player {1} clicked command "/donate ' . $donation_values[0] . '"', $player->login); - // donate panel field 1 - $command = array(); - $command['author'] = $player; - $command['params'] = $donation_values[0]; - chat_donate($aseco, $command); - return; - case 31: - // log clicked command - $aseco->console('player {1} clicked command "/donate ' . $donation_values[1] . '"', $player->login); - // donate panel field 2 - $command = array(); - $command['author'] = $player; - $command['params'] = $donation_values[1]; - chat_donate($aseco, $command); - return; - case 32: - // log clicked command - $aseco->console('player {1} clicked command "/donate ' . $donation_values[2] . '"', $player->login); - // donate panel field 3 - $command = array(); - $command['author'] = $player; - $command['params'] = $donation_values[2]; - chat_donate($aseco, $command); - return; - case 33: - // log clicked command - $aseco->console('player {1} clicked command "/donate ' . $donation_values[3] . '"', $player->login); - // donate panel field 4 - $command = array(); - $command['author'] = $player; - $command['params'] = $donation_values[3]; - chat_donate($aseco, $command); - return; - case 34: - // log clicked command - $aseco->console('player {1} clicked command "/donate ' . $donation_values[4] . '"', $player->login); - // donate panel field 5 - $command = array(); - $command['author'] = $player; - $command['params'] = $donation_values[4]; - chat_donate($aseco, $command); - return; - case 35: - // log clicked command - $aseco->console('player {1} clicked command "/donate ' . $donation_values[5] . '"', $player->login); - // donate panel field 6 - $command = array(); - $command['author'] = $player; - $command['params'] = $donation_values[5]; - chat_donate($aseco, $command); - return; - case 36: - // log clicked command - $aseco->console('player {1} clicked command "/donate ' . $donation_values[6] . '"', $player->login); - // donate panel field 7 - $command = array(); - $command['author'] = $player; - $command['params'] = $donation_values[6]; - chat_donate($aseco, $command); - return; - } - - // Handle multi-page ManiaLink windows in all styles - // update page pointer - $tot = count($player->msgs) - 1; - switch ($answer[2]) { - case -4: $player->msgs[0][0] = 1; break; - case -3: $player->msgs[0][0] -= 5; break; - case -2: $player->msgs[0][0] -= 1; break; - case 1: break; // stay on current page - case 2: $player->msgs[0][0] += 1; break; - case 3: $player->msgs[0][0] += 5; break; - case 4: $player->msgs[0][0] = $tot; break; - } - - // stay within boundaries - if ($player->msgs[0][0] < 1) - $player->msgs[0][0] = 1; - elseif ($player->msgs[0][0] > $tot) - $player->msgs[0][0] = $tot; - - // get control variables - $ptr = $player->msgs[0][0]; - $header = $player->msgs[0][1]; - $widths = $player->msgs[0][2]; - $icon = $player->msgs[0][3]; - $style = $player->style; - - // check for old TMN-style window - if (empty($style)) { - - $tsp = 'B'; // 'F' = solid, '0' = invisible - $txt = '333' . $tsp; // dark grey - $bgd = 'FFF' . $tsp; // white - $spc = 'DDD' . $tsp; // light grey - - // build manialink header - $xml = '' . - '' . LF . - '' . LF; - - // add header - $xml .= ' $o' . htmlspecialchars(validateUTF8String($header)) . '' . - '$n(' . $ptr . '/' . $tot . ')' . LF; - - // add spacer - $xml .= '' . LF; - $xml .= '$' . LF; - - // add lines with optional columns - foreach ($player->msgs[$ptr] as $line) { - $xml .= ''; - if (!empty($line)) { - if (count($line) > 1) { - for ($i = 0; $i < count($widths)-1; $i++) { - if (is_array($line[$i])) { - $xml .= ' $o' . htmlspecialchars(validateUTF8String($line[$i][0])) . ''; - } else { - $xml .= ' $o' . htmlspecialchars(validateUTF8String($line[$i])) . ''; - } - } - } else { - $xml .= ' $o' . htmlspecialchars(validateUTF8String($line[0])) . ''; - } - } else { // spacer - $xml .= '$'; - } - $xml .= '' . LF; - } - - // add spacer - $xml .= '$' . LF; - - // add button(s) - $add5 = ($tot > 5); - $butw = ($widths[0] - ($add5 ? 0.22 : 0)) / 3; - $xml .= ''; - // check for preceding page(s), then Prev(5) button(s) - if ($ptr > 1) { - if ($add5) - $xml .= '$oPrev5'; - $xml .= '$oPrev'; - } else { - if ($add5) - $xml .= '$'; - $xml .= '$'; - } - // always a Close button - $xml .= '$oClose'; - // check for succeeding page(s), then Next(5) button(s) - if ($ptr < $tot) { - $xml .= '$oNext'; - if ($add5) - $xml .= '$oNext5'; - } else { - $xml .= '$'; - if ($add5) - $xml .= '$'; - } - $xml .= ''; - - } else { // TMF-style window - $hsize = $style['HEADER'][0]['TEXTSIZE'][0]; - $bsize = $style['BODY'][0]['TEXTSIZE'][0]; - $lines = count($player->msgs[$ptr]); - // fill up multipage windows - if ($tot > 1) - $lines = max($lines, count($player->msgs[1])); - - // build manialink header & window - $xml = '' . - '' . LF; - - // add header - $xml .= '' . LF; - if (is_array($icon)) { - $isize = $hsize; - if (isset($icon[2])) - $isize += $icon[2]; - $xml .= '' . LF; - $xml .= ''; - $xml = str_replace('{#black}', $style['WINDOW'][0]['BLACKCOLOR'][0], $xml); - } - - //$aseco->console_text($xml); - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($player->login, $aseco->formatColors($xml), 0, false)); -} // event_manialink - - -/** - * Displays a payment dialog - * - * $login : player login to send dialog to - * $server: server name for payment - * $label : payment label string - */ -function display_payment($aseco, $login, $server, $label) { - - // build manialink - $xml = '' . - '' . - ''; - - //$aseco->console_text($xml); - // disable dialog once clicked - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, true)); -} // display_payment - -/** - * Closes main window - * - * $login: player login to close window for - */ -function mainwindow_off($aseco, $login) { - - // close main window - $xml = ''; - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, false)); -} // mainwindow_off - -// called @ onEndRace -function allwindows_off($aseco, $data) { - - if ($aseco->server->getGame() == 'TMF') { - // disable all pop-up windows and records & donate panels - $xml = ''; - $aseco->client->query('SendDisplayManialinkPage', $xml, 0, false); - } -} // allwindows_off - - -/** - * Displays a CheckPoints panel - * - * $login: player login(s) to send panel to - * $cp : CP number - * $diff : color+sign+diff - */ -function display_cpspanel($aseco, $login, $cp, $diff) { - - // build manialink - $xml = '' . - '' . - ''; - - //$aseco->console_text($xml); - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, false)); -} // display_cpspanel - -/** - * Disables a CheckPoints panel - * - * $login: player login(s) to disable panel for - */ -function cpspanel_off($aseco, $login) { - - $xml = ''; - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, false)); -} // cpspanel_off - -/** - * Disables all CheckPoints panels - */ -function allcpspanels_off($aseco) { - - $xml = ''; - $aseco->client->query('SendDisplayManialinkPage', $xml, 0, false); -} // allcpspanels_off - - -/** - * Displays an Admin panel - * - * $player: player to send panel to - */ -function display_admpanel($aseco, $player) { - - // build manialink - $xml = $player->panels['admin']; - - //$aseco->console_text($xml); - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($player->login, $xml, 0, false)); -} // display_admpanel - -/** - * Disables an Admin panel - * - * $login: player login to disable panel for - */ -function admpanel_off($aseco, $login) { - - $xml = ''; - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, false)); -} // admpanel_off - - -/** - * Displays a Donate panel - * - * $player : player to send panel to - * $coppers: donation values - */ -function display_donpanel($aseco, $player, $coppers) { - - // build manialink - $xml = $player->panels['donate']; - for ($i = 1; $i <= 7; $i++) - $xml = str_replace('%COP' . $i . '%', $coppers[$i-1], $xml); - - //$aseco->console_text($xml); - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($player->login, $xml, 0, false)); -} // display_donpanel - -/** - * Disables a Donate panel - * - * $login: player login to disable panel for - */ -function donpanel_off($aseco, $login) { - - $xml = ''; - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, false)); -} // donpanel_off - - -/** - * Displays a Records panel - * - * $player: player to send panel to - * $pb : personal best - */ -function display_recpanel($aseco, $player, $pb) { - global $ml_records; - - // build manialink - $xml = str_replace(array('%PB%', '%TMX%', '%LCL%', '%DED%'), - array($pb, $ml_records['tmx'], $ml_records['local'], $ml_records['dedi']), - $player->panels['records']); - - //$aseco->console_text($xml); - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($player->login, $xml, 0, false)); -} // display_recpanel - -/** - * Disables a Records panel - * - * $login: player login to disable panel for - */ -function recpanel_off($aseco, $login) { - - $xml = ''; - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, false)); -} // recpanel_off - -function setRecordsPanel($field, $value) { - global $ml_records; - - $ml_records[$field] = $value; -} // setRecordsPanel - - -/** - * Displays a Vote panel - * - * $player : player to send panel to - * $yesstr : string for the Yes button - * $nostr : string for the No button - * $timeout: timeout for temporary panel (used only by /votepanel list) - */ -function display_votepanel($aseco, $player, $yesstr, $nostr, $timeout) { - - // build manialink - $xml = str_replace(array('%YES%', '%NO%'), - array($yesstr, $nostr), $player->panels['vote']); - - //$aseco->console_text($xml); - // disable panel once clicked - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($player->login, $xml, $timeout, true)); -} // display_votepanel - -/** - * Disables a Vote panel - * - * $login: player login to disable panel for - */ -function votepanel_off($aseco, $login) { - - $xml = ''; - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, false)); -} // votepanel_off - -/** - * Disables all Vote panels - */ -function allvotepanels_off($aseco) { - - $xml = ''; - $aseco->client->addCall('SendDisplayManialinkPage', array($xml, 0, false)); -} // allvotepanels_off - - -/** - * Displays the Message window - * - * $msgs : lines to be displayed - * $timeout: timeout for window in msec - */ -function display_msgwindow($aseco, $msgs, $timeout) { - - $cnt = count($msgs); - $xml = '' . LF . - '' . LF; - $pos = -1; - foreach ($msgs as $msg) { - $xml .= ''; - - //$aseco->console_text($xml); - $aseco->client->addCall('SendDisplayManialinkPage', array($xml, $timeout, false)); -} // display_msgwindow - -/** - * Displays the /msglog button - * - * $login: player login to display button for - */ -function display_msglogbutton($aseco, $login) { - - $xml = '' . LF . - '' . LF . - ''; - - //$aseco->console_text($xml); - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($login, $xml, 0, false)); -} // display_msglogbutton - - -/** - * Displays a Scoreboard Stats panel - * - * $player : player to send panel to - * $rank : server rank - * $avg : record average - * $recs : records total - * $wins : wins total - * $play : session play time - * $dons : donations total - */ -function display_statspanel($aseco, $player, $rank, $avg, $recs, $wins, $play, $dons) { - - // build manialink - $xml = str_replace(array('%RANK%', '%AVG%', '%RECS%', '%WINS%', '%PLAY%', '%DONS%'), - array($rank, $avg, $recs, $wins, $play, $dons), $aseco->statspanel); - - //$aseco->console_text($xml); - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($player->login, $xml, 0, false)); -} // display_votepanel - -/** - * Disables all Scoreboard Stats panels - * - * called @ onNewChallenge - */ -function statspanels_off($aseco, $data) { - - $xml = ''; - $aseco->client->addCall('SendDisplayManialinkPage', array($xml, 0, false)); -} // statspanels_off - - -// called @ onNewChallenge -// Disables Automatic Scorepanel at start of track if $auto_scorepanel = off -function scorepanel_off($aseco, $data) { - global $auto_scorepanel; - - if ($aseco->server->getGame() == 'TMF' && !$auto_scorepanel) { - setCustomUIField('scoretable', false); - // dummy ManiaLink to preserve custom_ui - $xml = '' . - getCustomUIBlock() . ''; - $aseco->client->addCall('SendDisplayManialinkPage', array($xml, 0, false)); - } -} // scorepanel_off - -// called @ onEndRace -// Enables Automatic Scorepanel at end of track -function scorepanel_on($aseco, $data) { - - if ($aseco->server->getGame() == 'TMF') { - setCustomUIField('scoretable', true); - // dummy ManiaLink to preserve custom_ui - $xml = '' . - getCustomUIBlock() . ''; - $aseco->client->addCall('SendDisplayManialinkPage', array($xml, 0, false)); - } -} // scorepanel_on - -// called @ onBeginRound -// Disables Rounds Finishpanel at start of round if $rounds_finishpanel = off -function roundspanel_off($aseco) { - global $auto_scorepanel, $rounds_finishpanel; - - // check for Rounds/Team/Cup modes - if ($aseco->server->gameinfo->mode == Gameinfo::RNDS || - $aseco->server->gameinfo->mode == Gameinfo::TEAM || - $aseco->server->gameinfo->mode == Gameinfo::CUP) { - // check whether to disable panel - if ($aseco->server->getGame() == 'TMF' && !$rounds_finishpanel) { - setCustomUIField('round_scores', false); - // dummy ManiaLink to preserve custom_ui - $xml = '' . - getCustomUIBlock() . ''; - $aseco->client->addCall('SendDisplayManialinkPage', array($xml, 0, false)); - } - } -} // roundspanel_off - -// called @ onPlayerFinish -// Enables Rounds Finishpanel at player finish -function roundspanel_on($aseco, $finish_item) { - global $auto_scorepanel, $rounds_finishpanel; - - // check for Rounds/Team/Cup modes - if ($aseco->server->gameinfo->mode == Gameinfo::RNDS || - $aseco->server->gameinfo->mode == Gameinfo::TEAM || - $aseco->server->gameinfo->mode == Gameinfo::CUP) { - // check whether panel was disabled - if ($aseco->server->getGame() == 'TMF' && !$rounds_finishpanel) { - setCustomUIField('round_scores', true); - // dummy ManiaLink to preserve custom_ui - $xml = '' . - getCustomUIBlock() . ''; - $aseco->client->addCall('SendDisplayManialinkPageToLogin', array($finish_item->player->login, $xml, 0, false)); - } - } -} // roundspanel_on - -function setCustomUIField($field, $value) { - global $ml_custom_ui; - - $ml_custom_ui[$field] = $value; -} // setCustomUIField - -function getCustomUIBlock() { - global $ml_custom_ui; - - return '' . - '' . - '' . - '' . - '' . - '' . - '' . - '' . - '' . - ''; -} // getCustomUIBlock -?> diff --git a/xaseco/includes/ogg_comments.inc.php b/xaseco/includes/ogg_comments.inc.php deleted file mode 100644 index ff43535..0000000 --- a/xaseco/includes/ogg_comments.inc.php +++ /dev/null @@ -1,154 +0,0 @@ - - * Derived from Ogg.class.php v1.3e by Nicolas Ricquemaque - * For more info see: http://opensource.grisambre.net/ogg/ - * Ogg format: http://en.wikipedia.org/wiki/Ogg - * Comments : http://www.xiph.org/vorbis/doc/v-comment.html - * - * v1.1: Improved get_1block URL parsing; added User-Agent to the GET request - * v1.0: Initial release - */ - -define('BLOCKSIZE', 512); - -class Ogg_Comments { - - public $comments = array(); - - /** - * Fetches all comments from a .ogg local file or URL - * - * @param String $path - * The .ogg file or URL - * @param Boolean $utf8 - * If true, return fields in UTF-8, otherwise in ASCII/ISO-8859-1 - * @return Ogg_Comments - * If $comments is empty, no .ogg file - */ - public function Ogg_Comments($path, $utf8 = false) { - - // check for local file or URL - if (strpos($path, '://') === false) { - if (!$fp = @fopen($path, 'rb')) - return false; - $file = fread($fp, BLOCKSIZE); - fclose($fp); - if ($file === false) - return false; - } else { - $file = $this->get_1block($path); - if ($file === false || $file == -1) - return false; - } - - // read OGG pages - for ($pos = 0; ($pos = strpos($file, 'OggS', $pos)) !== false; $pos++) { - // check stream version - if (ord($file[$pos+4]) != 0) - continue; - - // compute offset of packet after header - $offset = $pos + 27 + ord($file[$pos+26]); - // check for second (== comments) packet - if ($this->read_intle($file, $pos+18) == 1) { - // check for vorbis comments - if (ord($file[$offset]) != 0x03 || - substr($file, $offset+1, 6) != 'vorbis') - continue; - - // read vendor string - $offset += 7; - $vndlen = $this->read_intle($file, $offset); - $this->comments['VENDOR'] = $this->decode_field(substr($file, $offset+4, $vndlen), $utf8); - // read comments count - $offset += 4 + $vndlen; - $cmtcnt = $this->read_intle($file, $offset); - - // read/parse all comment fields - $offset += 4; - for ($i = 0; $i < $cmtcnt; $i++) { - $cmtlen = $this->read_intle($file, $offset); - $comment = substr($file, $offset+4, $cmtlen); - $offset += 4 + $cmtlen; - // store field name=value pair - $comment = explode('=', $comment, 2); - // check for repeated field & append - if (isset($this->comments[strtoupper($comment[0])])) - $this->comments[strtoupper($comment[0])] .= ', ' . $this->decode_field($comment[1], $utf8); - else - $this->comments[strtoupper($comment[0])] = $this->decode_field($comment[1], $utf8); - } - } - // check whether done - if (isset($this->comments['VENDOR'])) - break; - } - } // Ogg_Comments - - // Read 32-bits Little Endian integer - private function read_intle(&$buf, $pos) { - - return (ord($buf[$pos+0]) + (ord($buf[$pos+1]) << 8) + - (ord($buf[$pos+2]) << 16) + (ord($buf[$pos+3]) << 24)); - } // read_intle - - // Decode comment field into specified charset - private function decode_field($str, $utf8) { - - if ($utf8) - // return UTF8 or encode it in UTF8 - return ((utf8_encode(utf8_decode($str)) == $str) ? - $str : utf8_encode($str)); - else - // decode it to ASCII or return ASCII - return ((utf8_encode(utf8_decode($str)) == $str) ? - utf8_decode($str) : $str); - } // decode_field - - // Simple HTTP Get 1 Block function with timeout - // ok: return string || error: return false || timeout: return -1 - private function get_1block($url) { - - $url = parse_url($url); - $port = isset($url['port']) ? $url['port'] : 80; - $query = isset($url['query']) ? '?' . $url['query'] : ''; - - $fp = @fsockopen($url['host'], $port, $errno, $errstr, 4); - if (!$fp) - return false; - - $uri = ''; - foreach (explode('/', $url['path']) as $subpath) - $uri .= rawurlencode($subpath) . '/'; - $uri = substr($uri, 0, strlen($uri)-1); // strip trailing '/' - - fwrite($fp, 'GET ' . $uri . $query . " HTTP/1.0\r\n" . - 'Host: ' . $url['host'] . "\r\n" . - 'User-Agent: Ogg_Comments (' . PHP_OS . ")\r\n\r\n"); - stream_set_timeout($fp, 2); - $res = ''; - $info['timed_out'] = false; - for ($i = 0; $i < 2; $i++) - if (feof($fp) || $info['timed_out']) { - break; - } else { - $res .= fread($fp, BLOCKSIZE); - $info = stream_get_meta_data($fp); - } - fclose($fp); - - if ($info['timed_out']) { - return -1; - } else { - if (substr($res, 9, 3) != '200') - return false; - $page = explode("\r\n\r\n", $res, 2); - return trim($page[1]); - } - } // get_1block -} // class Ogg_Comments -?> diff --git a/xaseco/includes/rasp.funcs.php b/xaseco/includes/rasp.funcs.php deleted file mode 100644 index 9e974a8..0000000 --- a/xaseco/includes/rasp.funcs.php +++ /dev/null @@ -1,1949 +0,0 @@ -debug) - $aseco->console_text('challenges cache cleared'); - } -} // clearChallengesCache - -// called @ onTracklistChanged & !TMF -function clearChallengesCache2($aseco, $event) { - global $challengeListCache; - - // clear cache on add/remove/read/juke/unjuke events if not TMF - if ($aseco->server->getGame() != 'TMF' && - $event[0] != 'rename' && $event[0] != 'write') { - $challengeListCache = array(); - if ($aseco->debug) - $aseco->console_text('challenges cache cleared upon: ' . $event[0]); - } -} // clearChallengesCache2 - -// called @ onNewChallenge2 -function initChallengesCache($aseco, $challenge) { - global $challengeListCache, $reset_cache_start; - - if ($reset_cache_start) { - $challengeListCache = array(); - if ($aseco->debug) - $aseco->console_text('challenges cache reset'); - } - getChallengesCache($aseco); - if ($aseco->debug) - $aseco->console_text('challenges cache inited: ' . count($challengeListCache)); -} // initChallengesCache - -function getChallengesCache($aseco) { - global $challengeListCache; - - if (empty($challengeListCache)) { - if ($aseco->debug) - $aseco->console_text('challenges cache loading...'); - // get new list of all tracks - $aseco->client->resetError(); - $newlist = array(); - $done = false; - $size = 300; - $i = 0; - while (!$done) { - $aseco->client->query('GetChallengeList', $size, $i); - $tracks = $aseco->client->getResponse(); - if (!empty($tracks)) { - if ($aseco->client->isError()) { - // warning if no tracks found - if (empty($newlist)) - trigger_error('[' . $aseco->client->getErrorCode() . '] GetChallengeList - ' . $aseco->client->getErrorMessage() . ' - No tracks found!', E_USER_WARNING); - $done = true; - break; - } - foreach ($tracks as $trow) { - // obtain various author fields too - $trackinfo = getChallengeData($aseco->server->trackdir . $trow['FileName'], false); - if ($trackinfo['name'] != 'file not found') { - if ($aseco->server->getGame() != 'TMF') - $trow['Author'] = $trackinfo['author']; - $trow['AuthorTime'] = $trackinfo['authortime']; - $trow['AuthorScore'] = $trackinfo['authorscore']; - } - $trow['Name'] = stripNewlines($trow['Name']); - $newlist[$trow['UId']] = $trow; - } - if (count($tracks) < $size) { - // got less than 300 tracks, might as well leave - $done = true; - } else { - $i += $size; - } - } else { - $done = true; - } - } - - $challengeListCache = $newlist; - if ($aseco->debug) - $aseco->console_text('challenges cache loaded: ' . count($challengeListCache)); - } - - return $challengeListCache; -} // getChallengesCache - - -// calls function get_recs() from chat.records2.php -function getAllChallenges($player, $wildcard, $env) { - global $aseco, $jb_buffer, $maxrecs; - - $player->tracklist = array(); - - // get list of ranked records - $reclist = get_recs($player->id); - // get new/cached list of tracks - $newlist = getChallengesCache($aseco); - - if ($aseco->server->getGame() == 'TMN') { - $head = 'Tracks On This Server:' . LF . 'Id Rec Name' . LF; - $msg = ''; - $tid = 1; - $lines = 0; - $player->msgs = array(); - $player->msgs[0] = 1; - - foreach ($newlist as $row) { - // check for wildcard, track name or author name - if ($wildcard == '*') { - $pos = 0; - } else { - $pos = stripos(stripColors($row['Name']), $wildcard); - if ($pos === false) { - $pos = stripos($row['Author'], $wildcard); - } - } - // check for any match - if ($pos !== false) { - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else - $trackname = '{#black}' . $trackname; - - // get corresponding record - $pos = isset($reclist[$row['UId']]) ? $reclist[$row['UId']] : 0; - $pos = ($pos >= 1 && $pos <= $maxrecs) ? str_pad($pos, 2, '0', STR_PAD_LEFT) : ' -- '; - - $msg .= '$z' . str_pad($tid, 3, '0', STR_PAD_LEFT) . '. ' . $pos . '. ' - . $trackname . LF; - $tid++; - if (++$lines > 9) { - $player->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - } - // add if last batch exists - if ($msg != '') - $player->msgs[] = $aseco->formatColors($head . $msg); - - } elseif ($aseco->server->getGame() == 'TMF') { - $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17); - $head = 'Tracks On This Server:'; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Rec', 'Name', 'Author', 'Env'); - else - $msg[] = array('Id', 'Rec', 'Name', 'Author'); - $tid = 1; - $lines = 0; - $player->msgs = array(); - // reserve extra width for $w tags - $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0); - if ($aseco->server->packmask != 'Stadium') - $player->msgs[0] = array(1, $head, array(1.39+$extra, 0.12, 0.1, 0.6+$extra, 0.4, 0.17), array('Icons128x128_1', 'NewTrack', 0.02)); - else - $player->msgs[0] = array(1, $head, array(1.22+$extra, 0.12, 0.1, 0.6+$extra, 0.4), array('Icons128x128_1', 'NewTrack', 0.02)); - - foreach ($newlist as $row) { - // check for wildcard, track name or author name - if ($wildcard == '*') { - $pos = 0; - } else { - $pos = stripos(stripColors($row['Name']), $wildcard); - if ($pos === false) { - $pos = stripos($row['Author'], $wildcard); - } - } - // check for environment - if ($env == '*') { - $pose = 0; - } else { - $pose = stripos($row['Environnement'], $env); - } - // check for any match - if ($pos !== false && $pose !== false) { - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['author'] = $row['Author']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else { - $trackname = '{#black}' . $trackname; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackname = array($trackname, $tid+100); // action id - } - // format author name - $trackauthor = $row['Author']; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackauthor = array($trackauthor, -100-$tid); // action id - // format env name - $trackenv = $row['Environnement']; - // add clickable button - if ($aseco->settings['clickable_lists']) - $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id - - // get corresponding record - $pos = isset($reclist[$row['UId']]) ? $reclist[$row['UId']] : 0; - $pos = ($pos >= 1 && $pos <= $maxrecs) ? str_pad($pos, 2, '0', STR_PAD_LEFT) : '-- '; - - if ($aseco->server->packmask != 'Stadium') - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $pos . '.', $trackname, $trackauthor, $trackenv); - else - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $pos . '.', $trackname, $trackauthor); - $tid++; - if (++$lines > 14) { - $player->msgs[] = $msg; - $lines = 0; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Rec', 'Name', 'Author', 'Env'); - else - $msg[] = array('Id', 'Rec', 'Name', 'Author'); - } - } - } - // add if last batch exists - if (count($msg) > 1) - $player->msgs[] = $msg; - } -} // getAllChallenges - -function getChallengesByKarma($player, $karmaval) { - global $aseco, $jb_buffer; - - $player->tracklist = array(); - - // get list of karma values for all matching tracks - $order = ($karmaval <= 0 ? 'ASC' : 'DESC'); - if ($karmaval == 0) { - $sql = '(SELECT uid, SUM(score) AS karma FROM challenges, rs_karma - WHERE challenges.id=rs_karma.challengeid - GROUP BY uid HAVING karma = 0) - UNION - (SELECT uid, 0 FROM challenges WHERE id NOT IN - (SELECT DISTINCT challengeid FROM rs_karma)) - ORDER BY karma ' . $order; - } else { - $sql = 'SELECT uid, SUM(score) AS karma FROM challenges, rs_karma - WHERE challenges.id=rs_karma.challengeid - GROUP BY uid - HAVING karma ' . ($karmaval < 0 ? "<= $karmaval" : ">= $karmaval") . ' - ORDER BY karma ' . $order; - } - $result = mysql_query($sql); - if (mysql_num_rows($result) == 0) { - mysql_free_result($result); - return; - } - - // get new/cached list of tracks - $newlist = getChallengesCache($aseco); - - if ($aseco->server->getGame() == 'TMN') { - $head = 'Tracks by Karma (' . $order . '):' . LF . 'Id Karma Name' . LF; - $msg = ''; - $tid = 1; - $lines = 0; - $player->msgs = array(); - $player->msgs[0] = 1; - - while ($dbrow = mysql_fetch_array($result)) { - // does the uid exist in the current server track list? - if (array_key_exists($dbrow[0], $newlist)) { - $row = $newlist[$dbrow[0]]; - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else - $trackname = '{#black}' . $trackname; - - $msg .= '$z' . str_pad($tid, 3, '0', STR_PAD_LEFT) . '. ' - . str_pad($dbrow[1], 4, ' ', STR_PAD_LEFT) . ' ' - . $trackname . LF; - $tid++; - if (++$lines > 9) { - $player->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - } - // add if last batch exists - if ($msg != '') - $player->msgs[] = $aseco->formatColors($head . $msg); - - } elseif ($aseco->server->getGame() == 'TMF') { - $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17); - $head = 'Tracks by Karma (' . $order . '):'; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Karma', 'Name', 'Author', 'Env'); - else - $msg[] = array('Id', 'Karma', 'Name', 'Author'); - $tid = 1; - $lines = 0; - $player->msgs = array(); - // reserve extra width for $w tags - $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0); - if ($aseco->server->packmask != 'Stadium') - $player->msgs[0] = array(1, $head, array(1.44+$extra, 0.12, 0.15, 0.6+$extra, 0.4, 0.17), array('Icons128x128_1', 'NewTrack', 0.02)); - else - $player->msgs[0] = array(1, $head, array(1.27+$extra, 0.12, 0.15, 0.6+$extra, 0.4), array('Icons128x128_1', 'NewTrack', 0.02)); - - while ($dbrow = mysql_fetch_array($result)) { - // does the uid exist in the current server track list? - if (array_key_exists($dbrow[0], $newlist)) { - $row = $newlist[$dbrow[0]]; - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['author'] = $row['Author']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else { - $trackname = '{#black}' . $trackname; - } - // format author name - $trackauthor = $row['Author']; - // format karma - $trackkarma = str_pad($dbrow[1], 4, ' ', STR_PAD_LEFT); - // format env name - $trackenv = $row['Environnement']; - // add clickable button - if ($aseco->settings['clickable_lists']) - $trackenv = array($trackenv, $envids[$row['Environnement']]); - - // add clickable buttons - if ($aseco->settings['clickable_lists'] && $tid <= 1900) { - $trackname = array($trackname, $tid+100); // action ids - $trackauthor = array($trackauthor, -100-$tid); - $trackkarma = array($trackkarma, -6000-$tid); - } - - if ($aseco->server->packmask != 'Stadium') - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $trackkarma, $trackname, $trackauthor, $trackenv); - else - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $trackkarma, $trackname, $trackauthor); - $tid++; - if (++$lines > 14) { - $player->msgs[] = $msg; - $lines = 0; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Karma', 'Name', 'Author', 'Env'); - else - $msg[] = array('Id', 'Karma', 'Name', 'Author'); - } - } - } - // add if last batch exists - if (count($msg) > 1) - $player->msgs[] = $msg; - } - - mysql_free_result($result); -} // getChallengesByKarma - -function getChallengesNoFinish($player) { - global $aseco, $jb_buffer; - - $player->tracklist = array(); - - // get list of finished tracks - $sql = 'SELECT DISTINCT challengeID FROM rs_times - WHERE playerID=' . $player->id . ' ORDER BY challengeID'; - $result = mysql_query($sql); - $finished = array(); - if (mysql_num_rows($result) > 0) { - while ($dbrow = mysql_fetch_array($result)) - $finished[] = $dbrow[0]; - } - mysql_free_result($result); - - // get list of unfinished tracks - // simpler but less efficient query: - // $sql = 'SELECT uid FROM challenges WHERE id NOT IN - // (SELECT DISTINCT challengeID FROM rs_times, players - // WHERE rs_times.playerID=players.id AND players.login=' . quotedString($player->login) . ')'; - $sql = 'SELECT uid FROM challenges'; - if (!empty($finished)) - $sql .= ' WHERE id NOT IN (' . implode(',', $finished) . ')'; - $result = mysql_query($sql); - if (mysql_num_rows($result) == 0) { - mysql_free_result($result); - return; - } - - // get new/cached list of tracks - $newlist = getChallengesCache($aseco); - - if ($aseco->server->getGame() == 'TMN') { - $head = 'Tracks You Haven\'t Finished:' . LF . 'Id Name' . LF; - $msg = ''; - $tid = 1; - $lines = 0; - $player->msgs = array(); - $player->msgs[0] = 1; - - while ($dbrow = mysql_fetch_array($result)) { - // does the uid exist in the current server track list? - if (array_key_exists($dbrow[0], $newlist)) { - $row = $newlist[$dbrow[0]]; - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else - $trackname = '{#black}' . $trackname; - - $msg .= '$z' . str_pad($tid, 3, '0', STR_PAD_LEFT) . '. ' - . $trackname . LF; - $tid++; - if (++$lines > 9) { - $player->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - } - // add if last batch exists - if ($msg != '') - $player->msgs[] = $aseco->formatColors($head . $msg); - - } elseif ($aseco->server->getGame() == 'TMF') { - $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17); - $head = 'Tracks You Haven\'t Finished:'; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Name', 'Author', 'Env'); - else - $msg[] = array('Id', 'Name', 'Author'); - $tid = 1; - $lines = 0; - $player->msgs = array(); - // reserve extra width for $w tags - $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0); - if ($aseco->server->packmask != 'Stadium') - $player->msgs[0] = array(1, $head, array(1.29+$extra, 0.12, 0.6+$extra, 0.4, 0.17), array('Icons128x128_1', 'NewTrack', 0.02)); - else - $player->msgs[0] = array(1, $head, array(1.12+$extra, 0.12, 0.6+$extra, 0.4), array('Icons128x128_1', 'NewTrack', 0.02)); - - while ($dbrow = mysql_fetch_array($result)) { - // does the uid exist in the current server track list? - if (array_key_exists($dbrow[0], $newlist)) { - $row = $newlist[$dbrow[0]]; - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['author'] = $row['Author']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else { - $trackname = '{#black}' . $trackname; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackname = array($trackname, $tid+100); // action id - } - // format author name - $trackauthor = $row['Author']; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackauthor = array($trackauthor, -100-$tid); // action id - // format env name - $trackenv = $row['Environnement']; - // add clickable button - if ($aseco->settings['clickable_lists']) - $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id - - if ($aseco->server->packmask != 'Stadium') - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $trackname, $trackauthor, $trackenv); - else - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $trackname, $trackauthor); - $tid++; - if (++$lines > 14) { - $player->msgs[] = $msg; - $lines = 0; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Name', 'Author', 'Env'); - else - $msg[] = array('Id', 'Name', 'Author'); - } - } - } - // add if last batch exists - if (count($msg) > 1) - $player->msgs[] = $msg; - } - - mysql_free_result($result); -} // getChallengesNoFinish - -function getChallengesNoRank($player) { - global $aseco, $jb_buffer, $maxrecs; - - $player->tracklist = array(); - - // get list of finished tracks - $sql = 'SELECT DISTINCT challengeID FROM rs_times - WHERE playerID=' . $player->id . ' ORDER BY challengeID'; - $result = mysql_query($sql); - $finished = array(); - if (mysql_num_rows($result) > 0) { - while ($dbrow = mysql_fetch_array($result)) - $finished[] = $dbrow[0]; - } - mysql_free_result($result); - - // get list of finished tracks - // simpler but less efficient query: - // $sql = 'SELECT id,uid FROM challenges WHERE id IN - // (SELECT DISTINCT challengeID FROM rs_times, players - // WHERE rs_times.playerID=players.id AND players.login=' . quotedString($player->login) . ')'; - $sql = 'SELECT id,uid FROM challenges WHERE id '; - if (!empty($finished)) - $sql .= 'IN (' . implode(',', $finished) . ')'; - else - $sql .= '= 0'; // empty list - $result = mysql_query($sql); - if (mysql_num_rows($result) == 0) { - mysql_free_result($result); - return; - } - - $order = ($aseco->server->gameinfo->mode == Gameinfo::STNT ? 'DESC' : 'ASC'); - $unranked = array(); - $i = 0; - // check if player not in top $maxrecs on each track - while ($dbrow = mysql_fetch_array($result)) { - // more efficient but unsupported query: :( - // $sql2 = 'SELECT id FROM players WHERE (id=' . $player->id . ') AND (id NOT IN - // (SELECT playerid FROM records WHERE challengeid=' . $dbrow[0] . ' ORDER by score, date LIMIT ' . $maxrecs . '))'; - $sql2 = 'SELECT playerid FROM records - WHERE challengeid=' . $dbrow[0] . ' - ORDER by score ' . $order . ', date ASC LIMIT ' . $maxrecs; - $result2 = mysql_query($sql2); - $found = false; - if (mysql_num_rows($result2) > 0) { - while ($plrow = mysql_fetch_array($result2)) { - if ($player->id == $plrow[0]) { - $found = true; - break; - } - } - } - if (!$found) { - $unranked[$i++] = $dbrow[1]; - } - mysql_free_result($result2); - } - if (empty($unranked)) { - mysql_free_result($result); - return; - } - - // get new/cached list of tracks - $newlist = getChallengesCache($aseco); - - if ($aseco->server->getGame() == 'TMN') { - $head = 'Tracks You Have No Rank On:' . LF . 'Id Name' . LF; - $msg = ''; - $tid = 1; - $lines = 0; - $player->msgs = array(); - $player->msgs[0] = 1; - - for ($i = 0; $i < count($unranked); $i++) { - // does the uid exist in the current server track list? - if (array_key_exists($unranked[$i], $newlist)) { - $row = $newlist[$unranked[$i]]; - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else - $trackname = '{#black}' . $trackname; - - $msg .= '$z' . str_pad($tid, 3, '0', STR_PAD_LEFT) . '. ' - . $trackname . LF; - $tid++; - if (++$lines > 9) { - $player->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - } - // add if last batch exists - if ($msg != '') - $player->msgs[] = $aseco->formatColors($head . $msg); - - } elseif ($aseco->server->getGame() == 'TMF') { - $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17); - $head = 'Tracks You Have No Rank On:'; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Name', 'Author', 'Env'); - else - $msg[] = array('Id', 'Name', 'Author'); - $tid = 1; - $lines = 0; - $player->msgs = array(); - // reserve extra width for $w tags - $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0); - if ($aseco->server->packmask != 'Stadium') - $player->msgs[0] = array(1, $head, array(1.29+$extra, 0.12, 0.6+$extra, 0.4, 0.17), array('Icons128x128_1', 'NewTrack', 0.02)); - else - $player->msgs[0] = array(1, $head, array(1.12+$extra, 0.12, 0.6+$extra, 0.4), array('Icons128x128_1', 'NewTrack', 0.02)); - - for ($i = 0; $i < count($unranked); $i++) { - // does the uid exist in the current server track list? - if (array_key_exists($unranked[$i], $newlist)) { - $row = $newlist[$unranked[$i]]; - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['author'] = $row['Author']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else { - $trackname = '{#black}' . $trackname; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackname = array($trackname, $tid+100); // action id - } - // format author name - $trackauthor = $row['Author']; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackauthor = array($trackauthor, -100-$tid); // action id - // format env name - $trackenv = $row['Environnement']; - // add clickable button - if ($aseco->settings['clickable_lists']) - $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id - - if ($aseco->server->packmask != 'Stadium') - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $trackname, $trackauthor, $trackenv); - else - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $trackname, $trackauthor); - $tid++; - if (++$lines > 9) { - $player->msgs[] = $msg; - $lines = 0; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Name', 'Author', 'Env'); - else - $msg[] = array('Id', 'Name', 'Author'); - } - } - } - // add if last batch exists - if (count($msg)) - $player->msgs[] = $msg; - } - - mysql_free_result($result); -} // getChallengesNoRank - -function getChallengesNoGold($player) { - global $aseco, $jb_buffer; - - $player->tracklist = array(); - - // check for Stunts mode - if ($aseco->server->gameinfo->mode != Gameinfo::STNT) { - - // get list of finished tracks with their best (minimum) times - $sql = 'SELECT DISTINCT c.uid,t1.score FROM rs_times t1, challenges c - WHERE (playerID=' . $player->id . ' AND t1.challengeID=c.id AND - score=(SELECT MIN(t2.score) FROM rs_times t2 - WHERE playerID=' . $player->id . ' AND t1.challengeID=t2.challengeID))'; - $result = mysql_query($sql); - if (mysql_num_rows($result) == 0) { - mysql_free_result($result); - return; - } - - // get new/cached list of tracks - $newlist = getChallengesCache($aseco); - - if ($aseco->server->getGame() == 'TMN') { - $head = 'Tracks You Didn\'t Beat Gold Time On:' . LF . 'Id Name $n(+Time)$m' . LF; - $msg = ''; - $tid = 1; - $lines = 0; - $player->msgs = array(); - $player->msgs[0] = 1; - - while ($dbrow = mysql_fetch_array($result)) { - // does the uid exist in the current server track list? - if (array_key_exists($dbrow[0], $newlist)) { - $row = $newlist[$dbrow[0]]; - // does best time beat track's Gold time? - if ($dbrow[1] > $row['GoldTime']) { - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else - $trackname = '{#black}' . $trackname; - - // compute difference to Gold time - $diff = $dbrow[1] - $row['GoldTime']; - $sec = floor($diff/1000); - $hun = ($diff - ($sec * 1000)) / 10; - - $msg .= str_pad($tid, 3, '0', STR_PAD_LEFT) . '. ' . $trackname - . ' $z$n(+' . sprintf("%d.%02d", $sec, $hun) . ')$m' . LF; - $tid++; - if (++$lines > 9) { - $player->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - } - } - // add if last batch exists - if ($msg != '') - $player->msgs[] = $aseco->formatColors($head . $msg); - - } elseif ($aseco->server->getGame() == 'TMF') { - $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17); - $head = 'Tracks You Didn\'t Beat Gold Time On:'; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Name', 'Author', 'Env', 'Time'); - else - $msg[] = array('Id', 'Name', 'Author', 'Time'); - $tid = 1; - $lines = 0; - $player->msgs = array(); - // reserve extra width for $w tags - $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0); - if ($aseco->server->packmask != 'Stadium') - $player->msgs[0] = array(1, $head, array(1.42+$extra, 0.12, 0.6+$extra, 0.4, 0.15, 0.15), array('Icons128x128_1', 'NewTrack', 0.02)); - else - $player->msgs[0] = array(1, $head, array(1.27+$extra, 0.12, 0.6+$extra, 0.4, 0.15), array('Icons128x128_1', 'NewTrack', 0.02)); - - while ($dbrow = mysql_fetch_array($result)) { - // does the uid exist in the current server track list? - if (array_key_exists($dbrow[0], $newlist)) { - $row = $newlist[$dbrow[0]]; - // does best time beat track's Gold time? - if ($dbrow[1] > $row['GoldTime']) { - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['author'] = $row['Author']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else { - $trackname = '{#black}' . $trackname; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackname = array($trackname, $tid+100); // action id - } - // format author name - $trackauthor = $row['Author']; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackauthor = array($trackauthor, -100-$tid); // action id - // format env name - $trackenv = $row['Environnement']; - // add clickable button - if ($aseco->settings['clickable_lists']) - $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id - - // compute difference to Gold time - $diff = $dbrow[1] - $row['GoldTime']; - $sec = floor($diff/1000); - $hun = ($diff - ($sec * 1000)) / 10; - - if ($aseco->server->packmask != 'Stadium') - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $trackname, $trackauthor, $trackenv, - '+' . sprintf("%d.%02d", $sec, $hun)); - else - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $trackname, $trackauthor, - '+' . sprintf("%d.%02d", $sec, $hun)); - $tid++; - if (++$lines > 14) { - $player->msgs[] = $msg; - $lines = 0; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Name', 'Author', 'Env', 'Time'); - else - $msg[] = array('Id', 'Name', 'Author', 'Time'); - } - } - } - } - // add if last batch exists - if (count($msg) > 1) - $player->msgs[] = $msg; - } - - } else { // Stunts mode - - // get list of finished tracks with their best (maximum) scores - $sql = 'SELECT DISTINCT c.uid,t1.score FROM rs_times t1, challenges c - WHERE (playerID=' . $player->id . ' AND t1.challengeID=c.id AND - score=(SELECT MAX(t2.score) FROM rs_times t2 - WHERE playerID=' . $player->id . ' AND t1.challengeID=t2.challengeID))'; - $result = mysql_query($sql); - if (mysql_num_rows($result) == 0) { - mysql_free_result($result); - return; - } - - // get new/cached list of tracks - $newlist = getChallengesCache($aseco); - - // only in TMUF anyway - { - $head = 'Tracks You Didn\'t Beat Gold Score On:'; - $msg = array(); - $msg[] = array('Id', 'Name', 'Author', 'Env', 'Score'); - $tid = 1; - $lines = 0; - $player->msgs = array(); - // reserve extra width for $w tags - $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0); - $player->msgs[0] = array(1, $head, array(1.42+$extra, 0.12, 0.6+$extra, 0.4, 0.15, 0.15), array('Icons128x128_1', 'NewTrack', 0.02)); - - while ($dbrow = mysql_fetch_array($result)) { - // does the uid exist in the current server track list? - if (array_key_exists($dbrow[0], $newlist)) { - $row = $newlist[$dbrow[0]]; - // does best score beat track's Gold score? - if ($dbrow[1] < $row['GoldTime']) { - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['author'] = $row['Author']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else { - $trackname = '{#black}' . $trackname; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackname = array($trackname, $tid+100); // action id - } - // format author name - $trackauthor = $row['Author']; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackauthor = array($trackauthor, -100-$tid); // action id - // format env name - $trackenv = $row['Environnement']; - // add clickable button - if ($aseco->settings['clickable_lists']) - $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id - - // compute difference to Gold score - $diff = $row['GoldTime'] - $dbrow[1]; - - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $trackname, $trackauthor, $trackenv, - '-' . $diff); - $tid++; - if (++$lines > 14) { - $player->msgs[] = $msg; - $lines = 0; - $msg = array(); - $msg[] = array('Id', 'Name', 'Author', 'Env', 'Score'); - } - } - } - } - // add if last batch exists - if (count($msg) > 1) - $player->msgs[] = $msg; - } - } - - mysql_free_result($result); -} // getChallengesNoGold - -function getChallengesNoAuthor($player) { - global $aseco, $jb_buffer; - - $player->tracklist = array(); - - // check for Stunts mode - if ($aseco->server->gameinfo->mode != Gameinfo::STNT) { - - // get list of finished tracks with their best (minimum) times - $sql = 'SELECT DISTINCT c.uid,t1.score FROM rs_times t1, challenges c - WHERE (playerID=' . $player->id . ' AND t1.challengeID=c.id AND - score=(SELECT MIN(t2.score) FROM rs_times t2 - WHERE playerID=' . $player->id . ' AND t1.challengeID=t2.challengeID))'; - $result = mysql_query($sql); - if (mysql_num_rows($result) == 0) { - mysql_free_result($result); - return; - } - - // get new/cached list of tracks - $newlist = getChallengesCache($aseco); - - if ($aseco->server->getGame() == 'TMN') { - $head = 'Tracks You Didn\'t Beat Author Time On:' . LF . 'Id Name $n(+Time)$m' . LF; - $msg = ''; - $tid = 1; - $lines = 0; - $player->msgs = array(); - $player->msgs[0] = 1; - - while ($dbrow = mysql_fetch_array($result)) { - // does the uid exist in the current server track list? - if (array_key_exists($dbrow[0], $newlist)) { - $row = $newlist[$dbrow[0]]; - // does best time beat track's Author time? - if ($dbrow[1] > $row['AuthorTime']) { - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else - $trackname = '{#black}' . $trackname; - - // compute difference to Author time - $diff = $dbrow[1] - $row['AuthorTime']; - $sec = floor($diff/1000); - $hun = ($diff - ($sec * 1000)) / 10; - - $msg .= str_pad($tid, 3, '0', STR_PAD_LEFT) . '. ' . $trackname - . ' $z$n(+' . sprintf("%d.%02d", $sec, $hun) . ')$m' . LF; - $tid++; - if (++$lines > 9) { - $player->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - } - } - // add if last batch exists - if ($msg != '') - $player->msgs[] = $aseco->formatColors($head . $msg); - - } elseif ($aseco->server->getGame() == 'TMF') { - $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17); - $head = 'Tracks You Didn\'t Beat Author Time On:'; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Name', 'Author', 'Env', 'Time'); - else - $msg[] = array('Id', 'Name', 'Author', 'Time'); - $tid = 1; - $lines = 0; - $player->msgs = array(); - // reserve extra width for $w tags - $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0); - if ($aseco->server->packmask != 'Stadium') - $player->msgs[0] = array(1, $head, array(1.42+$extra, 0.12, 0.6+$extra, 0.4, 0.15, 0.15), array('Icons128x128_1', 'NewTrack', 0.02)); - else - $player->msgs[0] = array(1, $head, array(1.27+$extra, 0.12, 0.6+$extra, 0.4, 0.15), array('Icons128x128_1', 'NewTrack', 0.02)); - - while ($dbrow = mysql_fetch_array($result)) { - // does the uid exist in the current server track list? - if (array_key_exists($dbrow[0], $newlist)) { - $row = $newlist[$dbrow[0]]; - // does best time beat track's Author time? - if ($dbrow[1] > $row['AuthorTime']) { - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['author'] = $row['Author']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else { - $trackname = '{#black}' . $trackname; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackname = array($trackname, $tid+100); // action id - } - // format author name - $trackauthor = $row['Author']; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackauthor = array($trackauthor, -100-$tid); // action id - // format env name - $trackenv = $row['Environnement']; - // add clickable button - if ($aseco->settings['clickable_lists']) - $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id - - // compute difference to Author time - $diff = $dbrow[1] - $row['AuthorTime']; - $sec = floor($diff/1000); - $hun = ($diff - ($sec * 1000)) / 10; - - if ($aseco->server->packmask != 'Stadium') - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $trackname, $trackauthor, $trackenv, - '+' . sprintf("%d.%02d", $sec, $hun)); - else - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $trackname, $trackauthor, - '+' . sprintf("%d.%02d", $sec, $hun)); - $tid++; - if (++$lines > 14) { - $player->msgs[] = $msg; - $lines = 0; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Name', 'Author', 'Env', 'Time'); - else - $msg[] = array('Id', 'Name', 'Author', 'Time'); - } - } - } - } - // add if last batch exists - if (count($msg) > 1) - $player->msgs[] = $msg; - } - - } else { // Stunts mode - - // get list of finished tracks with their best (maximum) scores - $sql = 'SELECT DISTINCT c.uid,t1.score FROM rs_times t1, challenges c - WHERE (playerID=' . $player->id . ' AND t1.challengeID=c.id AND - score=(SELECT MAX(t2.score) FROM rs_times t2 - WHERE playerID=' . $player->id . ' AND t1.challengeID=t2.challengeID))'; - $result = mysql_query($sql); - if (mysql_num_rows($result) == 0) { - mysql_free_result($result); - return; - } - - // get new/cached list of tracks - $newlist = getChallengesCache($aseco); - - // only in TMUF anyway - { - $head = 'Tracks You Didn\'t Beat Author Score On:'; - $msg = array(); - $msg[] = array('Id', 'Name', 'Author', 'Env', 'Score'); - $tid = 1; - $lines = 0; - $player->msgs = array(); - // reserve extra width for $w tags - $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0); - $player->msgs[0] = array(1, $head, array(1.42+$extra, 0.12, 0.6+$extra, 0.4, 0.15, 0.15), array('Icons128x128_1', 'NewTrack', 0.02)); - - while ($dbrow = mysql_fetch_array($result)) { - // does the uid exist in the current server track list? - if (array_key_exists($dbrow[0], $newlist)) { - $row = $newlist[$dbrow[0]]; - // does best score beat track's Author score? - if ($dbrow[1] < $row['AuthorScore']) { - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['author'] = $row['Author']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else { - $trackname = '{#black}' . $trackname; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackname = array($trackname, $tid+100); // action id - } - // format author name - $trackauthor = $row['Author']; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackauthor = array($trackauthor, -100-$tid); // action id - // format env name - $trackenv = $row['Environnement']; - // add clickable button - if ($aseco->settings['clickable_lists']) - $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id - - // compute difference to Author score - $diff = $row['AuthorScore'] - $dbrow[1]; - - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $trackname, $trackauthor, $trackenv, - '-' . $diff); - $tid++; - if (++$lines > 14) { - $player->msgs[] = $msg; - $lines = 0; - $msg = array(); - $msg[] = array('Id', 'Name', 'Author', 'Env', 'Score'); - } - } - } - } - // add if last batch exists - if (count($msg) > 1) - $player->msgs[] = $msg; - } - } - - mysql_free_result($result); -} // getChallengesNoAuthor - -// calls function get_recs() from chat.records2.php -function getChallengesNoRecent($player) { - global $aseco, $jb_buffer, $maxrecs; - - $player->tracklist = array(); - - // get list of finished tracks with their most recent (maximum) dates - $sql = 'SELECT DISTINCT c.uid,t1.date FROM rs_times t1, challenges c - WHERE (playerID=' . $player->id . ' AND t1.challengeID=c.id AND - date=(SELECT MAX(t2.date) FROM rs_times t2 - WHERE playerID=' . $player->id . ' AND t1.challengeID=t2.challengeID)) - ORDER BY t1.date'; - $result = mysql_query($sql); - if (mysql_num_rows($result) == 0) { - mysql_free_result($result); - return; - } - - // get list of ranked records - $reclist = get_recs($player->id); - // get new/cached list of tracks - $newlist = getChallengesCache($aseco); - - if ($aseco->server->getGame() == 'TMN') { - $head = 'Tracks You Didn\'t Play Recently:' . LF . 'Id Rec Name $n(Date)$m' . LF; - $msg = ''; - $tid = 1; - $lines = 0; - $player->msgs = array(); - $player->msgs[0] = 1; - - while ($dbrow = mysql_fetch_array($result)) { - // does the uid exist in the current server track list? - if (array_key_exists($dbrow[0], $newlist)) { - $row = $newlist[$dbrow[0]]; - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else - $trackname = '{#black}' . $trackname; - - // get corresponding record - $pos = isset($reclist[$dbrow[0]]) ? $reclist[$dbrow[0]] : 0; - $pos = ($pos >= 1 && $pos <= $maxrecs) ? str_pad($pos, 2, '0', STR_PAD_LEFT) : ' -- '; - - $msg .= str_pad($tid, 3, '0', STR_PAD_LEFT) . '. ' . $pos . '. ' - . $trackname . ' $z$n(' . date('Y/m/d', $dbrow[1]) . ')$m' . LF; - $tid++; - if (++$lines > 9) { - $player->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - } - // add if last batch exists - if ($msg != '') - $player->msgs[] = $aseco->formatColors($head . $msg); - - } elseif ($aseco->server->getGame() == 'TMF') { - $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17); - $head = 'Tracks You Didn\'t Play Recently:'; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Rec', 'Name', 'Author', 'Env', 'Date'); - else - $msg[] = array('Id', 'Rec', 'Name', 'Author', 'Date'); - $tid = 1; - $lines = 0; - $player->msgs = array(); - // reserve extra width for $w tags - $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0); - if ($aseco->server->packmask != 'Stadium') - $player->msgs[0] = array(1, $head, array(1.58+$extra, 0.12, 0.1, 0.6+$extra, 0.4, 0.15, 0.21), array('Icons128x128_1', 'NewTrack', 0.02)); - else - $player->msgs[0] = array(1, $head, array(1.43+$extra, 0.12, 0.1, 0.6+$extra, 0.4, 0.21), array('Icons128x128_1', 'NewTrack', 0.02)); - - while ($dbrow = mysql_fetch_array($result)) { - // does the uid exist in the current server track list? - if (array_key_exists($dbrow[0], $newlist)) { - $row = $newlist[$dbrow[0]]; - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['author'] = $row['Author']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else { - $trackname = '{#black}' . $trackname; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackname = array($trackname, $tid+100); // action id - } - // format author name - $trackauthor = $row['Author']; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackauthor = array($trackauthor, -100-$tid); // action id - // format env name - $trackenv = $row['Environnement']; - // add clickable button - if ($aseco->settings['clickable_lists']) - $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id - - // get corresponding record - $pos = isset($reclist[$dbrow[0]]) ? $reclist[$dbrow[0]] : 0; - $pos = ($pos >= 1 && $pos <= $maxrecs) ? str_pad($pos, 2, '0', STR_PAD_LEFT) : '-- '; - - if ($aseco->server->packmask != 'Stadium') - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $pos . '.', $trackname, $trackauthor, $trackenv, - date('Y/m/d', $dbrow[1])); - else - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $pos . '.', $trackname, $trackauthor, - date('Y/m/d', $dbrow[1])); - $tid++; - if (++$lines > 14) { - $player->msgs[] = $msg; - $lines = 0; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Rec', 'Name', 'Author', 'Env', 'Date'); - else - $msg[] = array('Id', 'Rec', 'Name', 'Author', 'Date'); - } - } - } - // add if last batch exists - if (count($msg) > 1) - $player->msgs[] = $msg; - } - - mysql_free_result($result); -} // getChallengesNoRecent - -function getChallengesByLength($player, $order) { - global $aseco, $jb_buffer; - - $player->tracklist = array(); - - // if Stunts mode, bail out immediately - if ($aseco->server->gameinfo->mode == Gameinfo::STNT) return; - - // get new/cached list of tracks - $newlist = getChallengesCache($aseco); - - // build list of author times - $times = array(); - foreach ($newlist as $uid => $row) - $times[$uid] = $row['AuthorTime']; - - // sort for shortest or longest author times - $order ? asort($times) : arsort($times); - - if ($aseco->server->getGame() == 'TMN') { - $head = ($order ? 'Shortest' : 'Longest') . ' Tracks On This Server:' . LF . 'Id Name $n(Author Time)$m' . LF; - $msg = ''; - $tid = 1; - $lines = 0; - $player->msgs = array(); - $player->msgs[0] = 1; - - foreach ($times as $uid => $time) { - $row = $newlist[$uid]; - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else - $trackname = '{#black}' . $trackname; - - $msg .= str_pad($tid, 3, '0', STR_PAD_LEFT) . '. ' . $trackname - . ' $z$n(' . formatTime($time) . ')$m' . LF; - $tid++; - if (++$lines > 9) { - $player->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - // add if last batch exists - if ($msg != '') - $player->msgs[] = $aseco->formatColors($head . $msg); - - } elseif ($aseco->server->getGame() == 'TMF') { - $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17); - $head = ($order ? 'Shortest' : 'Longest') . ' Tracks On This Server:'; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Name', 'Author', 'Env', 'AuthTime'); - else - $msg[] = array('Id', 'Name', 'Author', 'AuthTime'); - $tid = 1; - $lines = 0; - $player->msgs = array(); - // reserve extra width for $w tags - $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0); - if ($aseco->server->packmask != 'Stadium') - $player->msgs[0] = array(1, $head, array(1.44+$extra, 0.12, 0.6+$extra, 0.4, 0.15, 0.17), array('Icons128x128_1', 'NewTrack', 0.02)); - else - $player->msgs[0] = array(1, $head, array(1.29+$extra, 0.12, 0.6+$extra, 0.4, 0.17), array('Icons128x128_1', 'NewTrack', 0.02)); - - foreach ($times as $uid => $time) { - $row = $newlist[$uid]; - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['author'] = $row['Author']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else { - $trackname = '{#black}' . $trackname; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackname = array($trackname, $tid+100); // action id - } - // format author name - $trackauthor = $row['Author']; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackauthor = array($trackauthor, -100-$tid); // action id - // format env name - $trackenv = $row['Environnement']; - // add clickable button - if ($aseco->settings['clickable_lists']) - $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id - - if ($aseco->server->packmask != 'Stadium') - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $trackname, $trackauthor, $trackenv, formatTime($time)); - else - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $trackname, $trackauthor, formatTime($time)); - $tid++; - if (++$lines > 14) { - $player->msgs[] = $msg; - $lines = 0; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Name', 'Author', 'Env', 'AuthTime'); - else - $msg[] = array('Id', 'Name', 'Author', 'AuthTime'); - } - } - // add if last batch exists - if (count($msg) > 1) - $player->msgs[] = $msg; - } -} // getChallengesByLength - -function getChallengesByAdd($player, $order, $count) { - global $aseco, $jb_buffer; - - $player->tracklist = array(); - - // get list of tracks in reverse order of addition - $sql = 'SELECT uid FROM challenges - ORDER BY id ' . ($order ? 'DESC' : 'ASC'); - $result = mysql_query($sql); - if (mysql_num_rows($result) == 0) { - mysql_free_result($result); - return; - } - - // get new/cached list of tracks - $newlist = getChallengesCache($aseco); - - $tcnt = 0; - if ($aseco->server->getGame() == 'TMN') { - $head = ($order ? 'Newest' : 'Oldest') . ' Tracks On This Server:' . LF . 'Id Name' . LF; - $msg = ''; - $tid = 1; - $lines = 0; - $player->msgs = array(); - $player->msgs[0] = 1; - - while ($dbrow = mysql_fetch_array($result)) { - // does the uid exist in the current server track list? - if (array_key_exists($dbrow[0], $newlist)) { - $row = $newlist[$dbrow[0]]; - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else - $trackname = '{#black}' . $trackname; - - $msg .= '$z' . str_pad($tid, 3, '0', STR_PAD_LEFT) . '. ' - . $trackname . LF; - $tid++; - if (++$lines > 9) { - $player->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - // check if we have enough tracks already - if (++$tcnt == $count) break; - } - } - // add if last batch exists - if ($msg != '') - $player->msgs[] = $aseco->formatColors($head . $msg); - - } elseif ($aseco->server->getGame() == 'TMF') { - $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17); - $head = ($order ? 'Newest' : 'Oldest') . ' Tracks On This Server:'; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Name', 'Author', 'Env'); - else - $msg[] = array('Id', 'Name', 'Author'); - $tid = 1; - $lines = 0; - $player->msgs = array(); - // reserve extra width for $w tags - $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0); - if ($aseco->server->packmask != 'Stadium') - $player->msgs[0] = array(1, $head, array(1.29+$extra, 0.12, 0.6+$extra, 0.4, 0.17), array('Icons128x128_1', 'NewTrack', 0.02)); - else - $player->msgs[0] = array(1, $head, array(1.12+$extra, 0.12, 0.6+$extra, 0.4), array('Icons128x128_1', 'NewTrack', 0.02)); - - while ($dbrow = mysql_fetch_array($result)) { - // does the uid exist in the current server track list? - if (array_key_exists($dbrow[0], $newlist)) { - $row = $newlist[$dbrow[0]]; - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['author'] = $row['Author']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else { - $trackname = '{#black}' . $trackname; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackname = array($trackname, $tid+100); // action id - } - // format author name - $trackauthor = $row['Author']; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackauthor = array($trackauthor, -100-$tid); // action id - // format env name - $trackenv = $row['Environnement']; - // add clickable button - if ($aseco->settings['clickable_lists']) - $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id - - if ($aseco->server->packmask != 'Stadium') - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $trackname, $trackauthor, $trackenv); - else - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $trackname, $trackauthor); - $tid++; - if (++$lines > 14) { - $player->msgs[] = $msg; - $lines = 0; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Name', 'Author', 'Env'); - else - $msg[] = array('Id', 'Name', 'Author'); - } - // check if we have enough tracks already - if (++$tcnt == $count) break; - } - } - // add if last batch exists - if (count($msg) > 1) - $player->msgs[] = $msg; - } - - mysql_free_result($result); -} // getChallengesByAdd - -function getChallengesNoVote($player) { - global $aseco, $jb_buffer, $maxrecs; - - $player->tracklist = array(); - - // get list of ranked records - $reclist = get_recs($player->id); - - // get new/cached list of tracks - $newlist = getChallengesCache($aseco); - - // get list of voted tracks and remove those - $sql = 'SELECT uid FROM challenges c, rs_karma k - WHERE c.id=k.challengeID AND k.playerID=' . $player->id; - $result = mysql_query($sql); - if (mysql_num_rows($result) > 0) { - while ($dbrow = mysql_fetch_array($result)) - unset($newlist[$dbrow[0]]); - } - mysql_free_result($result); - - if ($aseco->server->getGame() == 'TMN') { - $head = 'Tracks You Didn\'t Vote For:' . LF . 'Id Rec Name' . LF; - $msg = ''; - $tid = 1; - $lines = 0; - $player->msgs = array(); - $player->msgs[0] = 1; - - foreach ($newlist as $row) { - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else - $trackname = '{#black}' . $trackname; - - // get corresponding record - $pos = isset($reclist[$row['UId']]) ? $reclist[$row['UId']] : 0; - $pos = ($pos >= 1 && $pos <= $maxrecs) ? str_pad($pos, 2, '0', STR_PAD_LEFT) : ' -- '; - - $msg .= '$z' . str_pad($tid, 3, '0', STR_PAD_LEFT) . '. ' . $pos . '. ' - . $trackname . LF; - $tid++; - if (++$lines > 9) { - $player->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - // add if last batch exists - if ($msg != '') - $player->msgs[] = $aseco->formatColors($head . $msg); - - } elseif ($aseco->server->getGame() == 'TMF') { - $envids = array('Stadium' => 11, 'Alpine' => 12, 'Bay' => 13, 'Coast' => 14, 'Island' => 15, 'Rally' => 16, 'Speed' => 17); - $head = 'Tracks You Didn\'t Vote For:'; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Rec', 'Name', 'Author', 'Env'); - else - $msg[] = array('Id', 'Rec', 'Name', 'Author'); - $tid = 1; - $lines = 0; - $player->msgs = array(); - // reserve extra width for $w tags - $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0); - if ($aseco->server->packmask != 'Stadium') - $player->msgs[0] = array(1, $head, array(1.39+$extra, 0.12, 0.1, 0.6+$extra, 0.4, 0.17), array('Icons128x128_1', 'NewTrack', 0.02)); - else - $player->msgs[0] = array(1, $head, array(1.22+$extra, 0.12, 0.1, 0.6+$extra, 0.4), array('Icons128x128_1', 'NewTrack', 0.02)); - - foreach ($newlist as $row) { - // store track in player object for jukeboxing - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['author'] = $row['Author']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $player->tracklist[] = $trkarr; - - // format track name - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - // grey out if in history - if (in_array($row['UId'], $jb_buffer)) - $trackname = '{#grey}' . stripColors($trackname); - else { - $trackname = '{#black}' . $trackname; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackname = array($trackname, $tid+100); // action id - } - // format author name - $trackauthor = $row['Author']; - // add clickable button - if ($aseco->settings['clickable_lists'] && $tid <= 1900) - $trackauthor = array($trackauthor, -100-$tid); // action id - // format env name - $trackenv = $row['Environnement']; - // add clickable button - if ($aseco->settings['clickable_lists']) - $trackenv = array($trackenv, $envids[$row['Environnement']]); // action id - - // get corresponding record - $pos = isset($reclist[$row['UId']]) ? $reclist[$row['UId']] : 0; - $pos = ($pos >= 1 && $pos <= $maxrecs) ? str_pad($pos, 2, '0', STR_PAD_LEFT) : '-- '; - - if ($aseco->server->packmask != 'Stadium') - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $pos . '.', $trackname, $trackauthor, $trackenv); - else - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - $pos . '.', $trackname, $trackauthor); - $tid++; - if (++$lines > 14) { - $player->msgs[] = $msg; - $lines = 0; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Rec', 'Name', 'Author', 'Env'); - else - $msg[] = array('Id', 'Rec', 'Name', 'Author'); - } - } - // add if last batch exists - if (count($msg) > 1) - $player->msgs[] = $msg; - } -} // getChallengesNoVote - - -// called @ onPlayerServerMessageAnswer -// handles all pop-up window responses -// [0]=PlayerUid, [1]=Login, [2]=Answer -function event_multi_message($aseco, $answer) { - - $login = $answer[1]; - $player = $aseco->server->players->getPlayer($login); - $cnt = count($player->msgs); - - // check for 'Next' response - if ($answer[2] == 2 && $cnt > 0) { - $player->msgs[0]++; // primed at 1 in the $player->msgs functions - if (($player->msgs[0] + 1) < $cnt) { // multiple pages to display - $btn1 = 'Close'; - $btn2 = 'Next'; - $msg = $player->msgs[$player->msgs[0]]; - } elseif (($player->msgs[0] + 1) == $cnt) { // last page to display - $btn1 = 'OK'; - $btn2 = ''; - $msg = $player->msgs[$player->msgs[0]]; - } else { // all done - return; - } - // display the next page - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $msg, $btn1, $btn2, 0); - // 'Close' response - } else { - } -} // event_multi_message - -function getChallengeData($filename, $rtnvotes) { - global $aseco, $tmxvoteratio; - - $ret = array(); - if (!file_exists($filename)) { - $ret['name'] = 'file not found'; - $ret['votes'] = 500; - return $ret; - } - // check whether votes are needed - if ($rtnvotes) { - $ret['votes'] = required_votes($tmxvoteratio); // from plugin.rasp_votes.php - if ($aseco->debug) { - $ret['votes'] = 1; - } - } else { - $ret['votes'] = 1; - } - - $gbx = new GBXChallMapFetcher(); - try - { - $gbx->processFile($filename); - - $ret['uid'] = $gbx->uid; - $ret['name'] = stripNewlines($gbx->name); - $ret['author'] = $gbx->author; - $ret['environment'] = $gbx->envir; - $ret['authortime'] = $gbx->authorTime; - $ret['authorscore'] = $gbx->authorScore; - $ret['coppers'] = $gbx->cost; - } - catch (Exception $e) - { - $ret['votes'] = 500; - $ret['name'] = $e->getMessage(); - } - return $ret; -} // getChallengeData -?> diff --git a/xaseco/includes/tmndatafetcher.inc.php b/xaseco/includes/tmndatafetcher.inc.php deleted file mode 100644 index 691e98d..0000000 --- a/xaseco/includes/tmndatafetcher.inc.php +++ /dev/null @@ -1,290 +0,0 @@ - - * - * v1.8: Improved error reporting via $error; fixed file check in extendedInfo - * processing; added magic __set_state function to support var_export() - * v1.7: Improve handling of empty API responses - * v1.6: Fixed $teamrank type into int; added User-Agent to the GET request - * v1.5: Tweaked initial get_file return value check - * v1.4: Fixed get_file return value checks; fixed $nationrank check - * v1.3: Optimized get_file URL parsing - * v1.2: Added get_file function to handle master server timeouts - * v1.1: General code cleanup; added more comments; added $lastmatch, - * $totalplayers, $nationplayers, $nationpos, $nationpoints, - * $totalnations, $servernick, $serverdesc, $servernation; - * renamed $actualserver to $serverlogin - * v1.0: Initial release - */ -class TMNDataFetcher { - - public $version, $extended, $error, - $login, $nickname, $worldrank, $totalplayers, - $points, $lastmatch, $wins, $losses, $draws, - $stars, $stardays, $teamname, $teamrank, $totalteams, - $nation, $nationrank, $nationplayers, - $nationpos, $nationpoints, $totalnations, - $online, $serverlogin, $servernick, $serverdesc, $servernation; - - /** - * Fetches a hell of a lot of data about a TMN login - * - * @param String $login - * The TMN login to search for - * @param Boolean $extendedInfo - * If true, the script also searches for the server that the - * player is on at the moment (also determines online-state) - * @return TMNDataFetcher - * If $nickname is empty, login was not found - */ - function TMNDataFetcher($login, $extendedInfo) { - - $this->version = '0.1.7.9'; - $this->error = ''; - $this->extended = $extendedInfo; - $this->login = strtolower($login); - $this->getData(); - } // TMNDataFetcher - - public static function __set_state($import) { - - $tmn = new TMNDataFetcher('', true); - - $tmn->version = $import['version']; - $tmn->extended = $import['extended']; - $tmn->error = ''; - $tmn->login = $import['login']; - $tmn->nickname = $import['nickname']; - $tmn->worldrank = $import['worldrank']; - $tmn->totalplayers = $import['totalplayers']; - $tmn->points = $import['points']; - $tmn->lastmatch = $import['lastmatch']; - $tmn->wins = $import['wins']; - $tmn->losses = $import['losses']; - $tmn->draws = $import['draws']; - $tmn->stars = $import['stars']; - $tmn->stardays = $import['stardays']; - $tmn->teamname = $import['teamname']; - $tmn->teamrank = $import['teamrank']; - $tmn->totalteams = $import['totalteams']; - $tmn->nation = $import['nation']; - $tmn->nationrank = $import['nationrank']; - $tmn->nationplayers = $import['nationplayers']; - $tmn->nationpos = $import['nationpos']; - $tmn->nationpoints = $import['nationpoints']; - $tmn->totalnations = $import['totalnations']; - $tmn->online = $import['online']; - $tmn->serverlogin = $import['serverlogin']; - $tmn->servernick = $import['servernick']; - $tmn->serverdesc = $import['serverdesc']; - $tmn->servernation = $import['servernation']; - - return $tmn; - } // __set_state - - private function getData() { - - $url = 'http://game.trackmanianations.com/online_game/getplayerinfos.php?ver=' . $this->version . '&lang=en&login=' . $this->login; - $line = $this->get_file($url); - if ($line === false) { - $this->error = 'Connection or response error on ' . $url; - return; - } else if ($line === -1) { - $this->error = 'Timed out while reading data from ' . $url; - return; - } else if ($line == '' || strpos($line, '
    ') !== false) { - $this->error = 'No data returned from ' . $url; - return; - } - - $array = explode(';', $line); - if (!isset($array[6])) { - $this->error = 'Cannot parse data returned data from ' . $url; - return; - } - // 0 = $array[0]; - // login = $array[1]; - $this->nickname = urldecode($array[2]); - $this->nation = $array[3]; - // empty = $array[4]; - $this->stars = $array[5]; - $this->stardays = $array[6]; - - $url = 'http://ladder.trackmanianations.com/ladder/getstats.php?ver=' . $this->version . '&laddertype=g&login=' . $this->login; - $line = $this->get_file($url); - if ($line === false) { - $this->error = 'Connection or response error on ' . $url; - return; - } else if ($line === -1) { - $this->error = 'Timed out while reading data from ' . $url; - return; - } else if ($line == '' || strpos($line, '
    ') !== false) { - $this->error = 'No data returned from ' . $url; - return; - } - - $array = explode(';', $line); - if (!isset($array[10])) { - $this->error = 'Cannot parse data returned data from ' . $url; - return; - } - // 0 = $array[0]; - $this->totalplayers = $array[1]; - $this->wins = $array[2]; - $this->losses = $array[3]; - $this->draws = $array[4]; - $this->worldrank = $array[5]; - $this->points = $array[6]; - $this->lastmatch = $array[7]; - $this->teamname = urldecode($array[8]); - $this->teamrank = (int)$array[9]; - $this->totalteams = $array[10]; - - $url = 'http://ladder.trackmanianations.com/ladder/getstats.php?ver=' . $this->version . '&laddertype=g&login=' . $this->login . '&country=' . $this->nation; - $line = $this->get_file($url); - if ($line === false) { - $this->error = 'Connection or response error on ' . $url; - return; - } else if ($line === -1) { - $this->error = 'Timed out while reading data from ' . $url; - return; - } else if ($line == '' || strpos($line, '
    ') !== false) { - $this->error = 'No data returned from ' . $url; - return; - } - - $array = explode(';', $line); - // 0 = $array[1]; - if (isset($array[5])) - $this->nationrank = $array[5]; - else - $this->nationrank = ''; - // the remaining fields are the same as the world stats above - - $url = 'http://ladder.trackmanianations.com/ladder/getrankings.php?ver=' . $this->version . '&laddertype=g&start=0&limit=0&country=' . $this->nation; - $line = $this->get_file($url); - if ($line === false) { - $this->error = 'Connection or response error on ' . $url; - return; - } else if ($line === -1) { - $this->error = 'Timed out while reading data from ' . $url; - return; - } else if ($line == '' || strpos($line, '
    ') !== false) { - $this->error = 'No data returned from ' . $url; - return; - } - - $array = explode(';', $line); - if (!isset($array[1])) { - $this->error = 'Cannot parse data returned data from ' . $url; - return; - } - // 0 = $array[1]; - $this->nationplayers = $array[1]; - // 1;login;nickname;nation;points = $array[2-6]; - // 2;login;nickname;nation;points = $array[7-11]; etc.etc. - - $url = 'http://ladder.trackmanianations.com/ladder/getcountriesrankings.php?ver=' . $this->version . '&laddertype=g&lang=en&start=0&limit=100'; - $line = $this->get_file($url); - if ($line === false) { - $this->error = 'Connection or response error on ' . $url; - return; - } else if ($line === -1) { - $this->error = 'Timed out while reading data from ' . $url; - return; - } else if ($line == '' || strpos($line, '
    ') !== false) { - $this->error = 'No data returned from ' . $url; - return; - } - - $array = explode(';', $line); - if (!isset($array[1])) { - $this->error = 'Cannot parse data returned data from ' . $url; - return; - } - // 0 = $array[1]; - $this->totalnations = $array[1]; - // 1;nation;points = $array[2-4]; - // 2;nation;points = $array[5-7]; etc.etc. - $i = 2; - while (isset($array[$i]) && $array[$i] != '') { - if ($array[$i+1] == $this->nation) { - $this->nationpos = $array[$i]; - $this->nationpoints = $array[$i+2]; - break; - } - $i += 3; - } - - $this->online = false; - // check online status too? - if ($this->extended) { - $page = $this->get_file('http://game.trackmanianations.com/online_game/www_serverslist.php'); - if ($page === false || $page == -1 || $page == '') - // no error message if main info was already fetched - return; - - $lines = explode('', $page); - foreach ($lines as $line) { - if (stripos($line, '' . $this->login . '') !== false) { - $this->online = true; - $this->serverlogin = substr($line, 0, strpos($line, '')); - break; - } - } - - if ($this->online) { - $page = $this->get_file('http://game.trackmanianations.com/online_game/browse_top.php?ver=0.1.7.9&lang=en&key=XXXX-XXXX-XXXX-XXXX-XXX&nb=100&page=1&flatall=1'); - if ($page === false || $page == -1 || $page == '') - // no error message if main info was already fetched - return; - - $server = $this->serverlogin; // can't use object member inside pattern - if (preg_match("/^${server};([^;]+);([^;]+);([A-Z]+);/m", $page, $fields)) { - $this->serverdesc = $fields[1]; - $this->servernick = ($fields[2] != 'x' ? urldecode($fields[2]) : ''); - $this->servernation = $fields[3]; - } - } - } - } // getData - - // Simple HTTP Get function with timeout - // ok: return string || error: return false || timeout: return -1 - private function get_file($url) { - - $url = parse_url($url); - $port = isset($url['port']) ? $url['port'] : 80; - $query = isset($url['query']) ? "?" . $url['query'] : ""; - - $fp = @fsockopen($url['host'], $port, $errno, $errstr, 4); - if (!$fp) - return false; - - fwrite($fp, 'GET ' . $url['path'] . $query . " HTTP/1.0\r\n" . - 'Host: ' . $url['host'] . "\r\n" . - 'User-Agent: TMNDataFetcher (' . PHP_OS . ")\r\n\r\n"); - stream_set_timeout($fp, 2); - $res = ''; - $info['timed_out'] = false; - while (!feof($fp) && !$info['timed_out']) { - $res .= fread($fp, 512); - $info = stream_get_meta_data($fp); - } - fclose($fp); - - if ($info['timed_out']) { - return -1; - } else { - if (substr($res, 9, 3) != '200') - return false; - $page = explode("\r\n\r\n", $res, 2); - return trim($page[1]); - } - } // get_file -} // class TMNDataFetcher -?> diff --git a/xaseco/includes/tmxinfofetcher.inc.php b/xaseco/includes/tmxinfofetcher.inc.php deleted file mode 100644 index da09eef..0000000 --- a/xaseco/includes/tmxinfofetcher.inc.php +++ /dev/null @@ -1,297 +0,0 @@ - - * Inspired by TMNDataFetcher & "Stats for TMN Aseco+RASP" - * - * v1.19: Allowed 24-char UIDs too - * v1.18: Added 'replayurl' to the entries in $recordslist - * v1.17: Allowed 25-char UIDs too - * v1.16: Improved error reporting via $error; parsed more comment formatting - * v1.15: Added User-Agent to the GET request - * v1.14: Fixed PHP Strict level warning - * v1.13: Fixed handling of empty API responses - * v1.12: Renamed $worldrec into $custimg to correct its meaning; fixed - * $replayid check - * v1.11: Added another check for valid $replayid - * v1.10: Added magic __set_state function to support var_export() - * v1.9: Fixed fetch records run-time warnings - * v1.8: Optimized get_file URL parsing - * v1.7: Added $worldrec (boolean); changed $visible to boolean; renamed - * $download to $dloadurl, $imgurl to $imageurl & $imgurlsmall to - * $thumburl; minor tweaks - * v1.6: Added check for valid $replayid & $replayurl - * v1.5: Added TMNF compatibility - * v1.4: Added get_file function to handle TMX site timeouts - * v1.3: Added $awards, $comments, $replayid, $replayurl; renamed $comment to - * $acomment (to better distinguish author comment from # of comments) - * v1.2: Allowed 26-char UIDs too; added $pageurl - * v1.1: Allowed TMX IDs too - * v1.0: Initial release - */ -class TMXInfoFetcher { - - public $section, $prefix, $uid, $id, $records, $error, - $name, $userid, $author, $uploaded, $updated, - $visible, $type, $envir, $mood, $style, $routes, - $length, $diffic, $lbrating, $awards, $comments, $custimg, - $game, $acomment, $pageurl, $replayid, $replayurl, - $imageurl, $thumburl, $dloadurl, $recordlist; - - /** - * Fetches a hell of a lot of data about a TMX track - * - * @param String $game - * TMX section for 'TMO', 'TMS', 'TMN', 'TMU', 'TMNF' - * @param String $id - * The challenge UID to search for (if a 24-27 char alphanum string), - * otherwise the TMX ID to search for (if a number) - * @param Boolean $records - * If true, the script also returns the world records (max. 10) - * @return TMXInfoFetcher - * If $error is not an empty string, it's an error message - */ - public function TMXInfoFetcher($game, $id, $records) { - - $this->section = $game; - switch ($game) { - case 'TMO': - $this->prefix = 'original'; - break; - case 'TMS': - $this->prefix = 'sunrise'; - break; - case 'TMN': - $this->prefix = 'nations'; - break; - case 'TMU': - $this->prefix = 'united'; - break; - case 'TMNF': - $this->prefix = 'tmnforever'; - break; - default: - $this->prefix = ''; - return; - } - - $this->error = ''; - $this->records = $records; - // check for UID string - if (preg_match('/^\w{24,27}$/', $id)) { - $this->uid = $id; - $this->getData(true); - // check for TMX ID - } elseif (is_numeric($id) && $id > 0) { - $this->id = floor($id); - $this->getData(false); - } - } // TMXInfoFetcher - - public static function __set_state($import) { - - $tmx = new TMXInfoFetcher('', 0, false); - - $tmx->section = $import['section']; - $tmx->prefix = $import['prefix']; - $tmx->uid = $import['uid']; - $tmx->id = $import['id']; - $tmx->records = $import['records']; - $tmx->error = ''; - $tmx->name = $import['name']; - $tmx->userid = $import['userid']; - $tmx->author = $import['author']; - $tmx->uploaded = $import['uploaded']; - $tmx->updated = $import['updated']; - $tmx->visible = $import['visible']; - $tmx->type = $import['type']; - $tmx->envir = $import['envir']; - $tmx->mood = $import['mood']; - $tmx->style = $import['style']; - $tmx->routes = $import['routes']; - $tmx->length = $import['length']; - $tmx->diffic = $import['diffic']; - $tmx->lbrating = $import['lbrating']; - $tmx->awards = $import['awards']; - $tmx->comments = $import['comments']; - $tmx->custimg = $import['custimg']; - $tmx->game = $import['game']; - $tmx->acomment = $import['acomment']; - $tmx->pageurl = $import['pageurl']; - $tmx->replayid = $import['replayid']; - $tmx->replayurl = $import['replayurl']; - $tmx->imageurl = $import['imageurl']; - $tmx->thumburl = $import['thumburl']; - $tmx->dloadurl = $import['dloadurl']; - $tmx->recordlist = null; - - return $tmx; - } // __set_state - - private function getData($isuid) { - - // get main track info - $url = 'http://' . $this->prefix . '.tm-exchange.com/apiget.aspx?action=apitrackinfo&' . ($isuid ? 'u' : '') . 'id=' . ($isuid ? $this->uid : $this->id); - $file = $this->get_file($url); - if ($file === false) { - $this->error = 'Connection or response error on ' . $url; - return; - } else if ($file === -1) { - $this->error = 'Timed out while reading data from ' . $url; - return; - } else if ($file == '') { - $this->error = 'No data returned from ' . $url; - return; - } - - // check for API error message - if (strpos($file, chr(27)) !== false) { - $this->error = 'Cannot decode main track info'; - return; - } - - // separate columns on Tabs - $fields = explode(chr(9), $file); - - if ($isuid) - $this->id = $fields[0]; - - $this->name = $fields[1]; - $this->userid = $fields[2]; - $this->author = $fields[3]; - $this->uploaded = $fields[4]; - $this->updated = $fields[5]; - $this->visible = (strtolower($fields[6]) == 'true'); - $this->type = $fields[7]; - $this->envir = $fields[8]; - $this->mood = $fields[9]; - $this->style = $fields[10]; - $this->routes = $fields[11]; - $this->length = $fields[12]; - $this->diffic = $fields[13]; - $this->lbrating = ($fields[14] > 0 ? $fields[14] : 'Classic!'); - $this->game = $fields[15]; - - $search = array(chr(31), '[b]', '[/b]', '[i]', '[/i]', '[u]', '[/u]', '[url]', '[/url]'); - $replace = array('
    ', '', '', '', '', '', '', '', ''); - $this->acomment = str_ireplace($search, $replace, $fields[16]); - $this->acomment = preg_replace('/\[url=".*"\]/', '', $this->acomment); - - $this->pageurl = 'http://' . $this->prefix . '.tm-exchange.com/main.aspx?action=trackshow&id=' . $this->id; - $this->imageurl = 'http://' . $this->prefix . '.tm-exchange.com/get.aspx?action=trackscreen&id=' . $this->id; - $this->thumburl = 'http://' . $this->prefix . '.tm-exchange.com/get.aspx?action=trackscreensmall&id=' . $this->id; - $this->dloadurl = 'http://' . $this->prefix . '.tm-exchange.com/get.aspx?action=trackgbx&id=' . $this->id; - - $this->awards = 0; - $this->comments = 0; - $this->custimg = false; - $this->replayid = 0; - $this->replayurl = ''; - - // get misc. track info - $url = 'http://' . $this->prefix . '.tm-exchange.com/apiget.aspx?action=apisearch&trackid=' . $this->id; - $file = $this->get_file($url); - if ($file === false || $file === -1 || $file == '') - // no error message if main info was already fetched - return; - - // check for API error message - if (strpos($file, chr(27)) !== false) - return; - - // separate columns on Tabs - $fields = explode(chr(9), $file); - - // id = $fields[0]; - // name = $fields[1]; - // userid = $fields[2]; - // author = $fields[3]; - // type = $fields[4]; - // envir = $fields[5]; - // mood = $fields[6]; - // style = $fields[7]; - // routes = $fields[8]; - // length = $fields[9]; - // diffic = $fields[10]; - // lbrating = ($fields[11] > 0 ? $fields[11] : 'Classic!'); - $this->awards = $fields[12]; - $this->comments = $fields[13]; - $this->custimg = (strtolower($fields[14]) == 'true'); - // game = $fields[15]; - $this->replayid = $fields[16]; - // unknown = $fields[17-21]; - // uploaded = $fields[22]; - // updated = $fields[23]; - - if ($this->replayid > 0) { - $this->replayurl = 'http://' . $this->prefix . '.tm-exchange.com/get.aspx?action=recordgbx&id=' . $this->replayid; - } - - // fetch records too? - $this->recordlist = array(); - if ($this->records) { - $url = 'http://' . $this->prefix . '.tm-exchange.com/apiget.aspx?action=apitrackrecords&id=' . $this->id; - $file = $this->get_file($url); - if ($file === false || $file === -1 || $file == '') - // no error message if main info was already fetched - return; - - $file = explode("\r\n", $file); - $i = 0; - while ($i < 10 && isset($file[$i]) && $file[$i] != '') { - // separate columns on Tabs - $fields = explode(chr(9), $file[$i]); - $this->recordlist[$i++] = array( - 'replayid' => $fields[0], - 'userid' => $fields[1], - 'name' => $fields[2], - 'time' => $fields[3], - 'replayat' => $fields[4], - 'trackat' => $fields[5], - 'approved' => $fields[6], - 'score' => $fields[7], - 'expires' => $fields[8], - 'lockspan' => $fields[9], - 'replayurl'=> 'http://' . $this->prefix . '.tm-exchange.com/get.aspx?action=recordgbx&id=' . $fields[0], - ); - } - } - } // getData - - // Simple HTTP Get function with timeout - // ok: return string || error: return false || timeout: return -1 - private function get_file($url) { - - $url = parse_url($url); - $port = isset($url['port']) ? $url['port'] : 80; - $query = isset($url['query']) ? "?" . $url['query'] : ""; - - $fp = @fsockopen($url['host'], $port, $errno, $errstr, 4); - if (!$fp) - return false; - - fwrite($fp, 'GET ' . $url['path'] . $query . " HTTP/1.0\r\n" . - 'Host: ' . $url['host'] . "\r\n" . - 'User-Agent: TMXInfoFetcher (' . PHP_OS . ")\r\n\r\n"); - stream_set_timeout($fp, 2); - $res = ''; - $info['timed_out'] = false; - while (!feof($fp) && !$info['timed_out']) { - $res .= fread($fp, 512); - $info = stream_get_meta_data($fp); - } - fclose($fp); - - if ($info['timed_out']) { - return -1; - } else { - if (substr($res, 9, 3) != '200') - return false; - $page = explode("\r\n\r\n", $res, 2); - return trim($page[1]); - } - } // get_file -} // class TMXInfoFetcher -?> diff --git a/xaseco/includes/tmxinfosearcher.inc.php b/xaseco/includes/tmxinfosearcher.inc.php deleted file mode 100644 index b699014..0000000 --- a/xaseco/includes/tmxinfosearcher.inc.php +++ /dev/null @@ -1,288 +0,0 @@ - - * Based on TMXInfoFetcher & http://united.tm-exchange.com/main.aspx?action=threadshow&id=619302 - * - * v1.7: Added Countable interface to searcher class - * v1.6: Fixed an error checking bug - * v1.5: Improved error reporting via $error - * v1.4: Added User-Agent to the GET request - * v1.3: Renamed $worldrec into $custimg to correct its meaning; fixed - * $replayid check - * v1.2: Fixed TMXInfo processing false $track - * v1.1: Optimized get_file URL parsing - * v1.0: Initial release - */ -class TMXInfoSearcher implements Iterator,Countable { - - public $error; - protected $tracks = array(); - private $section; - private $prefix; - - /** - * Searches TMX for tracks matching name, author and/or environment; - * or search TMX for the 10 most recent tracks - * - * @param String $game - * TMX section for 'TMO', 'TMS', 'TMN', 'TMU', 'TMNF' - * @param String $name - * The track name to search for (partial, case-insensitive match) - * @param String $author - * The track author to search for (partial, case-insensitive match) - * @param String $env - * The environment to search for (exact case-insensitive match - * from: Desert, Snow, Rally, Bay, Coast, Island, Stadium); - * ignored when searching TMN or TMNF - * @param Boolean $recent - * If true, ignore search parameters and just return 10 newest tracks - * (max. one per author) - * @return TMXInfoSearcher - * If ->valid() is false, no matching track was found; - * otherwise, an iterator of TMXInfo objects for a 'foreach' loop. - * Returns at most 500 tracks ($maxpage * 20) for TMNF/TMU(F), - * and at most 20 tracks for the other TMX sections. - */ - public function __construct($game, $name, $author, $env, $recent) { - - $this->section = $game; - switch ($game) { - case 'TMO': - $this->prefix = 'original'; - break; - case 'TMS': - $this->prefix = 'sunrise'; - break; - case 'TMN': - $this->prefix = 'nations'; - $env = ''; // ignore possible environment - break; - case 'TMU': - $this->prefix = 'united'; - break; - case 'TMNF': - $this->prefix = 'tmnforever'; - $env = ''; // ignore possible environment - break; - default: - $this->prefix = ''; - $this->error = 'Unknown TMX section: ' . $game; - return; - } - - $this->error = ''; - if ($recent) { - $this->tracks = $this->getRecent(); - } else { - $this->tracks = $this->getList($name, $author, $env); - } - } // __construct - - // define standard Iterator functions - public function rewind() { - reset($this->tracks); - } - public function current() { - return new TMXInfo($this->section, $this->prefix, current($this->tracks)); - } - public function next() { - return new TMXInfo($this->section, $this->prefix, next($this->tracks)); - } - public function key() { - return key($this->tracks); - } - public function valid() { - return (current($this->tracks) !== false); - } - // define standard Countable function - public function count() { - return count($this->tracks); - } - - private function getRecent() { - - // get 10 most recent tracks - $url = 'http://' . $this->prefix . '.tm-exchange.com/apiget.aspx?action=apirecent'; - $file = $this->get_file($url); - if ($file === false) { - $this->error = 'Connection or response error on ' . $url; - return array(); - } else if ($file === -1) { - $this->error = 'Timed out while reading data from ' . $url; - return array(); - } else if ($file == '') { - $this->error = 'No data returned from ' . $url; - return array(); - } - - // check for API error message - if (strpos($file, chr(27)) !== false) { - $this->error = 'Cannot decode recent track info from ' . $url; - return array(); - } - - // return list of tracks as array of strings - return explode("\r\n", $file); - } // getRecent - - private function getList($name, $author, $env) { - - // older TMX sections don't support multi-page results :( - $maxpage = 1; - if ($this->prefix == 'tmnforever' || $this->prefix == 'united') - $maxpage = 25; // max. 500 tracks - - // compile search URL - $url = 'http://' . $this->prefix . '.tm-exchange.com/apiget.aspx?action=apisearch'; - if ($name != '') - $url .= '&track=' . $name; - if ($author != '') - $url .= '&author=' . $author; - if ($env != '') - $url .= '&env=' . $env; - $url .= '&page='; - - $tracks = ''; - $page = 0; - $done = false; - - // get results 20 tracks at a time - while ($page < $maxpage && !$done) { - $file = $this->get_file($url . $page); - if ($file === false) { - $this->error = 'Connection or response error on ' . $url; - return array(); - } else if ($file === -1) { - $this->error = 'Timed out while reading data from ' . $url; - return array(); - } else if ($file == '') { - if ($tracks == '') { - $this->error = 'No data returned from ' . $url; - return array(); - } else { - break; - } - } - - // check for API error message - if (strpos($file, chr(27)) !== false) { - $this->error = 'Cannot decode searched track info from ' . $url; - return array(); - } - - // check for results - if ($file != '') { - // no line break before first page - $tracks .= ($page++ > 0 ? "\r\n" : '') . $file; - } else { - $done = true; - } - } - - // return list of tracks as array of strings - if ($tracks != '') - return explode("\r\n", $tracks); - else - return array(); - } // getList - - // Simple HTTP Get function with timeout - // ok: return string || error: return false || timeout: return -1 - private function get_file($url) { - - $url = parse_url($url); - $port = isset($url['port']) ? $url['port'] : 80; - $query = isset($url['query']) ? "?" . $url['query'] : ""; - - $fp = @fsockopen($url['host'], $port, $errno, $errstr, 4); - if (!$fp) - return false; - - fwrite($fp, 'GET ' . $url['path'] . $query . " HTTP/1.0\r\n" . - 'Host: ' . $url['host'] . "\r\n" . - 'User-Agent: TMXInfoSearcher (' . PHP_OS . ")\r\n\r\n"); - stream_set_timeout($fp, 2); - $res = ''; - $info['timed_out'] = false; - while (!feof($fp) && !$info['timed_out']) { - $res .= fread($fp, 512); - $info = stream_get_meta_data($fp); - } - fclose($fp); - - if ($info['timed_out']) { - return -1; - } else { - if (substr($res, 9, 3) != '200') - return false; - $page = explode("\r\n\r\n", $res, 2); - return trim($page[1]); - } - } // get_file -} // class TMXInfoSearcher - - -class TMXInfo { - - public $section, $prefix, $id, $name, $userid, $author, - $type, $envir, $mood, $style, $routes, $length, $diffic, - $lbrating, $awards, $comments, $custimg, $game, $uploaded, $updated, - $pageurl, $replayid, $replayurl, $imageurl, $thumburl, $dloadurl; - - /** - * Returns track object with a hell of a lot of data from TMX track string - * - * @param String $section - * TMX section - * @param String $prefix - * TMX URL prefix - * @param String $track - * The TMX track string from TMXInfoSearcher - * @return TMXInfo - */ - public function TMXInfo($section, $prefix, $track) { - - $this->section = $section; - $this->prefix = $prefix; - if ($track) { - // separate columns on Tabs - $fields = explode(chr(9), $track); - - $this->id = $fields[0]; - $this->name = $fields[1]; - $this->userid = $fields[2]; - $this->author = $fields[3]; - $this->type = $fields[4]; - $this->envir = $fields[5]; - $this->mood = $fields[6]; - $this->style = $fields[7]; - $this->routes = $fields[8]; - $this->length = $fields[9]; - $this->diffic = $fields[10]; - $this->lbrating = ($fields[11] > 0 ? $fields[11] : 'Classic!'); - $this->awards = $fields[12]; - $this->comments = $fields[13]; - $this->custimg = (strtolower($fields[14]) == 'true'); - $this->game = $fields[15]; - $this->replayid = $fields[16]; - // unknown = $fields[17-21]; - $this->uploaded = $fields[22]; - $this->updated = $fields[23]; - - $this->pageurl = 'http://' . $prefix . '.tm-exchange.com/main.aspx?action=trackshow&id=' . $this->id; - $this->imageurl = 'http://' . $prefix . '.tm-exchange.com/get.aspx?action=trackscreen&id=' . $this->id; - $this->thumburl = 'http://' . $prefix . '.tm-exchange.com/get.aspx?action=trackscreensmall&id=' . $this->id; - $this->dloadurl = 'http://' . $prefix . '.tm-exchange.com/get.aspx?action=trackgbx&id=' . $this->id; - - if ($this->replayid > 0) { - $this->replayurl = 'http://' . $prefix . '.tm-exchange.com/get.aspx?action=recordgbx&id=' . $this->replayid; - } else { - $this->replayurl = ''; - } - } - } // TMXInfo -} // class TMXInfo -?> diff --git a/xaseco/includes/types.inc.php b/xaseco/includes/types.inc.php deleted file mode 100644 index 277803a..0000000 --- a/xaseco/includes/types.inc.php +++ /dev/null @@ -1,511 +0,0 @@ -record_list = array(); - $this->max = $limit; - } - - function setLimit($limit) { - $this->max = $limit; - } - - function getRecord($rank) { - if (isset($this->record_list[$rank])) - return $this->record_list[$rank]; - else - return false; - } - - function setRecord($rank, $record) { - if (isset($this->record_list[$rank])) { - return $this->record_list[$rank] = $record; - } else { - return false; - } - } - - function moveRecord($from, $to) { - moveArrayElement($this->record_list, $from, $to); - } - - function addRecord($record, $rank = -1) { - - // if no rank was set for this record, then put it to the end of the list - if ($rank == -1) { - $rank = count($this->record_list); - } - - // do not insert a record behind the border of the list - if ($rank >= $this->max) return; - - // do not insert a record with no score - if ($record->score <= 0) return; - - // if the given object is a record - if (get_class($record) == 'Record') { - - // if records are getting too much, drop the last from the list - if (count($this->record_list) >= $this->max) { - array_pop($this->record_list); - } - - // insert the record at the specified position - return insertArrayElement($this->record_list, $record, $rank); - } - } - - function delRecord($rank = -1) { - - // do not remove a record outside the current list - if ($rank < 0 || $rank >= count($this->record_list)) return; - - // remove the record from the specified position - return removeArrayElement($this->record_list, $rank); - } - - function count() { - return count($this->record_list); - } - - function clear() { - $this->record_list = array(); - } -} // class RecordList - - -/** - * Structure of a Player. - * Can be instantiated with an RPC 'GetPlayerInfo' or - * 'GetDetailedPlayerInfo' response. - */ -class Player { - var $id; - var $pid; - var $login; - var $nickname; - var $teamname; - var $ip; - var $client; - var $ipport; - var $zone; - var $nation; - var $prevstatus; - var $isspectator; - var $isofficial; - var $rights; - var $language; - var $avatar; - var $teamid; - var $unlocked; - var $ladderrank; - var $ladderscore; - var $created; - var $wins; - var $newwins; - var $timeplayed; - var $tracklist; - var $playerlist; - var $msgs; - var $pmbuf; - var $mutelist; - var $mutebuf; - var $style; - var $panels; - var $speclogin; - var $dedirank; - - function getWins() { - return $this->wins + $this->newwins; - } - - function getTimePlayed() { - return $this->timeplayed + $this->getTimeOnline(); - } - - function getTimeOnline() { - return $this->created > 0 ? time() - $this->created : 0; - } - - // instantiates the player with an RPC response - function Player($rpc_infos = null) { - $this->id = 0; - if ($rpc_infos) { - $this->pid = $rpc_infos['PlayerId']; - $this->login = $rpc_infos['Login']; - $this->nickname = $rpc_infos['NickName']; - $this->ipport = $rpc_infos['IPAddress']; - $this->ip = preg_replace('/:\d+/', '', $rpc_infos['IPAddress']); // strip port - $this->prevstatus = false; - $this->isspectator = $rpc_infos['IsSpectator']; - $this->isofficial = $rpc_infos['IsInOfficialMode']; - $this->teamname = $rpc_infos['LadderStats']['TeamName']; - if (isset($rpc_infos['Nation'])) { // TMN (TMS/TMO?) - $this->zone = $rpc_infos['Nation']; - $this->nation = $rpc_infos['Nation']; - $this->ladderrank = $rpc_infos['LadderStats']['Ranking']; - $this->ladderscore = $rpc_infos['LadderStats']['Score']; - $this->client = ''; - $this->rights = false; - $this->language = ''; - $this->avatar = ''; - $this->teamid = 0; - } else { // TMF - $this->zone = substr($rpc_infos['Path'], 6); // strip 'World|' - $this->nation = explode('|', $rpc_infos['Path']); - if (isset($this->nation[1])) - $this->nation = $this->nation[1]; - else - $this->nation = ''; - $this->ladderrank = $rpc_infos['LadderStats']['PlayerRankings'][0]['Ranking']; - $this->ladderscore = round($rpc_infos['LadderStats']['PlayerRankings'][0]['Score'], 2); - $this->client = $rpc_infos['ClientVersion']; - $this->rights = ($rpc_infos['OnlineRights'] == 3); // United = true - $this->language = $rpc_infos['Language']; - $this->avatar = $rpc_infos['Avatar']['FileName']; - $this->teamid = $rpc_infos['TeamId']; - } - $this->created = time(); - } else { - // set defaults - $this->pid = 0; - $this->login = ''; - $this->nickname = ''; - $this->ipport = ''; - $this->ip = ''; - $this->prevstatus = false; - $this->isspectator = false; - $this->isofficial = false; - $this->teamname = ''; - $this->zone = ''; - $this->nation = ''; - $this->ladderrank = 0; - $this->ladderscore = 0; - $this->rights = false; - $this->created = 0; - } - $this->wins = 0; - $this->newwins = 0; - $this->timeplayed = 0; - $this->unlocked = false; - $this->pmbuf = array(); - $this->mutelist = array(); - $this->mutebuf = array(); - $this->style = array(); - $this->panels = array(); - $this->speclogin = ''; - $this->dedirank = 0; - } -} // class Player - -/** - * Manages players on the server. - * Add player and remove them. - */ -class PlayerList { - var $player_list; - - // instantiates the empty player list - function PlayerList() { - $this->player_list = array(); - } - - function nextPlayer() { - if (is_array($this->player_list)) { - $player_item = current($this->player_list); - next($this->player_list); - return $player_item; - } else { - $this->resetPlayers(); - return false; - } - } - - function resetPlayers() { - if (is_array($this->player_list)) { - reset($this->player_list); - } - } - - function addPlayer($player) { - if (get_class($player) == 'Player' && $player->login != '') { - $this->player_list[$player->login] = $player; - return true; - } else { - return false; - } - } - - function removePlayer($login) { - if (isset($this->player_list[$login])) { - $player = $this->player_list[$login]; - unset($this->player_list[$login]); - } else { - $player = false; - } - return $player; - } - - function getPlayer($login) { - if (isset($this->player_list[$login])) - return $this->player_list[$login]; - else - return false; - } -} // class PlayerList - - -/** - * Can store challenge information. - * You can instantiate with an RPC 'GetChallengeInfo' response. - */ -class Challenge { - var $id; - var $name; - var $uid; - var $filename; - var $author; - var $environment; - var $mood; - var $bronzetime; - var $silvertime; - var $goldtime; - var $authortime; - var $copperprice; - var $laprace; - var $forcedlaps; - var $nblaps; - var $nbchecks; - var $score; - var $starttime; - var $gbx; - var $tmx; - - // instantiates the challenge with an RPC response - function Challenge($rpc_infos = null) { - $this->id = 0; - if ($rpc_infos) { - $this->name = stripNewlines($rpc_infos['Name']); - $this->uid = $rpc_infos['UId']; - $this->filename = $rpc_infos['FileName']; - $this->author = $rpc_infos['Author']; - $this->environment = $rpc_infos['Environnement']; - $this->mood = $rpc_infos['Mood']; - $this->bronzetime = $rpc_infos['BronzeTime']; - $this->silvertime = $rpc_infos['SilverTime']; - $this->goldtime = $rpc_infos['GoldTime']; - $this->authortime = $rpc_infos['AuthorTime']; - $this->copperprice = $rpc_infos['CopperPrice']; - $this->laprace = $rpc_infos['LapRace']; - $this->forcedlaps = 0; - if (isset($rpc_infos['NbLaps'])) - $this->nblaps = $rpc_infos['NbLaps']; - else - $this->nblaps = 0; - if (isset($rpc_infos['NbCheckpoints'])) - $this->nbchecks = $rpc_infos['NbCheckpoints']; - else - $this->nbchecks = 0; - } else { - // set defaults - $this->name = 'undefined'; - } - } -} // class Challenge - - -/** - * Contains information about an RPC call. - */ -class RPCCall { - var $index; - var $id; - var $callback; - var $call; - - // instantiates the RPC call with the parameters - function RPCCall($id, $index, $callback, $call) { - $this->id = $id; - $this->index = $index; - $this->callback = $callback; - $this->call = $call; - } -} // class RPCCall - - -/** - * Contains information about a chat command. - */ -class ChatCommand { - var $name; - var $help; - var $isadmin; - - // instantiates the chat command with the parameters - function ChatCommand($name, $help, $isadmin) { - $this->name = $name; - $this->help = $help; - $this->isadmin = $isadmin; - } -} // class ChatCommand - - -/** - * Stores basic information of the server XASECO is running on. - */ -class Server { - var $id; - var $name; - var $game; - var $serverlogin; - var $nickname; - var $zone; - var $rights; - var $ip; - var $port; - var $timeout; - var $version; - var $build; - var $packmask; - var $laddermin; - var $laddermax; - var $login; - var $pass; - var $maxplay; - var $maxspec; - var $challenge; - var $records; - var $players; - var $mutelist; - var $gamestate; - var $gameinfo; - var $gamedir; - var $trackdir; - var $votetime; - var $voterate; - var $uptime; - var $starttime; - var $isrelay; - var $relaymaster; - var $relayslist; - - // game states - const RACE = 'race'; - const SCORE = 'score'; - - function getGame() { - switch ($this->game) { - case 'TmForever': - return 'TMF'; - case 'TmNationsESWC': - return 'TMN'; - case 'TmSunrise': - return 'TMS'; - case 'TmOriginal': - return 'TMO'; - default: // TMU was never supported - return 'Unknown'; - } - } - - // instantiates the server with default parameters - function Server($ip, $port, $login, $pass) { - $this->ip = $ip; - $this->port = $port; - $this->login = $login; - $this->pass = $pass; - $this->starttime = time(); - } -} // class Server - -/** - * Contains information to the current game which is played. - */ -class Gameinfo { - var $mode; - var $numchall; - var $rndslimit; - var $timelimit; - var $teamlimit; - var $lapslimit; - var $cuplimit; - var $forcedlaps; - - const RNDS = 0; - const TA = 1; - const TEAM = 2; - const LAPS = 3; - const STNT = 4; - const CUP = 5; - - // returns current game mode as string - function getMode() { - switch ($this->mode) { - case self::RNDS: - return 'Rounds'; - case self::TA: - return 'TimeAttack'; - case self::TEAM: - return 'Team'; - case self::LAPS: - return 'Laps'; - case self::STNT: - return 'Stunts'; - case self::CUP: - return 'Cup'; - default: - return 'Undefined'; - } - } - - // instantiates the game info with an RPC response - function Gameinfo($rpc_infos = null) { - if ($rpc_infos) { - $this->mode = $rpc_infos['GameMode']; - $this->numchall = $rpc_infos['NbChallenge']; - if (isset($rpc_infos['RoundsUseNewRules']) && $rpc_infos['RoundsUseNewRules']) - $this->rndslimit = $rpc_infos['RoundsPointsLimitNewRules']; - else - $this->rndslimit = $rpc_infos['RoundsPointsLimit']; - $this->timelimit = $rpc_infos['TimeAttackLimit']; - if (isset($rpc_infos['TeamUseNewRules']) && $rpc_infos['TeamUseNewRules']) - $this->teamlimit = $rpc_infos['TeamPointsLimitNewRules']; - else - $this->teamlimit = $rpc_infos['TeamPointsLimit']; - $this->lapslimit = $rpc_infos['LapsTimeLimit']; - if (isset($rpc_infos['CupPointsLimit'])) - $this->cuplimit = $rpc_infos['CupPointsLimit']; - if (isset($rpc_infos['RoundsForcedLaps'])) - $this->forcedlaps = $rpc_infos['RoundsForcedLaps']; - else - $this->forcedlaps = 0; - } else { - $this->mode = -1; - } - } -} // class Gameinfo -?> diff --git a/xaseco/includes/urlsafebase64.php b/xaseco/includes/urlsafebase64.php deleted file mode 100644 index 958cd7b..0000000 --- a/xaseco/includes/urlsafebase64.php +++ /dev/null @@ -1,26 +0,0 @@ - diff --git a/xaseco/includes/web_access.inc.php b/xaseco/includes/web_access.inc.php deleted file mode 100644 index 174b7e3..0000000 --- a/xaseco/includes/web_access.inc.php +++ /dev/null @@ -1,1341 +0,0 @@ -request($url, array('func_name',xxx), $datas, $is_xmlrpc, $keepalive_min_timeout); -// $url: the web script URL. -// $datas: string to send in http body (xml, xml_rpc or POST data) -// $is_xmlrpc: true if it's an xml or xml-rpc request, false if it's a -// standard html GET or POST -// $keepalive_min_timeout: minimal value of server keepalive timeout to -// send a keepalive request, -// else make a request with close connection. -// func_name is the callback function name, which will be called this way: -// func_name(array('Code'=>code,'Reason'=>reason,'Headers'=>headers,'Message'=>message), xxx) -// where: -// xxx is the same as given previously in callback description. -// code is the returned http code -// (http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6) -// reason is the returned http reason -// (http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6) -// headers are the http headers of the reply -// message is the returned text body -// -// IMPORTANT: to have this work, the main part of your program must include a -// $_webaccess->select() call periodically, which work exactly like stream_select(). -// This is because the send and receive are asynchronous and will be completed -// later in the select() when datas are ready to be received and sent. - - -// This class can be used to make a synchronous query too. For that use null -// for the callback, so make the request this way: -// $response = $_webaccess->request($url, null, $datas, $is_xmlrpc, $keepalive_min_timeout); -// where $response is an array('Code'=>code,'Reason'=>reason,'Headers'=>headers,'Message'=>message) -// like the one passed to the callback function of the asynchronous request. -// If you use only synchronous queries then there is no need to call select() -// as the function will return when the reply will be fully returned. -// If the connection itself fail, the array response will include a 'Error' string. - - -// Other functions: -// list($host, $port, $path) = getHostPortPath($url); -// gzdecode() workaround - - -global $_web_access_compress_xmlrpc_request, $_web_access_compress_reply, - $_web_access_keepalive, $_web_access_keepalive_timeout, - $_web_access_keepalive_max, $_web_access_retry_timeout, - $_web_access_retry_timeout_max, $_web_access_post_xmlrpc; - - -// Will compress xmlrpc request ('never','accept','force','force-gzip','force-deflate') -// If set to 'accept' the first request will be made without, and the eventual -// 'Accept-Encoding' in reply will permit to decide if request compression can -// be used (and if gzip or deflate) -$_web_access_compress_xmlrpc_request = 'accept'; - -// Will ask server for compressed reply (false, true) -// If true then will add a 'Accept-Encoding' header to tell the server to -// compress the reply if it supports it. -$_web_access_compress_reply = true; - - -// Keep alive connection ? else close it after the reply. -// Unless false, first request will be with keepalive, to get server timeout -// and max values after timeout will be compared with the request -// $keepalive_min_timeout value to decide if keepalive have to be used or not. -// Note that Apache2 timeout is short (about 15s). -// The classes will open, re-open or use existing connection as needed. -$_web_access_keepalive = true; -// timeout(s) without request before close, for keepalive -$_web_access_keepalive_timeout = 600; -// max requests before close, for keepalive -$_web_access_keepalive_max = 2000; - - -// For asynchronous call, in case of error, timeout before retrying. -// It will be x2 for each error (on request or auto retry) until max, -// then stop automatic retry, and next request calls will return false. -// When stopped, a retry() or synchronous request will force a retry. -$_web_access_retry_timeout = 20; -$_web_access_retry_timeout_max = 5*60; - - -// Use text/html with xmlrpc=, instead of of pure text/xml request (false, true) -// Standard xml-rpc use pure text/xml request, where the xml is simply the body -// of the http request (and it's how the xml-rpc reply will be made). As a -// facility Dedimania also supports to get the xml in a html GET or POST, -// where xmlrpc= will contain a urlsafe base64 of the xml. Default to false, -// so use pure text/xml. -$_web_access_post_xmlrpc = false; - -// Note that in each request the text/xml or xmlrpc= will be used only if -// $is_xmlrpc is true. If false then the request will be a standard -// application/x-www-form-urlencoded html GET or POST request; in that case -// you have to build the URL (GET) and/or body data (POST) yourself. -// If $is_xmlrpc is a string, then it's used as the Content-type: value. - - -require_once('includes/urlsafebase64.php'); - -class Webaccess { - - var $_WebaccessList; - - function Webaccess() { - $this->_WebaccessList = array(); - } - - - function request($url, $callback, $datas, $is_xmlrpc = false, $keepalive_min_timeout = 300, $opentimeout = 3, $waittimeout = 5, $agent = 'XMLaccess') { - global $aseco, $_web_access_keepalive, $_web_access_keepalive_timeout, $_web_access_keepalive_max; - - list($host, $port, $path) = getHostPortPath($url); - - if ($host === false) - $aseco->console('Webaccess request(): Bad URL: ' . $url); - - else { - $server = $host . ':' . $port; - // create object if needed - if (!isset($this->_WebaccessList[$server]) || $this->_WebaccessList[$server] === null) { - $this->_WebaccessList[$server] = new WebaccessUrl($this, $host, $port, - $_web_access_keepalive, - $_web_access_keepalive_timeout, - $_web_access_keepalive_max, - $agent); - } - - // increase the default timeout for sync/wait request - if ($callback == null && $waittimeout == 5) - $waittimeout = 12; - - // call request - if ($this->_WebaccessList[$server] !== null) { - $query = array('Path' => $path, - 'Callback' => $callback, - 'QueryData' => $datas, - 'IsXmlrpc' => $is_xmlrpc, - 'KeepaliveMinTimeout' => $keepalive_min_timeout, - 'OpenTimeout' => $opentimeout, - 'WaitTimeout' => $waittimeout - ); - - return $this->_WebaccessList[$server]->request($query); - } - } - return false; - } // request - - - function retry($url) { - global $aseco; - - list($host, $port, $path) = getHostPortPath($url); - if ($host === false) { - $aseco->console('Webaccess retry(): Bad URL: ' . $url); - } else { - $server = $host . ':' . $port; - if (isset($this->_WebaccessList[$server])) - $this->_WebaccessList[$server]->retry(); - } - } // retry - - - function select(&$read, &$write, &$except, $tv_sec, $tv_usec = 0) { - - $timeout = (int)($tv_sec*1000000 + $tv_usec); - if ($read == null) - $read = array(); - if ($write == null) - $write = array(); - if ($except == null) - $except = array(); - - $read = $this->_getWebaccessReadSockets($read); - $write = $this->_getWebaccessWriteSockets($write); - //$except = $this->_getWebaccessReadSockets($except); - - //print_r($this->_WebaccessList); - //print_r($read); - //print_r($write); - //print_r($except); - - // if no socket to select then return - if (count($read) + count($write) + count($except) == 0) { - // sleep the asked timeout... - if ($timeout > 1000) - usleep($timeout); - return 0; - } - - $utime = (int)(microtime(true)*1000000); - $nb = @stream_select($read, $write, $except, $tv_sec, $tv_usec); - if ($nb === false) { - // in case stream_select "forgot" to wait, sleep the remaining asked timeout... - $dtime = (int)(microtime(true)*1000000) - $utime; - $timeout -= $dtime; - if ($timeout > 1000) - usleep($timeout); - return false; - } - - $this->_manageWebaccessSockets($read, $write, $except); - // workaround for stream_select bug with amd64, replace $nb with sum of arrays - return count($read) + count($write) + count($except); - } // select - - - private function _manageWebaccessSockets(&$receive, &$send, &$except) { - - // send pending data on all webaccess sockets - if (is_array($send) && count($send) > 0) { - foreach ($send as $key => $socket) { - $i = $this->_findWebaccessSocket($socket); - if ($i !== false) { - if (isset($this->_WebaccessList[$i]->_spool[0]['State']) && - $this->_WebaccessList[$i]->_spool[0]['State'] == 'OPEN') - $this->_WebaccessList[$i]->_open(); - else - $this->_WebaccessList[$i]->_send(); - unset($send[$key]); - } - } - } - - // read data from all needed webaccess sockets - if (is_array($receive) && count($receive) > 0) { - foreach ($receive as $key => $socket) { - $i = $this->_findWebaccessSocket($socket); - if ($i !== false) { - $this->_WebaccessList[$i]->_receive(); - unset($receive[$key]); - } - } - } - } // _manageWebaccessSockets - - - private function _findWebaccessSocket($socket) { - - foreach ($this->_WebaccessList as $key => $wau) { - if ($wau->_socket == $socket) - return $key; - } - return false; - } // _findWebaccessSocket - - - private function _getWebaccessReadSockets($socks) { - - foreach ($this->_WebaccessList as $key => $wau) { - if ($wau->_state == 'OPENED' && $wau->_socket) - $socks[] = $wau->_socket; - } - return $socks; - } // _getWebaccessReadSockets - - - private function _getWebaccessWriteSockets($socks) { - - foreach ($this->_WebaccessList as $key => $wau) { - if (isset($wau->_spool[0]['State']) && - ($wau->_spool[0]['State'] == 'OPEN' || - $wau->_spool[0]['State'] == 'BAD' || - $wau->_spool[0]['State'] == 'SEND')) { - - if (($wau->_state == 'CLOSED' || $wau->_state == 'BAD') && !$wau->_socket) - $wau->_open(); - - if ($wau->_state == 'OPENED' && $wau->_socket) - $socks[] = $wau->_socket; - } - } - return $socks; - } // _getWebaccessWriteSockets - - - function getAllSpools() { - - $num = 0; - $bad = 0; - foreach ($this->_WebaccessList as $key => $wau) { - if ($wau->_state == 'OPENED' || $wau->_state == 'CLOSED') - $num += count($wau->_spool); - elseif ($wau->_state == 'BAD') - $bad += count($wau->_spool); - } - return array($num, $bad); - } // getAllSpools -} // class Webaccess - - -// useful data to handle received headers -$_wa_header_separator = array('cookie' => ';', 'set-cookie' => ';'); -$_wa_header_multi = array('set-cookie' => true); - - -class WebaccessUrl { - //----------------------------- - // Fields - //----------------------------- - - var $wa; - var $_host; - var $_port; - var $_compress_request; - var $_socket; - var $_state; - var $_keepalive; - var $_keepalive_timeout; - var $_keepalive_max; - var $_serv_keepalive_timeout; - var $_serv_keepalive_max; - var $_spool; - var $_wait; - var $_response; - var $_query_num; - var $_request_time; - var $_cookies; - var $_webaccess_str; - var $_bad_time; - var $_bad_timeout; - var $_read_time; - var $_agent; - - // $_state values: - // 'OPENED' : socket is opened - // 'CLOSED' : socket is closed (asked, completed, or closed by server) - // 'BAD' : socket is closed, bad/error or beginning state - - // $query['State'] values: (note: $query is added in $_spool, so $this->_spool[0] is the first $query to handle) - // 'BAD' : bad/error or beginning state - // 'OPEN' : should prepare request data then send them - // 'SEND' : request data are prepared, send them - // 'RECEIVE': request data are sent, receive reply data - // 'DONE' : request completed - - //----------------------------- - // Methods - //----------------------------- - - function WebaccessUrl(&$wa, $host, $port, $keepalive = true, $keepalive_timeout = 600, $keepalive_max = 300, $agent = 'XMLaccess') { - global $_web_access_compress_xmlrpc_request, $_web_access_retry_timeout; - - $this->wa = &$wa; - $this->_host = $host; - $this->_port = $port; - $this->_webaccess_str = 'Webaccess (' . $this->_host . ':' . $this->_port . '): '; - $this->_agent = $agent; - - // request compression setting - if ($_web_access_compress_xmlrpc_request == 'accept') - $this->_compress_request = 'accept'; - elseif ($_web_access_compress_xmlrpc_request == 'force') { - if (function_exists('gzencode')) - $this->_compress_request = 'gzip'; - elseif (function_exists('gzdeflate')) - $this->_compress_request = 'deflate'; - else - $this->_compress_request = false; - } - elseif ($_web_access_compress_xmlrpc_request == 'force-gzip' && function_exists('gzencode')) - $this->_compress_request = 'gzip'; - elseif ($_web_access_compress_xmlrpc_request == 'force-deflate' && function_exists('gzdeflate')) - $this->_compress_request = 'deflate'; - else - $this->_compress_request = false; - - $this->_socket = null; - $this->_state = 'CLOSED'; - $this->_keepalive = $keepalive; - $this->_keepalive_timeout = $keepalive_timeout; - $this->_keepalive_max = $keepalive_max; - $this->_serv_keepalive_timeout = $keepalive_timeout; - $this->_serv_keepalive_max = $keepalive_max; - $this->_spool = array(); - $this->_wait = false; - $this->_response = ''; - $this->_query_num = 0; - $this->_query_time = time(); - $this->_cookies = array(); - $this->_bad_time = time(); - $this->_bad_timeout = 0; - $this->_read_time = 0; - } // WebaccessUrl - - - // put connection in BAD state - function _bad($errstr, $isbad = true) { - global $aseco, $_web_access_retry_timeout; - - $aseco->console($this->_webaccess_str . $errstr); - $this->infos(); - - if ($this->_socket) - @fclose($this->_socket); - $this->_socket = null; - - if ($isbad) { - if (isset($this->_spool[0]['State'])) - $this->_spool[0]['State'] = 'BAD'; - $this->_state = 'BAD'; - - $this->_bad_time = time(); - if ($this->_bad_timeout < $_web_access_retry_timeout) - $this->_bad_timeout = $_web_access_retry_timeout; - else - $this->_bad_timeout *= 2; - - } else { - if (isset($this->_spool[0]['State'])) - $this->_spool[0]['State'] = 'OPEN'; - $this->_state = 'CLOSED'; - } - $this->_callCallback($this->_webaccess_str . $errstr); - } // _bad - - - function retry() { - global $_web_access_retry_timeout; - - if ($this->_state == 'BAD') { - $this->_bad_time = time(); - $this->_bad_timeout = 0; - } - } // retry - - - //$query = array('Path' => $path, - // 'Callback' => $callback, - // 'QueryData' => $datas, - // 'IsXmlrpc' => $is_xmlrpc, - // 'KeepaliveMinTimeout' => $keepalive_min_timeout, - // 'OpenTimeout' => $opentimeout, - // 'WaitTimeout' => $waittimeout ); - // will add: 'State', 'HDatas', 'Datas', 'DatasSize', 'DatasSent', - // 'Response', 'ResponseSize', 'Headers', 'Close', 'Times' - function request(&$query) { - global $aseco, $_web_access_compress_reply, $_web_access_post_xmlrpc, $_web_access_retry_timeout, $_web_access_retry_timeout_max; - - $query['State'] = 'BAD'; - $query['HDatas'] = ''; - $query['Datas'] = ''; - $query['DatasSize'] = 0; - $query['DatasSent'] = 0; - $query['Response'] = ''; - $query['ResponseSize'] = 0; - $query['Headers'] = array(); - $query['Close'] = false; - $query['Times'] = array('open' => array(-1.0,-1.0), 'send' => array(-1.0,-1.0), 'receive' => array(-1.0,-1.0,0)); - - // if asynch, in error, and maximal timeout, then forget the request and return false - if (($query['Callback'] != null) && ($this->_state == 'BAD')) { - if ($this->_bad_timeout > $_web_access_retry_timeout_max) { - $aseco->console($this->_webaccess_str . 'Request refused for consecutive errors (' . $this->_bad_timeout . ' / ' . $_web_access_retry_timeout_max . ')'); - return false; - - } else { - // if not max then accept the request and try a request (minimum $_web_access_retry_timeout/2 after previous try) - $time = time(); - $timeout = ($this->_bad_timeout / 2) - ($time - $this->_bad_time); - if ($timeout < 0) - $timeout = 0; - $this->_bad_time = $time - $this->_bad_timeout + $timeout; - } - } - - // build data to send - if (($query['Callback'] == null) || (is_array($query['Callback']) && - isset($query['Callback'][0]) && - is_callable($query['Callback'][0]))) { - - if (is_string($query['QueryData']) && strlen($query['QueryData']) > 0) { - $msg = "POST " . $query['Path'] . " HTTP/1.1\r\n"; - $msg .= "Host: " . $this->_host . "\r\n"; - $msg .= "User-Agent: " . $this->_agent . "\r\n"; - $msg .= "Cache-Control: no-cache\r\n"; - - if ($_web_access_compress_reply) { - // ask compression of response if gzdecode() and/or gzinflate() is available - if (function_exists('gzdecode') && function_exists('gzinflate')) - $msg .= "Accept-Encoding: deflate, gzip\r\n"; - elseif (function_exists('gzdecode')) - $msg .= "Accept-Encoding: gzip\r\n"; - elseif (function_exists('gzinflate')) - $msg .= "Accept-Encoding: deflate\r\n"; - } - - //echo "\nData:\n\n" . $query['QueryData'] . "\n"; - - if ($query['IsXmlrpc'] === true) { - if ($_web_access_post_xmlrpc) { - $msg .= "Content-type: application/x-www-form-urlencoded; charset=UTF-8\r\n"; - - //echo "\n=========================== Data =================================\n\n" . $datas . "\n"; - //$d2 = urlsafe_base64_encode($datas); - //$d3 = urlsafe_base64_decode($d2); - //echo "\n--------------------------- Data ---------------------------------\n\n" . $d3 . "\n"; - - $query['QueryData'] = 'xmlrpc=' . urlsafe_base64_encode($query['QueryData']); - } - else { - $msg .= "Content-type: text/xml; charset=UTF-8\r\n"; - } - - if ($this->_compress_request == 'gzip' && function_exists('gzencode')) { - $msg .= "Content-Encoding: gzip\r\n"; - $query['QueryData'] = gzencode($query['QueryData']); - } elseif ($this->_compress_request == 'deflate' && function_exists('gzdeflate')) { - $msg .= "Content-Encoding: deflate\r\n"; - $query['QueryData'] = gzdeflate($query['QueryData']); - } - - } - elseif (is_string($query['IsXmlrpc'])) { - $msg .= "Content-type: " . $query['IsXmlrpc'] . "\r\n"; - $msg .= "Accept: */*\r\n"; - } else { - $msg .= "Content-type: application/x-www-form-urlencoded; charset=UTF-8\r\n"; - } - $msg .= "Content-length: " . strlen($query['QueryData']) . "\r\n"; - $query['HDatas'] = $msg; - $query['State'] = 'OPEN'; - $query['Retries'] = 0; - - //print_r($msg); - - // add the query in spool - $this->_spool[] = &$query; - - if ($query['Callback'] == null) { - $this->_wait = true; - $this->_open($query['OpenTimeout'], $query['WaitTimeout']); // wait more in not callback mode - $this->_spool = array(); - $this->_wait = false; - return $query['Response']; - } else - $this->_open(); - - } else { - $aseco->console($this->_webaccess_str . 'Bad data'); - return false; - } - - } else { - $aseco->console($this->_webaccess_str . 'Bad callback function: ' . $query['Callback']); - return false; - } - return true; - } // request - - - // open the socket (close it before if needed) - private function _open_socket($opentimeout = 0.0) { - global $aseco; - - // if socket not opened, then open it (2 tries) - if (!$this->_socket || $this->_state != 'OPENED') { - $time = microtime(true); - $this->_spool[0]['Times']['open'][0] = $time; - - $errno = ''; - $errstr = ''; - $this->_socket = @fsockopen($this->_host, $this->_port, $errno, $errstr, 1.8); // first try - if (!$this->_socket) { - - if ($opentimeout >= 1.0) - $this->_socket = @fsockopen($this->_host, $this->_port, $errno, $errstr, $opentimeout); - if (!$this->_socket) { - $this->_bad('Error(' . $errno . ') ' . $errstr . ', connection failed!'); - return; - } - } - $this->_state = 'OPENED'; - //$aseco->console($this->_webaccess_str . 'connection opened!'); - - // new socket connection: reset all pending request original values - for ($i = 0; $i < count($this->_spool); $i++) { - $this->_spool[$i]['State'] = 'OPEN'; - $this->_spool[$i]['DatasSent'] = 0; - $this->_spool[$i]['Response'] = ''; - $this->_spool[$i]['Headers'] = array(); - } - $this->_response = ''; - $this->_query_num = 0; - $this->_query_time = time(); - $time = microtime(true); - $this->_spool[0]['Times']['open'][1] = $time - $this->_spool[0]['Times']['open'][0]; - } - } // _open_socket - - - // open the connection (if not already opened) and send - function _open($opentimeout = 0.0, $waittimeout = 5.0) { - global $aseco, $_web_access_retry_timeout_max; - - if (!isset($this->_spool[0]['State'])) - return false; - $time = time(); - - // if asynch, in error, then return false until timeout or if >max) - if (!$this->_wait && $this->_state == 'BAD' && - (($this->_bad_timeout > $_web_access_retry_timeout_max) || - (($time - $this->_bad_time) < $this->_bad_timeout))) { - //$aseco->console($this->_webaccess_str . 'wait to retry (' . ($time - $this->_bad_time) . ' / ' . $this->_bad_timeout . ')'); - return false; - } - - // if the socket is probably in timeout, close it - if ($this->_socket && $this->_state == 'OPENED' && - ($this->_serv_keepalive_timeout <= ($time - $this->_query_time))) { - //$aseco->console($this->_webaccess_str . 'timeout, close it!'); - $this->_state = 'CLOSED'; - @fclose($this->_socket); - $this->_socket = null; - } - - // if socket is not opened, open it - if (!$this->_socket || $this->_state != 'OPENED') - $this->_open_socket($opentimeout); - - // if socket is open, send data if possible - if ($this->_socket) { - $this->_read_time = microtime(true); - - // if wait (synchronous query) then go on all pending write/read until the last - if ($this->_wait) { - @stream_set_timeout($this->_socket, 0, 10000); // timeout 10 ms - - while (isset($this->_spool[0]['State']) && - ($this->_spool[0]['State'] == 'OPEN' || - $this->_spool[0]['State'] == 'SEND' || - $this->_spool[0]['State'] == 'RECEIVE')) { - //echo 'State=' . $this->_spool[0]['State'] . " (" . count($this->_spool) . ")\n"; - if (!$this->_socket || $this->_state != 'OPENED') - $this->_open_socket($opentimeout); - - if ($this->_spool[0]['State'] == 'OPEN') { - $time = microtime(true); - $this->_spool[0]['Times']['send'][0] = $time; - $this->_send($waittimeout); - } - elseif ($this->_spool[0]['State'] == 'SEND') - $this->_send($waittimeout); - elseif ($this->_spool[0]['State'] == 'RECEIVE') - $this->_receive($waittimeout*4); - - // if timeout then error - if (($difftime = microtime(true) - $this->_read_time) > $waittimeout) { - $this->_bad('Request timeout, in _open (' . round($difftime) . ' > ' . $waittimeout . 's) state=' . $this->_spool[0]['State']); - return; - } - } - if ($this->_socket) - @stream_set_timeout($this->_socket, 0, 2000); // timeout 2 ms - } - - // else just do a send on the current - elseif (isset($this->_spool[0]['State']) && $this->_spool[0]['State'] == 'OPEN') { - @stream_set_timeout($this->_socket, 0, 2000); // timeout 2 ms - $this->_send($waittimeout); - } - } - } // _open - - - function _send($waittimeout = 20) { - - if (!isset($this->_spool[0]['State'])) - return; - - // if OPEN then become SEND - if ($this->_spool[0]['State'] == 'OPEN') { - - $this->_spool[0]['State'] = 'SEND'; - $time = microtime(true); - $this->_spool[0]['Times']['send'][0] = $time; - $this->_spool[0]['Response'] = ''; - $this->_spool[0]['Headers'] = array(); - - // finish to prepare header and data to send - $msg = $this->_spool[0]['HDatas']; - if (!$this->_keepalive || ($this->_spool[0]['KeepaliveMinTimeout'] < 0) || - ($this->_serv_keepalive_timeout < $this->_spool[0]['KeepaliveMinTimeout']) || - ($this->_serv_keepalive_max <= ($this->_query_num + 2)) || - ($this->_serv_keepalive_timeout <= (time() - $this->_query_time + 2))) { - $msg .= "Connection: close\r\n"; - $this->_spool[0]['Close'] = true; - } - else { - $msg .= 'Keep-Alive: timeout=' . $this->_keepalive_timeout . ', max=' . $this->_keepalive_max - . "\r\nConnection: Keep-Alive\r\n"; - } - - // add cookie header - if (count($this->_cookies) > 0) { - $cookie_msg = ''; - $sep = ''; - foreach ($this->_cookies as $name => $cookie) { - if (!isset($cookie['path']) || - strncmp($this->_spool[0]['Path'], $cookie['path'], strlen($cookie['path'])) == 0) { - $cookie_msg .= $sep . $name . '=' . $cookie['Value']; - $sep = '; '; - } - } - if ($cookie_msg != '') - $msg .= "Cookie: $cookie_msg\r\n"; - } - - $msg .= "\r\n"; - $msg .= $this->_spool[0]['QueryData']; - $this->_spool[0]['Datas'] = $msg; - $this->_spool[0]['DatasSize'] = strlen($msg); - $this->_spool[0]['DatasSent'] = 0; - - //print_r($msg); - } - - // if not SEND then stop - if ($this->_spool[0]['State'] != 'SEND') - return; - - do { - $sent = @stream_socket_sendto($this->_socket, - substr($this->_spool[0]['Datas'], $this->_spool[0]['DatasSent'], - ($this->_spool[0]['DatasSize'] - $this->_spool[0]['DatasSent']))); - if ($sent == false) { - - $time = microtime(true); - $this->_spool[0]['Times']['send'][1] = $time - $this->_spool[0]['Times']['send'][0]; - //var_dump($this->_spool[0]['Datas']); - $this->_bad('Error(' . $errno . ') ' . $errstr . ', could not send data! (' - . $sent . ' / ' . ($this->_spool[0]['DatasSize'] - $this->_spool[0]['DatasSent']) . ', ' - . $this->_spool[0]['DatasSent'] . ' / ' . $this->_spool[0]['DatasSize'] . ')'); - if ($this->_wait) - return; - break; - - } else { - $this->_spool[0]['DatasSent'] += $sent; - if ($this->_spool[0]['DatasSent'] >= $this->_spool[0]['DatasSize']) { - // All is sent, prepare to receive the reply - $this->_query_num++; - $this->_query_time = time(); - - $time = microtime(true); - $this->_spool[0]['Times']['send'][1] = $time - $this->_spool[0]['Times']['send'][0]; - - //@stream_set_blocking($this->_socket, 0); - $this->_spool[0]['State'] = 'RECEIVE'; - $this->_spool[0]['Times']['receive'][0] = $time; - } - - // if timeout then error - elseif (($difftime = microtime(true) - $this->_read_time) > $waittimeout) { - $this->_bad('Request timeout, in _send (' . round($difftime) . ' > ' . $waittimeout . 's)'); - } - } - - // if not async-callback then continue until all is sent - } while ($this->_wait && isset($this->_spool[0]['State']) && ($this->_spool[0]['State'] == 'SEND')); - } // _send - - - function _receive($waittimeout = 40) { - global $aseco, $_Webaccess_last_response; - - if (!$this->_socket || $this->_state != 'OPENED') - return; - - $state = false; - $time0 = microtime(true); - $timeout = ($this->_wait) ? $waittimeout : 0; - do { - $r = array($this->_socket); - $w = null; - $e = null; - $nb = @stream_select($r, $w, $e, $timeout); - if ($nb === 0) - $nb = count($r); - - while (!@feof($this->_socket) && $nb !== false && $nb > 0) { - $timeout = 0; - - if (count($r) > 0) { - $res = @stream_socket_recvfrom($this->_socket, 8192); - - if ($res == '') { // should not happen habitually, but... - break; - } elseif ($res !== false) { - $this->_response .= $res; - } - else { - if (isset($this->_spool[0])) { - $time = microtime(true); - $this->_spool[0]['Times']['receive'][1] = $time - $this->_spool[0]['Times']['receive'][0]; - } - $this->_bad('Error(' . $errno . ') ' . $errstr . ', could not read all data!'); - return; - } - } - - // if timeout then error - if (($difftime = microtime(true) - $this->_read_time) > $waittimeout) { - $this->_bad('Request timeout, in _receive (' . round($difftime) . ' > ' . $waittimeout . 's)'); - break; - } - - $r = array($this->_socket); - $w = null; - $e = null; - $nb = @stream_select($r, $w, $e, $timeout); - if ($nb === 0) - $nb = count($r); - } - - if (isset($this->_spool[0]['Times']['receive'][2])) { - $time = microtime(true); - $this->_spool[0]['Times']['receive'][2] += ($time - $time0); - } - - // get headers and full message - $state = $this->_handleHeaders(); - //echo "receive9\n"; - //var_dump($state); - - } while ($this->_wait && $state === false && $this->_socket && !@feof($this->_socket)); - - if (!isset($this->_spool[0]['State']) || $this->_spool[0]['State'] != 'RECEIVE') { - // in case of (probably keepalive) connection closed by server - if ($this->_socket && @feof($this->_socket)){ - //$aseco->console($this->_webaccess_str . 'Socket closed by server (' . $this->_host . ')'); - $this->_state = 'CLOSED'; - @fclose($this->_socket); - $this->_socket = null; - } - return; - } - - // terminated but incomplete! more than probably closed by server... - if ($state === false && $this->_socket && @feof($this->_socket)) { - $this->_state = 'CLOSED'; - if (isset($this->_spool[0])) { - $time = microtime(true); - $this->_spool[0]['State'] = 'OPEN'; - $this->_spool[0]['Times']['receive'][1] = $time - $this->_spool[0]['Times']['receive'][0]; - } - if (strlen($this->_response) > 0) // if not 0 sized then show error message - $this->_bad('Error: closed with incomplete read: re-open socket and re-send! (' . strlen($this->_response) . ')'); - else - $this->_bad('Closed by server when reading: re-open socket and re-send! (' . strlen($this->_response) . ')', false); - - $this->_spool[0]['Retries']++; - if ($this->_spool[0]['Retries'] > 2) { - // 3 tries failed, remove entry from spool - $aseco->console($this->_webaccess_str . "failed {$this->_spool[0]['Retries']} times: skip current request"); - array_shift($this->_spool); - } - - return; - } - - // reply is complete :) - if ($state === true) { - $this->_bad_timeout = 0; // reset error timeout - - $this->_spool[0]['Times']['receive'][1] = $time - $this->_spool[0]['Times']['receive'][0]; - $this->_spool[0]['State'] = 'DONE'; - - // store http/xml response in global $_Webaccess_last_response for debugging use - $_Webaccess_last_response = $this->_spool[0]['Response']; - //debugPrint('Webaccess->_receive - Response', $_Webaccess_last_response); - - // call callback func - $this->_callCallback(); - $this->_query_time = time(); - - if (!$this->_keepalive || $this->_spool[0]['Close']) { - //if ($this->_spool[0]['Close']) - // $aseco->console($this->_webaccess_str . 'close connection (asked in headers)'); - $this->_state = 'CLOSED'; - @fclose($this->_socket); - $this->_socket = null; - } - - $this->infos(); - - // request completed, remove it from spool! - array_shift($this->_spool); - } - } // _receive - - private function _callCallback($error = null) { - - // store optional error message - if ($error !== null) - $this->_spool[0]['Response']['Error'] = $error; - - // call callback func - if (isset($this->_spool[0]['Callback'])) { - $callbackinfo = $this->_spool[0]['Callback']; - if (isset($callbackinfo[0]) && is_callable($callbackinfo[0])) { - $callback_func = $callbackinfo[0]; - $callbackinfo[0] = $this->_spool[0]['Response']; - call_user_func_array($callback_func, $callbackinfo); - } - } - } - - private function _handleHeaders() { - global $aseco, $_wa_header_separator, $_wa_header_multi; - - if (!isset($this->_spool[0]['State'])) - return false; - - if (strlen($this->_response) < 8) // not enough data, continue read - return false; - if (strncmp($this->_response, 'HTTP/', 5) != 0) { // not HTTP! - $this->_bad("Error, not HTTP response ! **********\n" . substr($this->_response, 0, 300) . "\n***************\n"); - return null; - } - - // separate headers and data - $datas = explode("\r\n\r\n", $this->_response, 2); - if (count($datas) < 2) { - $datas = explode("\n\n", $this->_response, 2); - if (count($datas) < 2) { - $datas = explode("\r\r", $this->_response, 2); - if (count($datas) < 2) - return false; // not complete headers, continue read - } - } - - // get headers if not done on previous read - if (!isset($this->_spool[0]['Headers']['Command'][0])) { - // separate headers - //echo "Get Headers! (" . strlen($datas[0]) . ")\n"; - - $headers = array(); - $heads = explode("\n", str_replace("\r", "\n", str_replace("\r\n", "\n", $datas[0]))); - if (count($heads) < 2) { - $this->_bad("Error, uncomplete headers! **********\n" . $datas[0] . "\n***************\n"); - return null; - } - - $headers['Command'] = explode(' ', $heads[0], 3); - - for ($i = 1; $i < count($heads); $i++) { - $header = explode(':', $heads[$i], 2); - if (count($header) > 1) { - $headername = strtolower(trim($header[0])); - if (isset($_wa_header_separator[$headername])) - $sep = $_wa_header_separator[$headername]; - else - $sep = ','; - if (isset($_wa_header_multi[$headername]) && $_wa_header_multi[$headername]) { - if (!isset($headers[$headername])) - $headers[$headername] = array(); - $headers[$headername][] = explode($sep, trim($header[1])); - } else - $headers[$headername] = explode($sep, trim($header[1])); - } - } - - if (isset($headers['content-length'][0])) - $headers['content-length'][0] += 0; // convert to int - - $this->_spool[0]['Headers'] = $headers; - - // add header specific info in case of Dedimania reply - if (isset($headers['server'][0])) - $this->_webaccess_str = 'Webaccess (' . $this->_host . ':' . $this->_port .'/'. $headers['server'][0] . '): '; - } - else { - $headers = &$this->_spool[0]['Headers']; - //echo "Previous Headers! (" . strlen($datas[0]) . ")\n"; - } - - // get real message - $datasize = strlen($datas[1]); - if (isset($headers['content-length'][0]) && $headers['content-length'][0] >= 0) { - //echo 'mess_size0=' . strlen($datas[1]) . "\n"; - - if ($headers['content-length'][0] > $datasize) // incomplete message - return false; - - elseif ($headers['content-length'][0] < $datasize) { - $message = substr($datas[1], 0, $headers['content-length'][0]); - // remaining buffer for next reply - $this->_response = substr($datas[1], $headers['content-length'][0]); - } - else { - $message = $datas[1]; - $this->_response = ''; - } - $this->_spool[0]['ResponseSize'] = strlen($datas[0]) + 4 + $headers['content-length'][0]; - } - - // get real message when reply is chunked - elseif (isset($headers['transfer-encoding'][0]) && $headers['transfer-encoding'][0] == 'chunked') { - - // get chunk size and make message with chunks data - $size = -1; - $chunkpos = 0; - if (($datapos = strpos($datas[1], "\r\n", $chunkpos)) !== false) { - $message = ''; - $chunk = explode(';', substr($datas[1], $chunkpos, $datapos - $chunkpos)); - $size = hexdec($chunk[0]); - //debugPrint("Webaccess->Response - chunk - $chunkpos, $datapos, $size (" . strlen($datas[1]) . ")", $chunk); - while ($size > 0) { - if ($datapos + 2 + $size > $datasize) // incomplete message - return false; - $message .= substr($datas[1], $datapos + 2, $size); - $chunkpos = $datapos + 2 + $size + 2; - if (($datapos = strpos($datas[1], "\r\n", $chunkpos)) !== false) { - $chunk = explode(';', substr($datas[1], $chunkpos, $datapos - $chunkpos)); - $size = hexdec($chunk[0]); - } else - $size = -1; - //debugPrint("Webaccess->Response - chunk - $chunkpos, $datapos, $size (" . strlen($datas[1]) . ")", $chunk); - } - - } - if ($size < 0) // error bad size or incomplete message - return false; - - if (strpos($datas[1], "\r\n\r\n", $chunkpos) === false) // incomplete message: end is missing - return false; - - // store complete message size - $msize = strlen($message); - $headers['transfer-encoding'][1] = 'total_size=' . $msize; // add message size after 'chunked' for information - $this->_spool[0]['ResponseSize'] = strlen($datas[0]) + 4 + $msize; - - // after the message itself... - $message_end = explode("\r\n\r\n", substr($datas[1], $chunkpos), 2); - - // add end headers if any - $heads = explode("\n", str_replace("\r", "\n", str_replace("\r\n", "\n", $message_end[0]))); - for ($i = 1; $i < count($heads); $i++) { - $header = explode(':', $heads[$i], 2); - if (count($header) > 1) { - $headername = strtolower(trim($header[0])); - if (isset($_wa_header_separator[$headername])) - $sep = $_wa_header_separator[$headername]; - else - $sep = ','; - if (isset($_wa_header_multi[$headername]) && $_wa_header_multi[$headername]) { - if (!isset($headers[$headername])) - $headers[$headername] = array(); - $headers[$headername][] = explode($sep, trim($header[1])); - } else - $headers[$headername] = explode($sep, trim($header[1])); - } - } - $this->_spool[0]['Headers'] = $headers; - - // remaining buffer for next reply - if (isset($message_end[1]) && strlen($message_end[1]) > 0) { - $this->_response = $message_end[1]; - } - else - $this->_response = ''; - } - // no content-length and not chunked! - else { - $this->_bad("Error, bad http, no content-length and not chunked! **********\n" . $datas[0] . "\n***************\n"); - return null; - } - - //echo 'mess_size1=' . strlen($message) . "\n"; - - // if Content-Encoding: gzip or Content-Encoding: deflate - if (isset($headers['content-encoding'][0])) { - if ($headers['content-encoding'][0] == 'gzip') - $message = @gzdecode($message); - elseif ($headers['content-encoding'][0] == 'deflate') - $message = @gzinflate($message); - } - - // if Accept-Encoding: gzip or deflate - if ($this->_compress_request == 'accept' && isset($headers['accept-encoding'][0])) { - foreach ($headers['accept-encoding'] as $comp) { - $comp = trim($comp); - if ($comp == 'gzip' && function_exists('gzencode')) { - $this->_compress_request = 'gzip'; - break; - } - elseif ($comp == 'deflate' && function_exists('gzdeflate')) { - $this->_compress_request = 'deflate'; - break; - } - } - if ($this->_compress_request == 'accept') - $this->_compress_request = false; - - $aseco->console($this->_webaccess_str . 'send: ' . ($this->_compress_request === false ? 'no compression' : $this->_compress_request) - . ', receive: ' . (isset($headers['content-encoding'][0]) ? $headers['content-encoding'][0] : 'no compression')); - } - - // get cookies values - if (isset($headers['set-cookie'])) { - foreach ($headers['set-cookie'] as $cookie) { - $cook = explode('=', $cookie[0], 2); - if (count($cook) > 1) { - // set main cookie value - $cookname = trim($cook[0]); - if (!isset($this->_cookies[$cookname])) - $this->_cookies[$cookname] = array(); - $this->_cookies[$cookname]['Value'] = trim($cook[1]); - - // set cookie options - for ($i = 1; $i < count($cookie); $i++) { - $cook = explode('=', $cookie[$i], 2); - $cookarg = strtolower(trim($cook[0])); - if (isset($cook[1])) - $this->_cookies[$cookname][$cookarg] = trim($cook[1]); - } - } - } - //debugPrint('SET-COOKIES: ', $headers['set-cookie']); - //debugPrint('STORED COOKIES: ', $this->_cookies); - } - - // if the server reply ask to close, then close - if (!isset($headers['connection'][0]) || $headers['connection'][0] == 'close') { - //if (!$this->_spool[0]['Close']) - // $aseco->console($this->_webaccess_str . 'server ask to close connection'); - $this->_spool[0]['Close'] = true; - } - - // verify server keepalive value and use them if lower - if (isset($headers['keep-alive'])) { - $kasize = count($headers['keep-alive']); - for ($i = 0; $i < $kasize; $i++) { - $keep = explode('=', $headers['keep-alive'][$i], 2); - if (count($keep) > 1) - $headers['keep-alive'][trim(strtolower($keep[0]))] = intval(trim($keep[1])); - } - if (isset($headers['keep-alive']['timeout'])) - $this->_serv_keepalive_timeout = $headers['keep-alive']['timeout']; - if (isset($headers['keep-alive']['max'])) - $this->_serv_keepalive_max = $headers['keep-alive']['max']; - //$aseco->console($this->_webaccess_str . 'max=' . $this->_serv_keepalive_max . ', timeout=' . $this->_serv_keepalive_timeout . "\n"); - } - - // store complete reply message for the request - $this->_spool[0]['Response'] = array('Code' => intval($headers['Command'][1]), - 'Reason' => $headers['Command'][2], - 'Headers' => $headers, - 'Message' => $message - ); - //echo 'mess_size2=' . strlen($message) . "\n"; - return true; - } // _handleHeaders - - - function infos() { - global $aseco; - - $size = (isset($this->_spool[0]['Response']['Message'])) ? strlen($this->_spool[0]['Response']['Message']) : 0; - $msg = $this->_webaccess_str - . sprintf('[%s,%s]: %0.3f / %0.3f / %0.3f (%0.3f) / %d [%d,%d,%d]', - $this->_state, $this->_spool[0]['State'], - $this->_spool[0]['Times']['open'][1], - $this->_spool[0]['Times']['send'][1], - $this->_spool[0]['Times']['receive'][1], - $this->_spool[0]['Times']['receive'][2], - $this->_query_num, $this->_spool[0]['DatasSize'], - $size, $this->_spool[0]['ResponseSize']); - //$aseco->console($msg); - } // infos -} // class WebaccessUrl - - -// use: list($host, $port, $path) = getHostPortPath($url); -function getHostPortPath($url) { - - $http_pos = strpos($url, 'http://'); - if ($http_pos !== false) { - $script = explode('/', substr($url, $http_pos + 7), 2); - if (isset($script[1])) - $path = '/' . $script[1]; - else - $path = '/'; - $serv = explode(':', $script[0], 2); - $host = $serv[0]; - if (isset($serv[1])) - $port = (int)$serv[1]; - else - $port = 80; - if (strlen($host) > 2) - return array($host, $port, $path); - } - return array(false, false, false); -} // getHostPortPath - - -// gzdecode() workaround -if (!function_exists('gzdecode') && function_exists('gzinflate')) { - - function gzdecode($data) { - - $len = strlen($data); - if ($len < 18 || strcmp(substr($data, 0, 2), "\x1f\x8b")) { - return null; // Not GZIP format (See RFC 1952) - } - $method = ord(substr($data, 2, 1)); // Compression method - $flags = ord(substr($data, 3, 1)); // Flags - if ($flags & 31 != $flags) { - // Reserved bits are set -- NOT ALLOWED by RFC 1952 - return null; - } - // NOTE: $mtime may be negative (PHP integer limitations) - $mtime = unpack('V', substr($data, 4, 4)); - $mtime = $mtime[1]; - $xfl = substr($data, 8, 1); - $os = substr($data, 8, 1); - $headerlen = 10; - $extralen = 0; - $extra = ''; - if ($flags & 4) { - // 2-byte length prefixed EXTRA data in header - if ($len - $headerlen - 2 < 8) { - return false; // Invalid format - } - $extralen = unpack('v', substr($data, 8, 2)); - $extralen = $extralen[1]; - if ($len - $headerlen - 2 - $extralen < 8) { - return false; // Invalid format - } - $extra = substr($data, 10, $extralen); - $headerlen += $extralen + 2; - } - - $filenamelen = 0; - $filename = ''; - if ($flags & 8) { - // C-style string file NAME data in header - if ($len - $headerlen - 1 < 8) { - return false; // Invalid format - } - $filenamelen = strpos(substr($data, 8 + $extralen), chr(0)); - if ($filenamelen === false || $len - $headerlen - $filenamelen - 1 < 8) { - return false; // Invalid format - } - $filename = substr($data, $headerlen, $filenamelen); - $headerlen += $filenamelen + 1; - } - - $commentlen = 0; - $comment = ''; - if ($flags & 16) { - // C-style string COMMENT data in header - if ($len - $headerlen - 1 < 8) { - return false; // Invalid format - } - $commentlen = strpos(substr($data, 8 + $extralen + $filenamelen), chr(0)); - if ($commentlen === false || $len - $headerlen - $commentlen - 1 < 8) { - return false; // Invalid header format - } - $comment = substr($data, $headerlen, $commentlen); - $headerlen += $commentlen + 1; - } - - $headercrc = ''; - if ($flags & 1) { - // 2-bytes (lowest order) of CRC32 on header present - if ($len - $headerlen - 2 < 8) { - return false; // Invalid format - } - $calccrc = crc32(substr($data, 0, $headerlen)) & 0xffff; - $headercrc = unpack('v', substr($data, $headerlen, 2)); - $headercrc = $headercrc[1]; - if ($headercrc != $calccrc) { - return false; // Bad header CRC - } - $headerlen += 2; - } - - // GZIP FOOTER - These be negative due to PHP's limitations - $datacrc = unpack('V', substr($data, -8, 4)); - $datacrc = $datacrc[1]; - $isize = unpack('V', substr($data, -4)); - $isize = $isize[1]; - - // Perform the decompression: - $bodylen = $len - $headerlen - 8; - if ($bodylen < 1) { - // This should never happen - IMPLEMENTATION BUG! - return null; - } - $body = substr($data, $headerlen, $bodylen); - $data = ''; - if ($bodylen > 0) { - switch ($method) { - case 8: - // Currently the only supported compression method: - $data = gzinflate($body); - break; - default: - // Unknown compression method - return false; - } - } else { - // I'm not sure if zero-byte body content is allowed. - // Allow it for now... Do nothing... - } - - // Verify decompressed size and CRC32: - // NOTE: This may fail with large data sizes depending on how - // PHP's integer limitations affect strlen() since $isize - // may be negative for large sizes - if ($isize != strlen($data) || crc32($data) != $datacrc) { - // Bad format! Length or CRC doesn't match! - return false; - } - return $data; - } // gzdecode -} -?> diff --git a/xaseco/includes/xmlparser.inc.php b/xaseco/includes/xmlparser.inc.php deleted file mode 100644 index e53d80c..0000000 --- a/xaseco/includes/xmlparser.inc.php +++ /dev/null @@ -1,121 +0,0 @@ -stack = array(); - $this->struct = array(); - $this->utf8enc = $utf8enc; - - // create the parser - $this->parser = xml_parser_create(); - xml_set_object($this->parser, $this); - xml_set_element_handler($this->parser, 'openTag', 'closeTag'); - xml_set_character_data_handler($this->parser, 'tagData'); - - // load the xml file - if ($isfile) - $this->data = @file_get_contents($source); - else - $this->data = $source; - - // escape '&' characters - $this->data = str_replace('&', '', $this->data); - - // parse xml file - $parsed = xml_parse($this->parser, $this->data); - - // display errors - if (!$parsed) { - $code = xml_get_error_code($this->parser); - $err = xml_error_string($code); - $line = xml_get_current_line_number($this->parser); - trigger_error("[XML Error $code] $err on line $line", E_USER_WARNING); - return false; - } - return $this->struct; - } - - private function openTag($parser, $name, $attributes) { - $this->stack[] = $name; - $this->struct[$name] = ''; - } - - private function tagData($parser, $data) { - if (trim($data)) { - $index = $this->stack[count($this->stack)-1]; - // use raw, don't decode '+' into space - if ($this->utf8enc) - $this->struct[$index] .= rawurldecode($data); - else - $this->struct[$index] .= utf8_decode(rawurldecode($data)); - } - } - - private function closeTag($parser, $name) { - if (count($this->stack) > 1) { - $from = array_pop($this->stack); - $to = $this->stack[count($this->stack)-1]; - $this->struct[$to][$from][] = $this->struct[$from]; - unset($this->struct[$from]); - } - } - - /** - * Parses an array into an XML structure. - */ - function parseArray($array) { - $xmlstring = ''; - $xmlstring .= $this->parseArrayElements($array); - return $xmlstring; - } - - private function parseArrayElements($array, $opt_tag = '') { - - // read each element of the array - for ($i = 0; $i < count($array); $i++) { - - // check if array is associative - if (is_numeric(key($array))) { - $xml .= '<'.$opt_tag.'>'; - if (is_array(current($array))) { - $xml .= $this->parseArrayElements(current($array), key($array)); - } else { - // use raw, don't encode space into '+' - $xml .= rawurlencode(utf8_encode(current($array))); - } - $xml .= ''; - } else { - if (is_array(current($array))) { - $xml .= $this->parseArrayElements(current($array), key($array)); - } else { - $xml .= '<'.key($array).'>'; - // use raw, don't encode space into '+' - $xml .= rawurlencode(utf8_encode(current($array))); - $xml .= ''; - } - } - next($array); - } - return $xml; - } -} -?> diff --git a/xaseco/includes/xmlrpc_db.inc.php b/xaseco/includes/xmlrpc_db.inc.php deleted file mode 100644 index e19a930..0000000 --- a/xaseco/includes/xmlrpc_db.inc.php +++ /dev/null @@ -1,385 +0,0 @@ -_debug = 0; // max debug level = 3 - $this->_webaccess = $webaccess; - $this->_url = $url; - $this->_server = array('Game' => $game, - 'Login' => $login, - 'Password' => $password, - 'Tool' => $tool, - 'Version' => $version, - 'Nation' => $nation, - 'Packmask' => $packmask, - 'PlayersGame' => true - ); - $this->_auth_cb = array('xmlrpc_auth_cb'); - - $this->_bad = false; - $this->_bad_time = -1; - // in case webaccess URL connection was previously in error, ask to retry - $this->_webaccess->retry($this->_url); - - // prepare to add requests - $this->_initRequest(); - } // XmlrpcDB - - // change the packmask value - function setPackmask($packmask = '') { - - $this->_server['Packmask'] = $packmask; - } // setPackmask - - // is the connection in recurrent error? - function isBad() { - - return $this->_bad; - } // isBad - - // get time since the error state was set - function badTime() { - - return (time() - $this->_bad_time); - } // badTime - - // stop the bad state: will try again at next RequestWait(), - // sendRequestsWait() or sendRequests() - function retry() { - - $this->_bad = false; - $this->_bad_time = -1; - // set webaccess object to retry on that URL too - $this->_webaccess->retry($this->_url); - } // retry - - // clear all requests, and get them if asked - function clearRequests($get_requests = false) { - - if ($get_requests) { - $return = array($_requests, $_callbacks); - $this->_initRequest(); - return $return; - } - $this->_initRequest(); - } // clearRequests - - // add a request - function addRequest($callback, $method) { - - $args = func_get_args(); - $callback = array_shift($args); - $method = array_shift($args); - return $this->addRequestArray($callback, $method, $args); - } // addRequest - - // add a request - function addRequestArray($callback, $method, $args) { - - $this->_callbacks[] = $callback; - $this->_requests[] = array('methodName' => $method, 'params' => $args); - return count($this->_requests) - 1; - } // addRequestArray - - // send added requests, callbacks will be called when response come - function sendRequests() { - global $aseco; - - if (count($this->_callbacks) > 1) { - $this->addRequest(null, 'dedimania.WarningsAndTTR'); - $webdatas = $this->_makeXMLdatas(); - $response = $this->_webaccess->request($this->_url, - array( array($this, '_callCB'), $this->_callbacks, $this->_requests), - $webdatas, true); - $this->_initRequest(); - if ($response === false) { - if (!$this->_bad) { - $this->_bad = true; - $this->_bad_time = time(); - } - if ($this->_debug > 2) - $aseco->console_text('XmlrpcDB->sendRequests - this' . CRLF . print_r($this, true)); - return false; - } - } - return true; - } // sendRequests - - // send added requests, wait response, then call callbacks - function sendRequestsWait() { - global $aseco; - - if (count($this->_callbacks) > 1) { - $this->addRequest(null, 'dedimania.WarningsAndTTR'); - $webdatas = $this->_makeXMLdatas(); - $response = $this->_webaccess->request($this->_url, - null, $webdatas, true); - if ($response === false) { - if (!$this->_bad) { - $this->_bad = true; - $this->_bad_time = time(); - } - if ($this->_debug > 0) - $aseco->console_text('XmlrpcDB->sendRequestsWait - this' . CRLF . print_r($this, true)); - $this->_initRequest(); - return false; - } else { - $this->_callCB($response, $this->_callbacks, $this->_requests); - $this->_initRequest(); - } - } - return true; - } // sendRequestsWait - - // send a request, wait response, and return the response - function RequestWait($method) { - - $args = func_get_args(); - $method = array_shift($args); - return $this->RequestWaitArray($method, $args); - } // RequestWait - - // send a request, wait response, and return the response - function RequestWaitArray($method, $args) { - global $aseco; - - if ($this->sendRequestsWait() === false) { - if (!$this->_bad) { - $this->_bad = true; - $this->_bad_time = time(); - } - return false; - } - - $reqnum = $this->addRequestArray(null, $method, $args); - - $this->addRequest(null, 'dedimania.WarningsAndTTR'); - $webdatas = $this->_makeXMLdatas(); - $response = $this->_webaccess->request($this->_url, null, $webdatas, true); - if (isset($response['Message']) && is_string($response['Message'])) { - if ($this->_debug > 1) - $aseco->console_text('XmlrpcDB->RequestWaitArray() - response[Message]' . CRLF . print_r($response['Message'], true)); - - $xmlrpc_message = new IXR_Message($response['Message']); - if ($xmlrpc_message->parse() && $xmlrpc_message->messageType != 'fault') { - if ($this->_debug > 1) { - $aseco->console_text('XmlrpcDB->RequestWaitArray() - message' . CRLF . print_r($xmlrpc_message->message, true)); - $aseco->console_text('XmlrpcDB->RequestWaitArray() - params' . CRLF . print_r($xmlrpc_message->params, true)); - } - - //$datas = array('methodName' => $xmlrpc_message->methodName, 'params' => $xmlrpc_message->params); - $datas = $this->_makeResponseDatas($xmlrpc_message->methodName, $xmlrpc_message->params, $this->_requests); - } else { - if ($this->_debug > 0) - $aseco->console_text('XmlrpcDB->RequestWaitArray() - message fault' . CRLF . print_r($xmlrpc_message->message, true)); - $datas = array(); - } - } else { - $datas = array(); - } - - if ($this->_debug > 0) - $aseco->console_text('XmlrpcDB->RequestWaitArray() - datas' . CRLF . print_r($datas, true)); - if (isset($datas['params']) && isset($datas['params'][$reqnum])) { - $response['Data'] = $datas['params'][$reqnum]; - $param_end = end($datas['params']); - if (isset($param_end['globalTTR']) && !isset($response['Data']['globalTTR'])) - $response['Data']['globalTTR'] = $param_end['globalTTR']; - } else { - $response['Data'] = $datas; - } - if ($this->_debug > 0) - $aseco->console_text('XmlrpcDB->RequestWaitArray() - response[Data]' . CRLF . print_r($response['Data'], true)); - - $this->_initRequest(); - return $response; - } // RequestWaitArray - - // init the request and callback array - function _initRequest() { - - $this->_callbacks = array(); - $this->_requests = array(); - $this->addRequest($this->_auth_cb, 'dedimania.Authenticate', $this->_server); - } // _initRequest - - // make the xmlrpc string, encode it in base64, and pass it as value - // to xmlrpc URL post parameter - function _makeXMLdatas() { - global $aseco; - - $xmlrpc_request = new IXR_RequestStd('system.multicall', $this->_requests); - if ($this->_debug > 1) - $aseco->console_text('XmlrpcDB->_makeXMLdatas() - getXml()' . CRLF . print_r($xmlrpc_request->getXml(), true)); - return $xmlrpc_request->getXml(); - } // _makeXMLdatas - - function _callCB($response, $callbacks, $requests) { - global $aseco; - - $globalTTR = 0; - if (isset($response['Message']) && is_string($response['Message'])) { - $xmlrpc_message = new IXR_Message($response['Message']); - if ($xmlrpc_message->parse() && $xmlrpc_message->messageType != 'fault') { - if ($this->_debug > 1) - $aseco->console_text('XmlrpcDB->_callCB() - message' . CRLF . print_r($xmlrpc_message->message, true)); - - //$datas = array('methodName' => $xmlrpc_message->methodName, 'params' => $xmlrpc_message->params); - $datas = $this->_makeResponseDatas($xmlrpc_message->methodName, $xmlrpc_message->params, $requests); - - if (isset($datas['params']) && is_array($datas['params'])) { - $param_end = end($datas['params']); - if (isset($param_end['globalTTR'])) - $globalTTR = $param_end['globalTTR']; - } else { - if ($this->_debug > 0) - $aseco->console_text('XmlrpcDB->_callCB() - message fault' . CRLF . print_r($xmlrpc_message->message, true)); - $datas = array(); - } - } else { - if ($this->_debug > 0) - $aseco->console_text('XmlrpcDB->_callCB() - message fault' . CRLF . print_r($xmlrpc_message->message, true)); - $datas = array(); - } - } else { - if (!isset($response['Error']) || - (strpos($response['Error'], 'connection failed') === false && strpos($response['Error'], 'Request timeout') === false)) { - $infos = array('Url'=>$this->_url, 'Requests'=>$requests, 'Callbacks'=>$callbacks, 'Response'=>$response); - $serinfos = serialize($infos); - $aseco->console_text('XmlrpcDB->_callCB() - no response message:' . CRLF . $serinfos); - } - $datas = array(); - } - - for ($i = 0; $i < count($callbacks); $i++) { - if ($callbacks[$i] != null) { - $callback = $callbacks[$i][0]; - if (isset($datas['params']) && isset($datas['params'][$i])) { - $response['Data'] = $datas['params'][$i]; - if (!isset($response['Data']['globalTTR'])) - $response['Data']['globalTTR'] = $globalTTR; - } else { - $response['Data'] = $datas; - } - $callbacks[$i][0] = $response; - call_user_func_array($callback, $callbacks[$i]); - } - } - } // _callCB - - // build the datas array from XAseco or Dedimania server - // remove the first array level into params if needed - // add methodResponse name if needed - // rename sub responses params array from [0] to ['params'] if needed - function _makeResponseDatas($methodname, $params, $requests) { - global $aseco; - - if (is_array($params) && count($params) == 1 && is_array($params[0])) - $params = $params[0]; - if ($this->_debug > 2) - $aseco->console_text('XmlrpcDB->_makeResponseDatas() - requests' . CRLF . print_r($requests, true)); - if ($this->_debug > 1) - $aseco->console_text('XmlrpcDB->_makeResponseDatas() - params' . CRLF . print_r($params, true)); - - if (is_array($params) && is_array($params[0]) && !isset($params[0]['methodResponse'])) { - $params2 = array(); - foreach ($params as $key => $param) { - $errors = null; - if (isset($param['faultCode'])) { - $errors[] = array('Code' => $param['faultCode'], 'Message' => $param['faultString']); - } - - if (isset($requests[$key]['methodName'])) - $methodresponse = $requests[$key]['methodName']; - else - $methodresponse = 'Unknown'; - - $ttr = 0.000001; - - if (isset($param[0])) - $param = $param[0]; - else - $param = array(); - - $params2[$key] = array('methodResponse' => $methodresponse, - 'params' => $param, - 'errors' => $errors, - 'TTR' => $ttr, - 'globalTTR' => $ttr - ); - - if ($methodresponse == 'dedimania.WarningsAndTTR') { - if ($this->_debug > 1) { - $aseco->console_text('XmlrpcDB->_makeResponseDatas() - param' . CRLF . print_r($param, true)); - $aseco->console_text('XmlrpcDB->_makeResponseDatas() - params2' . CRLF . print_r($params2, true)); - } - $globalTTR = $param['globalTTR']; - foreach ($param['methods'] as $key3 => $param3) { - $key2 = 0; - while ($key2 < count($params2) && $params2[$key2]['methodResponse'] != $param3['methodName']) { - $params2[$key2]['globalTTR'] = $globalTTR; - $key2++; - } - if ($this->_debug > 1) - $aseco->console_text("XmlrpcDB->_makeResponseDatas() - key2=$key2 - key3=$key3 - param3" . CRLF . print_r($param3, true)); - if ($key2 < count($params2)) { - $params2[$key2]['errors'] = $param3['errors']; - $params2[$key2]['TTR'] = $param3['TTR']; - $params2[$key2]['globalTTR'] = $globalTTR; - } - } - } - } - if ($this->_debug > 1) - $aseco->console_text('XmlrpcDB->_makeResponseDatas() - params2' . CRLF . print_r($params2, true)); - return array('methodName' => $methodname, 'params' => $params2); - - } else { - return array('methodName' => $methodname, 'params' => $params); - } - } // _makeResponseDatas -} // class XmlrpcDB - - -// Dedimania.Authenticate callback used to catch errors -function xmlrpc_auth_cb($response) { - global $aseco; - - if (isset($response['Data']['errors']) && $response['Data']['errors'] != '') { - if (is_array($response['Data']['errors'])) - $aseco->console('xmlrpc_auth_cb() - response[Data][errors]' . CRLF . print_r($response['Data']['errors'], true)); - else - $aseco->console('xmlrpc_auth_cb() - response[Data][errors]' . CRLF . print_r($response, true)); - } -} // xmlrpc_auth_cb -?> diff --git a/xaseco/jfreu.config.php b/xaseco/jfreu.config.php deleted file mode 100644 index a737c15..0000000 --- a/xaseco/jfreu.config.php +++ /dev/null @@ -1,136 +0,0 @@ - paths to config, vip/vip_team & bans files - $conf_file = 'plugins/jfreu/jfreu.config.xml'; - $vips_file = 'plugins/jfreu/jfreu.vips.xml'; - $bans_file = 'plugins/jfreu/jfreu.bans.xml'; - - //-> Server's base name: (ex: '$000Jfreu') - // Max. length: 26 chars (incl. colors & tags, and optional "TopXXX") - $servername = 'YOUR SERVER NAME'; - //-> Word between the servername and the limit (usually " Top") - $top = ' $449TOP'; - //-> Change the servername when the limit changes: "Servername TopXXX" (0 = OFF, 1 = ON) - $autochangename = 0; - - //-> ranklimit: ranklimiting default state (0 = OFF, 1 = ON) - $ranklimit = 0; - - //-> limit: ranklimit default value (when autorank is OFF) - $limit = 500000; - - //-> spec ranklimit - $hardlimit = 1000000; - - //-> autorank: autorank default state (0 = OFF, 1 = ON) - $autorank = 0; - - //-> offset (average + offset = Auto-RankLimit) - $offset = 999; - - //-> autorankminplayers (autorank disabled when not enough players) - $autorankminplayers = 10; - //-> autorankvip: include VIP/unSpec in autorank calculation (0 = OFF, 1 = ON) - $autorankvip = 0; - - //-> kick hirank when server is full and new player arrives (0 = OFF, 1 = ON) - $kickhirank = 0; - //-> maxplayers value for kickhirank (must be less than server's ) - $maxplayers = 20; - - //-> allow user /unspec vote (0 = OFF, 1 = ON) - $unspecvote = 1; - - //-> player join/leave messages - $player_join = '{#server}>> {1}: {#highlite}{2}$z$s{#message} Nation: {#highlite}{3}{#message} Ladder: {#highlite}{4}'; - $player_joins = '{#server}>> {1}: {#highlite}{2}$z$s{#message} Nation: {#highlite}{3}{#message} Ladder: {#highlite}{4}{#message} Server: {#highlite}{5}'; - $player_left = '{#server}>> {#highlite}{1}$z$s{#message} has left the game. Played: {#highlite}{2}'; - - //-> random info messages at the end of the race (0 = OFF, 1 = in chat, 2 = in TMF message window) - $infomessages = 1; - //-> prefix for info messages - $message_start = '$z$s$ff0>> [$f00INFO$ff0] $fff'; - - //-> random information messages (if you add a message don't forget to change the number) (999 messages max :-P) - // $message1 = 'Jfreu\'s plugin: "http://reload.servegame.com/plugin/"'; - $message1 = 'Information about and download of this XASECO on ' . XASECO_TMN; - $message2 = 'Use "/list" -> "/jukebox ##" to add a track in the jukebox.'; - $message3 = 'Please don\'t sound your horn throughout the entire track.'; - $message4 = 'When going AFK, please set your car to Spectator mode.'; - $message5 = 'Don\'t use Enter to skip intros - instead use Space & Enter'; - $message6 = 'For player & server info use the "/stats" and "/server" commands.'; - $message7 = 'Looking for the name of this server? Use the "/server" command.'; - $message8 = 'Use "/list nofinish" to find tracks you haven\'t completed yet, then /jukebox them!'; - $message9 = 'Use "/list norank" to find tracks you aren\'t ranked on, then /jukebox them!'; - $message10 = 'Can you beat the Gold time on all tracks? Use "/list nogold" to find out!'; - $message11 = 'Can you beat the Author time on all tracks? Use "/list noauthor" to find out!'; - $message12 = 'Wondering which tracks you haven\'t played recently? Use "/list norecent" to find out!'; - $message13 = 'Use the "/best" & "/worst" commands to find your best and worst records!'; - $message14 = 'Use the "/clans" & "/topclans" commands to see clan members and ranks!'; - $message15 = 'Use the "/ranks" commands to see the server ranks of all online players!'; - $message16 = 'Who is the most victorious player? Use "/topwins" to find out!'; - $message17 = 'Who has the most ranked records? Use "/toprecs" to find out!'; - $message18 = 'Wondering what tracks were played recently? Use the "/history" command.'; - $message19 = 'Looking for the next better ranked record to beat? Use "/nextrec"!'; - $message20 = 'Find the difference between your personal best and the track record with the "/diffrec" command!'; - $message21 = 'Check how many records were driven on the current track with the "/newrecs" command!'; - $message22 = 'Check how many records, and the 3 best ones, you have with the "/summary" command!'; - $message23 = 'Who has the most top-3 ranked records? Use "/topsums" to find out!'; - $message24 = 'Jukeboxed the wrong track? Use "/jukebox drop" to remove it!'; - $message25 = 'Forgot what someone said? Use "/chatlog" to check the chat history!'; - $message26 = 'Forgot what someone pm-ed you? Use "/pmlog" to check your PM history!'; - $message27 = 'Looking for the next better ranked player to beat? Use "/nextrank"!'; - $message28 = 'Use "/list newest <#>" to find the newest tracks added to the server, then /jukebox them!'; - $message29 = 'Find the longest and shortest tracks with the "/list longest / shortest" commands!'; - $message30 = 'Use "/mute" and "/unmute" to mute / unmute other players, and "/mutelist" to list them!'; - $message31 = 'Wondering when a player was last online? Use "/laston " to find out!'; - $message32 = 'Looking for any player\'s world stats? Use the "/statsall " command!'; - $message33 = 'Use checkpoints tracking in Rounds/Team/Cup modes with the "/cps" command!'; - $message34 = 'Find the TMX info & records for a track with the "/tmxinfo" & "/tmxrecs" commands!'; - $message35 = 'Looking for the name of the current track\'s song? Use the "/song" command!'; - $message36 = 'Looking for the name of the current track\'s mod? Use the "/mod" command!'; - $message37 = 'Use the "/style" command to select your personal window style!'; - $message38 = 'Use the "/recpanel" command to select your personal records panel!'; - $message39 = 'Use the "/votepanel" command to select your personal vote panel!'; - $message40 = 'Find out all about the Dedimania world records system with "/helpdedi"!'; - $message41 = 'Check out the XASECO[2] site at ' . XASECO_ORG . ' !'; - global $feature_votes; - if ($feature_votes) { - $message42 = 'Find out all about the chat-based voting commands with "/helpvote"!'; - } - if (function_exists('send_window_message')) { - $message43 = 'Missed a system message? Use "/msglog" to check the message history!'; - } - - //-> Badwords checking (0 = OFF, 1 = ON) - $badwords = 0; - //-> Badwords banning (0 = OFF, 1 = ON) - $badwordsban = 0; - //-> Number of badwords allowed - $badwordsnum = 3; - //-> Banning period (minutes) - $badwordstime = 10; - - //-> Badwords to check for - $badwordslist = array( - 'putain','ptain','klote','kIote','kanker','kenker', - 'arschl','wichs','fick','fikk','salop','siktirgit','gvd', - 'hitler','nutte','dick','cock','faitchier','bordel','shit', - 'encul','sucks','a.q','conerie','scheise','scheiße','scheis', - 'baskasole','cocugu','kodugumun','cazo','hoer','bitch', - 'penis','fotze','maul','frese','pizda','gay','fuck','tyfus', - 'sugi','cacat','pisat','labagiu','gaozar','muist','orospu', - 'pédé','cunt','godve','godfe','kut','kudt','lul','iui'); - - //-> novote (auto-cancel votes) (0 = OFF, 1 = ON) - $novote = 0; -?> diff --git a/xaseco/localdatabase.xml b/xaseco/localdatabase.xml deleted file mode 100644 index 5aa1f21..0000000 --- a/xaseco/localdatabase.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - --$DB_HOST-- - --$DB_LOGIN-- - --$DB_LOGIN_PASSWORD-- - --$DB_NAME-- - - true - - - - 50 - - - - {#server}>> {#highlite}{1}{#record} secured his/her {#rank}{2}{#record}. Local Record! {3}: {#highlite}{4}{#record} $n({#rank}{5}{#highlite}{6}{#record}) - {#server}>> {#highlite}{1}{#record} equaled his/her {#rank}{2}{#record}. Local Record! {3}: {#highlite}{4} - {#server}>> {#highlite}{1}{#record} gained the {#rank}{2}{#record}. Local Record! {3}: {#highlite}{4}{#record} $n({#rank}{5}{#highlite}{6}{#record}) - {#server}>> {#highlite}{1}{#record} claimed the {#rank}{2}{#record}. Local Record! {3}: {#highlite}{4} - - diff --git a/xaseco/localdb/aseco.sql b/xaseco/localdb/aseco.sql deleted file mode 100644 index 3b05520..0000000 --- a/xaseco/localdb/aseco.sql +++ /dev/null @@ -1,56 +0,0 @@ --- Database: `aseco` --- --- -------------------------------------------------------- - --- --- Tablestructure for Table `challenges` --- - -CREATE TABLE IF NOT EXISTS `challenges` ( - `Id` mediumint(9) NOT NULL auto_increment, - `Uid` varchar(27) NOT NULL default '', - `Name` varchar(100) NOT NULL default '', - `Author` varchar(30) NOT NULL default '', - `Environment` varchar(10) NOT NULL default '', - PRIMARY KEY (`Id`), - UNIQUE KEY `Uid` (`Uid`) -) ENGINE=MyISAM; - --- -------------------------------------------------------- - --- --- Tablestructure for Table `players` --- - -CREATE TABLE IF NOT EXISTS `players` ( - `Id` mediumint(9) NOT NULL auto_increment, - `Login` varchar(50) NOT NULL default '', - `Game` varchar(3) NOT NULL default '', - `NickName` varchar(100) NOT NULL default '', - `Nation` varchar(3) NOT NULL default '', - `UpdatedAt` datetime NOT NULL default '1970-01-01 00:00:00', - `Wins` mediumint(9) NOT NULL default 0, - `TimePlayed` int(10) unsigned NOT NULL default 0, - `TeamName` char(60) NOT NULL default '', - PRIMARY KEY (`Id`), - UNIQUE KEY `Login` (`Login`), - KEY `Game` (`Game`) -) ENGINE=MyISAM; - --- -------------------------------------------------------- - --- --- Tablestructure for Table `records` --- - -CREATE TABLE IF NOT EXISTS `records` ( - `Id` int(11) NOT NULL auto_increment, - `ChallengeId` mediumint(9) NOT NULL default 0, - `PlayerId` mediumint(9) NOT NULL default 0, - `Score` int(11) NOT NULL default 0, - `Date` datetime NOT NULL default '1970-01-01 00:00:00', - `Checkpoints` text NOT NULL, - PRIMARY KEY (`Id`), - UNIQUE KEY `PlayerId` (`PlayerId`,`ChallengeId`), - KEY `ChallengeId` (`ChallengeId`) -) ENGINE=MyISAM; diff --git a/xaseco/localdb/extra.sql b/xaseco/localdb/extra.sql deleted file mode 100644 index 2629128..0000000 --- a/xaseco/localdb/extra.sql +++ /dev/null @@ -1,18 +0,0 @@ --- Database: `aseco` --- --- -------------------------------------------------------- - --- --- Tablestructure for Table `players_extra` --- - -CREATE TABLE IF NOT EXISTS `players_extra` ( - `playerID` mediumint(9) NOT NULL default 0, - `cps` smallint(3) NOT NULL default -1, - `dedicps` smallint(3) NOT NULL default -1, - `donations` mediumint(9) NOT NULL default 0, - `style` varchar(20) NOT NULL default '', - `panels` varchar(255) NOT NULL default '', - PRIMARY KEY (`playerID`), - KEY `donations` (`donations`) -) ENGINE=MyISAM; diff --git a/xaseco/localdb/rasp.sql b/xaseco/localdb/rasp.sql deleted file mode 100644 index 898b5d8..0000000 --- a/xaseco/localdb/rasp.sql +++ /dev/null @@ -1,47 +0,0 @@ --- Database: `aseco` --- --- -------------------------------------------------------- - --- --- Tablestructure for Table `rs_karma` --- - -CREATE TABLE IF NOT EXISTS `rs_karma` ( - `Id` int(11) NOT NULL auto_increment, - `ChallengeId` mediumint(9) NOT NULL default 0, - `PlayerId` mediumint(9) NOT NULL default 0, - `Score` tinyint(4) NOT NULL default 0, - PRIMARY KEY (`Id`), - UNIQUE KEY `PlayerId` (`PlayerId`,`ChallengeId`), - KEY `ChallengeId` (`ChallengeId`) -) ENGINE=MyISAM; - --- -------------------------------------------------------- - --- --- Tablestructure for Table `rs_rank` --- - -CREATE TABLE IF NOT EXISTS `rs_rank` ( - `playerID` mediumint(9) NOT NULL default 0, - `avg` float NOT NULL default 0, - KEY `playerID` (`playerID`) -) ENGINE=MyISAM; - --- -------------------------------------------------------- - --- --- Tablestructure for Table `rs_times` --- - -CREATE TABLE IF NOT EXISTS `rs_times` ( - `ID` int(11) NOT NULL auto_increment, - `challengeID` mediumint(9) NOT NULL default 0, - `playerID` mediumint(9) NOT NULL default 0, - `score` int(11) NOT NULL default 0, - `date` int(10) unsigned NOT NULL default 0, - `checkpoints` text NOT NULL, - PRIMARY KEY (`ID`), - KEY `playerID` (`playerID`,`challengeID`), - KEY `challengeID` (`challengeID`) -) ENGINE=MyISAM; diff --git a/xaseco/matchsave.xml b/xaseco/matchsave.xml deleted file mode 100644 index d9ad128..0000000 --- a/xaseco/matchsave.xml +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - XXX - - - xxx-max - siteguru - - - - - - - - Challengers - - - - - - - $i$0AFPoints This Round - $i$AF0Points This Match - {#emotic} - {#message} - {#server} - $000 - $f00 - $0af - $i$n - $fff - $n$888 - {#highlite} - {#highlite} - $0af - {#server} - $af0 - {#server} - - - - True - - - {#server}To join a team type "{#welcome}/team {#message}yourteamname{#server}" - - {#server}See all team options by typing "{#welcome}/team {#message}help{#server}" - {#server}Join a team and chat with your teammates via "{#welcome}/tc {#message}Hello Teammates{#server}" - {#server}See the current match standings by typing "{#welcome}/standings{#server}" - - - {#server}Change your teamname with "{#welcome}/team {#message}yourteamname{#server}" - - {#server}See all team options by typing "{#welcome}/team {#message}help{#server}" - {#server}See the current match standings by typing "{#welcome}/standings{#server}" - {#server}Chat with your teammates by typing "{#welcome}/tc {#message}Hello Teammates{#server}" - - - - - true - True - true - {#highlite}[Team] - False - True - False - 12 - 5000 - True - True - - - html.tpl - race_results.html - race_results_last.html - match_results.html - - - - - - /home/tmf/TMF/GameData/Tracks/MatchSettings - - - 16 - Y-m-d - H:i - 10, 8, 6, 4, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0 - - - True - - diff --git a/xaseco/musicserver.xml b/xaseco/musicserver.xml deleted file mode 100644 index 22e578d..0000000 --- a/xaseco/musicserver.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - False - - True - - False - - True - - False - - False - - - True - - False - - musictagscache.xml - - - - - - YOUR_SERVER_URL - - - - - - YOUR_FIRST_SONG.ogg - YOUR_SECOND_SONG.mux - YOUR_THIRD_SONG.ogg - - - - - {#server}> {#music}The current song is: {#highlite}{1} - {#server}>> {#music}{1}$z$s {#highlite}{2}$z$s{#music} loaded the next song: {#highlite}{3} - {#server}>> {#music}{1}$z$s {#highlite}{2}$z$s{#music} reloaded music config and cleared jukebox! - {#server}>> {#music}{1}$z$s {#highlite}{2}$z$s{#music} sorted song list and cleared jukebox! - {#server}>> {#music}{1}$z$s {#highlite}{2}$z$s{#music} shuffled song list and cleared jukebox! - {#server}>> {#highlite}{1}{#music} jukeboxed song: {#highlite}{2} - {#server}> {#music}You already have a song in the jukebox! Wait till it's been played before adding another. - {#server}> {#music}This song has already been added to the jukebox, pick another one. - {#server}> {#music}Song_ID not found - Type {#highlite}/music list{#music} to see all songs. - {#server}>> {#music}Player {#highlite}{1}{#music} dropped his/her song {#highlite}{2}{#music} from jukebox! - {#server}> {#music}You don't have a song in the jukebox, use {#highlite}/music Song_ID{#music} to add one... - {#server}> {#music}No songs in the jukebox, use {#highlite}/music Song_ID{#music} to add one... - {#server}> {#highlite}/music #{#music} is not currently enabled on this server. - {#server}>> {#music}{1}$z$s {#highlite}{2}$z$s{#music} disabled server music! - - diff --git a/xaseco/panels/00README.txt b/xaseco/panels/00README.txt deleted file mode 100644 index 391aa9e..0000000 --- a/xaseco/panels/00README.txt +++ /dev/null @@ -1,34 +0,0 @@ -Panels -====== - -This directory holds Admin, Donate, Records and Vote panel templates, -managed by plugin.panels.php. -Templates define the complete ManiaLink panel with position, size and -fonts, so you have full control to develop custom panels. - -To create a new template, copy an existing one to a new filename and -edit that, as the existing set will be overwritten in future releases. - -Use ManiaLink http://smurf1.free.fr/mle/index.xml -and webpage http://smurf1.free.fr/mle/list.php -to select styles and fonts. Note that not every (sub)style and font -fits everywhere due to size variations. - -New Admin templates must stick to manialink id="3" and preserve -action="21" through action="27" for the buttons. - -New Donate templates must stick to manialink id="6" and preserve -action="30" through action="36" for the buttons. - -New Record templates must stick to manialink id="4" and preserve the -mapping of text="%PB%" to action="7", "%LCL%" to "8", "%DED%" to "9" -and "%TMX%" to "10". - -New Vote templates must stick to manialink id="5" and preserve -action="18" for the Yes button and action="19" for the No button. - -If you create a nice template for any panel that's sufficiently distinct -from the standard ones, send it to me and I might include it in the -next XAseco release. :) - -Xymph diff --git a/xaseco/panels/AdminAboveCPList.xml b/xaseco/panels/AdminAboveCPList.xml deleted file mode 100644 index 5013657..0000000 --- a/xaseco/panels/AdminAboveCPList.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/xaseco/panels/AdminAboveCPListWide.xml b/xaseco/panels/AdminAboveCPListWide.xml deleted file mode 100644 index ec41084..0000000 --- a/xaseco/panels/AdminAboveCPListWide.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/xaseco/panels/AdminAboveChat.xml b/xaseco/panels/AdminAboveChat.xml deleted file mode 100644 index f2fea7c..0000000 --- a/xaseco/panels/AdminAboveChat.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/xaseco/panels/AdminAboveChatWide.xml b/xaseco/panels/AdminAboveChatWide.xml deleted file mode 100644 index 120b2b2..0000000 --- a/xaseco/panels/AdminAboveChatWide.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/xaseco/panels/AdminAboveSpeed.xml b/xaseco/panels/AdminAboveSpeed.xml deleted file mode 100644 index 303be95..0000000 --- a/xaseco/panels/AdminAboveSpeed.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/xaseco/panels/AdminAboveSpeed2.xml b/xaseco/panels/AdminAboveSpeed2.xml deleted file mode 100644 index 2582b30..0000000 --- a/xaseco/panels/AdminAboveSpeed2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/xaseco/panels/AdminBelowChat.xml b/xaseco/panels/AdminBelowChat.xml deleted file mode 100644 index 5a203a1..0000000 --- a/xaseco/panels/AdminBelowChat.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/xaseco/panels/AdminBottomCenter.xml b/xaseco/panels/AdminBottomCenter.xml deleted file mode 100644 index 7925896..0000000 --- a/xaseco/panels/AdminBottomCenter.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/xaseco/panels/AdminBottomCenterWide.xml b/xaseco/panels/AdminBottomCenterWide.xml deleted file mode 100644 index a3981b0..0000000 --- a/xaseco/panels/AdminBottomCenterWide.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/xaseco/panels/AdminCallVote.xml b/xaseco/panels/AdminCallVote.xml deleted file mode 100644 index d7a9cd3..0000000 --- a/xaseco/panels/AdminCallVote.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/xaseco/panels/AdminCallVoteBlur.xml b/xaseco/panels/AdminCallVoteBlur.xml deleted file mode 100644 index 002111d..0000000 --- a/xaseco/panels/AdminCallVoteBlur.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/xaseco/panels/AdminLeftEdge.xml b/xaseco/panels/AdminLeftEdge.xml deleted file mode 100644 index 1149266..0000000 --- a/xaseco/panels/AdminLeftEdge.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/xaseco/panels/AdminRightEdge.xml b/xaseco/panels/AdminRightEdge.xml deleted file mode 100644 index 0d24fcd..0000000 --- a/xaseco/panels/AdminRightEdge.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/xaseco/panels/AdminTopCenter.xml b/xaseco/panels/AdminTopCenter.xml deleted file mode 100644 index 8f8bbb8..0000000 --- a/xaseco/panels/AdminTopCenter.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/xaseco/panels/AdminTopCenterWide.xml b/xaseco/panels/AdminTopCenterWide.xml deleted file mode 100644 index 48c7e27..0000000 --- a/xaseco/panels/AdminTopCenterWide.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/xaseco/panels/DonateBelowCPList.xml b/xaseco/panels/DonateBelowCPList.xml deleted file mode 100644 index 9b84e2c..0000000 --- a/xaseco/panels/DonateBelowCPList.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/DonateBelowCPListRM.xml b/xaseco/panels/DonateBelowCPListRM.xml deleted file mode 100644 index aa3207a..0000000 --- a/xaseco/panels/DonateBelowCPListRM.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/DonateLeftEdge.xml b/xaseco/panels/DonateLeftEdge.xml deleted file mode 100644 index 1931f0a..0000000 --- a/xaseco/panels/DonateLeftEdge.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/DonateLeftEdge2.xml b/xaseco/panels/DonateLeftEdge2.xml deleted file mode 100644 index 0edc2a3..0000000 --- a/xaseco/panels/DonateLeftEdge2.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/DonateLeftSmall.xml b/xaseco/panels/DonateLeftSmall.xml deleted file mode 100644 index 1b2ddc2..0000000 --- a/xaseco/panels/DonateLeftSmall.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/DonateRightEdge.xml b/xaseco/panels/DonateRightEdge.xml deleted file mode 100644 index 9242cee..0000000 --- a/xaseco/panels/DonateRightEdge.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/DonateRightEdge2.xml b/xaseco/panels/DonateRightEdge2.xml deleted file mode 100644 index 02e45bd..0000000 --- a/xaseco/panels/DonateRightEdge2.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/DonateRightSmall.xml b/xaseco/panels/DonateRightSmall.xml deleted file mode 100644 index 1fffe61..0000000 --- a/xaseco/panels/DonateRightSmall.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/DonateTopLeft.xml b/xaseco/panels/DonateTopLeft.xml deleted file mode 100644 index fb88626..0000000 --- a/xaseco/panels/DonateTopLeft.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/RecordsLeftBlackBold.xml b/xaseco/panels/RecordsLeftBlackBold.xml deleted file mode 100644 index fd66692..0000000 --- a/xaseco/panels/RecordsLeftBlackBold.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/RecordsLeftBlue.xml b/xaseco/panels/RecordsLeftBlue.xml deleted file mode 100644 index cde43be..0000000 --- a/xaseco/panels/RecordsLeftBlue.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/RecordsLeftBlueBold.xml b/xaseco/panels/RecordsLeftBlueBold.xml deleted file mode 100644 index 09c5862..0000000 --- a/xaseco/panels/RecordsLeftBlueBold.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/RecordsLeftBlueLight.xml b/xaseco/panels/RecordsLeftBlueLight.xml deleted file mode 100644 index a5dbd1c..0000000 --- a/xaseco/panels/RecordsLeftBlueLight.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/RecordsLeftGray.xml b/xaseco/panels/RecordsLeftGray.xml deleted file mode 100644 index 28007db..0000000 --- a/xaseco/panels/RecordsLeftGray.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/RecordsLeftGrayBold.xml b/xaseco/panels/RecordsLeftGrayBold.xml deleted file mode 100644 index 9837f4f..0000000 --- a/xaseco/panels/RecordsLeftGrayBold.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/RecordsLeftItalic.xml b/xaseco/panels/RecordsLeftItalic.xml deleted file mode 100644 index 945659e..0000000 --- a/xaseco/panels/RecordsLeftItalic.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/RecordsLeftOrange.xml b/xaseco/panels/RecordsLeftOrange.xml deleted file mode 100644 index 7963877..0000000 --- a/xaseco/panels/RecordsLeftOrange.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/RecordsLeftSize1.xml b/xaseco/panels/RecordsLeftSize1.xml deleted file mode 100644 index 8035615..0000000 --- a/xaseco/panels/RecordsLeftSize1.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/RecordsLeftSize1NoDedi.xml b/xaseco/panels/RecordsLeftSize1NoDedi.xml deleted file mode 100644 index ef73496..0000000 --- a/xaseco/panels/RecordsLeftSize1NoDedi.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/xaseco/panels/RecordsLeftSize2.xml b/xaseco/panels/RecordsLeftSize2.xml deleted file mode 100644 index e7ab8fb..0000000 --- a/xaseco/panels/RecordsLeftSize2.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/RecordsLeftSize2NoDedi.xml b/xaseco/panels/RecordsLeftSize2NoDedi.xml deleted file mode 100644 index 548720a..0000000 --- a/xaseco/panels/RecordsLeftSize2NoDedi.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/xaseco/panels/RecordsLeftWhite.xml b/xaseco/panels/RecordsLeftWhite.xml deleted file mode 100644 index f418f60..0000000 --- a/xaseco/panels/RecordsLeftWhite.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/xaseco/panels/RecordsRightBottom.xml b/xaseco/panels/RecordsRightBottom.xml deleted file mode 100644 index f0c9688..0000000 --- a/xaseco/panels/RecordsRightBottom.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/xaseco/panels/RecordsRightBottomNoDedi.xml b/xaseco/panels/RecordsRightBottomNoDedi.xml deleted file mode 100644 index 4e17240..0000000 --- a/xaseco/panels/RecordsRightBottomNoDedi.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/xaseco/panels/RecordsRightBottomRM.xml b/xaseco/panels/RecordsRightBottomRM.xml deleted file mode 100644 index e12c740..0000000 --- a/xaseco/panels/RecordsRightBottomRM.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/xaseco/panels/StatsNations.xml b/xaseco/panels/StatsNations.xml deleted file mode 100644 index edddd8d..0000000 --- a/xaseco/panels/StatsNations.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - diff --git a/xaseco/panels/StatsUnited.xml b/xaseco/panels/StatsUnited.xml deleted file mode 100644 index 2090e0d..0000000 --- a/xaseco/panels/StatsUnited.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - diff --git a/xaseco/panels/VoteBelowChat.xml b/xaseco/panels/VoteBelowChat.xml deleted file mode 100644 index 347590a..0000000 --- a/xaseco/panels/VoteBelowChat.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/xaseco/panels/VoteBottomCenter.xml b/xaseco/panels/VoteBottomCenter.xml deleted file mode 100644 index fecc056..0000000 --- a/xaseco/panels/VoteBottomCenter.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/xaseco/panels/VoteBottomCenterTransp.xml b/xaseco/panels/VoteBottomCenterTransp.xml deleted file mode 100644 index 98c15aa..0000000 --- a/xaseco/panels/VoteBottomCenterTransp.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/xaseco/panels/VoteCallVote.xml b/xaseco/panels/VoteCallVote.xml deleted file mode 100644 index b6b51ca..0000000 --- a/xaseco/panels/VoteCallVote.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/xaseco/panels/VoteTopCenter.xml b/xaseco/panels/VoteTopCenter.xml deleted file mode 100644 index 58868aa..0000000 --- a/xaseco/panels/VoteTopCenter.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/xaseco/plugins.xml b/xaseco/plugins.xml deleted file mode 100644 index baad696..0000000 --- a/xaseco/plugins.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - plugin.localdatabase.php - plugin.rounds.php - chat.admin.php - chat.help.php - chat.records.php - chat.records2.php - chat.recrels.php - chat.dedimania.php - chat.players.php - chat.players2.php - chat.wins.php - chat.laston.php - chat.lastwin.php - chat.stats.php - chat.server.php - chat.songmod.php - chat.me.php - - plugin.tmxinfo.php - plugin.track.php - plugin.checkpoints.php - plugin.dedimania.php - plugin.rasp.php - plugin.rasp_jukebox.php - plugin.rasp_chat.php - plugin.rasp_karma.php - plugin.rasp_nextmap.php - plugin.rasp_nextrank.php - plugin.rasp_votes.php - plugin.chatlog.php - - plugin.style.php - plugin.panels.php - - - plugin.uptodate.php - - - - jfreu.plugin.php - mistral.idlekick.php - diff --git a/xaseco/plugins/chat.admin.php b/xaseco/plugins/chat.admin.php deleted file mode 100644 index c7bc75b..0000000 --- a/xaseco/plugins/chat.admin.php +++ /dev/null @@ -1,5772 +0,0 @@ -... {sec})', true); -Aseco::addChatCommand('addthis', 'Adds current /add-ed track permanently', true); -Aseco::addChatCommand('addlocal', 'Adds a local track ()', true); -Aseco::addChatCommand('warn', 'Sends a kick/ban warning to a player', true); -Aseco::addChatCommand('kick', 'Kicks a player from server', true); -Aseco::addChatCommand('kickghost', 'Kicks a ghost player from server', true); -Aseco::addChatCommand('ban', 'Bans a player from server', true); -Aseco::addChatCommand('unban', 'UnBans a player from server', true); -Aseco::addChatCommand('banip', 'Bans an IP address from server', true); -Aseco::addChatCommand('unbanip', 'UnBans an IP address from server', true); -Aseco::addChatCommand('black', 'Blacklists a player from server', true); -Aseco::addChatCommand('unblack', 'UnBlacklists a player from server', true); -Aseco::addChatCommand('addguest', 'Adds a guest player to server', true); -Aseco::addChatCommand('removeguest', 'Removes a guest player from server', true); -Aseco::addChatCommand('pass', 'Passes a chat-based or TMX /add vote', true); -Aseco::addChatCommand('cancel/can', 'Cancels any running vote', true); -Aseco::addChatCommand('endround/er', 'Forces end of current round', true); -Aseco::addChatCommand('players', 'Displays list of known players {string}', true); -Aseco::addChatCommand('showbanlist/listbans', 'Displays current ban list', true); -Aseco::addChatCommand('showiplist/listips', 'Displays current banned IPs list', true); -Aseco::addChatCommand('showblacklist/listblacks', 'Displays current black list', true); -Aseco::addChatCommand('showguestlist/listguests', 'Displays current guest list', true); -Aseco::addChatCommand('writeiplist', 'Saves current banned IPs list (def: bannedips.xml)', true); -Aseco::addChatCommand('readiplist', 'Loads current banned IPs list (def: bannedips.xml)', true); -Aseco::addChatCommand('writeblacklist', 'Saves current black list (def: blacklist.txt)', true); -Aseco::addChatCommand('readblacklist', 'Loads current black list (def: blacklist.txt)', true); -Aseco::addChatCommand('writeguestlist', 'Saves current guest list (def: guestlist.txt)', true); -Aseco::addChatCommand('readguestlist', 'Loads current guest list (def: guestlist.txt)', true); -Aseco::addChatCommand('cleanbanlist', 'Cleans current ban list', true); -Aseco::addChatCommand('cleaniplist', 'Cleans current banned IPs list', true); -Aseco::addChatCommand('cleanblacklist', 'Cleans current black list', true); -Aseco::addChatCommand('cleanguestlist', 'Cleans current guest list', true); -Aseco::addChatCommand('mergegbl', 'Merges a global black list {URL}', true); -Aseco::addChatCommand('access', 'Handles player access control (see: /admin access help)', true); -Aseco::addChatCommand('writetracklist', 'Saves current track list (def: tracklist.txt)', true); -Aseco::addChatCommand('readtracklist', 'Loads current track list (def: tracklist.txt)', true); -Aseco::addChatCommand('shuffle/shufflemaps', 'Randomizes current track list', true); -Aseco::addChatCommand('listdupes', 'Displays list of duplicate tracks', true); -Aseco::addChatCommand('remove', 'Removes a track from rotation', true); -Aseco::addChatCommand('erase', 'Removes a track from rotation & deletes track file', true); -Aseco::addChatCommand('removethis', 'Removes this track from rotation', true); -Aseco::addChatCommand('erasethis', 'Removes this track from rotation & deletes track file', true); -Aseco::addChatCommand('mute/ignore', 'Adds a player to global mute/ignore list', true); -Aseco::addChatCommand('unmute/unignore', 'Removes a player from global mute/ignore list', true); -Aseco::addChatCommand('mutelist/listmutes', 'Displays global mute/ignore list', true); -Aseco::addChatCommand('ignorelist/listignores', 'Displays global mute/ignore list', true); -Aseco::addChatCommand('cleanmutes/cleanignores', 'Cleans global mute/ignore list', true); -Aseco::addChatCommand('addadmin', 'Adds a new admin', true); -Aseco::addChatCommand('removeadmin', 'Removes an admin', true); -Aseco::addChatCommand('addop', 'Adds a new operator', true); -Aseco::addChatCommand('removeop', 'Removes an operator', true); -Aseco::addChatCommand('listmasters', 'Displays current masteradmin list', true); -Aseco::addChatCommand('listadmins', 'Displays current admin list', true); -Aseco::addChatCommand('listops', 'Displays current operator list', true); -Aseco::addChatCommand('adminability', 'Shows/changes admin ability {ON/OFF}', true); -Aseco::addChatCommand('opability', 'Shows/changes operator ability {ON/OFF}', true); -Aseco::addChatCommand('listabilities', 'Displays current abilities list', true); -Aseco::addChatCommand('writeabilities', 'Saves current abilities list (def: adminops.xml)', true); -Aseco::addChatCommand('readabilities', 'Loads current abilities list (def: adminops.xml)', true); -Aseco::addChatCommand('wall/mta', 'Displays popup message to all players', true); -Aseco::addChatCommand('delrec', 'Deletes specific record on current track', true); -Aseco::addChatCommand('prunerecs', 'Deletes records for specified track', true); -Aseco::addChatCommand('rpoints', 'Sets custom Rounds points (see: /admin rpoints help)', true); -Aseco::addChatCommand('match', '{begin/end} to start/stop match tracking', true); -Aseco::addChatCommand('acdl', 'Sets AllowChallengeDownload {ON/OFF}', true); -Aseco::addChatCommand('autotime', 'Sets Auto TimeLimit {ON/OFF}', true); -Aseco::addChatCommand('disablerespawn', 'Disables respawn at CPs {ON/OFF}', true); -Aseco::addChatCommand('forceshowopp', 'Forces to show opponents {##/ALL/OFF}', true); -Aseco::addChatCommand('scorepanel', 'Shows automatic scorepanel {ON/OFF}', true); -Aseco::addChatCommand('roundsfinish', 'Shows rounds panel upon first finish {ON/OFF}', true); -Aseco::addChatCommand('forceteam', 'Forces player into {Blue} or {Red} team', true); -Aseco::addChatCommand('forcespec', 'Forces player into free spectator', true); -Aseco::addChatCommand('specfree', 'Forces spectator into free mode', true); -Aseco::addChatCommand('panel', 'Selects admin panel (see: /admin panel help)', true); -Aseco::addChatCommand('style', 'Selects default window style', true); -Aseco::addChatCommand('admpanel', 'Selects default admin panel', true); -Aseco::addChatCommand('donpanel', 'Selects default donate panel', true); -Aseco::addChatCommand('recpanel', 'Selects default records panel', true); -Aseco::addChatCommand('votepanel', 'Selects default vote panel', true); -Aseco::addChatCommand('coppers', 'Shows server\'s coppers amount', true); -Aseco::addChatCommand('pay', 'Pays server coppers to login', true); -Aseco::addChatCommand('relays', 'Displays relays list or shows relay master', true); -Aseco::addChatCommand('server', 'Displays server\'s detailed settings', true); -Aseco::addChatCommand('pm', 'Sends private message to all available admins', true); -Aseco::addChatCommand('pmlog', 'Displays log of recent private admin messages', true); -Aseco::addChatCommand('call', 'Executes direct server call (see: /admin call help)', true); -Aseco::addChatCommand('unlock', 'Unlocks admin commands & features', true); -Aseco::addChatCommand('debug', 'Toggles debugging output', true); -Aseco::addChatCommand('shutdown', 'Shuts down XASECO', true); -Aseco::addChatCommand('shutdownall', 'Shuts down Server & XASECO', true); -//Aseco::addChatCommand('uptodate', 'Checks current version of XASECO', true); // already defined in plugin.uptodate.php - -global $pmbuf; // pm history buffer -global $pmlen; // length of pm history -global $lnlen; // max length of pm line - -$pmbuf = array(); -$pmlen = 30; -$lnlen = 40; - -global $method_results, $auto_scorepanel, $rounds_finishpanel; -$auto_scorepanel = true; -$rounds_finishpanel = true; - -function chat_admin($aseco, $command) { - global $jukebox; // from plugin.rasp_jukebox.php - - $admin = $command['author']; - $login = $admin->login; - - // split params into arrays & insure optional parameters exist - $arglist = explode(' ', $command['params'], 2); - if (!isset($arglist[1])) $arglist[1] = ''; - $command['params'] = explode(' ', preg_replace('/ +/', ' ', $command['params'])); - if (!isset($command['params'][1])) $command['params'][1] = ''; - - // check if chat command was allowed for a masteradmin/admin/operator - if ($aseco->isMasterAdmin($admin)) { - $logtitle = 'MasterAdmin'; - $chattitle = $aseco->titles['MASTERADMIN'][0]; - } else { - if ($aseco->isAdmin($admin) && $aseco->allowAdminAbility($command['params'][0])) { - $logtitle = 'Admin'; - $chattitle = $aseco->titles['ADMIN'][0]; - } else { - if ($aseco->isOperator($admin) && $aseco->allowOpAbility($command['params'][0])) { - $logtitle = 'Operator'; - $chattitle = $aseco->titles['OPERATOR'][0]; - } else { - // write warning in console - $aseco->console($login . ' tried to use admin chat command (no permission!): ' . $arglist[0] . ' ' . $arglist[1]); - // show chat message - $aseco->client->query('ChatSendToLogin', $aseco->formatColors('{#error}You don\'t have the required admin rights to do that!'), $login); - return false; - } - } - } - - // check for unlocked password (or unlock command) - if ($aseco->settings['lock_password'] != '' && !$admin->unlocked && - $command['params'][0] != 'unlock') { - // write warning in console - $aseco->console($login . ' tried to use admin chat command (not unlocked!): ' . $arglist[0] . ' ' . $arglist[1]); - // show chat message - $aseco->client->query('ChatSendToLogin', $aseco->formatColors('{#error}You don\'t have the required admin rights to do that!'), $login); - return false; - } - - /** - * Show admin help. - */ - if ($command['params'][0] == 'help') { - // build list of currently active commands - $active_commands = array(); - foreach ($aseco->chat_commands as $cc) { - // strip off optional abbreviation - $name = preg_replace('/\/.*/', '', $cc->name); - - // check if admin command is within this admin's tier - if ($cc->isadmin && $aseco->allowAbility($admin, $name)) { - $active_command = new ChatCommand($cc->name, $cc->help, true); - $active_commands[] = $active_command; - } - } - - // show active admin commands on command line - showHelp($admin, $active_commands, $logtitle, true, false); - - /** - * Display admin help. - */ - } elseif ($command['params'][0] == 'helpall') { - - // build list of currently active commands - $active_commands = array(); - foreach ($aseco->chat_commands as $cc) { - // strip off optional abbreviation - $name = preg_replace('/\/.*/', '', $cc->name); - - // check if admin command is within this admin's tier - if ($cc->isadmin && $aseco->allowAbility($admin, $name)) { - $active_command = new ChatCommand($cc->name, $cc->help, true); - $active_commands[] = $active_command; - } - } - - // display active admin commands in popup with descriptions - showHelp($admin, $active_commands, $logtitle, true, true, 0.42); - - /** - * Sets a new server name (on the fly). - */ - } elseif ($command['params'][0] == 'setservername' && $command['params'][1] != '') { - - // set a new servername - $aseco->client->query('SetServerName', $arglist[1]); - - // log console message - $aseco->console('{1} [{2}] set new server name [{3}]', $logtitle, $login, $arglist[1]); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets servername to {#highlite}{3}', - $chattitle, $admin->nickname, $arglist[1]); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - /** - * Sets a new server comment (on the fly). - */ - } elseif ($command['params'][0] == 'setcomment' && $command['params'][1] != '') { - - // set a new server comment - $aseco->client->query('SetServerComment', $arglist[1]); - - // log console message - $aseco->console('{1} [{2}] set new server comment [{3}]', $logtitle, $login, $arglist[1]); - - // show chat message - $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets server comment to {#highlite}{3}', - $chattitle, $admin->nickname, $arglist[1]); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Sets a new player password (on the fly). - */ - } elseif ($command['params'][0] == 'setpwd') { - - // set a new player password - $aseco->client->query('SetServerPassword', $arglist[1]); - - if ($arglist[1] != '') { - // log console message - $aseco->console('{1} [{2}] set new player password [{3}]', $logtitle, $login, $arglist[1]); - - // show chat message - $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets player password to {#highlite}{3}', - $chattitle, $admin->nickname, $arglist[1]); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - // log console message - $aseco->console('{1} [{2}] disabled player password', $logtitle, $login); - - // show chat message - $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} disables player password', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Sets a new spectator password (on the fly). - */ - } elseif ($command['params'][0] == 'setspecpwd') { - - // set a new spectator password - $aseco->client->query('SetServerPasswordForSpectator', $arglist[1]); - - if ($arglist[1] != '') { - // log console message - $aseco->console('{1} [{2}] set new spectator password [{3}]', $logtitle, $login, $arglist[1]); - - // show chat message - $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets spectator password to {#highlite}{3}', - $chattitle, $admin->nickname, $arglist[1]); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - // log console message - $aseco->console('{1} [{2}] disabled spectator password', $logtitle, $login); - - // show chat message - $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} disables spectator password', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Sets a new referee password (on the fly). - */ - } elseif ($command['params'][0] == 'setrefpwd') { - - if ($aseco->server->getGame() == 'TMF') { - // set a new referee password - $aseco->client->query('SetRefereePassword', $arglist[1]); - - if ($arglist[1] != '') { - // log console message - $aseco->console('{1} [{2}] set new referee password [{3}]', $logtitle, $login, $arglist[1]); - - // show chat message - $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets referee password to {#highlite}{3}', - $chattitle, $admin->nickname, $arglist[1]); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - // log console message - $aseco->console('{1} [{2}] disabled referee password', $logtitle, $login); - - // show chat message - $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} disables referee password', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Sets a new player maximum that is able to connect to the server. - */ - } elseif ($command['params'][0] == 'setmaxplayers' && is_numeric($command['params'][1]) && $command['params'][1] > 0) { - - // tell server to set new player max - $aseco->client->query('SetMaxPlayers', (int) $command['params'][1]); - - // log console message - $aseco->console('{1} [{2}] set new player maximum [{3}]', $logtitle, $login, $command['params'][1]); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets new player maximum to {#highlite}{3}{#admin} !', - $chattitle, $admin->nickname, $command['params'][1]); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - /** - * Sets a new spectator maximum that is able to connect to the server. - */ - } elseif ($command['params'][0] == 'setmaxspecs' && is_numeric($command['params'][1]) && $command['params'][1] >= 0) { - - // tell server to set new spectator max - $aseco->client->query('SetMaxSpectators', (int) $command['params'][1]); - - // log console message - $aseco->console('{1} [{2}] set new spectator maximum [{3}]', $logtitle, $login, $command['params'][1]); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets new spectator maximum to {#highlite}{3}{#admin} !', - $chattitle, $admin->nickname, $command['params'][1]); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - /** - * Sets new game mode that will be active upon the next track: - * ta,rounds,team,laps,stunts - */ - } elseif ($command['params'][0] == 'setgamemode' && $command['params'][1] != '') { - - // check mode parameter - switch (strtolower($command['params'][1])) { - case 'ta': - $mode = Gameinfo::TA; - break; - case 'round': // permit shortcut - case 'rounds': - $mode = Gameinfo::RNDS; - break; - case 'team': - $mode = Gameinfo::TEAM; - break; - case 'laps': - $mode = Gameinfo::LAPS; - break; - case 'stunts': - $mode = Gameinfo::STNT; - break; - case 'cup': - if ($aseco->server->getGame() == 'TMF') - $mode = Gameinfo::CUP; - else - $mode = -1; - break; - default: - $mode = -1; - } - - if ($mode >= 0) { - if ($aseco->changingmode || $mode != $aseco->server->gameinfo->mode) { - // tell server to set new game mode - $aseco->client->query('SetGameMode', $mode); - $aseco->changingmode = true; - - // log console message - $aseco->console('{1} [{2}] set new game mode [{3}]', $logtitle, $login, strtoupper($command['params'][1])); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets next game mode to {#highlite}{3}{#admin} !', - $chattitle, $admin->nickname, strtoupper($command['params'][1])); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - $aseco->changingmode = false; - $message = '{#server}> Same game mode {#highlite}' . strtoupper($command['params'][1]); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = '{#server}> {#error}Invalid game mode {#highlite}$i ' . strtoupper($command['params'][1]) . '$z$s {#error}!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Sets new referee mode (0 = top3, 1 = all). - */ - } elseif ($command['params'][0] == 'setrefmode') { - - if ($aseco->server->getGame() == 'TMF') { - if (($mode = $command['params'][1]) != '') { - if (is_numeric($mode) && ($mode == 0 || $mode == 1)) { - // tell server to set new referee mode - $aseco->client->query('SetRefereeMode', (int) $mode); - - // log console message - $aseco->console('{1} [{2}] set new referee mode [{3}]', $logtitle, $login, strtoupper($mode)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} sets referee mode to {#highlite}{3}{#admin} !', - $chattitle, $admin->nickname, $mode); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - $message = '{#server}> {#error}Invalid referee mode {#highlite}$i ' . strtoupper($mode) . '$z$s {#error}!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - // tell server to get current referee mode - $aseco->client->query('GetRefereeMode'); - $mode = $aseco->client->getResponse(); - - // show chat message - $message = formatText('{#server}> {#admin}Referee mode is set to {#highlite}{1}', - ($mode == 1 ? 'All' : 'Top-3')); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Forces the server to load next track. - */ - } elseif ($command['params'][0] == 'nextmap' || - $command['params'][0] == 'next' || - $command['params'][0] == 'skipmap' || - $command['params'][0] == 'skip') { - - // load the next map - // don't clear scores if in TMF Cup mode - if ($aseco->server->gameinfo->mode == Gameinfo::CUP) - $aseco->client->query('NextChallenge', true); - else - $aseco->client->query('NextChallenge'); - - // log console message - $aseco->console('{1} [{2}] skips challenge!', $logtitle, $login); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} skips challenge!', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - /** - * Forces the server to load previous track. - */ - } elseif ($command['params'][0] == 'previous' || - $command['params'][0] == 'prev') { - - // get current track - $aseco->client->query('GetCurrentChallengeIndex'); - $current = $aseco->client->getResponse(); - - // check if not the first track - if ($current > 0) { - // find previous track - $aseco->client->query('GetChallengeList', 1, --$current); - $track = $aseco->client->getResponse(); - $prev = array(); - $prev['name'] = $track[0]['Name']; - $prev['environment'] = $track[0]['Environnement']; - $prev['filename'] = $track[0]['FileName']; - $prev['uid'] = $track[0]['UId']; - } else { - // dummy player to easily obtain entire track list - $list = new Player(); - getAllChallenges($list, '*', '*'); - // find last track - $prev = end($list->tracklist); - unset($list); - } - - // prepend previous challenge to start of jukebox - $uid = $prev['uid']; - $jukebox = array_reverse($jukebox, true); - $jukebox[$uid]['FileName'] = $prev['filename']; - $jukebox[$uid]['Name'] = $prev['name']; - $jukebox[$uid]['Env'] = $prev['environment']; - $jukebox[$uid]['Login'] = $admin->login; - $jukebox[$uid]['Nick'] = $admin->nickname; - $jukebox[$uid]['source'] = 'Previous'; - $jukebox[$uid]['tmx'] = false; - $jukebox[$uid]['uid'] = $uid; - $jukebox = array_reverse($jukebox, true); - - if ($aseco->debug) { - $aseco->console_text('/admin prev jukebox:' . CRLF . - print_r($jukebox, true)); - } - - // load the previous track - // don't clear scores if in TMF Cup mode - if ($aseco->server->gameinfo->mode == Gameinfo::CUP) - $aseco->client->query('NextChallenge', true); - else - $aseco->client->query('NextChallenge'); - - // log console message - $aseco->console('{1} [{2}] revisits previous challenge!', $logtitle, $login); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} revisits previous challenge!', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - // throw 'jukebox changed' event - $aseco->releaseEvent('onJukeboxChanged', array('previous', $jukebox[$uid])); - - /** - * Loads the next track in the same environment. - */ - } elseif ($command['params'][0] == 'nextenv') { - - // check for TMF United - if ($aseco->server->getGame() == 'TMF' && - $aseco->server->packmask != 'Stadium') { - // dummy player to easily obtain environment track list - $list = new Player(); - getAllChallenges($list, '*', $aseco->server->challenge->environment); - - // search for current track - $next = null; - $found = false; - foreach ($list->tracklist as $track) { - if ($found) { - $next = $track; - break; - } - if ($track['uid'] == $aseco->server->challenge->uid) - $found = true; - } - // check for last track and loop back to first - if ($next === null) - $next = $list->tracklist[0]; - unset($list); - - // prepend next env challenge to start of jukebox - $uid = $next['uid']; - $jukebox = array_reverse($jukebox, true); - $jukebox[$uid]['FileName'] = $next['filename']; - $jukebox[$uid]['Name'] = $next['name']; - $jukebox[$uid]['Env'] = $next['environment']; - $jukebox[$uid]['Login'] = $admin->login; - $jukebox[$uid]['Nick'] = $admin->nickname; - $jukebox[$uid]['source'] = 'Previous'; - $jukebox[$uid]['tmx'] = false; - $jukebox[$uid]['uid'] = $uid; - $jukebox = array_reverse($jukebox, true); - - if ($aseco->debug) { - $aseco->console_text('/admin nextenv jukebox:' . CRLF . - print_r($jukebox, true)); - } - - // load the next environment track - // don't clear scores if in TMF Cup mode - if ($aseco->server->gameinfo->mode == Gameinfo::CUP) - $aseco->client->query('NextChallenge', true); - else - $aseco->client->query('NextChallenge'); - - // log console message - $aseco->console('{1} [{2}] skips to next {3} challenge!', $logtitle, $login, $aseco->server->challenge->environment); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} skips to next {#highlite}{3}{#admin} challenge!', - $chattitle, $admin->nickname, $aseco->server->challenge->environment); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - // throw 'jukebox changed' event - $aseco->releaseEvent('onJukeboxChanged', array('nextenv', $jukebox[$uid])); - - } else { // TMN(F) - $message = '{#server}> {#error}Command only available on TMU Forever!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Restarts the currently running map. - */ - } elseif ($command['params'][0] == 'restartmap' || - $command['params'][0] == 'res') { - global $atl_restart; // from plugin.autotime.php - - // restart the track - if (isset($atl_restart)) $atl_restart = true; - // don't clear scores if in TMF Cup mode - if ($aseco->server->gameinfo->mode == Gameinfo::CUP) - $aseco->client->query('ChallengeRestart', true); - else - $aseco->client->query('ChallengeRestart'); - - // log console message - $aseco->console('{1} [{2}] restarts challenge!', $logtitle, $login); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} restarts challenge!', - $chattitle, $admin->nickname); - - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - /** - * Replays the current map (queues it at start of jukebox). - */ - } elseif ($command['params'][0] == 'replaymap' || - $command['params'][0] == 'replay') { - global $chatvote; // from plugin.rasp_votes.php - - // cancel possibly ongoing replay/restart vote - $aseco->client->query('CancelVote'); - if (!empty($chatvote) && $chatvote['type'] == 2) { - $chatvote = array(); - // disable all vote panels - if ($aseco->server->getGame() == 'TMF') - allvotepanels_off($aseco); - } - - // check if track already in jukebox - if (!empty($jukebox) && array_key_exists($aseco->server->challenge->uid, $jukebox)) { - $message = '{#server}> {#error}Track is already getting replayed!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - return; - } - - // prepend current challenge to start of jukebox - $uid = $aseco->server->challenge->uid; - $jukebox = array_reverse($jukebox, true); - $jukebox[$uid]['FileName'] = $aseco->server->challenge->filename; - $jukebox[$uid]['Name'] = $aseco->server->challenge->name; - $jukebox[$uid]['Env'] = $aseco->server->challenge->environment; - $jukebox[$uid]['Login'] = $admin->login; - $jukebox[$uid]['Nick'] = $admin->nickname; - $jukebox[$uid]['source'] = 'AdminReplay'; - $jukebox[$uid]['tmx'] = false; - $jukebox[$uid]['uid'] = $uid; - $jukebox = array_reverse($jukebox, true); - - if ($aseco->debug) { - $aseco->console_text('/admin replay jukebox:' . CRLF . - print_r($jukebox, true)); - } - - // log console message - $aseco->console('{1} [{2}] requeues challenge!', $logtitle, $login); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} queues challenge for replay!', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - // throw 'jukebox changed' event - $aseco->releaseEvent('onJukeboxChanged', array('replay', $jukebox[$uid])); - - /** - * Drops a track from the jukebox (for use with rasp jukebox plugin). - */ - } elseif ($command['params'][0] == 'dropjukebox' || - $command['params'][0] == 'djb') { - - // verify parameter - if (is_numeric($command['params'][1]) && - $command['params'][1] >= 1 && $command['params'][1] <= count($jukebox)) { - $i = 1; - foreach ($jukebox as $item) { - if ($i++ == $command['params'][1]) { - $name = stripColors($item['Name']); - $uid = $item['uid']; - break; - } - } - $drop = $jukebox[$uid]; - unset($jukebox[$uid]); - - // log console message - $aseco->console('{1} [{2}] drops track {3} from jukebox!', $logtitle, $login, stripColors($name, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} drops track {#highlite}{3}{#admin} from jukebox!', - $chattitle, $admin->nickname, $name); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - // throw 'jukebox changed' event - $aseco->releaseEvent('onJukeboxChanged', array('drop', $drop)); - } else { - $message = '{#server}> {#error}Jukebox entry not found! Type {#highlite}$i /jukebox list{#error} or {#highlite}$i /jukebox display{#error} for its contents.'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Clears the jukebox (for use with rasp jukebox plugin). - */ - } elseif ($command['params'][0] == 'clearjukebox' || - $command['params'][0] == 'cjb') { - - // clear jukebox - $jukebox = array(); - - // log console message - $aseco->console('{1} [{2}] clears jukebox!', $logtitle, $login); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} clears jukebox!', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - // throw 'jukebox changed' event - $aseco->releaseEvent('onJukeboxChanged', array('clear', null)); - - /** - * Clears (part of) track history. - */ - } elseif ($command['params'][0] == 'clearhist') { - global $buffersize, $jb_buffer; // from rasp.settings.php - - // check for optional portion (pos = newest, neg = oldest) - if ($command['params'][1] != '' && is_numeric($command['params'][1]) && $command['params'][1] != 0) { - $clear = intval($command['params'][1]); - - // log console message - $aseco->console('{1} [{2}] clears {3} track{4} from history!', $logtitle, $login, - ($clear > 0 ? 'newest ' : 'oldest ') . abs($clear), - abs($clear) == 1 ? '' : 's'); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} clears {3}{#admin} track{4} from history!', - $chattitle, $admin->nickname, - ($clear > 0 ? 'newest {#highlite}' : 'oldest {#highlite}') . abs($clear), - abs($clear) == 1 ? '' : 's'); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } elseif (strtolower($command['params'][1]) == 'all') { // entire history - $clear = $buffersize; - - // log console message - $aseco->console('{1} [{2}] clears entire track history!', $logtitle, $login); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} clears entire track history!', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - // show chat message - $message = formatText('{#server}> {#admin}The track history contains {#highlite}{3}{#admin} track{4}', - $chattitle, $admin->nickname, count($jb_buffer), - (count($jb_buffer) == 1 ? '' : 's')); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - return; - } - - // clear track history (portion) - $i = 0; - if ($clear > 0) { - if ($clear > $buffersize) $clear = $buffersize; - while ($i++ < $clear) array_pop($jb_buffer); - } else { - if ($clear < -$buffersize) $clear = -$buffersize; - while ($i-- > $clear) array_shift($jb_buffer); - } - - /** - * Adds TMX tracks to the track rotation. - */ - } elseif ($command['params'][0] == 'add') { - global $rasp, $tmxdir, $jukebox_adminadd; // from plugin.rasp.php, rasp.settings.php - - $sections = array('TMO' => 'original', - 'TMS' => 'sunrise', - 'TMN' => 'nations', - 'TMU' => 'united', - 'TMNF' => 'tmnforever'); - - // check last parameter - $last = strtoupper(end($command['params'])); - // try to load the track(s) from TMX - $source = 'TMX'; - $section = $aseco->server->getGame(); - if ($section == 'TMF' && count($command['params']) > 2 && - substr($last, 0, 2) == 'TM') { - $section = $last; - array_pop($command['params']); - if (!array_key_exists($section, $sections)) { - $message = '{#server}> {#error}No such section on TMX!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - return; - } - } else { // TMN/TMS/TMO or no section - if ($section == 'TMF') { - if ($aseco->server->packmask == 'Stadium') - $section = 'TMNF'; - else - $section = 'TMU'; - } - } - $remotelink = 'http://' . $sections[$section] . '.tm-exchange.com/get.aspx?action=trackgbx&id='; - - if (count($command['params']) == 1) { - $message = '{#server}> {#error}You must include a TMX Track_ID!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - return; - } - - // try all specified tracks - for ($id = 1; $id < count($command['params']); $id++) { - // check for valid TMX ID - if (is_numeric($command['params'][$id]) && $command['params'][$id] >= 0) { - $trkid = ltrim($command['params'][$id], '0'); - $file = http_get_file($remotelink . $trkid); - if ($file === false || $file == -1) { - $message = '{#server}> {#error}Error downloading, or wrong TMX section, or TMX is down!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - // check for maximum online track size (256 KB) - if (strlen($file) >= 256 * 1024) { - $message = formatText($rasp->messages['TRACK_TOO_LARGE'][0], - round(strlen($file) / 1024)); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - continue; - } - $sepchar = substr($aseco->server->trackdir, -1, 1); - $partialdir = $tmxdir . $sepchar . $trkid . '.Challenge.gbx'; - $localfile = $aseco->server->trackdir . $partialdir; - if ($nocasepath = file_exists_nocase($localfile)) { - if (!unlink($nocasepath)) { - $message = '{#server}> {#error}Error erasing old file - unable to erase {#highlite}$i ' . $localfile; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - continue; - } - } - if (!$lfile = @fopen($localfile, 'wb')) { - $message = '{#server}> {#error}Error creating file - unable to create {#highlite}$i ' . $localfile; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - continue; - } - if (!fwrite($lfile, $file)) { - $message = '{#server}> {#error}Error saving file - unable to write data'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - fclose($lfile); - continue; - } - fclose($lfile); - $newtrk = getChallengeData($localfile, false); // 2nd parm is whether or not to get players & votes required - if ($newtrk['votes'] == 500 && $newtrk['name'] == 'Not a GBX file') { - $message = '{#server}> {#error}No such track on ' . $source; - if ($source == 'TMX' && $aseco->server->getGame() == 'TMF') - $message .= ' section ' . $section; - $message .= '!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - unlink($localfile); - continue; - } - // dummy player to easily obtain entire track list - $list = new Player(); - getAllChallenges($list, '*', '*'); - // check for track presence on server - foreach ($list->tracklist as $key) { - if ($key['uid'] == $newtrk['uid']) { - $message = $rasp->messages['ADD_PRESENT'][0]; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - unlink($localfile); - unset($list); - continue 2; // outer for loop - } - } - unset($list); - // rename ID filename to track's name - $md5new = md5_file($localfile); - $filename = trim(utf8_decode(stripColors($newtrk['name']))); - $filename = preg_replace('/[^A-Za-z0-9 \'#=+~_,.-]/', '_', $filename); - $filename = preg_replace('/ +/', ' ', preg_replace('/_+/', '_', $filename)); - $partialdir = $tmxdir . $sepchar . $filename . '_' . $trkid . '.Challenge.gbx'; - // insure unique filename by incrementing sequence number, - // if not a duplicate track - $i = 1; - $dupl = false; - while ($nocasepath = file_exists_nocase($aseco->server->trackdir . $partialdir)) { - $md5old = md5_file($nocasepath); - if ($md5old == $md5new) { - $dupl = true; - $partialdir = str_replace($aseco->server->trackdir, '', $nocasepath); - break; - } else { - $partialdir = $tmxdir . $sepchar . $filename . '_' . $trkid . '-' . $i++ . '.Challenge.gbx'; - } - } - if ($dupl) { - unlink($localfile); - } else { - rename($localfile, $aseco->server->trackdir . $partialdir); - } - - // check track vs. server settings - if ($aseco->server->getGame() == 'TMF') - $rtn = $aseco->client->query('CheckChallengeForCurrentServerParams', $partialdir); - else - $rtn = true; - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] CheckChallengeForCurrentServerParams - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - $message = formatText($rasp->messages['JUKEBOX_IGNORED'][0], - stripColors($newtrk['name']), $aseco->client->getErrorMessage()); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - // permanently add the track to the server list - $rtn = $aseco->client->query('AddChallenge', $partialdir); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] AddChallenge - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - } else { - $aseco->client->resetError(); - $aseco->client->query('GetChallengeInfo', $partialdir); - $track = $aseco->client->getResponse(); - if ($aseco->client->isError()) { - trigger_error('[' . $aseco->client->getErrorCode() . '] GetChallengeInfo - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - $message = formatText('{#server}> {#error}Error getting info on track {#highlite}$i {1} {#error}!', - $partialdir); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - $track['Name'] = stripNewlines($track['Name']); - // check whether to jukebox as well - // overrules /add-ed but not yet played track - if ($jukebox_adminadd) { - $uid = $track['UId']; - $jukebox[$uid]['FileName'] = $track['FileName']; - $jukebox[$uid]['Name'] = $track['Name']; - $jukebox[$uid]['Env'] = $track['Environnement']; - $jukebox[$uid]['Login'] = $login; - $jukebox[$uid]['Nick'] = $admin->nickname; - $jukebox[$uid]['source'] = $source; - $jukebox[$uid]['tmx'] = false; - $jukebox[$uid]['uid'] = $uid; - } - - // log console message - $aseco->console('{1} [{2}] adds track "{3}" from {4}!', $logtitle, $login, stripColors($track['Name'], false), $source); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}adds {3}track: {#highlite}{4} {#admin}from {5}', - $chattitle, $admin->nickname, - ($jukebox_adminadd ? '& jukeboxes ' : ''), - stripColors($track['Name']), $source); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - // throw 'tracklist changed' event - $aseco->releaseEvent('onTracklistChanged', array('add', $partialdir)); - - // throw 'jukebox changed' event - if ($jukebox_adminadd) - $aseco->releaseEvent('onJukeboxChanged', array('add', $jukebox[$uid])); - } - } - } - } - } else { - $message = formatText('{#server}> {#highlite}{1}{#error} is not a valid TMX Track_ID!', - $command['params'][$id]); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - - /** - * Adds current /add-ed track permanently to server's track list - * by preventing its removal that normally occurs afterwards - */ - } elseif ($command['params'][0] == 'addthis') { - global $tmxplayed, $tmxdir, $tmxtmpdir; // from plugin.rasp_jukebox.php, rasp.settings.php - - // check for TMX /add-ed track - if ($tmxplayed) { - // remove track with old path - $rtn = $aseco->client->query('RemoveChallenge', $tmxplayed); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] RemoveChallenge - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - return; - } else { - // move the track file - $tmxnew = str_replace($tmxtmpdir, $tmxdir, $tmxplayed); - if (!rename($aseco->server->trackdir . $tmxplayed, - $aseco->server->trackdir . $tmxnew)) { - trigger_error('Could not rename TMX track ' . $tmxplayed . ' to ' . $tmxnew, E_USER_WARNING); - return; - } else { - // add track with new path - $rtn = $aseco->client->query('AddChallenge', $tmxnew); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] AddChallenge - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - return; - } else { // store new path - $aseco->server->challenge->filename = $tmxnew; - - // throw 'tracklist changed' event - $aseco->releaseEvent('onTracklistChanged', array('rename', $tmxnew)); - } - } - } - - // disable track removal afterwards - $tmxplayed = false; - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}permanently adds current track: {#highlite}{3}', - $chattitle, $admin->nickname, - stripColors($aseco->server->challenge->name)); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - $message = formatText('{#server}> {#error}Current track {#highlite}$i {1} {#error}already permanently in track list!', - stripColors($aseco->server->challenge->name)); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Add a local track to the track rotation. - */ - } elseif ($command['params'][0] == 'addlocal') { - global $rasp, $jukebox_adminadd; // from plugin.rasp.php, rasp.settings.php - - // check for local track file - if ($arglist[1] != '') { - $sepchar = substr($aseco->server->trackdir, -1, 1); - $partialdir = 'Challenges' . $sepchar . 'Downloaded' . $sepchar . $arglist[1]; - if (!stristr($partialdir, '.Challenge.gbx')) { - $partialdir .= '.Challenge.gbx'; - } - $localfile = $aseco->server->trackdir . $partialdir; - if ($nocasepath = file_exists_nocase($localfile)) { - // check for maximum online track size (256 KB) - if (filesize($nocasepath) >= 256 * 1024) { - $message = formatText($rasp->messages['TRACK_TOO_LARGE'][0], - round(filesize($nocasepath) / 1024)); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - return; - } - $partialdir = str_replace($aseco->server->trackdir, '', $nocasepath); - - // check track vs. server settings - if ($aseco->server->getGame() == 'TMF') - $rtn = $aseco->client->query('CheckChallengeForCurrentServerParams', $partialdir); - else - $rtn = true; - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] CheckChallengeForCurrentServerParams - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - $message = formatText($rasp->messages['JUKEBOX_IGNORED'][0], - stripColors(str_replace('Challenges' . $sepchar . 'Downloaded' . $sepchar, '', $partialdir)), $aseco->client->getErrorMessage()); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - // permanently add the track to the server list - $rtn = $aseco->client->query('AddChallenge', $partialdir); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] AddChallenge - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - } else { - $aseco->client->resetError(); - $aseco->client->query('GetChallengeInfo', $partialdir); - $track = $aseco->client->getResponse(); - if ($aseco->client->isError()) { - trigger_error('[' . $aseco->client->getErrorCode() . '] GetChallengeInfo - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - $message = formatText('{#server}> {#error}Error getting info on track {#highlite}$i {1} {#error}!', - $partialdir); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - $track['Name'] = stripNewlines($track['Name']); - // check whether to jukebox as well - // overrules /add-ed but not yet played track - if ($jukebox_adminadd) { - $uid = $track['UId']; - $jukebox[$uid]['FileName'] = $track['FileName']; - $jukebox[$uid]['Name'] = $track['Name']; - $jukebox[$uid]['Env'] = $track['Environnement']; - $jukebox[$uid]['Login'] = $login; - $jukebox[$uid]['Nick'] = $admin->nickname; - $jukebox[$uid]['source'] = 'Local'; - $jukebox[$uid]['tmx'] = false; - $jukebox[$uid]['uid'] = $uid; - } - - // log console message - $aseco->console('{1} [{2}] adds local track {3} !', $logtitle, $login, stripColors($track['Name'], false)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}adds {3}track: {#highlite}{4}', - $chattitle, $admin->nickname, - ($jukebox_adminadd ? '& jukeboxes ' : ''), - stripColors($track['Name'])); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - // throw 'tracklist changed' event - $aseco->releaseEvent('onTracklistChanged', array('add', $partialdir)); - - // throw 'jukebox changed' event - if ($jukebox_adminadd) - $aseco->releaseEvent('onJukeboxChanged', array('add', $jukebox[$uid])); - } - } - } - } else { - $message = '{#server}> {#highlite}' . $partialdir . '{#error} not found!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = '{#server}> {#error}You must include a local track filename!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Warns a player with the specified login/PlayerID. - */ - } elseif ($command['params'][0] == 'warn' && $command['params'][1] != '') { - - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) { - // display warning message - $message = $aseco->getChatMessage('WARNING'); - if ($aseco->server->getGame() == 'TMF') { - $message = preg_split('/{br}/', $aseco->formatColors($message)); - foreach ($message as &$line) - $line = array($line); - - display_manialink($target->login, $aseco->formatColors('{#welcome}WARNING:'), array('Icons64x64_1', 'TV'), - $message, array(0.8), 'OK'); - } else { // TMN - $message = str_replace('{br}', LF, $aseco->formatColors($message)); - $aseco->client->query('SendDisplayServerMessageToLogin', $target->login, $message, 'OK', '', 0); - } - // log console message - $aseco->console('{1} [{2}] warned player {3}!', $logtitle, $login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} warned {#highlite}{3}$z$s{#admin} !', - $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname)); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } - - /** - * Kicks a player with the specified login/PlayerID. - */ - } elseif ($command['params'][0] == 'kick' && $command['params'][1] != '') { - - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) { - // log console message - $aseco->console('{1} [{2}] kicked player {3}!', $logtitle, $login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} kicked {#highlite}{3}$z$s{#admin} !', - $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname)); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - // kick the player - $aseco->client->query('Kick', $target->login); - } - - /** - * Kicks a ghost player with the specified login. - * This variant for ghost players that got disconnected doesn't - * check the login for validity and doesn't work with Player_IDs. - */ - } elseif ($command['params'][0] == 'kickghost' && $command['params'][1] != '') { - - // get player login without validation - $target = $command['params'][1]; - - // log console message - $aseco->console('{1} [{2}] kicked ghost player {3}!', $logtitle, $login, $target); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} kicked ghost {#highlite}{3}$z$s{#admin} !', - $chattitle, $admin->nickname, $target); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - // kick the ghost player - $aseco->client->query('Kick', $target); - - /** - * Ban a player with the specified login/PlayerID. - */ - } elseif ($command['params'][0] == 'ban' && $command['params'][1] != '') { - - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) { - // log console message - $aseco->console('{1} [{2}] bans player {3}!', $logtitle, $login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} bans {#highlite}{3}$z$s{#admin} !', - $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname)); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - // update banned IPs file - $aseco->bannedips[] = $target->ip; - $aseco->writeIPs(); - - // ban the player and also kick him - $aseco->client->query('Ban', $target->login); - } - - /** - * Un-bans player with the specified login/PlayerID. - */ - } elseif ($command['params'][0] == 'unban' && $command['params'][1] != '') { - - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1], true)) { - $bans = get_banlist($aseco); - // unban the player - $rtn = $aseco->client->query('UnBan', $target->login); - if (!$rtn) { - $message = formatText('{#server}> {#highlite}{1}{#error} is not a banned player!', - $command['params'][1]); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - if (($i = array_search($bans[$target->login][2], $aseco->bannedips)) !== false) { - // update banned IPs file - $aseco->bannedips[$i] = ''; - $aseco->writeIPs(); - } - - // log console message - $aseco->console('{1} [{2}] unbans player {3}', $logtitle, $login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} un-bans {#highlite}{3}', - $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname)); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - - /** - * Ban a player with the specified IP address. - */ - } elseif ($command['params'][0] == 'banip' && $command['params'][1] != '') { - - // check for valid IP not already banned - $ipaddr = $command['params'][1]; - if (preg_match('/^\d+\.\d+\.\d+\.\d+$/', $ipaddr)) { - if (empty($aseco->bannedips) || !in_array($ipaddr, $aseco->bannedips)) { - // log console message - $aseco->console('{1} [{2}] banned IP {3}!', $logtitle, $login, $ipaddr); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} bans IP {#highlite}{3}$z$s{#admin} !', - $chattitle, $admin->nickname, $ipaddr); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - // update banned IPs file - $aseco->bannedips[] = $ipaddr; - $aseco->writeIPs(); - } else { - $message = formatText('{#server}> {#highlite}{1}{#error} is already banned!', - $ipaddr); - } - } else { - $message = formatText('{#server}> {#highlite}{1}{#error} is not a valid IP address!', - $ipaddr); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Un-bans player with the specified IP address. - */ - } elseif ($command['params'][0] == 'unbanip' && $command['params'][1] != '') { - - // check for banned IP - if (($i = array_search($command['params'][1], $aseco->bannedips)) === false) { - $message = formatText('{#server}> {#highlite}{1}{#error} is not a banned IP address!', - $command['params'][1]); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - // update banned IPs file - $aseco->bannedips[$i] = ''; - $aseco->writeIPs(); - - // log console message - $aseco->console('{1} [{2}] unbans IP {3}', $logtitle, $login, $command['params'][1]); - - // show chat message - $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} un-bans IP {#highlite}{3}', - $chattitle, $admin->nickname, $command['params'][1]); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Blacklists a player with the specified login/PlayerID. - */ - } elseif ($command['params'][0] == 'black' && $command['params'][1] != '') { - - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1], true)) { - // log console message - $aseco->console('{1} [{2}] blacklists player {3}!', $logtitle, $login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} blacklists {#highlite}{3}$z$s{#admin} !', - $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname)); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - // blacklist the player and then kick him - $aseco->client->query('BlackList', $target->login); - $aseco->client->query('Kick', $target->login); - - // update blacklist file - $filename = $aseco->settings['blacklist_file']; - $rtn = $aseco->client->query('SaveBlackList', $filename); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] SaveBlackList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - } - } - - /** - * Un-blacklists player with the specified login/PlayerID. - */ - } elseif ($command['params'][0] == 'unblack' && $command['params'][1] != '') { - - $target = false; - $param = $command['params'][1]; - - // get new list of all blacklisted players - $blacks = get_blacklist($aseco); - // check as login - if (array_key_exists($param, $blacks)) { - $target = new Player(); - // check as player ID - } elseif (is_numeric($param) && $param > 0) { - if (empty($admin->playerlist)) { - $message = '{#server}> {#error}Use {#highlite}$i/players {#error}first (optionally {#highlite}$i/players {#error})'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - return false; - } - $pid = ltrim($param, '0'); - $pid--; - // find player by given # - if (array_key_exists($pid, $admin->playerlist)) { - $param = $admin->playerlist[$pid]['login']; - $target = new Player(); - } else { - $message = '{#server}> {#error}Player_ID not found! Type {#highlite}$i/players {#error}to see all players.'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - return false; - } - } - - // check for valid param - if ($target !== false) { - $target->login = $param; - $target->nickname = $aseco->getPlayerNick($param); - if ($target->nickname == '') - $target->nickname = $param; - - // unblacklist the player - $rtn = $aseco->client->query('UnBlackList', $target->login); - if (!$rtn) { - $message = formatText('{#server}> {#highlite}{1}{#error} is not a blacklisted player!', - $command['params'][1]); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - // log console message - $aseco->console('{1} [{2}] unblacklists player {3}', $logtitle, $login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} un-blacklists {#highlite}{3}', - $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname)); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - // update blacklist file - $filename = $aseco->settings['blacklist_file']; - $rtn = $aseco->client->query('SaveBlackList', $filename); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] SaveBlackList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - } - } - } else { - $message = '{#server}> {#highlite}' . $param . ' {#error}is not a valid player! Use {#highlite}$i/players {#error}to find the correct login or Player_ID.'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Adds a guest player with the specified login/PlayerID. - */ - } elseif ($command['params'][0] == 'addguest' && $command['params'][1] != '') { - - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1], true)) { - // add the guest player - $aseco->client->query('AddGuest', $target->login); - - // log console message - $aseco->console('{1} [{2}] adds guest player {3}', $logtitle, $login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} adds guest {#highlite}{3}', - $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname)); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - // update guestlist file - $filename = $aseco->settings['guestlist_file']; - $rtn = $aseco->client->query('SaveGuestList', $filename); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] SaveGuestList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - } - } - - /** - * Removes a guest player with the specified login/PlayerID. - */ - } elseif ($command['params'][0] == 'removeguest' && $command['params'][1] != '') { - - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1], true)) { - // remove the guest player - $rtn = $aseco->client->query('RemoveGuest', $target->login); - if (!$rtn) { - $message = formatText('{#server}> {#highlite}{1}{#error} is not a guest player!', - $command['params'][1]); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - // log console message - $aseco->console('{1} [{2}] removes guest player {3}', $logtitle, $login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} removes guest {#highlite}{3}', - $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname)); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - // update guestlist file - $filename = $aseco->settings['guestlist_file']; - $rtn = $aseco->client->query('SaveGuestList', $filename); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] SaveGuestList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - } - } - } - - /** - * Passes a chat-based or TMX /add vote. - */ - } elseif ($command['params'][0] == 'pass') { - global $tmxadd, $chatvote, $plrvotes; // from plugin.rasp_jukebox.php, plugin.rasp_votes.php - - // pass any TMX and chat vote - if (!empty($tmxadd)) { - // force required votes down to the last one - $tmxadd['votes'] = 1; - } - elseif (!empty($chatvote)) { - $chatvote['votes'] = 1; - } - else { // no vote in progress - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}There is no vote right now!'), $login); - return; - } - - // log console message - $aseco->console('{1} [{2}] passes vote!', $logtitle, $login); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} passes vote!', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - // bypass double vote check - $plrvotes = array(); - // enter the last vote - chat_y($aseco, $command); - - /** - * Cancels any vote. - */ - } elseif ($command['params'][0] == 'cancel' || - $command['params'][0] == 'can') { - global $tmxadd, $chatvote; // from plugin.rasp_jukebox.php, plugin.rasp_votes.php - - // cancel any CallVote, TMX and chat vote - $aseco->client->query('CancelVote'); - $tmxadd = array(); - $chatvote = array(); - // disable all vote panels - if ($aseco->server->getGame() == 'TMF') - allvotepanels_off($aseco); - - // log console message - $aseco->console('{1} [{2}] cancels vote!', $logtitle, $login); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} cancels vote!', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - /** - * Forces end of current round. - */ - } elseif ($command['params'][0] == 'endround' || - $command['params'][0] == 'er') { - global $chatvote; // from plugin.rasp_votes.php - - // cancel possibly ongoing endround vote - if (!empty($chatvote) && $chatvote['type'] == 0) { - $chatvote = array(); - // disable all vote panels - if ($aseco->server->getGame() == 'TMF') - allvotepanels_off($aseco); - } - - // end this round - $aseco->client->query('ForceEndRound'); - - // log console message - $aseco->console('{1} [{2}] forces round end!', $logtitle, $login); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} forces round end!', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - /** - * Displays the live or known players (on/offline) list. - * TMF player management inspired by Mistral. - */ - } elseif ($command['params'][0] == 'players') { - - $admin->playerlist = array(); - $admin->msgs = array(); - - // remember players parameter for possible refresh - $admin->panels['plyparam'] = $command['params'][1]; - $onlineonly = (strtolower($command['params'][1]) == 'live'); - // get current ignore/ban/black/guest lists - if ($aseco->server->getGame() == 'TMF') { - $ignores = get_ignorelist($aseco); - $bans = get_banlist($aseco); - $blacks = get_blacklist($aseco); - $guests = get_guestlist($aseco); - } - - // create new list of online players - $aseco->client->resetError(); - $onlinelist = array(); - // get current players on the server (hardlimited to 300) - if ($aseco->server->getGame() == 'TMF') - $aseco->client->query('GetPlayerList', 300, 0, 1); - else - $aseco->client->query('GetPlayerList', 300, 0); - $players = $aseco->client->getResponse(); - if ($aseco->client->isError()) { - trigger_error('[' . $aseco->client->getErrorCode() . '] GetPlayerList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - } else { - foreach ($players as $pl) - if ($aseco->server->getGame() == 'TMF') { - // on relay, check for player from master server - if (!$aseco->server->isrelay || floor($pl['Flags'] / 10000) % 10 == 0) - $onlinelist[$pl['Login']] = array('login' => $pl['Login'], - 'nick' => $pl['NickName'], - 'spec' => $pl['SpectatorStatus']); - } else { - $onlinelist[$pl['Login']] = array('login' => $pl['Login'], - 'nick' => $pl['NickName'], - 'spec' => $pl['IsSpectator']); - } - } - - // use online list? - if ($onlineonly) { - $playerlist = $onlinelist; - } else { - // search for known players - $query = 'SELECT login,nickname FROM players - WHERE login LIKE ' . quotedString('%' . $arglist[1] . '%') . - ' OR nickname LIKE ' . quotedString('%' . $arglist[1] . '%') . - ' LIMIT 5000'; // prevent possible memory overrun - $result = mysql_query($query); - - $playerlist = array(); - if (mysql_num_rows($result) > 0) { - while ($row = mysql_fetch_row($result)) { - // skip any LAN logins - if (!isLANLogin($row[0])) - $playerlist[$row[0]] = array('login' => $row[0], - 'nick' => $row[1], - 'spec' => false); - } - } - mysql_free_result($result); - } - - if (!empty($playerlist)) { - if ($aseco->server->getGame() == 'TMN') { - $head = ($onlineonly ? 'Online' : 'Known') . ' Players On This Server:' . LF . - 'Id {#nick}Nick $g/{#login} Login' . LF; - $msg = ''; - $pid = 1; - $lines = 0; - $admin->msgs[0] = 1; - foreach ($playerlist as $pl) { - $plarr = array(); - $plarr['login'] = $pl['login']; - $admin->playerlist[] = $plarr; - - $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#black}' - . str_ireplace('$w', '', $pl['nick']) . '$z / ' - . ($aseco->isAnyAdminL($pl['login']) ? '{#logina}' : '{#login}' ) - . $pl['login'] . LF; - $pid++; - if (++$lines > 9) { - $admin->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - // add if last batch exists - if ($msg != '') - $admin->msgs[] = $aseco->formatColors($head . $msg); - - // display popup message - if (count($admin->msgs) == 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0); - } elseif (count($admin->msgs) > 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No player(s) found!'), $login); - } - - } elseif ($aseco->server->getGame() == 'TMF') { - $head = ($onlineonly ? 'Online' : 'Known') . ' Players On This Server:'; - $msg = array(); - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login', 'Warn', 'Ignore', 'Kick', 'Ban', 'Black', 'Guest', 'Spec'); - $pid = 1; - $lines = 0; - $admin->msgs[0] = array(1, $head, array(1.49, 0.15, 0.5, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12), array('Icons128x128_1', 'Buddies')); - - foreach ($playerlist as $lg => $pl) { - $plarr = array(); - $plarr['login'] = $lg; - $admin->playerlist[] = $plarr; - - // format nickname & login - $ply = '{#black}' . str_ireplace('$w', '', $pl['nick']) . '$z / ' - . ($aseco->isAnyAdminL($pl['login']) ? '{#logina}' : '{#login}' ) - . $pl['login']; - // define colored column strings - $wrn = '$ff3Warn'; - $ign = '$f93Ignore'; - $uig = '$d93UnIgn'; - $kck = '$c3fKick'; - $ban = '$f30Ban'; - $ubn = '$c30UnBan'; - $blk = '$f03Black'; - $ubk = '$c03UnBlack'; - $gst = '$3c3Add'; - $ugt = '$393Remove'; - $frc = '$09fForce'; - $off = '$09cOffln'; - $spc = '$09cSpec'; - - // always add clickable buttons - if ($pid <= 200) { - $ply = array($ply, $pid+2000); - if (array_key_exists($lg, $onlinelist)) { - // determine online operations - $wrn = array($wrn, $pid+2200); - if (array_key_exists($lg, $ignores)) - $ign = array($uig, $pid+2600); - else - $ign = array($ign, $pid+2400); - $kck = array($kck, $pid+2800); - if (array_key_exists($lg, $bans)) - $ban = array($ubn, $pid+3200); - else - $ban = array($ban, $pid+3000); - if (array_key_exists($lg, $blacks)) - $blk = array($ubk, $pid+3600); - else - $blk = array($blk, $pid+3400); - if (array_key_exists($lg, $guests)) - $gst = array($ugt, $pid+4000); - else - $gst = array($gst, $pid+3800); - if (!$onlinelist[$lg]['spec']) - $spc = array($frc, $pid+4200); - } else { - // determine offline operations - if (array_key_exists($lg, $ignores)) - $ign = array($uig, $pid+2600); - if (array_key_exists($lg, $bans)) - $ban = array($ubn, $pid+3200); - if (array_key_exists($lg, $blacks)) - $blk = array($ubk, $pid+3600); - else - $blk = array($blk, $pid+3400); - if (array_key_exists($lg, $guests)) - $gst = array($ugt, $pid+4000); - else - $gst = array($gst, $pid+3800); - $spc = $off; - } - } else { - // no more buttons - if (array_key_exists($lg, $ignores)) - $ign = $uig; - if (array_key_exists($lg, $bans)) - $ban = $ubn; - if (array_key_exists($lg, $blacks)) - $blk = $ubk; - if (array_key_exists($lg, $guests)) - $gst = $ugt; - if (array_key_exists($lg, $onlinelist)) { - if (!$onlinelist[$lg]['spec']) - $spc = $frc; - } else { - $spc = $off; - } - } - - $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.', $ply, - $wrn, $ign, $kck, $ban, $blk, $gst, $spc); - $pid++; - if (++$lines > 14) { - $admin->msgs[] = $msg; - $lines = 0; - $msg = array(); - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login', 'Warn', 'Ignore', 'Kick', 'Ban', 'Black', 'Guest', 'Spec'); - } - } - // add if last batch exists - if (count($msg) > 1) - $admin->msgs[] = $msg; - - // display ManiaLink message - if (count($admin->msgs) > 1) { - display_manialink_multi($admin); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No player(s) found!'), $login); - } - } - } else { - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No player(s) found!'), $login); - } - - /** - * Displays the ban list. - */ - } elseif ($command['params'][0] == 'showbanlist' || - $command['params'][0] == 'listbans') { - - $admin->playerlist = array(); - $admin->msgs = array(); - - // get new list of all banned players - $newlist = get_banlist($aseco); - - if ($aseco->server->getGame() == 'TMN') { - $head = 'Currently Banned Players:' . LF . 'Id {#nick}Nick $g/{#login} Login' . LF; - $msg = ''; - $pid = 1; - $lines = 0; - $admin->msgs[0] = 1; - foreach ($newlist as $player) { - $plarr = array(); - $plarr['login'] = $player[0]; - $admin->playerlist[] = $plarr; - - $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#black}' - . str_ireplace('$w', '', $player[1]) . '$z / {#login}' . $player[0] . LF; - $pid++; - if (++$lines > 9) { - $admin->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - // add if last batch exists - if ($msg != '') - $admin->msgs[] = $aseco->formatColors($head . $msg); - - // display popup message - if (count($admin->msgs) == 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0); - } elseif (count($admin->msgs) > 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No banned player(s) found!'), $login); - } - - } elseif ($aseco->server->getGame() == 'TMF') { - $head = 'Currently Banned Players:'; - $msg = array(); - if ($aseco->settings['clickable_lists']) - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login$g (click to UnBan)'); - else - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login'); - $pid = 1; - $lines = 0; - $admin->msgs[0] = array(1, $head, array(0.9, 0.1, 0.8), array('Icons64x64_1', 'NotBuddy')); - foreach ($newlist as $player) { - $plarr = array(); - $plarr['login'] = $player[0]; - $admin->playerlist[] = $plarr; - - // format nickname & login - $ply = '{#black}' . str_ireplace('$w', '', $player[1]) - . '$z / {#login}' . $player[0]; - // add clickable button - if ($aseco->settings['clickable_lists'] && $pid <= 200) - $ply = array($ply, $pid+4600); // action id - - $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.', $ply); - $pid++; - if (++$lines > 14) { - $admin->msgs[] = $msg; - $lines = 0; - $msg = array(); - if ($aseco->settings['clickable_lists']) - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login$g (click to UnBan)'); - else - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login'); - } - } - // add if last batch exists - if (count($msg) > 1) - $admin->msgs[] = $msg; - - // display ManiaLink message - if (count($admin->msgs) > 1) { - display_manialink_multi($admin); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No banned player(s) found!'), $login); - } - } - - /** - * Displays the banned IPs list. - */ - } elseif ($command['params'][0] == 'showiplist' || - $command['params'][0] == 'listips') { - - $admin->playerlist = array(); - $admin->msgs = array(); - - // get new list of all banned IPs - $newlist = $aseco->bannedips; - if (empty($newlist)) { - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No banned IP(s) found!'), $login); - return; - } - - if ($aseco->server->getGame() == 'TMN') { - $head = 'Currently Banned IPs:' . LF . 'Id {#login}IP' . LF; - $msg = ''; - $pid = 1; - $lines = 0; - $admin->msgs[0] = 1; - foreach ($newlist as $ip) { - if ($ip != '') { - $plarr = array(); - $plarr['ip'] = $ip; - $admin->playerlist[] = $plarr; - - $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#login}' . $ip . LF; - $pid++; - if (++$lines > 9) { - $admin->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - } - // add if last batch exists - if ($msg != '') - $admin->msgs[] = $aseco->formatColors($head . $msg); - - // display popup message - if (count($admin->msgs) == 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0); - } elseif (count($admin->msgs) > 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No banned IP(s) found!'), $login); - } - - } elseif ($aseco->server->getGame() == 'TMF') { - $head = 'Currently Banned IPs:'; - $msg = array(); - if ($aseco->settings['clickable_lists']) - $msg[] = array('Id', '{#nick}IP$g (click to UnBan)'); - else - $msg[] = array('Id', '{#nick}IP'); - $pid = 1; - $lines = 0; - $admin->msgs[0] = array(1, $head, array(0.6, 0.1, 0.5), array('Icons64x64_1', 'NotBuddy')); - foreach ($newlist as $ip) { - if ($ip != '') { - $plarr = array(); - $plarr['ip'] = $ip; - $admin->playerlist[] = $plarr; - - // format IP - $ply = '{#black}' . $ip; - // add clickable button - if ($aseco->settings['clickable_lists'] && $pid <= 200) - $ply = array($ply, -7900-$pid); // action id - - $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.', $ply); - $pid++; - if (++$lines > 14) { - $admin->msgs[] = $msg; - $lines = 0; - $msg = array(); - if ($aseco->settings['clickable_lists']) - $msg[] = array('Id', '{#login}IP$g (click to UnBan)'); - else - $msg[] = array('Id', '{#login}IP'); - } - } - } - // add if last batch exists - if (count($msg) > 1) - $admin->msgs[] = $msg; - - // display ManiaLink message - if (count($admin->msgs) > 1) { - display_manialink_multi($admin); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No banned IP(s) found!'), $login); - } - } - - /** - * Displays the black list. - */ - } elseif ($command['params'][0] == 'showblacklist' || - $command['params'][0] == 'listblacks') { - - $admin->playerlist = array(); - $admin->msgs = array(); - - // get new list of all blacklisted players - $newlist = get_blacklist($aseco); - - if ($aseco->server->getGame() == 'TMN') { - $head = 'Currently Blacklisted Players:' . LF . 'Id {#nick}Nick $g/{#login} Login' . LF; - $msg = ''; - $pid = 1; - $lines = 0; - $admin->msgs[0] = 1; - foreach ($newlist as $player) { - $plarr = array(); - $plarr['login'] = $player[0]; - $admin->playerlist[] = $plarr; - - $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#black}' - . str_ireplace('$w', '', $player[1]) . '$z / {#login}' . $player[0] . LF; - $pid++; - if (++$lines > 9) { - $admin->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - // add if last batch exists - if ($msg != '') - $admin->msgs[] = $aseco->formatColors($head . $msg); - - // display popup message - if (count($admin->msgs) == 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0); - } elseif (count($admin->msgs) > 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No blacklisted player(s) found!'), $login); - } - - } elseif ($aseco->server->getGame() == 'TMF') { - $head = 'Currently Blacklisted Players:'; - $msg = array(); - if ($aseco->settings['clickable_lists']) - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login$g (click to UnBlack)'); - else - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login'); - $pid = 1; - $lines = 0; - $admin->msgs[0] = array(1, $head, array(0.9, 0.1, 0.8), array('Icons64x64_1', 'NotBuddy')); - foreach ($newlist as $player) { - $plarr = array(); - $plarr['login'] = $player[0]; - $admin->playerlist[] = $plarr; - - // format nickname & login - $ply = '{#black}' . str_ireplace('$w', '', $player[1]) - . '$z / {#login}' . $player[0]; - // add clickable button - if ($aseco->settings['clickable_lists'] && $pid <= 200) - $ply = array($ply, $pid+4800); // action id - - $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.', $ply); - $pid++; - if (++$lines > 14) { - $admin->msgs[] = $msg; - $lines = 0; - $msg = array(); - if ($aseco->settings['clickable_lists']) - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login$g (click to UnBlack)'); - else - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login'); - } - } - // add if last batch exists - if (count($msg) > 1) - $admin->msgs[] = $msg; - - // display ManiaLink message - if (count($admin->msgs) > 1) { - display_manialink_multi($admin); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No blacklisted player(s) found!'), $login); - } - } - - /** - * Displays the guest list. - */ - } elseif ($command['params'][0] == 'showguestlist' || - $command['params'][0] == 'listguests') { - - $admin->playerlist = array(); - $admin->msgs = array(); - - // get new list of all guest players - $newlist = get_guestlist($aseco); - - if ($aseco->server->getGame() == 'TMN') { - $head = 'Current Guest Players:' . LF . 'Id {#nick}Nick $g/{#login} Login' . LF; - $msg = ''; - $pid = 1; - $lines = 0; - $admin->msgs[0] = 1; - foreach ($newlist as $player) { - $plarr = array(); - $plarr['login'] = $player[0]; - $admin->playerlist[] = $plarr; - - $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#black}' - . str_ireplace('$w', '', $player[1]) . '$z / {#login}' . $player[0] . LF; - $pid++; - if (++$lines > 9) { - $admin->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - // add if last batch exists - if ($msg != '') - $admin->msgs[] = $aseco->formatColors($head . $msg); - - // display popup message - if (count($admin->msgs) == 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0); - } elseif (count($admin->msgs) > 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No guest player(s) found!'), $login); - } - - } elseif ($aseco->server->getGame() == 'TMF') { - $head = 'Current Guest Players:'; - $msg = array(); - if ($aseco->settings['clickable_lists']) - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login$g (click to Remove)'); - else - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login'); - $pid = 1; - $lines = 0; - $admin->msgs[0] = array(1, $head, array(0.9, 0.1, 0.8), array('Icons128x128_1', 'Invite')); - foreach ($newlist as $player) { - $plarr = array(); - $plarr['login'] = $player[0]; - $admin->playerlist[] = $plarr; - - // format nickname & login - $ply = '{#black}' . str_ireplace('$w', '', $player[1]) - . '$z / {#login}' . $player[0]; - // add clickable button - if ($aseco->settings['clickable_lists'] && $pid <= 200) - $ply = array($ply, $pid+5000); // action id - - $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.', $ply); - $pid++; - if (++$lines > 14) { - $admin->msgs[] = $msg; - $lines = 0; - $msg = array(); - if ($aseco->settings['clickable_lists']) - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login$g (click to Remove)'); - else - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login'); - } - } - // add if last batch exists - if (count($msg) > 1) - $admin->msgs[] = $msg; - - // display ManiaLink message - if (count($admin->msgs) > 1) { - display_manialink_multi($admin); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No guest player(s) found!'), $login); - } - } - - /** - * Saves the banned IPs list to bannedips.xml (default). - */ - } elseif ($command['params'][0] == 'writeiplist') { - - // write banned IPs file - $filename = $aseco->settings['bannedips_file']; - if (!$aseco->writeIPs()) { - $message = '{#server}> {#error}Error writing {#highlite}$i ' . $filename . ' {#error}!'; - } else { - // log console message - $aseco->console('{1} [{2}] wrote ' . $filename . '!', $logtitle, $login); - - $message = '{#server}> {#highlite}' . $filename . ' {#admin}written'; - } - // show chat message - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Loads the banned IPs list from bannedips.xml (default). - */ - } elseif ($command['params'][0] == 'readiplist') { - - // read banned IPs file - $filename = $aseco->settings['bannedips_file']; - if (!$aseco->readIPs()) { - $message = '{#server}> {#highlite}' . $filename . ' {#error}not found, or error reading!'; - } else { - // log console message - $aseco->console('{1} [{2}] read ' . $filename . '!', $logtitle, $login); - - $message = '{#server}> {#highlite}' . $filename . ' {#admin}read'; - } - // show chat message - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Saves the black list to blacklist.txt (default). - */ - } elseif ($command['params'][0] == 'writeblacklist') { - - $filename = $aseco->settings['blacklist_file']; - $rtn = $aseco->client->query('SaveBlackList', $filename); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] SaveBlackList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - $message = '{#server}> {#error}Error writing {#highlite}$i ' . $filename . ' {#error}!'; - } else { - // log console message - $aseco->console('{1} [{2}] wrote ' . $filename . '!', $logtitle, $login); - - $message = '{#server}> {#highlite}' . $filename . ' {#admin}written'; - } - // show chat message - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Loads the black list from blacklist.txt (default). - */ - } elseif ($command['params'][0] == 'readblacklist') { - - $filename = $aseco->settings['blacklist_file']; - $rtn = $aseco->client->query('LoadBlackList', $filename); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] LoadBlackList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - $message = '{#server}> {#highlite}' . $filename . ' {#error}not found, or error reading!'; - } else { - // log console message - $aseco->console('{1} [{2}] read ' . $filename . '!', $logtitle, $login); - - $message = '{#server}> {#highlite}' . $filename . ' {#admin}read'; - } - // show chat message - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Saves the guest list to guestlist.txt (default). - */ - } elseif ($command['params'][0] == 'writeguestlist') { - - $filename = $aseco->settings['guestlist_file']; - $rtn = $aseco->client->query('SaveGuestList', $filename); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] SaveGuestList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - $message = '{#server}> {#error}Error writing {#highlite}$i ' . $filename . ' {#error}!'; - } else { - // log console message - $aseco->console('{1} [{2}] wrote ' . $filename . '!', $logtitle, $login); - - $message = '{#server}> {#highlite}' . $filename . ' {#admin}written'; - } - // show chat message - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Loads the guest list from guestlist.txt (default). - */ - } elseif ($command['params'][0] == 'readguestlist') { - - $filename = $aseco->settings['guestlist_file']; - $rtn = $aseco->client->query('LoadGuestList', $filename); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] LoadGuestList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - $message = '{#server}> {#highlite}' . $filename . ' {#error}not found, or error loading!'; - } else { - // log console message - $aseco->console('{1} [{2}] read ' . $filename . '!', $logtitle, $login); - - $message = '{#server}> {#highlite}' . $filename . ' {#admin}read'; - } - // show chat message - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Cleans the ban list. - */ - } elseif ($command['params'][0] == 'cleanbanlist') { - - // clean server ban list - $aseco->client->query('CleanBanList'); - - // log console message - $aseco->console('{1} [{2}] cleaned ban list!', $logtitle, $login); - - // show chat message - $message = '{#server}> {#admin}Cleaned ban list!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Cleans the banned IPs list. - */ - } elseif ($command['params'][0] == 'cleaniplist') { - - // clean banned IPs file - $aseco->bannedips = array(); - $aseco->writeIPs(); - - // log console message - $aseco->console('{1} [{2}] cleaned banned IPs list!', $logtitle, $login); - - // show chat message - $message = '{#server}> {#admin}Cleaned banned IPs list!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Cleans the black list. - */ - } elseif ($command['params'][0] == 'cleanblacklist') { - - // clean server black list - $aseco->client->query('CleanBlackList'); - - // log console message - $aseco->console('{1} [{2}] cleaned black list!', $logtitle, $login); - - // show chat message - $message = '{#server}> {#admin}Cleaned black list!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Cleans the guest list. - */ - } elseif ($command['params'][0] == 'cleanguestlist') { - - // clean server guest list - $aseco->client->query('CleanGuestList'); - - // log console message - $aseco->console('{1} [{2}] cleaned guest list!', $logtitle, $login); - - // show chat message - $message = '{#server}> {#admin}Cleaned guest list!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Merges a global black list. - */ - } elseif ($command['params'][0] == 'mergegbl') { - global $globalbl_url; // from rasp.settings.php - - if (function_exists('admin_mergegbl')) { - if (isset($command['params'][1]) && $command['params'][1] != '') { - if (preg_match('/^https?:\/\/[-\w:.]+\//i', $command['params'][1])) { - admin_mergegbl($aseco, $logtitle, $login, true, $command['params'][1]); // from plugin.uptodate.php - } else { - $message = '{#server}> {#highlite}' . $command['params'][1] . ' {#error}is an invalid HTTP URL!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - admin_mergegbl($aseco, $logtitle, $login, true, $globalbl_url); - } - } else { - // show chat message - $message = '{#server}> {#admin}Merge Global BL unavailable - include plugins.uptodate.php in plugins.xml'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Shows/reloads player access control. - */ - } elseif ($command['params'][0] == 'access') { - - if (function_exists('admin_access')) { - $command['params'] = $command['params'][1]; - admin_access($aseco, $command); // from plugin.access.php - } else { - // show chat message - $message = '{#server}> {#admin}Access control unavailable - include plugins.access.php in plugins.xml'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Saves the track list to tracklist.txt (default). - */ - } elseif ($command['params'][0] == 'writetracklist') { - - $filename = $aseco->settings['default_tracklist']; - // check for optional alternate filename - if ($command['params'][1] != '') { - $filename = $command['params'][1]; - if (!stristr($filename, '.txt')) { - $filename .= '.txt'; - } - } - $rtn = $aseco->client->query('SaveMatchSettings', 'MatchSettings/' . $filename); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] SaveMatchSettings - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - $message = '{#server}> {#error}Error writing {#highlite}$i ' . $filename . ' {#error}!'; - } else { - // should a random filter be added? - if ($aseco->settings['writetracklist_random']) { - $tracksfile = $aseco->server->trackdir . 'MatchSettings/' . $filename; - // read the match settings file - if (!$list = @file_get_contents($tracksfile)) { - trigger_error('Could not read match settings file ' . $tracksfile . ' !', E_USER_WARNING); - } else { - // insert random filter after section - $list = preg_replace('/<\/gameinfos>/', '$0' . CRLF . CRLF . - "\t" . CRLF . - "\t\t1" . CRLF . - "\t", $list); - - // write out the match settings file - if (!@file_put_contents($tracksfile, $list)) { - trigger_error('Could not write match settings file ' . $tracksfile . ' !', E_USER_WARNING); - } - } - } - - // log console message - $aseco->console('{1} [{2}] wrote track list: {3} !', $logtitle, $login, $filename); - - $message = '{#server}> {#highlite}' . $filename . '{#admin} written'; - - // throw 'tracklist changed' event - $aseco->releaseEvent('onTracklistChanged', array('write', null)); - } - // show chat message - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Loads the track list from tracklist.txt (default). - */ - } elseif ($command['params'][0] == 'readtracklist') { - - $filename = $aseco->settings['default_tracklist']; - // check for optional alternate filename - if ($command['params'][1] != '') { - $filename = $command['params'][1]; - if (!stristr($filename, '.txt')) { - $filename .= '.txt'; - } - } - if (file_exists($aseco->server->trackdir . 'MatchSettings/' . $filename)) { - $rtn = $aseco->client->query('LoadMatchSettings', 'MatchSettings/' . $filename); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] LoadMatchSettings - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - $message = '{#server}> {#error}Error reading {#highlite}$i ' . $filename . ' {#error}!'; - } else { - // get track count - $cnt = $aseco->client->getResponse(); - - // log console message - $aseco->console('{1} [{2}] read track list: {3} ({4} tracks)!', $logtitle, $login, $filename, $cnt); - - $message = '{#server}> {#highlite}' . $filename . '{#admin} read with {#highlite}' . $cnt . '{#admin} track' . ($cnt == 1 ? '' : 's'); - - // throw 'tracklist changed' event - $aseco->releaseEvent('onTracklistChanged', array('read', null)); - } - } else { - $message = '{#server}> {#error}Cannot find {#highlite}$i ' . $filename . ' {#error}!'; - } - // show chat message - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Randomizes current track list. - */ - } elseif ($command['params'][0] == 'shuffle' || - $command['params'][0] == 'shufflemaps') { - global $autosave_matchsettings; // from rasp.settings.php - - if ($aseco->settings['writetracklist_random']) { - if ($autosave_matchsettings) { - if (file_exists($aseco->server->trackdir . 'MatchSettings/' . $autosave_matchsettings)) { - $rtn = $aseco->client->query('LoadMatchSettings', 'MatchSettings/' . $autosave_matchsettings); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] LoadMatchSettings - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - $message = '{#server}> {#error}Error reading {#highlite}$i ' . $autosave_matchsettings . ' {#error}!'; - } else { - // get track count - $cnt = $aseco->client->getResponse(); - - // log console message - $aseco->console('{1} [{2}] shuffled track list: {3} ({4} tracks)!', $logtitle, $login, $autosave_matchsettings, $cnt); - - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} shuffled track list with {#highlite}{3}{#admin} track{4}!', - $chattitle, $admin->nickname, $cnt, ($cnt == 1 ? '' : 's')); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - return; - } - } else { - $message = '{#server}> {#error}Cannot find autosave matchsettings file {#highlite}$i ' . $autosave_matchsettings . ' {#error}!'; - } - } else { - $message = '{#server}> {#error}No autosave matchsettings file defined in {#highlite}$i rasp.settings.php {#error}!'; - } - } else { - $message = '{#server}> {#error}No tracklist randomization defined in {#highlite}$i config.xml {#error}!'; - } - // show chat message - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Displays list of duplicate tracks. - */ - } elseif ($command['params'][0] == 'listdupes') { - - $admin->tracklist = array(); - $admin->msgs = array(); - - // get new list of all tracks - $aseco->client->resetError(); - $dupelist = array(); - $newlist = array(); - $done = false; - $size = 300; - $i = 0; - while (!$done) { - $aseco->client->query('GetChallengeList', $size, $i); - $tracks = $aseco->client->getResponse(); - if (!empty($tracks)) { - if ($aseco->client->isError()) { - // warning if no tracks found - if (empty($newlist)) - trigger_error('[' . $aseco->client->getErrorCode() . '] GetChallengeList - ' . $aseco->client->getErrorMessage() . ' - No tracks found!', E_USER_WARNING); - $done = true; - break; - } - foreach ($tracks as $trow) { - $trow['Name'] = stripNewlines($trow['Name']); - // store duplicate track - if (isset($newlist[$trow['UId']])) { - $dupelist[] = $trow; - } else { - $newlist[$trow['UId']] = $trow; - } - } - if (count($tracks) < $size) { - // got less than 300 tracks, might as well leave - $done = true; - } else { - $i += $size; - } - } else { - $done = true; - } - } - - // check for duplicate tracks - if (!empty($dupelist)) { - if ($aseco->server->getGame() == 'TMN') { - $head = 'Duplicate Tracks On This Server:' . LF . 'Id Name' . LF; - $msg = ''; - $tid = 1; - $lines = 0; - $admin->msgs[0] = 1; - foreach ($dupelist as $row) { - $trackname = $row['Name']; - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - - // store track in player object for remove/erase - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $admin->tracklist[] = $trkarr; - - $msg .= '$g' . str_pad($tid, 3, '0', STR_PAD_LEFT) . '. {#black}' . $trackname . LF; - $tid++; - if (++$lines > 9) { - $admin->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - // add if last batch exists - if ($msg != '') - $admin->msgs[] = $aseco->formatColors($head . $msg); - - // display popup message - if (count($admin->msgs) == 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0); - } else { // > 2 - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0); - } - - } elseif ($aseco->server->getGame() == 'TMF') { - $head = 'Duplicate Tracks On This Server:'; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Name', 'Env'); - else - $msg[] = array('Id', 'Name'); - $tid = 1; - $lines = 0; - // reserve extra width for $w tags - $extra = ($aseco->settings['lists_colortracks'] ? 0.2 : 0); - if ($aseco->server->packmask != 'Stadium') - $admin->msgs[0] = array(1, $head, array(0.90+$extra, 0.15, 0.6+$extra, 0.15), array('Icons128x128_1', 'Challenge')); - else - $admin->msgs[0] = array(1, $head, array(0.75+$extra, 0.15, 0.6+$extra), array('Icons128x128_1', 'Challenge')); - foreach ($dupelist as $row) { - $trackname = stripColors($row['Name']); - if (!$aseco->settings['lists_colortracks']) - $trackname = stripColors($trackname); - - // store track in player object for remove/erase - $trkarr = array(); - $trkarr['name'] = $row['Name']; - $trkarr['environment'] = $row['Environnement']; - $trkarr['filename'] = $row['FileName']; - $trkarr['uid'] = $row['UId']; - $admin->tracklist[] = $trkarr; - - if ($aseco->server->packmask != 'Stadium') - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - '{#black}' . $trackname, - $trkarr['environment']); - else - $msg[] = array(str_pad($tid, 3, '0', STR_PAD_LEFT) . '.', - '{#black}' . $trackname); - $tid++; - if (++$lines > 14) { - $admin->msgs[] = $msg; - $lines = 0; - $msg = array(); - if ($aseco->server->packmask != 'Stadium') - $msg[] = array('Id', 'Name', 'Env'); - else - $msg[] = array('Id', 'Name'); - } - } - // add if last batch exists - if (count($msg) > 1) - $admin->msgs[] = $msg; - - // display ManiaLink message - display_manialink_multi($admin); - } - - } else { - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No duplicate track(s) found!'), $login); - return; - } - - /** - * Remove a track from the active rotation, optionally erase track file too. - * Doesn't update match settings unfortunately - command 'writetracklist' will though. - */ - } elseif (($command['params'][0] == 'remove' && $command['params'][1] != '') || - ($command['params'][0] == 'erase' && $command['params'][1] != '')) { - global $rasp; // from plugin.rasp.php - - // verify parameter - $param = $command['params'][1]; - if (is_numeric($param) && $param >= 0) { - if (empty($admin->tracklist)) { - $message = $rasp->messages['LIST_HELP'][0]; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - return; - } - // find track by given # - $tid = ltrim($param, '0'); - $tid--; - if (array_key_exists($tid, $admin->tracklist)) { - $name = stripColors($admin->tracklist[$tid]['name']); - $filename = $aseco->server->trackdir . $admin->tracklist[$tid]['filename']; - $rtn = $aseco->client->query('RemoveChallenge', $filename); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] RemoveChallenge - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - $message = formatText('{#server}> {#error}Error removing track {#highlite}$i {1} {#error}!', - $filename); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}removes track: {#highlite}{3}', - $chattitle, $admin->nickname, $name); - if ($command['params'][0] == 'erase' && is_file($filename)) { - if (unlink($filename)) { - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}erases track: {#highlite}{3}', - $chattitle, $admin->nickname, $name); - } else { - $message = '{#server}> {#error}Delete file {#highlite}$i ' . $filename . '{#error} failed'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}erase track failed: {#highlite}{3}', - $chattitle, $admin->nickname, $name); - } - } - // show chat message - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - // log console message - $aseco->console('{1} [{2}] ' . $command['params'][0] . 'd track {3}', $logtitle, $login, stripColors($name, false)); - - // throw 'tracklist changed' event - $aseco->releaseEvent('onTracklistChanged', array('remove', $filename)); - } - } else { - $message = $rasp->messages['JUKEBOX_NOTFOUND'][0]; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = $rasp->messages['JUKEBOX_HELP'][0]; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Remove current track from the active rotation, optionally erase track file too. - * Doesn't update match settings unfortunately - command 'writetracklist' will though. - */ - } elseif ($command['params'][0] == 'removethis' || - $command['params'][0] == 'erasethis') { - - // get current track info and remove it from rotation - $name = stripColors($aseco->server->challenge->name); - $filename = $aseco->server->trackdir . $aseco->server->challenge->filename; - $rtn = $aseco->client->query('RemoveChallenge', $filename); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] RemoveChallenge - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - $message = formatText('{#server}> {#error}Error removing track {#highlite}$i {1} {#error}!', - $filename); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}removes current track: {#highlite}{3}', - $chattitle, $admin->nickname, $name); - if ($command['params'][0] == 'erasethis' && is_file($filename)) { - if (unlink($filename)) { - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}erases current track: {#highlite}{3}', - $chattitle, $admin->nickname, $name); - } else { - $message = '{#server}> {#error}Delete file {#highlite}$i ' . $filename . '{#error} failed'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}erase track failed: {#highlite}{3}', - $chattitle, $admin->nickname, $name); - } - } - // show chat message - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - // log console message - $aseco->console('{1} [{2}] ' . $command['params'][0] . '-ed track {3}', $logtitle, $login, stripColors($name, false)); - - // throw 'tracklist changed' event - $aseco->releaseEvent('onTracklistChanged', array('remove', $filename)); - } - - /** - * Adds a player to global mute/ignore list - */ - } elseif (($command['params'][0] == 'mute' || $command['params'][0] == 'ignore') - && $command['params'][1] != '') { - global $muting_available; // from plugin.muting.php - - if ($aseco->server->getGame() != 'TMF') { - // check for muting plugin - if ($muting_available) { - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) { - // check if not yet in global mute/ignore list - if (!in_array($target->login, $aseco->server->mutelist)) { - // mute this player - $aseco->server->mutelist[] = $target->login; - - // log console message - $aseco->console('{1} [{2}] mutes player [{3} : {4}]!', $logtitle, $login, $target->login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} mutes {#highlite}{3}$z$s{#admin} !', - $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname)); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - $message = '{#server}> {#error}Player {#highlite}$i ' . stripColors($target->nickname) . '{#error} is already in global mute/ignore list!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - } else { - $message = '{#server}> {#error}Player muting not available!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { // TMF - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) { - // ignore the player - $aseco->client->query('Ignore', $target->login); - - // check if in global mute/ignore list - if (!in_array($target->login, $aseco->server->mutelist)) { - // add player to list - $aseco->server->mutelist[] = $target->login; - } - - // log console message - $aseco->console('{1} [{2}] ignores player {3}!', $logtitle, $login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} ignores {#highlite}{3}$z$s{#admin} !', - $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname)); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } - } - - /** - * Removes a player from global mute/ignore list - */ - } elseif (($command['params'][0] == 'unmute' || $command['params'][0] == 'unignore') - && $command['params'][1] != '') { - global $muting_available; // from plugin.muting.php - - if ($aseco->server->getGame() != 'TMF') { - // check for muting plugin - if ($muting_available) { - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1], true)) { - // check if already in global mute/ignore list - if (($i = array_search($target->login, $aseco->server->mutelist)) !== false) { - // unmute this player - $aseco->server->mutelist[$i] = ''; - - // log console message - $aseco->console('{1} [{2}] unmutes player [{3} : {4}]!', $logtitle, $login, $target->login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} unmutes {#highlite}{3}$z$s{#admin} !', - $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname)); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - $message = '{#server}> {#error}Player {#highlite}$i ' . stripColors($target->nickname) . '{#error} is not in global mute/ignore list!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - } else { - $message = '{#server}> {#error}Player muting not available!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { // TMF - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1], true)) { - // unignore the player - $rtn = $aseco->client->query('UnIgnore', $target->login); - if (!$rtn) { - $message = formatText('{#server}> {#highlite}{1}{#error} is not an ignored player!', - $command['params'][1]); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - // check if in global mute/ignore list - if (($i = array_search($target->login, $aseco->server->mutelist)) !== false) { - // remove player from list - $aseco->server->mutelist[$i] = ''; - } - - // log console message - $aseco->console('{1} [{2}] unignores player {3}', $logtitle, $login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} un-ignores {#highlite}{3}', - $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname)); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - } - - /** - * Displays the global mute/ignore list. - */ - } elseif ($command['params'][0] == 'mutelist' || - $command['params'][0] == 'listmutes' || - $command['params'][0] == 'ignorelist' || - $command['params'][0] == 'listignores') { - global $muting_available; // from plugin.muting.php - - $admin->playerlist = array(); - $admin->msgs = array(); - - if ($aseco->server->getGame() == 'TMN') { - // check for muting plugin - if ($muting_available) { - // check for muted players - if (empty($aseco->server->mutelist)) { - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No muted/ignored players found!'), $login); - return; - } - - $head = 'Globally Muted/Ignored Players:' . LF . 'Id {#nick}Nick $g/{#login} Login' . LF; - $msg = ''; - $pid = 1; - $lines = 0; - $admin->msgs[0] = 1; - foreach ($aseco->server->mutelist as $pl) { - if ($pl != '') { - $plarr = array(); - $plarr['login'] = $pl; - $admin->playerlist[] = $plarr; - - $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#black}' - . $aseco->getPlayerNick($pl) . '$z / {#login}' . $pl . LF; - $pid++; - if (++$lines > 9) { - $admin->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - } - // add if last batch exists - if ($msg != '') - $admin->msgs[] = $aseco->formatColors($head . $msg); - - // display popup message - if (count($admin->msgs) == 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0); - } elseif (count($admin->msgs) > 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No muted/ignored players found!'), $login); - } - - } else { - $message = '{#server}> {#error}Player muting not available!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - } elseif ($aseco->server->getGame() == 'TMF') { - // get new list of all ignored players - $newlist = get_ignorelist($aseco); - - $head = 'Globally Muted/Ignored Players:'; - $msg = array(); - if ($aseco->settings['clickable_lists']) - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login$g (click to UnIgnore)'); - else - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login'); - $pid = 1; - $lines = 0; - $admin->msgs[0] = array(1, $head, array(0.9, 0.1, 0.8), array('Icons128x128_1', 'Padlock', 0.01)); - foreach ($newlist as $player) { - $plarr = array(); - $plarr['login'] = $player[0]; - $admin->playerlist[] = $plarr; - - // format nickname & login - $ply = '{#black}' . str_ireplace('$w', '', $player[1]) - . '$z / {#login}' . $player[0]; - // add clickable button - if ($aseco->settings['clickable_lists'] && $pid <= 200) - $ply = array($ply, $pid+4400); // action id - - $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.', $ply); - $pid++; - if (++$lines > 14) { - $admin->msgs[] = $msg; - $lines = 0; - $msg = array(); - if ($aseco->settings['clickable_lists']) - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login$g (click to UnIgnore)'); - else - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login'); - } - } - // add if last batch exists - if (count($msg) > 1) - $admin->msgs[] = $msg; - - // display ManiaLink message - if (count($admin->msgs) > 1) { - display_manialink_multi($admin); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No muted/ignored players found!'), $login); - } - } - - /** - * Cleans the global mute/ignore list. - */ - } elseif ($command['params'][0] == 'cleanmutes' || - $command['params'][0] == 'cleanignores') { - - // clean internal and server list - $aseco->server->mutelist = array(); - if ($aseco->server->getGame() == 'TMF') - $aseco->client->query('CleanIgnoreList'); - - // log console message - $aseco->console('{1} [{2}] cleaned global mute/ignore list!', $logtitle, $login); - - // show chat message - $message = '{#server}> {#admin}Cleaned global mute/ignore list!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Adds a new admin. - */ - } elseif ($command['params'][0] == 'addadmin' && $command['params'][1] != '') { - - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) { - // check if player not already admin - if (!$aseco->isAdminL($target->login)) { - // add the new admin - $aseco->admin_list['TMLOGIN'][] = $target->login; - $aseco->admin_list['IPADDRESS'][] = ($aseco->settings['auto_admin_addip'] ? $target->ip : ''); - $aseco->writeLists(); - - // log console message - $aseco->console('{1} [{2}] adds admin [{3} : {4}]!', $logtitle, $login, $target->login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} adds new {3}$z$s{#admin}: {#highlite}{4}$z$s{#admin} !', - $chattitle, $admin->nickname, - $aseco->titles['ADMIN'][0], $target->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - $message = formatText('{#server}> {#error}Login {#highlite}$i {1}{#error} is already in Admin List!', $target->login); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - - /** - * Removes an admin. - */ - } elseif ($command['params'][0] == 'removeadmin' && $command['params'][1] != '') { - - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1], true)) { - // check if player is indeed admin - if ($aseco->isAdminL($target->login)) { - $i = array_search($target->login, $aseco->admin_list['TMLOGIN']); - $aseco->admin_list['TMLOGIN'][$i] = ''; - $aseco->admin_list['IPADDRESS'][$i] = ''; - $aseco->writeLists(); - - // log console message - $aseco->console('{1} [{2}] removes admin [{3} : {4}]!', $logtitle, $login, $target->login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} removes {3}$z$s{#admin}: {#highlite}{4}$z$s{#admin} !', - $chattitle, $admin->nickname, - $aseco->titles['ADMIN'][0], $target->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - $message = formatText('{#server}> {#error}Login {#highlite}$i {1}{#error} is not in Admin List!', $target->login); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - - /** - * Adds a new operator. - */ - } elseif ($command['params'][0] == 'addop' && $command['params'][1] != '') { - - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) { - // check if player not already operator - if (!$aseco->isOperatorL($target->login)) { - // add the new operator - $aseco->operator_list['TMLOGIN'][] = $target->login; - $aseco->operator_list['IPADDRESS'][] = ($aseco->settings['auto_admin_addip'] ? $target->ip : ''); - $aseco->writeLists(); - - // log console message - $aseco->console('{1} [{2}] adds operator [{3} : {4}]!', $logtitle, $login, $target->login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} adds new {3}$z$s{#admin}: {#highlite}{4}$z$s{#admin} !', - $chattitle, $admin->nickname, - $aseco->titles['OPERATOR'][0], $target->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - $message = formatText('{#server}> {#error}Login {#highlite}$i {1}{#error} is already in Operator List!', $target->login); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - - /** - * Removes an operator. - */ - } elseif ($command['params'][0] == 'removeop' && $command['params'][1] != '') { - - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1], true)) { - // check if player is indeed operator - if ($aseco->isOperatorL($target->login)) { - $i = array_search($target->login, $aseco->operator_list['TMLOGIN']); - $aseco->operator_list['TMLOGIN'][$i] = ''; - $aseco->operator_list['IPADDRESS'][$i] = ''; - $aseco->writeLists(); - - // log console message - $aseco->console('{1} [{2}] removes operator [{3} : {4}]!', $logtitle, $login, $target->login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} removes {3}$z$s{#admin}: {#highlite}{4}$z$s{#admin} !', - $chattitle, $admin->nickname, - $aseco->titles['OPERATOR'][0], $target->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - $message = formatText('{#server}> {#error}Login {#highlite}$i {1}{#error} is not in Operator List!', $target->login); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - - /** - * Displays the masteradmins list. - */ - } elseif ($command['params'][0] == 'listmasters') { - - $admin->playerlist = array(); - $admin->msgs = array(); - - if ($aseco->server->getGame() == 'TMN') { - $head = 'Current MasterAdmins:' . LF . 'Id {#nick}Nick $g/{#login} Login' . LF; - $msg = ''; - $pid = 1; - $lines = 0; - $admin->msgs[0] = 1; - foreach ($aseco->masteradmin_list['TMLOGIN'] as $player) { - // skip any LAN logins - if ($player != '' && !isLANLogin($player)) { - $plarr = array(); - $plarr['login'] = $player; - $admin->playerlist[] = $plarr; - - $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#black}' - . $aseco->getPlayerNick($player) . '$z / {#login}' . $player . LF; - $pid++; - if (++$lines > 9) { - $admin->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - } - // add if last batch exists - if ($msg != '') - $admin->msgs[] = $aseco->formatColors($head . $msg); - - // display popup message - if (count($admin->msgs) == 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0); - } elseif (count($admin->msgs) > 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No masteradmin(s) found!'), $login); - } - - } elseif ($aseco->server->getGame() == 'TMF') { - $head = 'Current MasterAdmins:'; - $msg = array(); - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login'); - $pid = 1; - $lines = 0; - $admin->msgs[0] = array(1, $head, array(0.9, 0.1, 0.8), array('Icons128x128_1', 'Solo')); - foreach ($aseco->masteradmin_list['TMLOGIN'] as $player) { - // skip any LAN logins - if ($player != '' && !isLANLogin($player)) { - $plarr = array(); - $plarr['login'] = $player; - $admin->playerlist[] = $plarr; - - $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.', - '{#black}' . $aseco->getPlayerNick($player) - . '$z / {#login}' . $player); - $pid++; - if (++$lines > 14) { - $admin->msgs[] = $msg; - $lines = 0; - $msg = array(); - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login'); - } - } - } - // add if last batch exists - if (count($msg) > 1) - $admin->msgs[] = $msg; - - // display ManiaLink message - if (count($admin->msgs) > 1) { - display_manialink_multi($admin); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No masteradmin(s) found!'), $login); - } - } - - /** - * Displays the admins list. - */ - } elseif ($command['params'][0] == 'listadmins') { - - if (empty($aseco->admin_list['TMLOGIN'])) { - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No admin(s) found!'), $login); - return; - } - - $admin->playerlist = array(); - $admin->msgs = array(); - - if ($aseco->server->getGame() == 'TMN') { - $head = 'Current Admins:' . LF . 'Id {#nick}Nick $g/{#login} Login' . LF; - $msg = ''; - $pid = 1; - $lines = 0; - $admin->msgs[0] = 1; - foreach ($aseco->admin_list['TMLOGIN'] as $player) { - if ($player != '') { - $plarr = array(); - $plarr['login'] = $player; - $admin->playerlist[] = $plarr; - - $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#black}' - . $aseco->getPlayerNick($player) . '$z / {#login}' . $player . LF; - $pid++; - if (++$lines > 9) { - $admin->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - } - // add if last batch exists - if ($msg != '') - $admin->msgs[] = $aseco->formatColors($head . $msg); - - // display popup message - if (count($admin->msgs) == 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0); - } elseif (count($admin->msgs) > 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No admin(s) found!'), $login); - } - - } elseif ($aseco->server->getGame() == 'TMF') { - $head = 'Current Admins:'; - $msg = array(); - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login'); - $pid = 1; - $lines = 0; - $admin->msgs[0] = array(1, $head, array(0.9, 0.1, 0.8), array('Icons128x128_1', 'Solo')); - foreach ($aseco->admin_list['TMLOGIN'] as $player) { - if ($player != '') { - $plarr = array(); - $plarr['login'] = $player; - $admin->playerlist[] = $plarr; - - $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.', - '{#black}' . $aseco->getPlayerNick($player) - . '$z / {#login}' . $player); - $pid++; - if (++$lines > 14) { - $admin->msgs[] = $msg; - $lines = 0; - $msg = array(); - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login'); - } - } - } - // add if last batch exists - if (count($msg) > 1) - $admin->msgs[] = $msg; - - // display ManiaLink message - if (count($admin->msgs) > 1) { - display_manialink_multi($admin); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No admin(s) found!'), $login); - } - } - - /** - * Displays the operators list. - */ - } elseif ($command['params'][0] == 'listops') { - - if (empty($aseco->operator_list['TMLOGIN'])) { - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No operator(s) found!'), $login); - return; - } - - $admin->playerlist = array(); - $admin->msgs = array(); - - if ($aseco->server->getGame() == 'TMN') { - $head = 'Current Operators:' . LF . 'Id {#nick}Nick $g/{#login} Login' . LF; - $msg = ''; - $pid = 1; - $lines = 0; - $admin->msgs[0] = 1; - foreach ($aseco->operator_list['TMLOGIN'] as $player) { - if ($player != '') { - $plarr = array(); - $plarr['login'] = $player; - $admin->playerlist[] = $plarr; - - $msg .= '$g' . str_pad($pid, 2, '0', STR_PAD_LEFT) . '. {#black}' - . $aseco->getPlayerNick($player) . '$z / {#login}' . $player . LF; - $pid++; - if (++$lines > 9) { - $admin->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - } - // add if last batch exists - if ($msg != '') - $admin->msgs[] = $aseco->formatColors($head . $msg); - - // display popup message - if (count($admin->msgs) == 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0); - } elseif (count($admin->msgs) > 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No operator(s) found!'), $login); - } - - } elseif ($aseco->server->getGame() == 'TMF') { - $head = 'Current Operators:'; - $msg = array(); - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login'); - $pid = 1; - $lines = 0; - $admin->msgs[0] = array(1, $head, array(0.9, 0.1, 0.8), array('Icons128x128_1', 'Solo')); - foreach ($aseco->operator_list['TMLOGIN'] as $player) { - if ($player != '') { - $plarr = array(); - $plarr['login'] = $player; - $admin->playerlist[] = $plarr; - - $msg[] = array(str_pad($pid, 2, '0', STR_PAD_LEFT) . '.', - '{#black}' . $aseco->getPlayerNick($player) - . '$z / {#login}' . $player); - $pid++; - if (++$lines > 14) { - $admin->msgs[] = $msg; - $lines = 0; - $msg = array(); - $msg[] = array('Id', '{#nick}Nick $g/{#login} Login'); - } - } - } - // add if last batch exists - if (count($msg) > 1) - $admin->msgs[] = $msg; - - // display ManiaLink message - if (count($admin->msgs) > 1) { - display_manialink_multi($admin); - } else { // == 1 - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No operator(s) found!'), $login); - } - } - - /** - * Show/change an admin ability - */ - } elseif ($command['params'][0] == 'adminability') { - - // check for ability parameter - if ($command['params'][1] != '') { - // map to uppercase before checking list - $ability = strtoupper($command['params'][1]); - - // check for valid ability - if (isset($aseco->adm_abilities[$ability])) { - if (isset($command['params'][2]) && $command['params'][2] != '') { - // update ability - if (strtoupper($command['params'][2]) == 'ON') { - $aseco->adm_abilities[$ability][0] = true; - $aseco->writeLists(); - - // log console message - $aseco->console('{1} [{2}] set new Admin ability: {3} ON', $logtitle, $login, strtolower($ability)); - } - elseif (strtoupper($command['params'][2]) == 'OFF') { - $aseco->adm_abilities[$ability][0] = false; - $aseco->writeLists(); - - // log console message - $aseco->console('{1} [{2}] set new Admin ability: {3} OFF', $logtitle, $login, strtolower($ability)); - } // else ignore bogus parameter - } - // show current/new ability message - $message = formatText('{#server}> {#admin}{1}$z$s {#admin}ability {#highlite}{2}{#admin} is: {#highlite}{3}', - $aseco->titles['ADMIN'][0], strtolower($ability), - ($aseco->adm_abilities[$ability][0] ? 'ON' : 'OFF')); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - $message = formatText('{#server}> {#error}No ability {#highlite}$i {1}{#error} known!', - $command['params'][1]); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = '{#server}> {#error}No ability specified - see {#highlite}$i /admin helpall{#error} and {#highlite}$i /admin listabilities{#error}!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Show/change an operator ability - */ - } elseif ($command['params'][0] == 'opability') { - - // check for ability parameter - if ($command['params'][1] != '') { - // map to uppercase before checking list - $ability = strtoupper($command['params'][1]); - - // check for valid ability - if (isset($aseco->op_abilities[$ability])) { - if (isset($command['params'][2]) && $command['params'][2] != '') { - // update ability - if (strtoupper($command['params'][2]) == 'ON') { - $aseco->op_abilities[$ability][0] = true; - $aseco->writeLists(); - - // log console message - $aseco->console('{1} [{2}] set new Operator ability: {3} ON', $logtitle, $login, strtolower($ability)); - } - elseif (strtoupper($command['params'][2]) == 'OFF') { - $aseco->op_abilities[$ability][0] = false; - $aseco->writeLists(); - - // log console message - $aseco->console('{1} [{2}] set new Operator ability: {3} OFF', $logtitle, $login, strtolower($ability)); - } // else ignore bogus parameter - } - // show current/new ability message - $message = formatText('{#server}> {#admin}{1}$z$s {#admin}ability {#highlite}{2}{#admin} is: {#highlite}{3}', - $aseco->titles['OPERATOR'][0], strtolower($ability), - ($aseco->op_abilities[$ability][0] ? 'ON' : 'OFF')); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - $message = formatText('{#server}> {#error}No ability {#highlite}$i {1}{#error} known!', - $command['params'][1]); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = '{#server}> {#error}No ability specified - see {#highlite}$i /admin helpall{#error} and {#highlite}$i /admin listabilities{#error}!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Displays Admin and Operator abilities - */ - } elseif ($command['params'][0] == 'listabilities') { - - $master = false; - if ($aseco->isMasterAdminL($login)) { - if ($command['params'][1] == '') { - $master = true; - $abilities = $aseco->adm_abilities; - $title = 'MasterAdmin'; - } else { - if (stripos('admin', $command['params'][1]) === 0) { - $abilities = $aseco->adm_abilities; - $title = 'Admin'; - } - elseif (stripos('operator', $command['params'][1]) === 0) { - $abilities = $aseco->op_abilities; - $title = 'Operator'; - } - // all three above fall through to listing below - else { - $message = formatText('{#server}> {#highlite}{1}{#error} is not a valid administrator tier!', - $command['params'][1]); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - return; - } - } - } - elseif ($aseco->isAdminL($login)) { - $abilities = $aseco->adm_abilities; - $title = 'Admin'; - } - else { // isOperator - $abilities = $aseco->op_abilities; - $title = 'Operator'; - } - - if ($aseco->server->getGame() == 'TMN') { - // compile current ability listing - $help = 'Current ' . $title . ' abilities:' . LF . LF; - $chat = false; - foreach ($abilities as $ability => $value) { - switch (strtolower($ability)) { - case 'chat_pma': - if ($value[0] || $master) { - $help .= 'chat_pma : {#black}/pma$g sends a PM to player & admins' . LF; - $chat = true; - } - break; - case 'chat_bestworst': - if ($value[0] || $master) { - $help .= 'chat_bestworst : {#black}/best$g & {#black}/worst$g accept login/Player_ID' . LF; - $chat = true; - } - break; - case 'chat_statsip': - if ($value[0] || $master) { - $help .= 'chat_statsip : {#black}/stats$g includes IP address' . LF; - $chat = true; - } - break; - case 'chat_summary': - if ($value[0] || $master) { - $help .= 'chat_summary : {#black}/summary$g accepts login/Player_ID' . LF; - $chat = true; - } - break; - case 'chat_jb_multi': - if ($value[0] || $master) { - $help .= 'chat_jb_multi : {#black}/jukebox$g adds more than one track' . LF; - $chat = true; - } - break; - case 'chat_jb_recent': - if ($value[0] || $master) { - $help .= 'chat_jb_recent : {#black}/jukebox$g adds recently played track' . LF; - $chat = true; - } - break; - case 'chat_add_tref': - if ($value[0] || $master) { - $help .= 'chat_add_tref : {#black}/add trackref$g writes TMX trackref file' . LF; - $chat = true; - } - break; - case 'chat_match': - if ($value[0] || $master) { - $help .= 'chat_match : {#black}/match$g allows match control' . LF; - $chat = true; - } - break; - case 'chat_tc_listen': - if ($value[0] || $master) { - $help .= 'chat_tc_listen : {#black}/tc$g will copy team chat to admins' . LF; - $chat = true; - } - break; - case 'chat_jfreu': - if ($value[0] || $master) { - $help .= 'chat_jfreu : use all {#black}/jfreu$g commands' . LF; - $chat = true; - } - break; - case 'noidlekick_play': - if ($value[0] || $master) { - $help .= 'noidlekick_play : no idlekick when {#black}player$g' . LF; - $chat = true; - } - break; - case 'noidlekick_spec': - if ($value[0] || $master) { - $help .= 'noidlekick_spec: no idlekick when {#black}spectator$g' . LF; - $chat = true; - } - break; - } - } - - if ($chat) $help .= LF; - $help .= 'See {#black}/admin helpall$g for available /admin commands'; - - // display popup message - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $aseco->formatColors($help), 'OK', '', 0); - - } elseif ($aseco->server->getGame() == 'TMF') { - // compile current ability listing - $header = 'Current ' . $title . ' abilities:'; - $help = array(); - $chat = false; - foreach ($abilities as $ability => $value) { - switch (strtolower($ability)) { - case 'chat_pma': - if ($value[0] || $master) { - $help[] = array('chat_pma', '{#black}/pma$g sends a PM to player & admins'); - $chat = true; - } - break; - case 'chat_bestworst': - if ($value[0] || $master) { - $help[] = array('chat_bestworst', '{#black}/best$g & {#black}/worst$g accept login/Player_ID'); - $chat = true; - } - break; - case 'chat_statsip': - if ($value[0] || $master) { - $help[] = array('chat_statsip', '{#black}/stats$g includes IP address'); - $chat = true; - } - break; - case 'chat_summary': - if ($value[0] || $master) { - $help[] = array('chat_summary', '{#black}/summary$g accepts login/Player_ID'); - $chat = true; - } - break; - case 'chat_jb_multi': - if ($value[0] || $master) { - $help[] = array('chat_jb_multi', '{#black}/jukebox$g adds more than one track'); - $chat = true; - } - break; - case 'chat_jb_recent': - if ($value[0] || $master) { - $help[] = array('chat_jb_recent', '{#black}/jukebox$g adds recently played track'); - $chat = true; - } - break; - case 'chat_add_tref': - if ($value[0] || $master) { - $help[] = array('chat_add_tref', '{#black}/add trackref$g writes TMX trackref file'); - $chat = true; - } - break; - case 'chat_match': - if ($value[0] || $master) { - $help[] = array('chat_match', '{#black}/match$g allows match control'); - $chat = true; - } - break; - case 'chat_tc_listen': - if ($value[0] || $master) { - $help[] = array('chat_tc_listen', '{#black}/tc$g will copy team chat to admins'); - $chat = true; - } - break; - case 'chat_jfreu': - if ($value[0] || $master) { - $help[] = array('chat_jfreu', 'use all {#black}/jfreu$g commands'); - $chat = true; - } - break; - case 'chat_musicadmin': - if ($value[0] || $master) { - $help[] = array('chat_musicadmin', 'use {#black}/music$g admin commands'); - $chat = true; - } - break; - case 'noidlekick_play': - if ($value[0] || $master) { - $help[] = array('noidlekick_play', 'no idlekick when {#black}player$g'); - $chat = true; - } - break; - case 'noidlekick_spec': - if ($value[0] || $master) { - $help[] = array('noidlekick_spec', 'no idlekick when {#black}spectator$g'); - $chat = true; - } - break; - case 'server_coppers': - if ($value[0] || $master) { - $help[] = array('server_coppers', 'view coppers amount in {#black}/server$g'); - $chat = true; - } - break; - } - } - - if ($chat) $help[] = array(); - $help[] = array('See {#black}/admin helpall$g for available /admin commands'); - - // display ManiaLink message - display_manialink($login, $header, array('Icons128x128_1', 'ProfileAdvanced', 0.02), $help, array(1.0, 0.3, 0.7), 'OK'); - } - - /** - * Saves the admins/operators/abilities list to adminops.xml (default). - */ - } elseif ($command['params'][0] == 'writeabilities') { - - // write admins/operators file - $filename = $aseco->settings['adminops_file']; - if (!$aseco->writeLists()) { - $message = '{#server}> {#error}Error writing {#highlite}$i ' . $filename . ' {#error}!'; - } else { - // log console message - $aseco->console('{1} [{2}] wrote ' . $filename . '!', $logtitle, $login); - - $message = '{#server}> {#highlite}' . $filename . ' {#admin}written'; - } - // show chat message - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Loads the admins/operators/abilities list from adminops.xml (default). - */ - } elseif ($command['params'][0] == 'readabilities') { - - // read admins/operators file - $filename = $aseco->settings['adminops_file']; - if (!$aseco->readLists()) { - $message = '{#server}> {#highlite}' . $filename . ' {#error}not found, or error reading!'; - } else { - // log console message - $aseco->console('{1} [{2}] read ' . $filename . '!', $logtitle, $login); - - $message = '{#server}> {#highlite}' . $filename . ' {#admin}read'; - } - // show chat message - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Display message in pop-up to all players - */ - } elseif ($command['params'][0] == 'wall' || - $command['params'][0] == 'mta') { - - // check for non-empty message - if ($arglist[1] != '') { - if ($aseco->server->getGame() == 'TMN') { - $message = $aseco->formatColors('{#black}') . $chattitle . ' ' . $admin->nickname . '$z :' . LF; - // insure window doesn't become too wide - $message .= wordwrap($aseco->formatColors('{#welcome}') . $arglist[1], 30, LF); - // display popup message to all players - $aseco->client->query('SendDisplayServerMessage', $message, 'OK', '', 0); - } elseif ($aseco->server->getGame() == 'TMF') { - $header = '{#black}' . $chattitle . ' ' . $admin->nickname . '$z :'; - // insure window doesn't become too wide - $message = wordwrap('{#welcome}' . $arglist[1], 40, LF . '{#welcome}'); - $message = explode(LF, $aseco->formatColors($message)); - foreach ($message as &$line) - $line = array($line); - - // display ManiaLink message to all players - foreach ($aseco->server->players->player_list as $target) - display_manialink($target->login, $header, array('Icons64x64_1', 'Inbox'), $message, array(0.8), 'OK'); - } - - // log console message - $aseco->console('{1} [{2}] sent wall message: {3}', $logtitle, $login, $arglist[1]); - } else { - $message = '{#server}> {#error}No message!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Delete records/rs_times database entries for specific record & sync. - */ - } elseif ($command['params'][0] == 'delrec' && $command['params'][1] != '') { - global $rasp; // from plugin.rasp.php - - // verify parameter - $param = $command['params'][1]; - if (is_numeric($param) && $param > 0 && $param <= $aseco->server->records->count()) { - $param = ltrim($param, '0'); - $param--; - // get record info - $record = $aseco->server->records->getRecord($param); - $pid = $aseco->getPlayerId($record->player->login); - - // remove times before record - if (method_exists($rasp, 'deleteTime')) - $rasp->deleteTime($aseco->server->challenge->id, $pid); - // remove record and fill up if necessary - ldb_removeRecord($aseco, $aseco->server->challenge->id, $pid, $param); - $param++; - - // log console message - $aseco->console('{1} [{2}] removed record {3} by {4} !', $logtitle, $login, $param, $record->player->login); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s {#admin}removes record {#highlite}{3}{#admin} by {#highlite}{4}', - $chattitle, $admin->nickname, $param, stripColors($record->player->nickname)); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - $message = '{#server}> {#error}No such record {#highlite}$i ' . $param . ' {#error}!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Prune records/rs_times database entries for specific track. - */ - } elseif ($command['params'][0] == 'prunerecs' && $command['params'][1] != '') { - global $rasp; // from plugin.rasp.php - - // verify parameter - $param = $command['params'][1]; - if (is_numeric($param) && $param >= 0) { - if (empty($admin->tracklist)) { - $message = $rasp->messages['LIST_HELP'][0]; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - return; - } - // find track by given # - $jid = ltrim($param, '0'); - $jid--; - if (array_key_exists($jid, $admin->tracklist)) { - $uid = $admin->tracklist[$jid]['uid']; - $name = stripColors($admin->tracklist[$jid]['name']); - $track = $aseco->getChallengeId($uid); - - if ($track > 0) { - // delete the records and rs_times - $query = 'DELETE FROM records WHERE ChallengeID=' . $track; - mysql_query($query); - $query = 'DELETE FROM rs_times WHERE challengeID=' . $track; - mysql_query($query); - - // log console message - $aseco->console('{1} [{2}] pruned records/times for track {3} !', $logtitle, $login, stripColors($name, false)); - - // show chat message - $message = '{#server}> {#admin}Deleted all records & times for track: {#highlite}' . $name; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - $message = '{#server}> {#error}Can\'t find ChallengeId for track: {#highlite}$i ' . $name . ' / ' . $uid; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = $rasp->messages['JUKEBOX_NOTFOUND'][0]; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = $rasp->messages['JUKEBOX_HELP'][0]; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Sets custom rounds points. - */ - } elseif ($command['params'][0] == 'rpoints' && $command['params'][1] != '') { - - if ($aseco->server->getGame() == 'TMF') { - if (function_exists('admin_rpoints')) { - admin_rpoints($aseco, $admin, $logtitle, $chattitle, $arglist[1]); // from plugin.rpoints.php - } else { - // show chat message - $message = '{#server}> {#admin}Custom Rounds points unavailable - include plugins.rpoints.php in plugins.xml'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Start or stop match tracking. - */ - } elseif ($command['params'][0] == 'match') { - global $MatchSettings; // from plugin.matchsave.php - - if (function_exists('match_loadsettings')) { - if ($command['params'][1] == 'begin') { - match_loadsettings(); // from plugin.matchsave.php - $MatchSettings['enable'] = true; - - // log console message - $aseco->console('{1} [{2}] started match!', $logtitle, $login); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} has started the match', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } - elseif ($command['params'][1] == 'end') { - $MatchSettings['enable'] = false; - - // log console message - $aseco->console('{1} [{2}] ended match!', $logtitle, $login); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} has ended the match', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } - else { - // show chat message - $message = '{#server}> {#admin}Match is currently ' . ($MatchSettings['enable'] ? 'Running' : 'Stopped'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - // show chat message - $message = '{#server}> {#admin}Match tracking unavailable - include plugins.matchsave.php in plugins.xml'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Shows or sets AllowChallengeDownload status. - */ - } elseif ($command['params'][0] == 'acdl') { - - $param = strtolower($command['params'][1]); - if ($param == 'on' || $param == 'off') { - $enabled = ($param == 'on'); - $aseco->client->query('AllowChallengeDownload', $enabled); - - // log console message - $aseco->console('{1} [{2}] set AllowChallengeDownload {3} !', $logtitle, $login, ($enabled ? 'ON' : 'OFF')); - - // show chat message - $message = '{#server}> {#admin}AllowChallengeDownload set to ' . ($enabled ? 'Enabled' : 'Disabled'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - $aseco->client->query('IsChallengeDownloadAllowed'); - $enabled = $aseco->client->getResponse(); - - // show chat message - $message = '{#server}> {#admin}AllowChallengeDownload is currently ' . ($enabled ? 'Enabled' : 'Disabled'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Shows or sets Auto TimeLimit status. - */ - } elseif ($command['params'][0] == 'autotime') { - global $atl_active; // from plugin.autotime.php - - // check for autotime plugin - if (isset($atl_active)) { - $param = strtolower($command['params'][1]); - if ($param == 'on' || $param == 'off') { - $atl_active = ($param == 'on'); - - // log console message - $aseco->console('{1} [{2}] set Auto TimeLimit {3} !', $logtitle, $login, ($atl_active ? 'ON' : 'OFF')); - - // show chat message - $message = '{#server}> {#admin}Auto TimeLimit set to ' . ($atl_active ? 'Enabled' : 'Disabled'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - // show chat message - $message = '{#server}> {#admin}Auto TimeLimit is currently ' . ($atl_active ? 'Enabled' : 'Disabled'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - // show chat message - $message = '{#server}> {#admin}Auto TimeLimit unavailable - include plugins.autotime.php in plugins.xml'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Shows or sets DisableRespawn status (TMF). - */ - } elseif ($command['params'][0] == 'disablerespawn') { - - if ($aseco->server->getGame() == 'TMF') { - $param = strtolower($command['params'][1]); - if ($param == 'on' || $param == 'off') { - $enabled = ($param == 'on'); - $aseco->client->query('SetDisableRespawn', $enabled); - - // log console message - $aseco->console('{1} [{2}] set DisableRespawn {3} !', $logtitle, $login, ($enabled ? 'ON' : 'OFF')); - - // show chat message - $message = '{#server}>> {#admin}DisableRespawn set to ' . ($enabled ? 'Enabled' : 'Disabled') . ' on the next track'; - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - $aseco->client->query('GetDisableRespawn'); - $enabled = $aseco->client->getResponse(); - - // show chat message - $message = '{#server}> {#admin}DisableRespawn is currently ' . ($enabled['CurrentValue'] ? 'Enabled' : 'Disabled'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Shows or sets ForceShowAllOpponents status (TMF). - */ - } elseif ($command['params'][0] == 'forceshowopp') { - - if ($aseco->server->getGame() == 'TMF') { - $param = strtolower($command['params'][1]); - if ($param == 'all' || $param == 'off') { - $enabled = ($param == 'all' ? 1 : 0); - $aseco->client->query('SetForceShowAllOpponents', $enabled); - - // log console message - $aseco->console('{1} [{2}] set ForceShowAllOpponents {3} !', $logtitle, $login, ($enabled ? 'ALL' : 'OFF')); - - // show chat message - $message = '{#server}>> {#admin}ForceShowAllOpponents set to {#highlite}' . ($enabled ? 'Enabled' : 'Disabled') . '{#admin} on the next track'; - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } elseif (is_numeric($param) && $param > 1) { - $enabled = intval($param); - $aseco->client->query('SetForceShowAllOpponents', $enabled); - - // log console message - $aseco->console('{1} [{2}] set ForceShowAllOpponents to {3} !', $logtitle, $login, $enabled); - - // show chat message - $message = '{#server}>> {#admin}ForceShowAllOpponents set to {#highlite}' . $enabled . '{#admin} on the next track'; - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - $aseco->client->query('GetForceShowAllOpponents'); - $enabled = $aseco->client->getResponse(); - $enabled = $enabled['CurrentValue']; - - // show chat message - $message = '{#server}> {#admin}ForceShowAllOpponents is set to: {#highlite}' . ($enabled != 0 ? ($enabled > 1 ? $enabled : 'All') : 'Off'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Shows or sets Automatic ScorePanel status (TMF). - */ - } elseif ($command['params'][0] == 'scorepanel') { - global $auto_scorepanel; - - if ($aseco->server->getGame() == 'TMF') { - $param = strtolower($command['params'][1]); - if ($param == 'on' || $param == 'off') { - $auto_scorepanel = ($param == 'on'); - scorepanel_off($aseco, null); - - // log console message - $aseco->console('{1} [{2}] set Automatic ScorePanel {3} !', $logtitle, $login, ($auto_scorepanel ? 'ON' : 'OFF')); - - // show chat message - $message = '{#server}>> {#admin}Automatic ScorePanel set to ' . ($auto_scorepanel ? 'Enabled' : 'Disabled'); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - // show chat message - $message = '{#server}> {#admin}Automatic ScorePanel is currently ' . ($auto_scorepanel ? 'Enabled' : 'Disabled'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Shows or sets Rounds Finishpanel status (TMF). - */ - } elseif ($command['params'][0] == 'roundsfinish') { - global $rounds_finishpanel; - - if ($aseco->server->getGame() == 'TMF') { - $param = strtolower($command['params'][1]); - if ($param == 'on' || $param == 'off') { - $rounds_finishpanel = ($param == 'on'); - - // log console message - $aseco->console('{1} [{2}] set Rounds Finishpanel {3} !', $logtitle, $login, ($rounds_finishpanel ? 'ON' : 'OFF')); - - // show chat message - $message = '{#server}>> {#admin}Rounds Finishpanel set to ' . ($rounds_finishpanel ? 'Enabled' : 'Disabled'); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - // show chat message - $message = '{#server}> {#admin}Rounds Finishpanel is currently ' . ($rounds_finishpanel ? 'Enabled' : 'Disabled'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Forces a player into Blue or Red team (TMF). - */ - } elseif ($command['params'][0] == 'forceteam' && $command['params'][1] != '') { - - if ($aseco->server->getGame() == 'TMF') { - // check for Team mode - if ($aseco->server->gameinfo->mode == Gameinfo::TEAM) { - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) { - // get player's team - $aseco->client->query('GetPlayerInfo', $target->login); - $info = $aseco->client->getResponse(); - // check for new team - if (isset($command['params'][2]) && $command['params'][2] != '') { - $team = strtolower($command['params'][2]); - - if (strpos('blue', $team) === 0) { - if ($info['TeamId'] != 0) { - // set player to Blue team - $aseco->client->query('ForcePlayerTeam', $target->login, 0); - - // log console message - $aseco->console('{1} [{2}] forces {3} into Blue team!', $logtitle, $login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} forces {#highlite}{3}$z$s{#admin} into $00fBlue{#admin} team!', - $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname)); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - $message = '{#server}> {#admin}Player {#highlite}' . - stripColors($target->nickname) . - '{#admin} is already in $00fBlue{#admin} team'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - } elseif (strpos('red', $team) === 0) { - if ($info['TeamId'] != 1) { - // set player to Red team - $aseco->client->query('ForcePlayerTeam', $target->login, 1); - - // log console message - $aseco->console('{1} [{2}] forces {3} into Red team!', $logtitle, $login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} forces {#highlite}{3}$z$s{#admin} into $f00Red{#admin} team!', - $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname)); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - $message = '{#server}> {#admin}Player {#highlite}' . - stripColors($target->nickname) . - '{#admin} is already in $f00Red{#admin} team'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - } else { - $message = '{#server}> {#highlite}' . $team . '$z$s{#error} is not a valid team!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - // show current team - $message = '{#server}> {#admin}Player {#highlite}' . - stripColors($target->nickname) . '{#admin} is in ' . - ($info['TeamId'] == 0 ? '$00fBlue' : '$f00Red') . - '{#admin} team'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - } else { - $message = '{#server}> {#error}Command only available in {#highlite}$i Team {#error}mode!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Forces player into free camera spectator (TMF). - */ - } elseif ($command['params'][0] == 'forcespec' && $command['params'][1] != '') { - - if ($aseco->server->getGame() == 'TMF') { - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) { - if (!$aseco->isSpectator($target)) { - // force player into free spectator - $rtn = $aseco->client->query('ForceSpectator', $target->login, 1); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] ForceSpectator - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - } else { - // allow spectator to switch back to player - $rtn = $aseco->client->query('ForceSpectator', $target->login, 0); - // force free camera mode on spectator - $aseco->client->addCall('ForceSpectatorTarget', array($target->login, '', 2)); - // free up player slot - $aseco->client->addCall('SpectatorReleasePlayerSlot', array($target->login)); - // log console message - $aseco->console('{1} [{2}] forces player {3} into spectator!', $logtitle, $login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} forces player {#highlite}{3}$z$s{#admin} into spectator!', - $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname)); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } - } else { - $message = formatText('{#server}> {#highlite}{1} {#error}is already a spectator!', - stripColors($target->nickname)); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Forces a spectator into free camera mode (TMF). - */ - } elseif ($command['params'][0] == 'specfree' && $command['params'][1] != '') { - - if ($aseco->server->getGame() == 'TMF') { - // get player information - if ($target = $aseco->getPlayerParam($admin, $command['params'][1])) { - if ($aseco->isSpectator($target)) { - // force free camera mode on spectator - $rtn = $aseco->client->query('ForceSpectatorTarget', $target->login, '', 2); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] ForceSpectatorTarget - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - } else { - // log console message - $aseco->console('{1} [{2}] forces spectator free mode on {3}!', $logtitle, $login, stripColors($target->nickname, false)); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} forces spectator free mode on {#highlite}{3}$z$s{#admin} !', - $chattitle, $admin->nickname, str_ireplace('$w', '', $target->nickname)); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } - } else { - $message = formatText('{#server}> {#highlite}{1} {#error}is not a spectator!', - stripColors($target->nickname)); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Selects default window style (TMF). - */ - } elseif ($command['params'][0] == 'panel') { - - if ($aseco->server->getGame() == 'TMF') { - if (function_exists('admin_panel')) { - $command['params'] = $command['params'][1]; - admin_panel($aseco, $command); // from plugin.panels.php - } else { - // show chat message - $message = '{#server}> {#admin}Admin panel unavailable - include plugins.panels.php in plugins.xml'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Selects default window style (TMF). - */ - } elseif ($command['params'][0] == 'style' && $command['params'][1] != '') { - - if ($aseco->server->getGame() == 'TMF') { - if (strtolower($command['params'][1]) == 'off') { - $aseco->style = array(); - $aseco->settings['window_style'] = 'Off'; - - // log console message - $aseco->console('{1} [{2}] reset default window style', $logtitle, $login); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} reset default window style', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - $style_file = 'styles/' . $command['params'][1] . '.xml'; - // load default style - if (($style = $aseco->xml_parser->parseXml($style_file)) && isset($style['STYLES'])) { - $aseco->style = $style['STYLES']; - - // log console message - $aseco->console('{1} [{2}] selects default window style [{3}]', $logtitle, $login, $command['params'][1]); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} selects default window style {#highlite}{3}', - $chattitle, $admin->nickname, $command['params'][1]); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - // Could not read/parse XML file - $message = '{#server}> {#error}No valid style file, use {#highlite}$i /style list {#error}!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Selects default admin panel (TMF). - */ - } elseif ($command['params'][0] == 'admpanel' && $command['params'][1] != '') { - - if ($aseco->server->getGame() == 'TMF') { - if (strtolower($command['params'][1]) == 'off') { - $aseco->panels['admin'] = ''; - $aseco->settings['admin_panel'] = 'Off'; - - // log console message - $aseco->console('{1} [{2}] reset default admin panel', $logtitle, $login); - - // show chat message - $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} reset default admin panel', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - // added file prefix - $panel = $command['params'][1]; - if (strtolower(substr($command['params'][1], 0, 5)) != 'admin') - $panel = 'Admin' . $panel; - $panel_file = 'panels/' . $panel . '.xml'; - // load default panel - if ($panel = @file_get_contents($panel_file)) { - $aseco->panels['admin'] = $panel; - - // log console message - $aseco->console('{1} [{2}] selects default admin panel [{3}]', $logtitle, $login, $command['params'][1]); - - // show chat message - $message = formatText('{#server}> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} selects default admin panel {#highlite}{3}', - $chattitle, $admin->nickname, $command['params'][1]); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - // Could not read XML file - $message = '{#server}> {#error}No valid admin panel file, use {#highlite}$i /admin panel list {#error}!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Selects default donate panel (TMUF). - */ - } elseif ($command['params'][0] == 'donpanel' && $command['params'][1] != '') { - - if ($aseco->server->getGame() == 'TMF') { - // check for TMUF server - if ($aseco->server->rights) { - if (strtolower($command['params'][1]) == 'off') { - $aseco->panels['donate'] = ''; - $aseco->settings['donate_panel'] = 'Off'; - - // log console message - $aseco->console('{1} [{2}] reset default donate panel', $logtitle, $login); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} reset default donate panel', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - // added file prefix - $panel = $command['params'][1]; - if (strtolower(substr($command['params'][1], 0, 6)) != 'donate') - $panel = 'Donate' . $panel; - $panel_file = 'panels/' . $panel . '.xml'; - // load default panel - if ($panel = @file_get_contents($panel_file)) { - $aseco->panels['donate'] = $panel; - - // log console message - $aseco->console('{1} [{2}] selects default donate panel [{3}]', $logtitle, $login, $command['params'][1]); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} selects default donate panel {#highlite}{3}', - $chattitle, $admin->nickname, $command['params'][1]); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - // Could not read XML file - $message = '{#server}> {#error}No valid donate panel file, use {#highlite}$i /donpanel list {#error}!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - } else { - $message = formatText($aseco->getChatMessage('UNITED_ONLY'), 'server'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Selects default records panel (TMF). - */ - } elseif ($command['params'][0] == 'recpanel' && $command['params'][1] != '') { - - if ($aseco->server->getGame() == 'TMF') { - if (strtolower($command['params'][1]) == 'off') { - $aseco->panels['records'] = ''; - $aseco->settings['records_panel'] = 'Off'; - - // log console message - $aseco->console('{1} [{2}] reset default records panel', $logtitle, $login); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} reset default records panel', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - // added file prefix - $panel = $command['params'][1]; - if (strtolower(substr($command['params'][1], 0, 7)) != 'records') - $panel = 'Records' . $panel; - $panel_file = 'panels/' . $panel . '.xml'; - // load default panel - if ($panel = @file_get_contents($panel_file)) { - $aseco->panels['records'] = $panel; - - // log console message - $aseco->console('{1} [{2}] selects default records panel [{3}]', $logtitle, $login, $command['params'][1]); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} selects default records panel {#highlite}{3}', - $chattitle, $admin->nickname, $command['params'][1]); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - // Could not read XML file - $message = '{#server}> {#error}No valid records panel file, use {#highlite}$i /recpanel list {#error}!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Selects default vote panel (TMF). - */ - } elseif ($command['params'][0] == 'votepanel' && $command['params'][1] != '') { - - if ($aseco->server->getGame() == 'TMF') { - if (strtolower($command['params'][1]) == 'off') { - $aseco->panels['vote'] = ''; - $aseco->settings['vote_panel'] = 'Off'; - - // log console message - $aseco->console('{1} [{2}] reset default vote panel', $logtitle, $login); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} reset default vote panel', - $chattitle, $admin->nickname); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - // added file prefix - $panel = $command['params'][1]; - if (strtolower(substr($command['params'][1], 0, 4)) != 'vote') - $panel = 'Vote' . $panel; - $panel_file = 'panels/' . $panel . '.xml'; - // load default panel - if ($panel = @file_get_contents($panel_file)) { - $aseco->panels['vote'] = $panel; - - // log console message - $aseco->console('{1} [{2}] selects default vote panel [{3}]', $logtitle, $login, $command['params'][1]); - - // show chat message - $message = formatText('{#server}>> {#admin}{1}$z$s {#highlite}{2}$z$s{#admin} selects default vote panel {#highlite}{3}', - $chattitle, $admin->nickname, $command['params'][1]); - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - } else { - // Could not read XML file - $message = '{#server}> {#error}No valid vote panel file, use {#highlite}$i /votepanel list {#error}!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Shows server's coppers amount (TMUF). - */ - } elseif ($command['params'][0] == 'coppers') { - - if ($aseco->server->getGame() == 'TMF') { - // check for TMUF server - if ($aseco->server->rights) { - // get server coppers - $aseco->client->query('GetServerCoppers'); - $coppers = $aseco->client->getResponse(); - - // show chat message - $message = formatText($aseco->getChatMessage('COPPERS'), - $aseco->server->name, $coppers); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - $message = formatText($aseco->getChatMessage('UNITED_ONLY'), 'server'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Pays server coppers to login (TMUF). - */ - } elseif ($command['params'][0] == 'pay') { - - if ($aseco->server->getGame() == 'TMF') { - // check for TMUF server - if ($aseco->server->rights) { - if (function_exists('admin_payment')) { - if (!isset($command['params'][2])) $command['params'][2] = ''; - admin_payment($aseco, $login, $command['params'][1], - $command['params'][2]); // from plugin.donate.php - } else { - // show chat message - $message = '{#server}> {#admin}Server payment unavailable - include plugins.donate.php in plugins.xml'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = formatText($aseco->getChatMessage('UNITED_ONLY'), 'server'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Displays relays list or shows relay master (TMF). - */ - } elseif ($command['params'][0] == 'relays') { - - if ($aseco->server->getGame() == 'TMF') { - if ($aseco->server->isrelay) { - // show chat message - $message = formatText($aseco->getChatMessage('RELAYMASTER'), - $aseco->server->relaymaster['Login'], $aseco->server->relaymaster['NickName']); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - if (empty($aseco->server->relayslist)) { - // show chat message - $message = formatText($aseco->getChatMessage('NO_RELAYS')); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - $header = 'Relay servers:'; - $relays = array(); - $relays[] = array('{#login}Login', '{#nick}Nick'); - foreach ($aseco->server->relayslist as $relay) - $relays[] = array($relay['Login'], $relay['NickName']); - - // display ManiaLink message - display_manialink($login, $header, array('BgRaceScore2', 'Spectator'), $relays, array(1.0, 0.35, 0.65), 'OK'); - } - } - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Shows server's detailed settings (TMF). - */ - } elseif ($command['params'][0] == 'server') { - - if ($aseco->server->getGame() == 'TMN') { - $version = $aseco->client->addCall('GetVersion', array()); - $network = $aseco->client->addCall('GetNetworkStats', array()); - $options = $aseco->client->addCall('GetServerOptions', array(1)); - $gameinfo = $aseco->client->addCall('GetCurrentGameInfo', array(1)); - if (!$aseco->client->multiquery()) { - trigger_error('[' . $aseco->client->getErrorCode() . '] GetServer (multi) - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - return; - } else { - $response = $aseco->client->getResponse(); - $version = $response[$version][0]; - $network = $response[$network][0]; - $options = $response[$options][0]; - $gameinfo = $response[$gameinfo][0]; - } - - // compile settings overview - $admin->msgs = array(); - $admin->msgs[0] = 1; - $head = 'System info for: ' . $options['Name'] . '$z' . LF . LF; - - $stats = $head . '{#black}GetVersion:' . LF; - foreach ($version as $key => $val) { - $stats .= '$g' . str_pad($key, 30) . '{#black}' . $val . LF; - } - - $stats .= '{#black}GetNetworkStats:' . LF; - foreach ($network as $key => $val) { - if ($key != 'PlayerNetInfos') - $stats .= '$g' . str_pad($key, 30) . '{#black}' . $val . LF; - } - - $admin->msgs[] = $aseco->formatColors($stats); - - $stats = $head . '{#black}GetServerOptions:' . LF; - foreach ($options as $key => $val) { - // show only Current values, not Next ones - if ($key != 'Name' && $key != 'Comment' && substr($key, 0, 4) != 'Next') - if (is_bool($val)) - $stats .= '$g' . str_pad($key, 30) . '{#black}' . bool2text($val) . LF; - else - $stats .= '$g' . str_pad($key, 30) . '{#black}' . $val . LF; - } - - $admin->msgs[] = $aseco->formatColors($stats); - - $stats = $head . '{#black}GetCurrentGameInfo:' . LF; - foreach ($gameinfo as $key => $val) { - if (is_bool($val)) - $stats .= '$g' . str_pad($key, 30) . '{#black}' . bool2text($val) . LF; - else - if ($key == 'GameMode') - $stats .= '$g' . str_pad($key, 30) . '{#black}' . $val . '$g (' . $aseco->server->gameinfo->getMode() . ')' . LF; - else - $stats .= '$g' . str_pad($key, 30) . '{#black}' . $val . LF; - } - - $admin->msgs[] = $aseco->formatColors($stats); - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0); - - } elseif ($aseco->server->getGame() == 'TMF') { - // get all server settings in one go - $version = $aseco->client->addCall('GetVersion', array()); - $info = $aseco->client->addCall('GetSystemInfo', array()); - $coppers = $aseco->client->addCall('GetServerCoppers', array()); - $ladderlim = $aseco->client->addCall('GetLadderServerLimits', array()); - $options = $aseco->client->addCall('GetServerOptions', array(1)); - $gameinfo = $aseco->client->addCall('GetCurrentGameInfo', array(1)); - $network = $aseco->client->addCall('GetNetworkStats', array()); - $callvotes = $aseco->client->addCall('GetCallVoteRatios', array()); - if (!$aseco->client->multiquery()) { - trigger_error('[' . $aseco->client->getErrorCode() . '] GetServer (multi) - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - return; - } else { - $response = $aseco->client->getResponse(); - $version = $response[$version][0]; - $info = $response[$info][0]; - $coppers = $response[$coppers][0]; - $ladderlim = $response[$ladderlim][0]; - $options = $response[$options][0]; - $gameinfo = $response[$gameinfo][0]; - $network = $response[$network][0]; - $callvotes = $response[$callvotes][0]; - } - - // compile settings overview - $head = 'System info for: ' . $options['Name']; - $admin->msgs = array(); - $admin->msgs[0] = array(1, $head, array(1.1, 0.6, 0.5), array('Icons64x64_1', 'DisplaySettings', 0.01)); - $stats = array(); - - $stats[] = array('{#black}GetVersion:', ''); - foreach ($version as $key => $val) { - $stats[] = array($key, '{#black}' . $val); - } - - $stats[] = array(); - $stats[] = array('{#black}GetSystemInfo:', ''); - foreach ($info as $key => $val) { - $stats[] = array($key, '{#black}' . $val); - } - - $stats[] = array(); - $stats[] = array('Rights', '{#black}' . ($aseco->server->rights ? 'United $gCoppers: {#black}' . $coppers : 'Nations')); - $stats[] = array('Packmask', '{#black}' . $aseco->server->packmask); - if ($aseco->server->isrelay) - $stats[] = array('Relays', '{#black}' . $aseco->server->relaymaster['Login']); - else - $stats[] = array('Master to', '{#black}' . count($aseco->server->relayslist) . - ' $grelay' . (count($aseco->server->relayslist) == 1 ? '' : 's')); - - $stats[] = array(); - $stats[] = array('{#black}GetLadderServerLimits:', ''); - foreach ($ladderlim as $key => $val) { - $stats[] = array($key, '{#black}' . $val); - } - - $admin->msgs[] = $stats; - $stats = array(); - - $stats[] = array('{#black}GetServerOptions:', ''); - foreach ($options as $key => $val) { - // show only Current values, not Next ones - if ($key != 'Name' && $key != 'Comment' && substr($key, 0, 4) != 'Next') - if (is_bool($val)) - $stats[] = array($key, '{#black}' . bool2text($val)); - else - $stats[] = array($key, '{#black}' . $val); - } - - $admin->msgs[] = $stats; - $stats = array(); - - $lines = 0; - $stats[] = array('{#black}GetCurrentGameInfo:', ''); - foreach ($gameinfo as $key => $val) { - if (is_bool($val)) - $stats[] = array($key, '{#black}' . bool2text($val)); - else - if ($key == 'GameMode') - $stats[] = array($key, '{#black}' . $val . '$g (' . $aseco->server->gameinfo->getMode() . ')'); - else - $stats[] = array($key, '{#black}' . $val); - - if (++$lines > 18) { - $admin->msgs[] = $stats; - $stats = array(); - $stats[] = array('{#black}GetCurrentGameInfo:', ''); - $lines = 0; - } - } - - $stats[] = array(); - $stats[] = array('{#black}GetNetworkStats:', ''); - foreach ($network as $key => $val) { - if ($key != 'PlayerNetInfos') - $stats[] = array($key, '{#black}' . $val); - } - - $stats[] = array(); - $stats[] = array('{#black}GetCallVoteRatios:', ''); - $stats[] = array('Command', 'Ratio'); - foreach ($callvotes as $entry) { - $stats[] = array('{#black}' . $entry['Command'], '{#black}' . round($entry['Ratio'], 2)); - } - - $admin->msgs[] = $stats; - display_manialink_multi($admin); - } else { - $message = $aseco->getChatMessage('FOREVER_ONLY'); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Send private message to all available admins. - */ - } elseif ($command['params'][0] == 'pm') { - global $pmbuf, $pmlen, $muting_available; // from plugin.muting.php - - // check for non-empty message - if ($arglist[1] != '') { - // drop oldest pm line if buffer full - if (count($pmbuf) >= $pmlen) { - array_shift($pmbuf); - } - // append timestamp, admin nickname (but strip wide font) and pm line to history - $nick = str_ireplace('$w', '', $admin->nickname); - $pmbuf[] = array(date('H:i:s'), $nick, $arglist[1]); - - // find and pm other masteradmins/admins/operators - $nicks = ''; - $msg = '{#error}-pm-$g[' . $nick . '$z$s$i->{#logina}Admins$g]$i {#interact}' . $arglist[1]; - $msg = $aseco->formatColors($msg); - foreach ($aseco->server->players->player_list as $pl) { - // check for admin ability - if ($pl->login != $login && $aseco->allowAbility($pl, 'pm')) { - $nicks .= str_ireplace('$w', '', $pl->nickname) . '$z$s$i,'; - $aseco->client->addCall('ChatSendServerMessageToLogin', array($msg, $pl->login)); - - // check if player muting is enabled - if ($muting_available) { - // drop oldest message if receiver's mute buffer full - if (count($pl->mutebuf) >= 28) { // chat window length - array_shift($pl->mutebuf); - } - // append pm line to receiver's mute buffer - $pl->mutebuf[] = $msg; - } - } - } - - // CC message to self - if ($nicks) { - $nicks = substr($nicks, 0, strlen($nicks)-1); // strip trailing ',' - $msg = '{#error}-pm-$g[' . $nick . '$z$s$i->' . $nicks . ']$i {#interact}' . $arglist[1]; - } else { - $msg = '{#server}> {#error}No other admins currectly available!'; - } - $msg = $aseco->formatColors($msg); - $aseco->client->addCall('ChatSendServerMessageToLogin', array($msg, $login)); - if (!$aseco->client->multiquery()) { - trigger_error('[' . $aseco->client->getErrorCode() . '] ChatSend PM (multi) - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - } - - // check if player muting is enabled - if ($muting_available) { - // drop oldest message if sender's mute buffer full - if (count($admin->mutebuf) >= 28) { // chat window length - array_shift($admin->mutebuf); - } - // append pm line to sender's mute buffer - $admin->mutebuf[] = $msg; - } - } else { - $msg = '{#server}> {#error}No message!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($msg), $login); - } - - /** - * Displays log of recent private admin messages. - */ - } elseif ($command['params'][0] == 'pmlog') { - global $pmbuf, $lnlen; - - if (!empty($pmbuf)) { - if ($aseco->server->getGame() == 'TMN') { - $head = 'Recent PM history:' . LF; - $msg = ''; - $lines = 0; - $admin->msgs = array(); - $admin->msgs[0] = 1; - foreach ($pmbuf as $item) { - // break up long lines into chunks with continuation strings - $multi = explode(LF, wordwrap(stripColors($item[2]), $lnlen, LF . '...')); - foreach ($multi as $line) { - $line = substr($line, 0, $lnlen+3); // chop off excessively long words - $msg .= '$z' . ($aseco->settings['chatpmlog_times'] ? '$n<{#server}' . $item[0] . '$z$n>$m ' : '') . - '[{#black}' . $item[1] . '$z] ' . $line . LF; - if (++$lines > 9) { - $admin->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - } - // add if last batch exists - if ($msg != '') - $admin->msgs[] = $aseco->formatColors($head . $msg); - - // display popup message - if (count($admin->msgs) == 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0); - } else { // > 2 - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0); - } - - } elseif ($aseco->server->getGame() == 'TMF') { - $head = 'Recent Admin PM history:'; - $msg = array(); - $lines = 0; - $admin->msgs = array(); - $admin->msgs[0] = array(1, $head, array(1.2), array('Icons64x64_1', 'Outbox')); - foreach ($pmbuf as $item) { - // break up long lines into chunks with continuation strings - $multi = explode(LF, wordwrap(stripColors($item[2]), $lnlen+30, LF . '...')); - foreach ($multi as $line) { - $line = substr($line, 0, $lnlen+33); // chop off excessively long words - $msg[] = array('$z' . ($aseco->settings['chatpmlog_times'] ? '<{#server}' . $item[0] . '$z> ' : '') . - '[{#black}' . $item[1] . '$z] ' . $line); - if (++$lines > 14) { - $admin->msgs[] = $msg; - $lines = 0; - $msg = ''; - } - } - } - // add if last batch exists - if (!empty($msg)) - $admin->msgs[] = $msg; - - // display ManiaLink message - display_manialink_multi($admin); - } - } else { - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors('{#server}> {#error}No PM history found!'), $login); - } - - /** - * Executes direct server call - */ - } elseif ($command['params'][0] == 'call') { - global $method_results; - - // extra admin tier check - if (!$aseco->isMasterAdmin($admin)) { - $aseco->client->query('ChatSendToLogin', $aseco->formatColors('{#error}You don\'t have the required admin rights to do that!'), $login); - return; - } - - // check parameter(s) - if ($command['params'][1] != '') { - if ($command['params'][1] == 'help') { - if (isset($command['params'][2]) && $command['params'][2] != '') { - // generate help message for method - $method = $command['params'][2]; - $sign = $aseco->client->addCall('system.methodSignature', array($method)); - $help = $aseco->client->addCall('system.methodHelp', array($method)); - if (!$aseco->client->multiquery()) { - trigger_error('[' . $aseco->client->getErrorCode() . '] system.method - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - } else { - $response = $aseco->client->getResponse(); - if (isset($response[0]['faultCode'])) { - $message = '{#server}> {#error}No such method {#highlite}$i ' . $method . ' {#error}!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } else { - $sign = $response[$sign][0][0]; - $help = $response[$help][0]; - - // format signature & help - $params = ''; - for ($i = 1; $i < count($sign); $i++) - $params .= $sign[$i] . ', '; - $params = substr($params, 0, strlen($params)-2); // strip trailing ", " - $sign = $sign[0] . ' {#black}' . $method . '$g (' . $params . ')'; - $sign = explode(LF, wordwrap($sign, 58, LF)); - $help = str_replace(array('', ''), - array('$i', '$i'), $help); - $help = explode(LF, wordwrap($help, 58, LF)); - - // compile & display help message - if ($aseco->server->getGame() == 'TMN') { - $info = 'Server Method help for:' . LF . LF; - foreach ($sign as $line) - $info .= $line . LF; - $info .= LF; - foreach ($help as $line) - $info .= $line . LF; - - // display popup message - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $aseco->formatColors($info), 'OK', '', 0); - } elseif ($aseco->server->getGame() == 'TMF') { - $header = 'Server Method help for:'; - $info = array(); - foreach ($sign as $line) - $info[] = array($line); - $info[] = array(); - foreach ($help as $line) - $info[] = array($line); - - // display ManiaLink message - display_manialink($login, $header, array('Icons128x128_1', 'Advanced', 0.02), $info, array(1.05), 'OK'); - } - } - } - - } else { - // compile & display help message - if ($aseco->server->getGame() == 'TMN') { - $help = '{#black}/admin call$g executes server method:' . LF; - $help .= ' - {#black}help$g, displays this help information' . LF; - $help .= ' - {#black}help Method$g, displays help for method' . LF; - $help .= ' - {#black}list$g, lists all available methods' . LF; - $help .= ' - {#black}Method {params}$g, executes method & displays result' . LF; - - // display popup message - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $aseco->formatColors($help), 'OK', '', 0); - - } elseif ($aseco->server->getGame() == 'TMF') { - $header = '{#black}/admin call$g executes server method:'; - $help = array(); - $help[] = array('...', '{#black}help', - 'Displays this help information'); - $help[] = array('...', '{#black}help Method', - 'Displays help for method'); - $help[] = array('...', '{#black}list', - 'Lists all available methods'); - $help[] = array('...', '{#black}Method {params}', - 'Executes method & displays result'); - - // display ManiaLink message - display_manialink($login, $header, array('Icons64x64_1', 'TrackInfo', -0.01), $help, array(1.0, 0.05, 0.35, 0.6), 'OK'); - } - } - - } elseif ($command['params'][1] == 'list') { - // get list of methods - $aseco->client->query('system.listMethods'); - $methods = $aseco->client->getResponse(); - $admin->msgs = array(); - - if ($aseco->server->getGame() == 'TMN') { - $head = 'Available Methods on this Server:' . LF . 'Id Method' . LF; - $msg = ''; - $mid = 1; - $lines = 0; - $admin->msgs[0] = 1; - foreach ($methods as $method) { - $msg .= '$g' . str_pad($mid, 3, '0', STR_PAD_LEFT) . '. {#black}' - . $method . LF; - $mid++; - if (++$lines > 9) { - $admin->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - // add if last batch exists - if ($msg != '') - $admin->msgs[] = $aseco->formatColors($head . $msg); - - // display popup message - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0); - - } elseif ($aseco->server->getGame() == 'TMF') { - $head = 'Available Methods on this Server:'; - $msg = array(); - $msg[] = array('Id', 'Method'); - $mid = 1; - $lines = 0; - $admin->msgs[0] = array(1, $head, array(0.9, 0.15, 0.75), array('Icons128x128_1', 'Advanced', 0.02)); - foreach ($methods as $method) { - $msg[] = array(str_pad($mid, 2, '0', STR_PAD_LEFT) . '.', - '{#black}' . $method); - $mid++; - if (++$lines > 14) { - $admin->msgs[] = $msg; - $lines = 0; - $msg = array(); - $msg[] = array('Id', 'Method'); - } - } - // add if last batch exists - if (count($msg) > 1) - $admin->msgs[] = $msg; - - // display ManiaLink message - display_manialink_multi($admin); - } - - } else { // server method - $method = $command['params'][1]; - // collect parameters with correct types - $args = array(); - $multistr = ''; - $in_multi = false; - for ($i = 2; $i < count($command['params']); $i++) { - if (!$in_multi && strtolower($command['params'][$i]) == 'true') - $args[] = true; - elseif (!$in_multi && strtolower($command['params'][$i]) == 'false') - $args[] = false; - elseif (!$in_multi && is_numeric($command['params'][$i])) - $args[] = intval($command['params'][$i]); - else - // check for multi-word strings - if ($in_multi) { - if (substr($command['params'][$i], -1) == '"') { - $args[] = $multistr . ' ' . substr($command['params'][$i], 0, -1); - $multistr = ''; - $in_multi = false; - } else { - $multistr .= ' ' . $command['params'][$i]; - } - } else { - if (substr($command['params'][$i], 0, 1) == '"') { - $multistr = substr($command['params'][$i], 1); - $in_multi = true; - } else { - $args[] = $command['params'][$i]; - } - } - } - - // execute method - switch (count($args)) { - case 0: $res = $aseco->client->query($method); - break; - case 1: $res = $aseco->client->query($method, $args[0]); - break; - case 2: $res = $aseco->client->query($method, $args[0], $args[1]); - break; - case 3: $res = $aseco->client->query($method, $args[0], $args[1], $args[2]); - break; - case 4: $res = $aseco->client->query($method, $args[0], $args[1], $args[2], $args[3]); - break; - case 5: $res = $aseco->client->query($method, $args[0], $args[1], $args[2], $args[3], $args[4]); - break; - } - // process result - if ($res) { - $res = $aseco->client->getResponse(); - $admin->msgs = array(); - $method_results = array(); - collect_results($method, $res, ''); - - // compile & display result message - if ($aseco->server->getGame() == 'TMN') { - $head = 'Method results for:' . LF . LF; - $msg = ''; - $mid = 1; - $lines = 0; - $admin->msgs[0] = 1; - foreach ($method_results as $line) { - $msg .= $line . '$z' . LF; - $mid++; - if (++$lines > 14) { - $admin->msgs[] = $aseco->formatColors($head . $msg); - $lines = 0; - $msg = ''; - } - } - // add if last batch exists - if ($msg != '') - $admin->msgs[] = $aseco->formatColors($head . $msg); - - // display popup message - if (count($admin->msgs) == 2) { - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'OK', '', 0); - } else { // > 2 - $aseco->client->query('SendDisplayServerMessageToLogin', $login, $admin->msgs[1], 'Close', 'Next', 0); - } - - } elseif ($aseco->server->getGame() == 'TMF') { - $head = 'Method results for:'; - $msg = array(); - $mid = 1; - $lines = 0; - $admin->msgs[0] = array(1, $head, array(1.1), array('Icons128x128_1', 'Advanced', 0.02)); - foreach ($method_results as $line) { - $msg[] = array($line); - $mid++; - if (++$lines > 20) { - $admin->msgs[] = $msg; - $lines = 0; - $msg = array(); - } - } - // add if last batch exists - if (!empty($msg)) - $admin->msgs[] = $msg; - - // display ManiaLink message - display_manialink_multi($admin); - } - } else { - $message = '{#server}> {#error}Method error for {#highlite}$i ' . $method . '{#error}: [' . $aseco->client->getErrorCode() . '] ' . $aseco->client->getErrorMessage(); - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - } - } else { - $message = '{#server}> {#error}No call specified - see {#highlite}$i /admin call help{#error} and {#highlite}$i /admin call list{#error}!'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - /** - * Unlocks admin commands & features. - */ - } elseif ($command['params'][0] == 'unlock' && $command['params'][1] != '') { - - // check unlock password - if ($aseco->settings['lock_password'] == $command['params'][1]) { - $admin->unlocked = true; - $message = '{#server}> {#admin}Password accepted: admin commands unlocked!'; - } else { - $message = '{#server}> {#error}Invalid password!'; - } - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Toggle debug on/off. - */ - } elseif ($command['params'][0] == 'debug') { - - $aseco->debug = !$aseco->debug; - if ($aseco->debug) { - $message = '{#server}> Debug is now enabled'; - } else { - $message = '{#server}> Debug is now disabled'; - } - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - - /** - * Shuts down XASECO. - */ - } elseif ($command['params'][0] == 'shutdown') { - - trigger_error('Shutdown XASECO!', E_USER_ERROR); - - /** - * Shuts down Server & XASECO. - */ - } elseif ($command['params'][0] == 'shutdownall') { - - $message = '{#server}>> {#error}$wShutting down server now!'; - $aseco->client->query('ChatSendServerMessage', $aseco->formatColors($message)); - - $rtn = $aseco->client->query('StopServer'); - if (!$rtn) { - trigger_error('[' . $aseco->client->getErrorCode() . '] StopServer - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - } else { - // test for /noautoquit - sleep(2); - $autoquit = new IXR_ClientMulticall_Gbx(); - if ($autoquit->InitWithIp($aseco->server->ip, $aseco->server->port)) - $aseco->client->query('QuitGame'); - - trigger_error('Shutdown ' . $aseco->server->getGame() . ' server & XASECO!', E_USER_ERROR); - } - - /** - * Checks current version of XASECO. - */ - } elseif ($command['params'][0] == 'uptodate') { - - if (function_exists('admin_uptodate')) { - admin_uptodate($aseco, $command); // from plugin.uptodate.php - } else { - // show chat message - $message = '{#server}> {#admin}Version checking unavailable - include plugins.uptodate.php in plugins.xml'; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } - - } else { - $message = '{#server}> {#error}Unknown admin command or missing parameter(s): {#highlite}$i ' . $arglist[0] . ' ' . $arglist[1]; - $aseco->client->query('ChatSendServerMessageToLogin', $aseco->formatColors($message), $login); - } -} // chat_admin - - -function get_ignorelist($aseco) { - - $aseco->client->resetError(); - $newlist = array(); - $done = false; - $size = 300; - $i = 0; - while (!$done) { - $aseco->client->query('GetIgnoreList', $size, $i); - $players = $aseco->client->getResponse(); - if (!empty($players)) { - if ($aseco->client->isError()) { - trigger_error('[' . $aseco->client->getErrorCode() . '] GetIgnoreList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - $done = true; - break; - } - foreach ($players as $prow) { - // fetch nickname for this login - $lgn = $prow['Login']; - $nick = $aseco->getPlayerNick($lgn); - $newlist[$lgn] = array($lgn, $nick); - } - if (count($players) < $size) { - // got less than 300 players, might as well leave - $done = true; - } else { - $i += $size; - } - } else { - $done = true; - } - } - return $newlist; -} // get_ignorelist - -function get_banlist($aseco) { - - $aseco->client->resetError(); - $newlist = array(); - $done = false; - $size = 300; - $i = 0; - while (!$done) { - $aseco->client->query('GetBanList', $size, $i); - $players = $aseco->client->getResponse(); - if (!empty($players)) { - if ($aseco->client->isError()) { - trigger_error('[' . $aseco->client->getErrorCode() . '] GetBanList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - $done = true; - break; - } - foreach ($players as $prow) { - // fetch nickname for this login - $lgn = $prow['Login']; - $nick = $aseco->getPlayerNick($lgn); - $newlist[$lgn] = array($lgn, $nick, - preg_replace('/:\d+/', '', $prow['IPAddress'])); // strip port - } - if (count($players) < $size) { - // got less than 300 players, might as well leave - $done = true; - } else { - $i += $size; - } - } else { - $done = true; - } - } - return $newlist; -} // get_banlist - -function get_blacklist($aseco) { - - $aseco->client->resetError(); - $newlist = array(); - $done = false; - $size = 300; - $i = 0; - while (!$done) { - $aseco->client->query('GetBlackList', $size, $i); - $players = $aseco->client->getResponse(); - if (!empty($players)) { - if ($aseco->client->isError()) { - trigger_error('[' . $aseco->client->getErrorCode() . '] GetBlackList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - $done = true; - break; - } - foreach ($players as $prow) { - // fetch nickname for this login - $lgn = $prow['Login']; - $nick = $aseco->getPlayerNick($lgn); - $newlist[$lgn] = array($lgn, $nick); - } - if (count($players) < $size) { - // got less than 300 players, might as well leave - $done = true; - } else { - $i += $size; - } - } else { - $done = true; - } - } - return $newlist; -} // get_blacklist - -function get_guestlist($aseco) { - - $aseco->client->resetError(); - $newlist = array(); - $done = false; - $size = 300; - $i = 0; - while (!$done) { - $aseco->client->query('GetGuestList', $size, $i); - $players = $aseco->client->getResponse(); - if (!empty($players)) { - if ($aseco->client->isError()) { - trigger_error('[' . $aseco->client->getErrorCode() . '] GetGuestList - ' . $aseco->client->getErrorMessage(), E_USER_WARNING); - $done = true; - break; - } - foreach ($players as $prow) { - // fetch nickname for this login - $lgn = $prow['Login']; - $nick = $aseco->getPlayerNick($lgn); - $newlist[$lgn] = array($lgn, $nick); - } - if (count($players) < $size) { - // got less than 300 players, might as well leave - $done = true; - } else { - $i += $size; - } - } else { - $done = true; - } - } - return $newlist; -} // get_guestlist - -function collect_results($key, $val, $indent) { - global $method_results; - - if (is_array($val)) { - // recursively compile array results - $method_results[] = $indent . '*' . $key . ' :'; - foreach ($val as $key2 => $val2) { - collect_results($key2, $val2, ' ' . $indent); - } - } else { - if (!is_string($val)) - $val = strval($val); - // format result key/value pair - $val = explode(LF, wordwrap($val, 32, LF . $indent . ' ', true)); - $firstline = true; - foreach ($val as $line) { - if ($firstline) - $method_results[] = $indent . $key . ' = ' . $line; - else - $method_results[] = $line; - $firstline = false; - } - } -} // collect_results - - -// called @ onPlayerManialinkPageAnswer -// Handles ManiaLink admin responses -// [0]=PlayerUid, [1]=Login, [2]=Answer -function event_admin($aseco, $answer) { - - // leave actions outside 2201 - 5200 to other handlers - if ($answer[2] < 2201 && $answer[2] > 5200 && - $answer[2] < -8100 && $answer[2] > -7901) - return; - - // get player & possible parameter - $player = $aseco->server->players->getPlayer($answer[1]); - if (isset($player->panels['plyparam'])) - $param = $player->panels['plyparam']; - - // check for /admin warn command - if ($answer[2] >= 2201 && $answer[2] <= 2400) { - $target = $player->playerlist[$answer[2]-2201]['login']; - - // log clicked command - $aseco->console('player {1} clicked command "/admin warn {2}"', - $player->login, $target); - - // warn selected player - $command = array(); - $command['author'] = $player; - $command['params'] = 'warn ' . $target; - chat_admin($aseco, $command); - } - - // check for /admin ignore command - elseif ($answer[2] >= 2401 && $answer[2] <= 2600) { - $target = $player->playerlist[$answer[2]-2401]['login']; - - // log clicked command - $aseco->console('player {1} clicked command "/admin ignore {2}"', - $player->login, $target); - - // ignore selected player - $command = array(); - $command['author'] = $player; - $command['params'] = 'ignore ' . $target; - chat_admin($aseco, $command); - - // log clicked command - $aseco->console('player {1} clicked command "/admin players {2}"', - $player->login, $param); - - // refresh players window - $command['params'] = 'players ' . $param; - chat_admin($aseco, $command); - } - - // check for /admin unignore command - elseif ($answer[2] >= 2601 && $answer[2] <= 2800) { - $target = $player->playerlist[$answer[2]-2601]['login']; - - // log clicked command - $aseco->console('player {1} clicked command "/admin unignore {2}"', - $player->login, $target); - - // unignore selected player - $command = array(); - $command['author'] = $player; - $command['params'] = 'unignore ' . $target; - chat_admin($aseco, $command); - - // log clicked command - $aseco->console('player {1} clicked command "/admin players {2}"', - $player->login, $param); - - // refresh players window - $command['params'] = 'players ' . $param; - chat_admin($aseco, $command); - } - - // check for /admin kick command - elseif ($answer[2] >= 2801 && $answer[2] <= 3000) { - $target = $player->playerlist[$answer[2]-2801]['login']; - - // log clicked command - $aseco->console('player {1} clicked command "/admin kick {2}"', - $player->login, $target); - - // kick selected player - $command = array(); - $command['author'] = $player; - $command['params'] = 'kick ' . $target; - chat_admin($aseco, $command); - - // log clicked command - $aseco->console('player {1} clicked command "/admin players {2}"', - $player->login, $param); - - // refresh players window - $command['params'] = 'players ' . $param; - chat_admin($aseco, $command); - } - - // check for /admin ban command - elseif ($answer[2] >= 3001 && $answer[2] <= 3200) { - $target = $player->playerlist[$answer[2]-3001]['login']; - - // log clicked command - $aseco->console('player {1} clicked command "/admin ban {2}"', - $player->login, $target); - - // ban selected player - $command = array(); - $command['author'] = $player; - $command['params'] = 'ban ' . $target; - chat_admin($aseco, $command); - - // log clicked command - $aseco->console('player {1} clicked command "/admin players {2}"', - $player->login, $param); - - // refresh players window - $command['params'] = 'players ' . $param; - chat_admin($aseco, $command); - } - - // check for /admin unban command - elseif ($answer[2] >= 3201 && $answer[2] <= 3400) { - $target = $player->playerlist[$answer[2]-3201]['login']; - - // log clicked command - $aseco->console('player {1} clicked command "/admin unban {2}"', - $player->login, $target); - - // unban selected player - $command = array(); - $command['author'] = $player; - $command['params'] = 'unban ' . $target; - chat_admin($aseco, $command); - - // log clicked command - $aseco->console('player {1} clicked command "/admin players {2}"', - $player->login, $param); - - // refresh players window - $command['params'] = 'players ' . $param; - chat_admin($aseco, $command); - } - - // check for /admin black command - elseif ($answer[2] >= 3401 && $answer[2] <= 3600) { - $target = $player->playerlist[$answer[2]-3401]['login']; - - // log clicked command - $aseco->console('player {1} clicked command "/admin black {2}"', - $player->login, $target); - - // black selected player - $command = array(); - $command['author'] = $player; - $command['params'] = 'black ' . $target; - chat_admin($aseco, $command); - - // log clicked command - $aseco->console('player {1} clicked command "/admin players {2}"', - $player->login, $param); - - // refresh players window - $command['params'] = 'players ' . $param; - chat_admin($aseco, $command); - } - - // check for /admin unblack command - elseif ($answer[2] >= 3601 && $answer[2] <= 3800) { - $target = $player->playerlist[$answer[2]-3601]['login']; - - // log clicked command - $aseco->console('player {1} clicked command "/admin unblack {2}"', - $player->login, $target); - - // unblack selected player - $command = array(); - $command['author'] = $player; - $command['params'] = 'unblack ' . $target; - chat_admin($aseco, $command); - - // log clicked command - $aseco->console('player {1} clicked command "/admin players {2}"', - $player->login, $param); - - // refresh players window - $command['params'] = 'players ' . $param; - chat_admin($aseco, $command); - } - - // check for /admin addguest command - elseif ($answer[2] >= 3801 && $answer[2] <= 4000) { - $target = $player->playerlist[$answer[2]-3801]['login']; - - // log clicked command - $aseco->console('player {1} clicked command "/admin addguest {2}"', - $player->login, $target); - - // addguest selected player - $command = array(); - $command['author'] = $player; - $command['params'] = 'addguest ' . $target; - chat_admin($aseco, $command); - - // log clicked command - $aseco->console('player {1} clicked command "/admin players {2}"', - $player->login, $param); - - // refresh players window - $command['params'] = 'players ' . $param; - chat_admin($aseco, $command); - } - - // check for /admin removeguest command - elseif ($answer[2] >= 4001 && $answer[2] <= 4200) { - $target = $player->playerlist[$answer[2]-4001]['login']; - - // log clicked command - $aseco->console('player {1} clicked command "/admin removeguest {2}"', - $player->login, $target); - - // removeguest selected player - $command = array(); - $command['author'] = $player; - $command['params'] = 'removeguest ' . $target; - chat_admin($aseco, $command); - - // log clicked command - $aseco->console('player {1} clicked command "/admin players {2}"', - $player->login, $param); - - // refresh players window - $command['params'] = 'players ' . $param; - chat_admin($aseco, $command); - } - - // check for /admin forcespec command - elseif ($answer[2] >= 4201 && $answer[2] <= 4400) { - $target = $player->playerlist[$answer[2]-4201]['login']; - - // log clicked command - $aseco->console('player {1} clicked command "/admin forcespec {2}"', - $player->login, $target); - - // forcespec selected player - $command = array(); - $command['author'] = $player; - $command['params'] = 'forcespec ' . $target; - chat_admin($aseco, $command); - - // log clicked command - $aseco->console('player {1} clicked command "/admin players {2}"', - $player->login, $param); - - // refresh players window - $command['params'] = 'players ' . $param; - chat_admin($aseco, $command); - } - - // check for /admin unignore command in listignores - elseif ($answer[2] >= 4401 && $answer[2] <= 4600) { - $target = $player->playerlist[$answer[2]-4401]['login']; - - // log clicked command - $aseco->console('player {1} clicked command "/admin unignore {2}"', - $player->login, $target); - - // unignore selected player - $command = array(); - $command['author'] = $player; - $command['params'] = 'unignore ' . $target; - chat_admin($aseco, $command); - - // check whether last player was unignored - $ignores = get_ignorelist($aseco); - if (empty($ignores)) { - // close main window - mainwindow_off($aseco, $player->login); - } else { - // log clicked command - $aseco->console('player {1} clicked command "/admin listignores"', - $player->login); - - // refresh listignores window - $command['params'] = 'listignores'; - chat_admin($aseco, $command); - } - } - - // check for /admin unban command in listbans - elseif ($answer[2] >= 4601 && $answer[2] <= 4800) { - $target = $player->playerlist[$answer[2]-4601]['login']; - - // log clicked command - $aseco->console('player {1} clicked command "/admin unban {2}"', - $player->login, $target); - - // unban selected player - $command = array(); - $command['author'] = $player; - $command['params'] = 'unban ' . $target; - chat_admin($aseco, $command); - - // check whether last player was unbanned - $bans = get_banlist($aseco); - if (empty($bans)) { - // close main window - mainwindow_off($aseco, $player->login); - } else { - // log clicked command - $aseco->console('player {1} clicked command "/admin listbans"', - $player->login); - - // refresh listbans window - $command['params'] = 'listbans'; - chat_admin($aseco, $command); - } - } - - // check for /admin unblack command in listblacks - elseif ($answer[2] >= 4801 && $answer[2] <= 5000) { - $target = $player->playerlist[$answer[2]-4801]['login']; - - // log clicked command - $aseco->console('player {1} clicked command "/admin unblack {2}"', - $player->login, $target); - - // unblack selected player - $command = array(); - $command['author'] = $player; - $command['params'] = 'unblack ' . $target; - chat_admin($aseco, $command); - - // check whether last player was unblacked - $blacks = get_blacklist($aseco); - if (empty($blacks)) { - // close main window - mainwindow_off($aseco, $player->login); - } else { - // log clicked command - $aseco->console('player {1} clicked command "/admin listblacks"', - $player->login); - - // refresh listblacks window - $command['params'] = 'listblacks'; - chat_admin($aseco, $command); - } - } - - // check for /admin removeguest command in listguests - elseif ($answer[2] >= 5001 && $answer[2] <= 5200) { - $target = $player->playerlist[$answer[2]-5001]['login']; - - // log clicked command - $aseco->console('player {1} clicked command "/admin removeguest {2}"', - $player->login, $target); - - // removeguest selected player - $command = array(); - $command['author'] = $player; - $command['params'] = 'removeguest ' . $target; - chat_admin($aseco, $command); - - // check whether last guest was removed - $guests = get_guestlist($aseco); - if (empty($guests)) { - // close main window - mainwindow_off($aseco, $player->login); - } else { - // log clicked command - $aseco->console('player {1} clicked command "/admin listguests"', - $player->login); - - // refresh listguests window - $command['params'] = 'listguests'; - chat_admin($aseco, $command); - } - } - - // check for /admin unbanip command - elseif ($answer[2] >= -8100 && $answer[2] <= -7901) { - $target = $player->playerlist[abs($answer[2])-7901]['ip']; - - // log clicked command - $aseco->console('player {1} clicked command "/admin unbanip {2}"', - $player->login, $target); - - // unbanip selected IP - $command = array(); - $command['author'] = $player; - $command['params'] = 'unbanip ' . $target; - chat_admin($aseco, $command); - - // check whether last IP was unbanned - if (!$empty = empty($aseco->bannedips)) { - $empty = true; - for ($i = 0; $i < count($aseco->bannedips); $i++) - if ($aseco->bannedips[$i] != '') { - $empty = false; - break; - } - } - if ($empty) { - // close main window - mainwindow_off($aseco, $player->login); - } else { - // log clicked command - $aseco->console('player {1} clicked command "/admin listips"', - $player->login); - - // refresh listips window - $command['params'] = 'listips'; - chat_admin($aseco, $command); - } - } -} // event_admin -?> diff --git a/xaseco/plugins/chat.dedimania.php b/xaseco/plugins/chat.dedimania.php deleted file mode 100644 index a89c2c5..0000000 --- a/xaseco/plugins/chat.dedimania.php +++ /dev/null @@ -1,941 +0,0 @@ -server->getGame() == 'TMN') { - $help = '{#dedimsg}Dedimania$g is an online World Records database for {#black}all$g' . LF; - $help .= 'TrackMania games. See its official site at:' . LF; - $help .= '{#black}http://www.dedimania.com/SITE/$g and the records database:' . LF; - $help .= '{#black}http://www.dedimania.com/tmstats/?do=stat$g .' . LF . LF; - $help .= 'Dedimania records are stored per game (TMN, TMU, etc)' . LF; - $help .= 'and mode (TimeAttack, Rounds, etc) and shared between' . LF; - $help .= 'all servers that operate with Dedimania support.' . LF . LF; - $help .= 'The available Dedimania commands are similar to local' . LF; - $help .= 'record commands:' . LF; - $help .= '{#black}/dedirecs$g, {#black}/dedinew$g, {#black}/dedilive$g, {#black}/dedipb$g, {#black}/dedicps$g, {#black}/dedistats$g,' . LF; - $help .= '{#black}/dedifirst$g, {#black}/dedilast$g, {#black}/dedinext$g, {#black}/dedidiff$g, {#black}/dedirange$g' . LF; - $help .= 'See the {#black}/helpall$g command for detailed descriptions.'; - - // display popup message - $aseco->client->query('SendDisplayServerMessageToLogin', $command['author']->login, $aseco->formatColors($help), 'OK', '', 0); - - } elseif ($aseco->server->getGame() == 'TMF') { - $header = 'Dedimania information:'; - $data = array(); - $data[] = array('{#dedimsg}Dedimania$g is an online World Records database for {#black}all'); - $data[] = array('TrackMania games. See its official site at:'); - $data[] = array('{#black}$l[http://www.dedimania.com/SITE/]http://www.dedimania.com/SITE/$l$g and the records database:'); - $data[] = array('{#black}$l[http://www.dedimania.com/tmstats/?do=stat]http://www.dedimania.com/tmstats/?do=stat$l$g .'); - $data[] = array(); - $data[] = array('Dedimania records are stored per game (TMN, TMU, etc)'); - $data[] = array('and mode (TimeAttack, Rounds, etc) and shared between'); - $data[] = array('all servers that operate with Dedimania support.'); - $data[] = array(); - $data[] = array('The available Dedimania commands are similar to local'); - $data[] = array('record commands:'); - $data[] = array('{#black}/dedirecs$g, {#black}/dedinew$g, {#black}/dedilive$g, {#black}/dedipb$g, {#black}/dedicps$g, {#black}/dedistats$g,'); - $data[] = array('{#black}/dedifirst$g, {#black}/dedilast$g, {#black}/dedinext$g, {#black}/dedidiff$g, {#black}/dedirange$g'); - $data[] = array(); - $data[] = array('See the {#black}/helpall$g command for detailed descriptions.'); - - // display ManiaLink message - display_manialink($command['author']->login, $header, array('Icons64x64_1', 'TrackInfo', -0.01), $data, array(0.95), 'OK'); - } -} // chat_helpdedi - -function chat_dedirecs($aseco, $command) { - global $dedi_db; - - $player = $command['author']; - $login = $player->login; - $dedi_recs = $dedi_db['Challenge']['Records']; - - // split params into array - $arglist = explode(' ', strtolower(preg_replace('/ +/', ' ', $command['params']))); - - // process optional relations commands - if ($arglist[0] == 'help') { - if ($aseco->server->getGame() == 'TMN') { - $help = '{#black}/dedirecs
    - diff --git a/xaseco/styles/00README.txt b/xaseco/styles/00README.txt deleted file mode 100644 index c1f9abe..0000000 --- a/xaseco/styles/00README.txt +++ /dev/null @@ -1,24 +0,0 @@ -Styles -====== - -This directory holds window style templates, managed by plugin.style.php. -Templates define the window, header and body background containers, as -well as the header and body text fonts, and the clickable button style. - -To create a new template, copy an existing one to a new filename and -edit that, as the existing set will be overwritten in future releases. - -Use ManiaLink http://smurf1.free.fr/mle/index.xml -and webpage http://smurf1.free.fr/mle/list.php -to select styles and fonts. Note that not every (sub)style and font -fits everywhere due to size variations. Also, some layout aspects -are fixed in manialinks.inc.php and cannot be customized easily. - -Use to invert the text highlight color on dark backgrounds. -This color is used to replace any '{#black}' tags in window output. - -If you create a nice style template that's sufficiently distinct from -the standard ones, send it to me and I might include it in the next -XAseco release. :) - -Xymph diff --git a/xaseco/styles/Black.xml b/xaseco/styles/Black.xml deleted file mode 100644 index 24a2511..0000000 --- a/xaseco/styles/Black.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgWindow1 - $ffc - - - -
    - - BgTitle2 - 0.07 - TextTitle2 -
    - - - - - BgList - 0.04 - TextTitle3 - - - - -
    diff --git a/xaseco/styles/BlackBlur.xml b/xaseco/styles/BlackBlur.xml deleted file mode 100644 index fb6e751..0000000 --- a/xaseco/styles/BlackBlur.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgWindow2 - $ffc - - - -
    - - BgTitle2 - 0.07 - TextTitle2 -
    - - - - - BgList - 0.04 - TextTitle3 - - - - -
    diff --git a/xaseco/styles/Blue.xml b/xaseco/styles/Blue.xml deleted file mode 100644 index 1463c12..0000000 --- a/xaseco/styles/Blue.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgWindow1 - $ffc - - - -
    - - BgTitle3_3 - 0.07 - TextTitle2 -
    - - - - - BgList - 0.04 - TextTitle3 - - - - -
    diff --git a/xaseco/styles/BlueBlur.xml b/xaseco/styles/BlueBlur.xml deleted file mode 100644 index 34ecccd..0000000 --- a/xaseco/styles/BlueBlur.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgWindow2 - $ffc - - - -
    - - BgTitle3_3 - 0.07 - TextTitle2 -
    - - - - - BgList - 0.04 - TextTitle3 - - - - -
    diff --git a/xaseco/styles/Cyan.xml b/xaseco/styles/Cyan.xml deleted file mode 100644 index 862a0df..0000000 --- a/xaseco/styles/Cyan.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgWindow1 - $ffc - - - -
    - - BgTitle3_2 - 0.07 - TextTitle2 -
    - - - - - BgList - 0.04 - TextTitle3 - - - - -
    diff --git a/xaseco/styles/CyanBlur.xml b/xaseco/styles/CyanBlur.xml deleted file mode 100644 index e987755..0000000 --- a/xaseco/styles/CyanBlur.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgWindow2 - $ffc - - - -
    - - BgTitle3_2 - 0.07 - TextTitle2 -
    - - - - - BgList - 0.04 - TextTitle3 - - - - -
    diff --git a/xaseco/styles/DarkBlur.xml b/xaseco/styles/DarkBlur.xml deleted file mode 100644 index 3bc3667..0000000 --- a/xaseco/styles/DarkBlur.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgWindow3 - $ffc - - - -
    - - BgPlayerName - 0.07 - TextTitle2 -
    - - - - - BgActivePlayerName - 0.04 - TextTitle3 - - - - -
    diff --git a/xaseco/styles/DarkTransp.xml b/xaseco/styles/DarkTransp.xml deleted file mode 100644 index b304e1d..0000000 --- a/xaseco/styles/DarkTransp.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgWindow3 - $ffc - - - -
    - - BgPlayerName - 0.07 - TextTitle2 -
    - - - - - BgActivePlayerName - 0.04 - TextTitle3 - - - - -
    diff --git a/xaseco/styles/Gray.xml b/xaseco/styles/Gray.xml deleted file mode 100644 index a9619f8..0000000 --- a/xaseco/styles/Gray.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgWindow1 - $ffc - - - -
    - - BgTitle3 - 0.07 - TextTitle2 -
    - - - - - BgList - 0.04 - TextTitle3 - - - - -
    diff --git a/xaseco/styles/GrayBlur.xml b/xaseco/styles/GrayBlur.xml deleted file mode 100644 index 5ffbf00..0000000 --- a/xaseco/styles/GrayBlur.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgWindow2 - $ffc - - - -
    - - BgTitle3 - 0.07 - TextTitle2 -
    - - - - - BgList - 0.04 - TextTitle3 - - - - -
    diff --git a/xaseco/styles/Green.xml b/xaseco/styles/Green.xml deleted file mode 100644 index ef6e743..0000000 --- a/xaseco/styles/Green.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgWindow1 - $ffc - - - -
    - - BgTitle3_4 - 0.07 - TextTitle2 -
    - - - - - BgList - 0.04 - TextTitle3 - - - - -
    diff --git a/xaseco/styles/GreenBlur.xml b/xaseco/styles/GreenBlur.xml deleted file mode 100644 index d809bf2..0000000 --- a/xaseco/styles/GreenBlur.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgWindow2 - $ffc - - - -
    - - BgTitle3_4 - 0.07 - TextTitle2 -
    - - - - - BgList - 0.04 - TextTitle3 - - - - -
    diff --git a/xaseco/styles/LightTransp.xml b/xaseco/styles/LightTransp.xml deleted file mode 100644 index d2c854f..0000000 --- a/xaseco/styles/LightTransp.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgRacePlayerName - $000 - - - -
    - - BgMediaTracker - 0.06 - TextButtonSmall -
    - - - - - BgRacePlayerName - 0.04 - TextChallengeNameSmall - - - - -
    diff --git a/xaseco/styles/LightTransp2.xml b/xaseco/styles/LightTransp2.xml deleted file mode 100644 index a769ab0..0000000 --- a/xaseco/styles/LightTransp2.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgRacePlayerName - $000 - - - -
    - - BgMediaTracker - 0.06 - TextButtonSmall -
    - - - - - BgMediaTracker - 0.04 - TextChallengeNameSmall - - - - -
    diff --git a/xaseco/styles/LightTransp3.xml b/xaseco/styles/LightTransp3.xml deleted file mode 100644 index 4a300d5..0000000 --- a/xaseco/styles/LightTransp3.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgRacePlayerName - $000 - - - -
    - - BgActivePlayerScore - 0.06 - TextButtonSmall -
    - - - - - BgMediaTracker - 0.04 - TextChallengeNameSmall - - - - -
    diff --git a/xaseco/styles/Orange.xml b/xaseco/styles/Orange.xml deleted file mode 100644 index 6148f58..0000000 --- a/xaseco/styles/Orange.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgWindow1 - $ffc - - - -
    - - BgTitle3_1 - 0.07 - TextTitle2 -
    - - - - - BgList - 0.04 - TextTitle3 - - - - -
    diff --git a/xaseco/styles/OrangeBlur.xml b/xaseco/styles/OrangeBlur.xml deleted file mode 100644 index 1997605..0000000 --- a/xaseco/styles/OrangeBlur.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgWindow2 - $ffc - - - -
    - - BgTitle3_1 - 0.07 - TextTitle2 -
    - - - - - BgList - 0.04 - TextTitle3 - - - - -
    diff --git a/xaseco/styles/ProgressBar.xml b/xaseco/styles/ProgressBar.xml deleted file mode 100644 index dddf4ed..0000000 --- a/xaseco/styles/ProgressBar.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - BgWindow2 - $fff - - - -
    - - ProgressBar - 0.065 - TextSubTitle2 -
    - - - - - NavButtonBlink - 0.04 - TextTitle3 - - - - -
    diff --git a/xaseco/styles/WhiteCard.xml b/xaseco/styles/WhiteCard.xml deleted file mode 100644 index 7bae4a5..0000000 --- a/xaseco/styles/WhiteCard.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgCard1 - $000 - - - -
    - - BgListLine - 0.06 - TextButtonSmall -
    - - - - - BgMediaTracker - 0.04 - TextChallengeNameSmall - - - - -
    diff --git a/xaseco/styles/WhiteRect.xml b/xaseco/styles/WhiteRect.xml deleted file mode 100644 index df23ce3..0000000 --- a/xaseco/styles/WhiteRect.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgIconBorder - $000 - - - -
    - - BgListLine - 0.06 - TextButtonSmall -
    - - - - - BgMediaTracker - 0.04 - TextChallengeNameSmall - - - - -
    diff --git a/xaseco/styles/WhiteRound.xml b/xaseco/styles/WhiteRound.xml deleted file mode 100644 index 979462c..0000000 --- a/xaseco/styles/WhiteRound.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - BgButtonBig - $000 - - - -
    - - BgActivePlayerScore - 0.06 - TextButtonSmall -
    - - - - - NavButton - 0.04 - TextChallengeNameSmall - - - - -
    diff --git a/xaseco/text.tpl b/xaseco/text.tpl deleted file mode 100644 index f409ff3..0000000 --- a/xaseco/text.tpl +++ /dev/null @@ -1,11 +0,0 @@ -Track: {TRACK} - Date: {DATE} - {TIME} -Rank Name - -{RANK}. {NICK} ({LAPTIME}) Team: {TEAM} Points: {POINTS} - - -Team Totals - -{TEAM} {POINTS} - diff --git a/xaseco/votes.config.php b/xaseco/votes.config.php deleted file mode 100644 index f5f5484..0000000 --- a/xaseco/votes.config.php +++ /dev/null @@ -1,151 +0,0 @@ - 1, // endround - 1 => 2, // ladder - 2 => 3, // replay - 3 => 2, // skip - 4 => 3, // kick - 5 => 3, // add - 6 => 3, // ignore - ); - // set to true to show a vote reminder at each of those rounds - $r_show_reminder = true; - - // maximum number of seconds before a vote expires - $ta_expire_limit = array( // seconds - 0 => 0, // endround, N/A - 1 => 90, // ladder - 2 => 120, // replay - 3 => 90, // skip - 4 => 120, // kick - 5 => 120, // add - 6 => 120, // ignore - ); - // set to true to show a vote reminder at an (approx.) interval - $ta_show_reminder = true; - // interval length at which to (approx.) repeat reminder - $ta_show_interval = 30; // seconds - - // check for active voting system - if ($feature_votes) { - // disable CallVotes - $aseco->client->query('SetCallVoteRatio', 1.0); - - // really disable all CallVotes on TMF - if ($aseco->server->getGame() == 'TMF') { - $ratios = array(array('Command' => '*', 'Ratio' => -1.0)); - $aseco->client->query('SetCallVoteRatios', $ratios); - } - - // if 2, the voting explanation is sent to all players when one - // new player joins; use this during an introduction period - // if 1, the voting explanation is only sent to the new player - // upon joining - // if 0, no explanations are sent at all - $global_explain = 2; - - // define the vote ratios for all types - $vote_ratios = array( - 0 => 0.4, // endround - 1 => 0.5, // ladder - 2 => 0.6, // replay - 3 => 0.6, // skip - 4 => 0.7, // kick - 5 => 1.0, // add - ignored, defined by $tmxvoteratio - 6 => 0.6, // ignore - ); - - // divert vote messages to TMF message window? - $vote_in_window = false; - - // disable voting commands while an admin (any tier) is online? - $disable_upon_admin = false; - - // disable voting commands during scoreboard at end of track? - $disable_while_sb = true; - - // allow kicks & allow user to kick-vote any admin? - $allow_kickvotes = true; - $allow_admin_kick = false; - // allow ignores & allow user to ignore-vote any admin? - $allow_ignorevotes = true; - $allow_admin_ignore = false; - - // maximum number of these votes per track; set to 0 to disable a - // vote type, or to some really high number for unlimited votes - $max_laddervotes = 2; - $max_replayvotes = 2; - $max_skipvotes = 2; - - // limit the number of times a track can be /replay-ed; 0 = unlimited - $replays_limit = 0; - - // if true, does restart via quick ChallengeRestart - // this is what most users are accustomed to, but it stops - // a track's music (if in use) - // if false, does restart via jukebox prepend & NextChallenge - // this takes longer and may confuse users into thinking - // the restart is actually loading the next track, but - // it insures music resumes playing - $ladder_fast_restart = true; - - // enable Rounds points limits? use this to restrict the use of the - // track-related votes if the _first_ player already has reached a - // specific percentage of the server's Rounds points limit - $r_points_limits = true; - - // percentage of Rounds points limit _after_ which /ladder is disabled - $r_ladder_max = 0.4; - // percentage of Rounds points limit _before_ which /replay is disabled - $r_replay_min = 0.5; - // percentage of Rounds points limit _after_ which /skip is disabled - $r_skip_max = 0.5; - - // enable Time Attack time limits? use this to restrict the use of the - // track-related votes if the current track is already _running_ for a - // specific percentage of the server's TA time limit - // this requires function time_playing() from plugin.track.php - $ta_time_limits = true; - - // percentage of TA time limit _after_ which /ladder is disabled - $ta_ladder_max = 0.4; - // percentage of TA time limit _before_ which /replay is disabled - $ta_replay_min = 0.5; - // percentage of TA time limit _after_ which /skip is disabled - $ta_skip_max = 0.5; - - // no restrictions in other modes - } -?>