From 33f85866982998931d9433f71692f6f12d343fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= Date: Sun, 10 Jan 2016 11:01:49 +0100 Subject: [PATCH] Added full source code --- AUTHORS | 11 + COPYING | 32 + COPYING.GPL | 339 + COPYING.MIT | 17 + README | 96 + TODO | 47 + admin/as_log.inc | 20 + admin/as_manager.inc | 24 + admin/bot.php | 80 + admin/cg_done.php | 56 + admin/cg_operation.inc | 45 + admin/cg_step0.php | 24 + admin/cg_step1.php | 30 + admin/cg_step10.php | 28 + admin/cg_step11.php | 60 + admin/cg_step12.php | 35 + admin/cg_step13.php | 33 + admin/cg_step2.php | 31 + admin/cg_step3.php | 29 + admin/cg_step4.php | 41 + admin/cg_step5.php | 205 + admin/cg_step6.php | 35 + admin/cg_step7.php | 33 + admin/cg_step8.php | 109 + admin/cg_step9.php | 27 + admin/cg_ticks_schedule.inc | 86 + admin/cg_user_dsp_1.inc | 12 + admin/cg_user_dsp_2.inc | 30 + admin/cg_user_dsp_3.inc | 111 + admin/cg_user_dsp_3ctf.inc | 137 + admin/cg_user_dsp_4.inc | 54 + admin/cg_user_dsp_5.inc | 43 + admin/cg_user_dsp_6.inc | 213 + admin/cg_user_dsp_7.inc | 23 + admin/cg_user_hdl_1.inc | 8 + admin/cg_user_hdl_2.inc | 27 + admin/cg_user_hdl_3.inc | 73 + admin/cg_user_hdl_3ctf.inc | 66 + admin/cg_user_hdl_4.inc | 30 + admin/cg_user_hdl_5.inc | 44 + admin/cg_user_hdl_6.inc | 10 + admin/cg_user_hdl_7.inc | 8 + admin/cg_user_hdr.inc | 9 + admin/config.inc | 39 + admin/create.php | 19 + admin/ctf_map.inc | 425 + admin/deathofrats.php | 494 + admin/game_status.php | 242 + admin/index.html | 25 + admin/maintenance.php | 79 + admin/map_edit.js | 703 + admin/maps.php | 189 + admin/proxy.php | 128 + admin/set_default.php | 104 + admin/ticks.php | 221 + ircbot/bot.conf | 147 + ircbot/bot.php | 476 + ircbot/chat.php | 364 + ircbot/command_reference.txt | 870 ++ ircbot/connection.php | 432 + ircbot/databases/ini.php | 515 + ircbot/databases/mysql.php | 169 + ircbot/databases/postgre.php | 162 + ircbot/databases/postgres.php | 148 + ircbot/dcc.php | 579 + ircbot/defines.php | 314 + ircbot/error.php | 107 + ircbot/file.php | 663 + ircbot/function.conf | 58 + ircbot/irc.php | 2501 ++++ ircbot/module.php | 126 + ircbot/modules/default/ads.ini | 0 ircbot/modules/default/dcc_mod.conf | 81 + ircbot/modules/default/dcc_mod.php | 1057 ++ ircbot/modules/default/priv_mod.conf | 35 + ircbot/modules/default/priv_mod.php | 315 + ircbot/modules/lw/lw_mod.conf | 21 + ircbot/modules/lw/lw_mod.php | 438 + ircbot/modules/more_mods.txt | 1 + ircbot/modules/seen/seen_mod.conf | 11 + ircbot/modules/seen/seen_mod.php | 234 + ircbot/modules/template.txt | 39 + ircbot/parser.php | 1143 ++ ircbot/queue.php | 339 + ircbot/readme.txt | 1364 ++ ircbot/remote.php | 213 + ircbot/socket.php | 1010 ++ ircbot/timers.php | 235 + ircbot/typedefs.conf | 75 + manual/beta5/en/account_games.lwdoc | 109 + manual/beta5/en/accounts.lwdoc | 35 + manual/beta5/en/alliance.lwdoc | 28 + manual/beta5/en/alliance_create.lwdoc | 47 + manual/beta5/en/alliance_manage.lwdoc | 173 + manual/beta5/en/allies.lwdoc | 27 + manual/beta5/en/background.lwdoc | 17 + manual/beta5/en/battles.lwdoc | 60 + manual/beta5/en/communications.lwdoc | 29 + manual/beta5/en/communications_page.lwdoc | 55 + manual/beta5/en/diplomacy.lwdoc | 34 + manual/beta5/en/diplomacy_page.lwdoc | 29 + manual/beta5/en/empire.lwdoc | 272 + manual/beta5/en/empire_overview.lwdoc | 68 + manual/beta5/en/enemies.lwdoc | 19 + manual/beta5/en/fleets.lwdoc | 44 + manual/beta5/en/fleets_actions.lwdoc | 37 + manual/beta5/en/fleets_moving.lwdoc | 199 + manual/beta5/en/fleets_page.lwdoc | 161 + manual/beta5/en/fleets_splitting.lwdoc | 54 + manual/beta5/en/forums.lwdoc | 214 + manual/beta5/en/game_interface.lwdoc | 154 + manual/beta5/en/game_overview.lwdoc | 28 + manual/beta5/en/game_rules.lwdoc | 43 + manual/beta5/en/general_game.lwdoc | 37 + manual/beta5/en/general_rules.lwdoc | 39 + manual/beta5/en/glossary.lwdoc | 160 + manual/beta5/en/good_practices.lwdoc | 27 + manual/beta5/en/home_page.lwdoc | 135 + manual/beta5/en/law_discrimination.lwdoc | 54 + manual/beta5/en/law_spam.lwdoc | 21 + manual/beta5/en/law_unauthorised_access.lwdoc | 57 + manual/beta5/en/legal.lwdoc | 41 + manual/beta5/en/main.lwdoc | 11 + manual/beta5/en/maps.lwdoc | 131 + manual/beta5/en/market_buy.lwdoc | 191 + manual/beta5/en/market_sell.lwdoc | 190 + manual/beta5/en/marketplace.lwdoc | 45 + manual/beta5/en/messages.lwdoc | 79 + manual/beta5/en/mod_rules.lwdoc | 77 + manual/beta5/en/money.lwdoc | 47 + manual/beta5/en/overview.lwdoc | 30 + manual/beta5/en/overview_page.lwdoc | 105 + manual/beta5/en/planet.lwdoc | 215 + manual/beta5/en/planets.lwdoc | 59 + manual/beta5/en/preferences.lwdoc | 160 + manual/beta5/en/probes.lwdoc | 20 + manual/beta5/en/rankings.lwdoc | 105 + manual/beta5/en/ships.lwdoc | 192 + manual/beta5/en/tech_1.lwdoc | 25 + manual/beta5/en/tech_10.lwdoc | 29 + manual/beta5/en/tech_11.lwdoc | 27 + manual/beta5/en/tech_12.lwdoc | 27 + manual/beta5/en/tech_13.lwdoc | 26 + manual/beta5/en/tech_14.lwdoc | 26 + manual/beta5/en/tech_15.lwdoc | 30 + manual/beta5/en/tech_16.lwdoc | 28 + manual/beta5/en/tech_17.lwdoc | 29 + manual/beta5/en/tech_18.lwdoc | 29 + manual/beta5/en/tech_2.lwdoc | 24 + manual/beta5/en/tech_20.lwdoc | 30 + manual/beta5/en/tech_21.lwdoc | 28 + manual/beta5/en/tech_22.lwdoc | 29 + manual/beta5/en/tech_23.lwdoc | 26 + manual/beta5/en/tech_24.lwdoc | 32 + manual/beta5/en/tech_25.lwdoc | 26 + manual/beta5/en/tech_26.lwdoc | 27 + manual/beta5/en/tech_27.lwdoc | 28 + manual/beta5/en/tech_28.lwdoc | 26 + manual/beta5/en/tech_29.lwdoc | 28 + manual/beta5/en/tech_3.lwdoc | 26 + manual/beta5/en/tech_30.lwdoc | 28 + manual/beta5/en/tech_31.lwdoc | 30 + manual/beta5/en/tech_32.lwdoc | 26 + manual/beta5/en/tech_33.lwdoc | 28 + manual/beta5/en/tech_34.lwdoc | 25 + manual/beta5/en/tech_35.lwdoc | 28 + manual/beta5/en/tech_36.lwdoc | 31 + manual/beta5/en/tech_37.lwdoc | 31 + manual/beta5/en/tech_38.lwdoc | 29 + manual/beta5/en/tech_39.lwdoc | 28 + manual/beta5/en/tech_4.lwdoc | 26 + manual/beta5/en/tech_40.lwdoc | 27 + manual/beta5/en/tech_41.lwdoc | 30 + manual/beta5/en/tech_42.lwdoc | 30 + manual/beta5/en/tech_43.lwdoc | 27 + manual/beta5/en/tech_44.lwdoc | 28 + manual/beta5/en/tech_45.lwdoc | 28 + manual/beta5/en/tech_46.lwdoc | 29 + manual/beta5/en/tech_48.lwdoc | 30 + manual/beta5/en/tech_49.lwdoc | 28 + manual/beta5/en/tech_5.lwdoc | 26 + manual/beta5/en/tech_50.lwdoc | 26 + manual/beta5/en/tech_51.lwdoc | 28 + manual/beta5/en/tech_52.lwdoc | 25 + manual/beta5/en/tech_53.lwdoc | 32 + manual/beta5/en/tech_54.lwdoc | 25 + manual/beta5/en/tech_55.lwdoc | 31 + manual/beta5/en/tech_56.lwdoc | 26 + manual/beta5/en/tech_57.lwdoc | 29 + manual/beta5/en/tech_58.lwdoc | 27 + manual/beta5/en/tech_59.lwdoc | 27 + manual/beta5/en/tech_6.lwdoc | 31 + manual/beta5/en/tech_60.lwdoc | 25 + manual/beta5/en/tech_61.lwdoc | 29 + manual/beta5/en/tech_62.lwdoc | 28 + manual/beta5/en/tech_63.lwdoc | 31 + manual/beta5/en/tech_64.lwdoc | 28 + manual/beta5/en/tech_65.lwdoc | 28 + manual/beta5/en/tech_68.lwdoc | 25 + manual/beta5/en/tech_69.lwdoc | 27 + manual/beta5/en/tech_7.lwdoc | 25 + manual/beta5/en/tech_70.lwdoc | 28 + manual/beta5/en/tech_71.lwdoc | 30 + manual/beta5/en/tech_72.lwdoc | 27 + manual/beta5/en/tech_73.lwdoc | 29 + manual/beta5/en/tech_74.lwdoc | 33 + manual/beta5/en/tech_75.lwdoc | 29 + manual/beta5/en/tech_76.lwdoc | 29 + manual/beta5/en/tech_77.lwdoc | 25 + manual/beta5/en/tech_78.lwdoc | 29 + manual/beta5/en/tech_79.lwdoc | 27 + manual/beta5/en/tech_8.lwdoc | 30 + manual/beta5/en/tech_80.lwdoc | 28 + manual/beta5/en/tech_81.lwdoc | 29 + manual/beta5/en/tech_82.lwdoc | 25 + manual/beta5/en/tech_83.lwdoc | 26 + manual/beta5/en/tech_84.lwdoc | 27 + manual/beta5/en/tech_85.lwdoc | 25 + manual/beta5/en/tech_86.lwdoc | 25 + manual/beta5/en/tech_87.lwdoc | 25 + manual/beta5/en/tech_88.lwdoc | 25 + manual/beta5/en/tech_89.lwdoc | 28 + manual/beta5/en/tech_9.lwdoc | 31 + manual/beta5/en/tech_90.lwdoc | 25 + manual/beta5/en/tech_exchange.lwdoc | 46 + manual/beta5/en/tech_list.lwdoc | 284 + manual/beta5/en/technology.lwdoc | 218 + manual/beta5/en/ticks.lwdoc | 79 + manual/beta5/en/topics.lwdoc | 57 + manual/beta5/en/tutorial.lwdoc | 81 + manual/beta5/en/universe.lwdoc | 38 + manual/beta5/en/universe_page.lwdoc | 39 + manual/beta5/en/vacation_mode.lwdoc | 133 + misc/Legacy Worlds.wdgt/Default.png | Bin 0 -> 17069 bytes misc/Legacy Worlds.wdgt/Icon.png | Bin 0 -> 4511 bytes misc/Legacy Worlds.wdgt/Info.plist | 16 + misc/Legacy Worlds.wdgt/LW.css | 109 + misc/Legacy Worlds.wdgt/LW.html | 58 + misc/Legacy Worlds.wdgt/images/blue.png | Bin 0 -> 17069 bytes misc/Legacy Worlds.wdgt/lib/Base.js | 189 + misc/Legacy Worlds.wdgt/lib/Base/Browser.js | 94 + misc/Legacy Worlds.wdgt/lib/Base/Comp.js | 217 + misc/Legacy Worlds.wdgt/lib/Base/Comp/Evt.js | 77 + misc/Legacy Worlds.wdgt/lib/Base/Comp/Slot.js | 50 + misc/Legacy Worlds.wdgt/lib/Base/Log.js | 52 + misc/Legacy Worlds.wdgt/lib/Base/Timer.js | 169 + misc/Legacy Worlds.wdgt/lib/Base/Util.js | 95 + .../lib/Base/Util/Hashtable.js | 125 + misc/Legacy Worlds.wdgt/lib/Base/XMLLoader.js | 313 + misc/Legacy Worlds.wdgt/lib/LWWidget.js | 85 + misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5.js | 77 + .../lib/LWWidget/Beta5/Cash.js | 17 + .../lib/LWWidget/Beta5/Fleets.js | 55 + .../lib/LWWidget/Beta5/Forums.js | 125 + .../lib/LWWidget/Beta5/Msg.js | 71 + .../lib/LWWidget/Beta5/Planets.js | 86 + .../lib/LWWidget/Beta5/Player.js | 82 + .../lib/LWWidget/Beta5/Techs.js | 37 + .../lib/LWWidget/Beta5/Ticks.js | 121 + misc/Legacy Worlds.wdgt/lib/LWWidget/Data.js | 25 + .../lib/LWWidget/Data/Leaf.js | 12 + .../lib/LWWidget/Data/Loader.js | 130 + .../lib/LWWidget/Data/Node.js | 25 + misc/Legacy Worlds.wdgt/lib/LWWidget/Debug.js | 67 + misc/Legacy Worlds.wdgt/lib/LWWidget/Game.js | 168 + .../lib/LWWidget/Game/Page.js | 34 + .../lib/LWWidget/GameDisplay.js | 73 + .../lib/LWWidget/GameSelector.js | 65 + misc/Legacy Worlds.wdgt/lib/LWWidget/Login.js | 77 + misc/Legacy Worlds.wdgt/lib/LWWidget/Main.js | 392 + misc/Legacy Worlds.wdgt/lib/LWWidget/Menu.js | 81 + .../lib/LWWidget/Menu/Command.js | 7 + .../lib/LWWidget/Menu/Entry.js | 7 + .../lib/LWWidget/Menu/Item.js | 38 + .../lib/LWWidget/Menu/Mode.js | 109 + .../lib/LWWidget/Menu/Text.js | 7 + .../lib/LWWidget/NewVersion.js | 33 + misc/Legacy Worlds.wdgt/lib/LWWidget/Page.js | 17 + .../lib/LWWidget/TextPage.js | 18 + .../lib/LWWidget/VersionCheck.js | 101 + .../lib/LWWidget/Versions.js | 2 + misc/forums-branch.patch | 11688 ++++++++++++++++ planetgen/generate.pl | 270 + planetgen/planetmaker.pl | 45 + planetgen/template.pov | 41 + planetgen/template2.pov | 50 + scripts/config.inc | 63 + scripts/control.pl | 574 + scripts/game/admin/beta5/library.inc | 106 + scripts/game/beta5/actions.inc | 1193 ++ scripts/game/beta5/actions/addTrustedAlly.inc | 120 + .../game/beta5/actions/banTrustingAlly.inc | 101 + .../game/beta5/actions/getCommsOverview.inc | 91 + .../game/beta5/actions/getEmpireOverview.inc | 69 + scripts/game/beta5/actions/getOverview.inc | 38 + .../game/beta5/actions/getTrustedAllies.inc | 54 + .../beta5/actions/getUniverseOverview.inc | 80 + .../beta5/actions/removeTrustingAllies.inc | 79 + scripts/game/beta5/alliance/library.inc | 94 + .../beta5/alliance/library/acceptRequest.inc | 51 + .../beta5/alliance/library/addCandidate.inc | 35 + .../beta5/alliance/library/cancelRequest.inc | 29 + .../beta5/alliance/library/changeRank.inc | 35 + .../game/beta5/alliance/library/create.inc | 39 + .../beta5/alliance/library/createForum.inc | 29 + .../beta5/alliance/library/createRank.inc | 47 + .../beta5/alliance/library/deleteForum.inc | 20 + .../beta5/alliance/library/deleteRank.inc | 18 + scripts/game/beta5/alliance/library/get.inc | 30 + .../beta5/alliance/library/getCandidates.inc | 29 + .../game/beta5/alliance/library/getForums.inc | 27 + .../alliance/library/getForumsComplete.inc | 34 + scripts/game/beta5/alliance/library/getId.inc | 21 + .../beta5/alliance/library/getKeepers.inc | 46 + .../beta5/alliance/library/getMembers.inc | 27 + .../beta5/alliance/library/getMilitary.inc | 34 + .../beta5/alliance/library/getPlanets.inc | 42 + .../beta5/alliance/library/getPrivileges.inc | 79 + .../alliance/library/getRankPrivileges.inc | 96 + .../beta5/alliance/library/getRankSize.inc | 32 + .../game/beta5/alliance/library/getRanks.inc | 22 + .../beta5/alliance/library/getRequests.inc | 22 + .../beta5/alliance/library/getTechList.inc | 93 + .../beta5/alliance/library/getTechOrder.inc | 36 + .../beta5/alliance/library/getTechOrders.inc | 58 + .../alliance/library/getTechRequests.inc | 38 + .../alliance/library/getTechSubmission.inc | 38 + .../game/beta5/alliance/library/getVoters.inc | 46 + scripts/game/beta5/alliance/library/kick.inc | 18 + scripts/game/beta5/alliance/library/leave.inc | 84 + .../beta5/alliance/library/modifyForum.inc | 20 + .../beta5/alliance/library/modifyRank.inc | 77 + .../game/beta5/alliance/library/moveForum.inc | 36 + .../beta5/alliance/library/rejectRequest.inc | 51 + .../alliance/library/removeCandidate.inc | 32 + .../beta5/alliance/library/sendRequest.inc | 29 + .../beta5/alliance/library/setDemocratic.inc | 47 + .../beta5/alliance/library/setForumAccess.inc | 23 + .../beta5/alliance/library/setSuccessor.inc | 38 + .../alliance/library/setTechRequests.inc | 46 + .../alliance/library/setTechTradeMode.inc | 34 + .../game/beta5/alliance/library/stepDown.inc | 58 + .../beta5/alliance/library/submitTechList.inc | 69 + .../alliance/library/submitTechOrders.inc | 119 + .../beta5/alliance/library/takePresidency.inc | 34 + .../beta5/alliance/library/updateRequests.inc | 75 + .../beta5/alliance/library/updateVictory.inc | 54 + scripts/game/beta5/bq/library.inc | 50 + scripts/game/beta5/bq/library/append.inc | 35 + scripts/game/beta5/bq/library/flush.inc | 32 + scripts/game/beta5/bq/library/get.inc | 52 + .../beta5/bq/library/getReplacementCost.inc | 41 + scripts/game/beta5/bq/library/remove.inc | 30 + scripts/game/beta5/bq/library/reorder.inc | 24 + scripts/game/beta5/bq/library/replace.inc | 43 + scripts/game/beta5/ctf/library.inc | 99 + scripts/game/beta5/ctf/library/assign.inc | 88 + .../game/beta5/ctf/library/checkTargets.inc | 279 + .../game/beta5/ctf/library/joinMessage.inc | 38 + scripts/game/beta5/ctf/library/resetGame.inc | 349 + scripts/game/beta5/ecm/library.inc | 99 + scripts/game/beta5/fleet/library.inc | 50 + scripts/game/beta5/fleet/library/arrival.inc | 151 + .../game/beta5/fleet/library/autoSplit.inc | 83 + scripts/game/beta5/fleet/library/disband.inc | 58 + scripts/game/beta5/fleet/library/get.inc | 69 + .../game/beta5/fleet/library/getLocation.inc | 22 + .../fleet/library/getPlayerLocations.inc | 22 + scripts/game/beta5/fleet/library/getPower.inc | 28 + scripts/game/beta5/fleet/library/getStats.inc | 85 + .../game/beta5/fleet/library/getUpkeep.inc | 23 + scripts/game/beta5/fleet/library/merge.inc | 163 + .../beta5/fleet/library/sendMoveMessages.inc | 195 + .../game/beta5/fleet/library/setOrders.inc | 245 + scripts/game/beta5/fleet/library/split.inc | 82 + .../game/beta5/fleet/library/switchStatus.inc | 31 + scripts/game/beta5/forums/library.inc | 65 + .../game/beta5/forums/library/deletePost.inc | 31 + .../game/beta5/forums/library/deleteTopic.inc | 26 + scripts/game/beta5/forums/library/edit.inc | 23 + .../game/beta5/forums/library/getLatest.inc | 43 + scripts/game/beta5/forums/library/getPost.inc | 46 + .../game/beta5/forums/library/getPosts.inc | 113 + .../beta5/forums/library/getStructure.inc | 98 + .../game/beta5/forums/library/getTopic.inc | 32 + .../game/beta5/forums/library/getTopics.inc | 37 + .../game/beta5/forums/library/moveTopic.inc | 29 + .../game/beta5/forums/library/newTopic.inc | 39 + scripts/game/beta5/forums/library/reply.inc | 34 + .../game/beta5/forums/library/searchPosts.inc | 41 + .../beta5/forums/library/searchTopics.inc | 41 + .../game/beta5/forums/library/updateLast.inc | 21 + scripts/game/beta5/library.inc | 58 + .../game/beta5/library/checkPlanetName.inc | 46 + scripts/game/beta5/library/getPlayerCount.inc | 23 + .../game/beta5/library/getPlayerStatus.inc | 30 + scripts/game/beta5/library/investigate.inc | 119 + scripts/game/beta5/library/isFinished.inc | 31 + scripts/game/beta5/library/leaveGame.inc | 142 + scripts/game/beta5/library/leaveVacation.inc | 23 + scripts/game/beta5/library/listing.inc | 118 + scripts/game/beta5/library/preJoin.inc | 45 + scripts/game/beta5/library/preRegister.inc | 31 + scripts/game/beta5/library/register.inc | 167 + scripts/game/beta5/library/startVacation.inc | 33 + scripts/game/beta5/library/updateRankings.inc | 243 + scripts/game/beta5/map/library.inc | 111 + scripts/game/beta5/moving/library.inc | 20 + .../game/beta5/moving/library/cloneObject.inc | 60 + .../moving/library/computeTrajectory.inc | 183 + .../game/beta5/moving/library/getLocation.inc | 26 + .../beta5/moving/library/getTrajectory.inc | 30 + .../game/beta5/moving/library/newObject.inc | 53 + .../game/beta5/moving/library/redirect.inc | 49 + scripts/game/beta5/moving/library/stop.inc | 42 + scripts/game/beta5/msg/library.inc | 119 + scripts/game/beta5/msg/library/get.inc | 63 + scripts/game/beta5/msg/library/getHeaders.inc | 64 + scripts/game/beta5/msg/library/send.inc | 51 + .../game/beta5/msg/library/sendInAlliance.inc | 63 + .../game/beta5/msg/library/sendToPlanet.inc | 51 + .../game/beta5/msg/library/sendToPlayer.inc | 36 + scripts/game/beta5/msgformat/en/abandon.inc | 69 + scripts/game/beta5/msgformat/en/admin.inc | 47 + scripts/game/beta5/msgformat/en/alint.inc | 146 + scripts/game/beta5/msgformat/en/alliance.inc | 51 + scripts/game/beta5/msgformat/en/battle.inc | 139 + scripts/game/beta5/msgformat/en/bid.inc | 100 + scripts/game/beta5/msgformat/en/cash.inc | 43 + scripts/game/beta5/msgformat/en/ctf.inc | 241 + scripts/game/beta5/msgformat/en/detect.inc | 104 + .../game/beta5/msgformat/en/endprotection.inc | 74 + scripts/game/beta5/msgformat/en/flmove.inc | 114 + scripts/game/beta5/msgformat/en/flswitch.inc | 53 + scripts/game/beta5/msgformat/en/hsloss.inc | 76 + scripts/game/beta5/msgformat/en/kfleet.inc | 67 + scripts/game/beta5/msgformat/en/kimpr.inc | 53 + scripts/game/beta5/msgformat/en/leave.inc | 78 + scripts/game/beta5/msgformat/en/ownerch.inc | 74 + scripts/game/beta5/msgformat/en/pkwarning.inc | 97 + scripts/game/beta5/msgformat/en/planet.inc | 58 + scripts/game/beta5/msgformat/en/plsc.inc | 49 + scripts/game/beta5/msgformat/en/rename.inc | 50 + scripts/game/beta5/msgformat/en/resdipl.inc | 76 + scripts/game/beta5/msgformat/en/revdmg.inc | 95 + scripts/game/beta5/msgformat/en/revolt.inc | 52 + scripts/game/beta5/msgformat/en/sale.inc | 85 + scripts/game/beta5/msgformat/en/std.inc | 63 + scripts/game/beta5/msgformat/en/warnname.inc | 60 + scripts/game/beta5/msgformat/en/whsn.inc | 118 + scripts/game/beta5/planet/library.inc | 176 + .../beta5/planet/library/buildFactories.inc | 27 + .../planet/library/checkBuildFactories.inc | 60 + .../planet/library/checkDestroyFactories.inc | 74 + .../beta5/planet/library/destroyTurrets.inc | 52 + .../beta5/planet/library/detectFleets.inc | 241 + .../game/beta5/planet/library/getIncome.inc | 52 + .../game/beta5/planet/library/getPower.inc | 36 + .../game/beta5/planet/library/getStats.inc | 37 + .../game/beta5/planet/library/ownerChange.inc | 44 + scripts/game/beta5/planet/library/rename.inc | 53 + .../beta5/planet/library/restoreNeutral.inc | 114 + .../beta5/planet/library/updateHappiness.inc | 150 + .../planet/library/updateMaxPopulation.inc | 66 + .../beta5/planet/library/updateMilStatus.inc | 107 + scripts/game/beta5/player/library.inc | 206 + .../beta5/player/library/addEnemyAlliance.inc | 23 + scripts/game/beta5/player/library/assign.inc | 53 + .../beta5/player/library/breakProtection.inc | 54 + .../game/beta5/player/library/checkAllies.inc | 35 + scripts/game/beta5/player/library/get.inc | 73 + .../game/beta5/player/library/getAllies.inc | 25 + .../beta5/player/library/getDiploSummary.inc | 25 + .../game/beta5/player/library/getEnemies.inc | 25 + .../player/library/getEnemyAlliances.inc | 25 + .../game/beta5/player/library/getFleets.inc | 22 + scripts/game/beta5/player/library/getName.inc | 29 + .../beta5/player/library/getPlanetCount.inc | 26 + .../game/beta5/player/library/getPlanets.inc | 25 + .../game/beta5/player/library/getPlayerId.inc | 31 + .../game/beta5/player/library/getPower.inc | 24 + .../player/library/getProtectionLevel.inc | 25 + .../player/library/getRealPlanetCount.inc | 21 + .../beta5/player/library/getTAListBans.inc | 27 + .../game/beta5/player/library/isAllyOf.inc | 25 + .../beta5/player/library/isOnVacation.inc | 32 + .../game/beta5/player/library/isOnline.inc | 22 + .../beta5/player/library/isRestrained.inc | 22 + .../game/beta5/player/library/lastOnline.inc | 24 + .../game/beta5/player/library/makeEnemies.inc | 53 + .../beta5/player/library/moveAllyDown.inc | 22 + .../game/beta5/player/library/moveAllyUp.inc | 22 + .../game/beta5/player/library/reassign.inc | 54 + .../beta5/player/library/reorderAllies.inc | 24 + .../beta5/player/library/transferFunds.inc | 27 + scripts/game/beta5/prot/library.inc | 224 + .../game/beta5/prot/library/checkSystem.inc | 349 + .../beta5/prot/library/isPlayerMarked.inc | 32 + .../game/beta5/prot/library/updateFleets.inc | 299 + scripts/game/beta5/rules/library.inc | 52 + scripts/game/beta5/rules/library/change.inc | 29 + scripts/game/beta5/rules/library/get.inc | 32 + scripts/game/beta5/sale/library.inc | 54 + scripts/game/beta5/sale/library/bid.inc | 119 + scripts/game/beta5/sale/library/buy.inc | 61 + scripts/game/beta5/sale/library/cancel.inc | 45 + .../beta5/sale/library/cancelTransfer.inc | 61 + scripts/game/beta5/sale/library/decline.inc | 39 + .../beta5/sale/library/getDirectSales.inc | 56 + .../game/beta5/sale/library/getFleetSale.inc | 49 + .../game/beta5/sale/library/getPlanetSale.inc | 33 + .../beta5/sale/library/getPublicSales.inc | 77 + .../game/beta5/sale/library/getSentOffers.inc | 85 + scripts/game/beta5/sale/library/sell.inc | 69 + scripts/game/beta5/standby/library.inc | 51 + scripts/game/beta5/tech/library.inc | 98 + .../game/beta5/tech/library/acceptOffer.inc | 63 + .../beta5/tech/library/checkDependencies.inc | 26 + .../game/beta5/tech/library/createTree.inc | 47 + .../game/beta5/tech/library/declineOffer.inc | 34 + .../beta5/tech/library/getAvailableTechs.inc | 82 + scripts/game/beta5/tech/library/getBudget.inc | 23 + scripts/game/beta5/tech/library/getLaws.inc | 26 + scripts/game/beta5/tech/library/getOffers.inc | 51 + scripts/game/beta5/tech/library/getPoints.inc | 25 + .../game/beta5/tech/library/getTopicData.inc | 27 + scripts/game/beta5/tech/library/getTopics.inc | 36 + scripts/game/beta5/tech/library/getTree.inc | 43 + scripts/game/beta5/tech/library/has.inc | 22 + scripts/game/beta5/tech/library/implement.inc | 45 + scripts/game/beta5/tech/library/makeOffer.inc | 29 + scripts/game/beta5/tech/library/switchLaw.inc | 45 + scripts/game/beta5/ticks/battle/library.inc | 521 + scripts/game/beta5/ticks/cash/library.inc | 250 + scripts/game/beta5/ticks/day/library.inc | 479 + scripts/game/beta5/ticks/hour/library.inc | 911 ++ scripts/game/beta5/ticks/move/library.inc | 121 + .../game/beta5/ticks/punishment/library.inc | 48 + scripts/game/beta5/ticks/quit/library.inc | 33 + scripts/game/beta5/ticks/sales/library.inc | 127 + scripts/game/beta5/ticks/universe/library.inc | 483 + scripts/game/main/account/library.inc | 87 + .../main/account/library/createAccount.inc | 58 + .../game/main/account/library/getAccounts.inc | 27 + .../game/main/account/library/getKickList.inc | 24 + .../game/main/account/library/getLanguage.inc | 22 + .../main/account/library/getQuitCountdown.inc | 22 + .../game/main/account/library/getUserName.inc | 25 + scripts/game/main/account/library/isLeech.inc | 32 + scripts/game/main/account/library/log.inc | 63 + .../game/main/account/library/requestKick.inc | 24 + .../main/account/library/setQuitCountdown.inc | 23 + .../game/main/account/library/terminate.inc | 32 + scripts/game/main/actions.inc | 238 + scripts/game/main/actions/joinGame.inc | 150 + scripts/game/main/actions/lostPassword.inc | 139 + scripts/game/main/forums/library.inc | 73 + .../game/main/forums/library/deletePost.inc | 27 + .../game/main/forums/library/deleteTopic.inc | 26 + scripts/game/main/forums/library/edit.inc | 22 + scripts/game/main/forums/library/get.inc | 28 + .../main/forums/library/getAdministrator.inc | 26 + .../main/forums/library/getCategories.inc | 23 + .../game/main/forums/library/getCategory.inc | 20 + .../game/main/forums/library/getForums.inc | 36 + .../game/main/forums/library/getModerator.inc | 21 + scripts/game/main/forums/library/getPost.inc | 49 + scripts/game/main/forums/library/getPosts.inc | 117 + scripts/game/main/forums/library/getTopic.inc | 36 + .../game/main/forums/library/getTopics.inc | 33 + scripts/game/main/forums/library/move.inc | 29 + scripts/game/main/forums/library/newTopic.inc | 41 + scripts/game/main/forums/library/reply.inc | 33 + .../game/main/forums/library/signature.inc | 39 + .../game/main/forums/library/substitute.inc | 60 + .../game/main/forums/library/updateLast.inc | 21 + scripts/game/main/library.inc | 37 + scripts/game/main/library/getTick.inc | 32 + scripts/game/main/library/getTicks.inc | 31 + scripts/game/main/library/isGameRunning.inc | 31 + scripts/game/main/library/preJoin.inc | 49 + .../game/main/library/requestGenPlanets.inc | 29 + scripts/game/main/library/sendMail.inc | 48 + scripts/game/main/links/library.inc | 212 + .../main/mail/mail-change-pass-conf.en.txt | 11 + .../game/main/mail/mail-change-pass.en.txt | 11 + .../game/main/mail/mail-kick-inactive.en.txt | 13 + scripts/game/main/mail/mail-quit.en.txt | 13 + scripts/game/main/mail/mail-reg.en.txt | 17 + scripts/game/main/mail/mail-restart.en.txt | 14 + .../game/main/mail/mail-warn-inactive.en.txt | 11 + scripts/game/main/manual/library.inc | 66 + .../game/main/manual/library/getFirstPage.inc | 30 + .../game/main/manual/library/getNavLinks.inc | 67 + .../game/main/manual/library/getPageId.inc | 31 + .../main/manual/library/getSectionsIn.inc | 35 + .../game/main/manual/library/getStructure.inc | 24 + .../game/main/manual/library/readXMLFile.inc | 158 + scripts/game/main/manual/library/search.inc | 229 + .../main/manual/library/updateSections.inc | 107 + scripts/game/main/paypal/library.inc | 147 + scripts/game/main/rankings/library.inc | 52 + scripts/game/main/rankings/library/append.inc | 30 + scripts/game/main/rankings/library/delete.inc | 20 + scripts/game/main/rankings/library/getAll.inc | 27 + scripts/game/main/rankings/library/update.inc | 39 + scripts/game/main/ticks/day/library.inc | 150 + .../game/main/ticks/deathofrats/library.inc | 1468 ++ scripts/game/main/ticks/mark/library.inc | 12 + scripts/game/main/ticks/session/library.inc | 68 + scripts/game/main/ticks/vacation/library.inc | 62 + scripts/game/main/vacation/library.inc | 45 + scripts/game/main/vacation/library/canSet.inc | 31 + scripts/game/main/vacation/library/leave.inc | 31 + scripts/game/main/vacation/library/start.inc | 31 + scripts/legacyworlds.xml | 137 + scripts/lib/account.inc | 13 + scripts/lib/actions.inc | 93 + scripts/lib/ajax.inc | 84 + scripts/lib/classloader.inc | 25 + scripts/lib/config.inc | 130 + scripts/lib/data_tree.inc | 130 + scripts/lib/db.inc | 69 + scripts/lib/db_accessor.inc | 127 + scripts/lib/db_connection.inc | 446 + scripts/lib/db_copy.inc | 210 + scripts/lib/engine.inc | 57 + scripts/lib/engines/css.inc | 71 + scripts/lib/engines/js.inc | 71 + scripts/lib/engines/page.inc | 452 + scripts/lib/engines/redirect.inc | 34 + scripts/lib/engines/rpc.inc | 167 + scripts/lib/engines/template.inc | 33 + scripts/lib/engines/xml.inc | 247 + scripts/lib/game.inc | 243 + scripts/lib/handler.inc | 77 + scripts/lib/input.inc | 120 + scripts/lib/library.inc | 79 + scripts/lib/log.inc | 295 + scripts/lib/output.inc | 141 + scripts/lib/pcheck.inc | 104 + scripts/lib/pcheck_manager.inc | 629 + scripts/lib/pcheck_thread.inc | 144 + scripts/lib/prefs.inc | 112 + scripts/lib/resource.inc | 149 + scripts/lib/session.inc | 185 + scripts/lib/tick.inc | 94 + scripts/lib/tick_manager.inc | 257 + scripts/lib/tracking.inc | 179 + scripts/lib/version.inc | 48 + scripts/lib/xml_config.inc | 376 + scripts/loader.inc | 29 + scripts/proxycheck.php | 67 + scripts/site/beta5/handlers/admin.inc | 698 + scripts/site/beta5/handlers/alliance.inc | 1266 ++ scripts/site/beta5/handlers/allies.inc | 237 + scripts/site/beta5/handlers/comms.inc | 57 + scripts/site/beta5/handlers/diplomacy.inc | 115 + scripts/site/beta5/handlers/empire.inc | 43 + scripts/site/beta5/handlers/enemylist.inc | 89 + scripts/site/beta5/handlers/fleets.inc | 1003 ++ scripts/site/beta5/handlers/forums.inc | 1072 ++ scripts/site/beta5/handlers/manual.inc | 85 + scripts/site/beta5/handlers/map.inc | 146 + scripts/site/beta5/handlers/market.inc | 463 + scripts/site/beta5/handlers/message.inc | 492 + scripts/site/beta5/handlers/money.inc | 131 + scripts/site/beta5/handlers/norealloc.inc | 13 + scripts/site/beta5/handlers/nplanet.inc | 59 + scripts/site/beta5/handlers/overview.inc | 87 + scripts/site/beta5/handlers/planet.inc | 670 + scripts/site/beta5/handlers/planetnf.inc | 13 + scripts/site/beta5/handlers/planets.inc | 311 + scripts/site/beta5/handlers/play.inc | 245 + scripts/site/beta5/handlers/preferences.inc | 142 + scripts/site/beta5/handlers/probes.inc | 260 + scripts/site/beta5/handlers/rank.inc | 266 + scripts/site/beta5/handlers/rat.inc | 11 + scripts/site/beta5/handlers/research.inc | 272 + scripts/site/beta5/handlers/techtrade.inc | 490 + scripts/site/beta5/handlers/ticks.inc | 32 + scripts/site/beta5/handlers/universe.inc | 31 + scripts/site/beta5/layout/classic/ajax.inc | 19 + .../site/beta5/layout/classic/footer.en.inc | 0 .../site/beta5/layout/classic/header.en.inc | 72 + scripts/site/beta5/layout/cripes/ajax.inc | 19 + .../site/beta5/layout/cripes/footer.en.inc | 1 + .../site/beta5/layout/cripes/header.en.inc | 102 + scripts/site/beta5/layout/default/ajax.inc | 40 + .../site/beta5/layout/default/footer.en.inc | 0 .../site/beta5/layout/default/header.en.inc | 239 + scripts/site/beta5/layout/invert/ajax.inc | 40 + .../site/beta5/layout/invert/footer.en.inc | 0 .../site/beta5/layout/invert/header.en.inc | 239 + scripts/site/beta5/output/admin.en.inc | 8 + scripts/site/beta5/output/admin/en/acmgmt.inc | 61 + .../site/beta5/output/admin/en/kicklst.inc | 87 + .../site/beta5/output/admin/en/kickreq.inc | 49 + scripts/site/beta5/output/admin/en/lkcat.inc | 47 + scripts/site/beta5/output/admin/en/lklist.inc | 42 + scripts/site/beta5/output/admin/en/lklk.inc | 65 + scripts/site/beta5/output/admin/en/lkrep.inc | 19 + scripts/site/beta5/output/admin/en/lksadd.inc | 31 + scripts/site/beta5/output/admin/en/lksub.inc | 25 + scripts/site/beta5/output/admin/en/main.inc | 30 + scripts/site/beta5/output/admin/en/pnlist.inc | 98 + scripts/site/beta5/output/admin/en/spam.inc | 55 + scripts/site/beta5/output/alliance.en.inc | 2 + scripts/site/beta5/output/allies.en.inc | 21 + scripts/site/beta5/output/comms.en.inc | 25 + scripts/site/beta5/output/diplomacy.en.inc | 25 + scripts/site/beta5/output/empire.en.inc | 73 + scripts/site/beta5/output/enemylist.en.inc | 25 + scripts/site/beta5/output/fleets.en.inc | 2 + scripts/site/beta5/output/forums.en.inc | 72 + .../site/beta5/output/forums/en/category.inc | 67 + .../beta5/output/forums/en/catnotfound.inc | 11 + scripts/site/beta5/output/forums/en/forum.inc | 146 + .../beta5/output/forums/en/forumnopost.inc | 11 + .../beta5/output/forums/en/forumnotfound.inc | 11 + .../site/beta5/output/forums/en/latest.inc | 98 + .../site/beta5/output/forums/en/overview.inc | 61 + scripts/site/beta5/output/forums/en/post.inc | 118 + .../beta5/output/forums/en/postnotfound.inc | 11 + .../site/beta5/output/forums/en/search.inc | 76 + .../site/beta5/output/forums/en/sresposts.inc | 86 + .../beta5/output/forums/en/srestopics.inc | 92 + scripts/site/beta5/output/forums/en/topic.inc | 102 + .../beta5/output/forums/en/topicnotfound.inc | 11 + scripts/site/beta5/output/manual.en.inc | 14 + .../site/beta5/output/manual/en/header.inc | 68 + .../site/beta5/output/manual/en/notfound.inc | 5 + scripts/site/beta5/output/manual/en/page.inc | 108 + .../site/beta5/output/manual/en/search.inc | 24 + scripts/site/beta5/output/map.en.inc | 3 + scripts/site/beta5/output/market.en.inc | 19 + scripts/site/beta5/output/message.en.inc | 4 + scripts/site/beta5/output/money.en.inc | 30 + scripts/site/beta5/output/norealloc.en.inc | 6 + scripts/site/beta5/output/nplanet.en.inc | 45 + scripts/site/beta5/output/overview.en.inc | 2 + scripts/site/beta5/output/planet.en.inc | 18 + scripts/site/beta5/output/planetnf.en.inc | 5 + scripts/site/beta5/output/planets.en.inc | 2 + scripts/site/beta5/output/preferences.en.inc | 201 + scripts/site/beta5/output/probes.en.inc | 10 + scripts/site/beta5/output/rank.en.inc | 10 + scripts/site/beta5/output/rat.en.inc | 10 + scripts/site/beta5/output/research.en.inc | 9 + scripts/site/beta5/output/techtrade.inc | 1 + scripts/site/beta5/output/ticks.en.inc | 24 + scripts/site/beta5/output/universe.en.inc | 38 + scripts/site/beta5/page.inc | 172 + scripts/site/main/handlers/about.inc | 11 + scripts/site/main/handlers/b6pp.inc | 158 + scripts/site/main/handlers/confirm.inc | 12 + scripts/site/main/handlers/contrib.inc | 17 + scripts/site/main/handlers/create.inc | 128 + scripts/site/main/handlers/credits.inc | 11 + scripts/site/main/handlers/css.inc | 12 + scripts/site/main/handlers/disclaimer.inc | 13 + scripts/site/main/handlers/donate.inc | 26 + scripts/site/main/handlers/index.inc | 242 + scripts/site/main/handlers/js.inc | 12 + scripts/site/main/handlers/links.inc | 172 + scripts/site/main/handlers/login.inc | 12 + scripts/site/main/handlers/logout.inc | 17 + scripts/site/main/handlers/lostpass.inc | 18 + scripts/site/main/handlers/macwidget.inc | 18 + scripts/site/main/handlers/manual.inc | 95 + scripts/site/main/handlers/notfound.inc | 12 + scripts/site/main/handlers/pcheck.inc | 18 + scripts/site/main/handlers/play.inc | 12 + scripts/site/main/handlers/ppipn.inc | 45 + scripts/site/main/handlers/rankings.inc | 54 + scripts/site/main/handlers/restart.inc | 12 + scripts/site/main/handlers/screenshots.inc | 55 + scripts/site/main/handlers/settings.inc | 115 + scripts/site/main/layout/actual-header.en.inc | 21 + scripts/site/main/layout/footer.inc | 8 + scripts/site/main/layout/header.inc | 4 + scripts/site/main/layout/lbox.en.inc | 20 + scripts/site/main/layout/players.en.inc | 2 + scripts/site/main/maintenance.inc | 79 + scripts/site/main/output/about.en.inc | 42 + scripts/site/main/output/account.en.inc | 277 + scripts/site/main/output/annoy.en.inc | 17 + scripts/site/main/output/b6pp.en.inc | 116 + scripts/site/main/output/back_confirm.en.inc | 20 + scripts/site/main/output/confirm.en.inc | 20 + scripts/site/main/output/contrib.en.inc | 33 + scripts/site/main/output/create.en.inc | 135 + scripts/site/main/output/created.en.inc | 25 + scripts/site/main/output/credits.en.inc | 40 + scripts/site/main/output/disclaimer.en.inc | 31 + scripts/site/main/output/donate.en.inc | 105 + scripts/site/main/output/index.en.inc | 10 + scripts/site/main/output/kicked.en.inc | 24 + scripts/site/main/output/links.en.inc | 189 + scripts/site/main/output/login.en.inc | 20 + scripts/site/main/output/logout.en.inc | 20 + scripts/site/main/output/lostpass.en.inc | 114 + scripts/site/main/output/manual-box.en.inc | 71 + scripts/site/main/output/manual.en.inc | 135 + .../site/main/output/manual_notfound.en.inc | 5 + scripts/site/main/output/manual_search.en.inc | 35 + scripts/site/main/output/menu.en.inc | 12 + scripts/site/main/output/notfound.en.inc | 14 + scripts/site/main/output/notregistered.en.inc | 14 + scripts/site/main/output/pcheck.inc | 1 + scripts/site/main/output/play.en.inc | 120 + scripts/site/main/output/ppipn.en.inc | 1 + scripts/site/main/output/quit_confirm.en.inc | 60 + scripts/site/main/output/rankings.en.inc | 48 + scripts/site/main/output/restart.en.inc | 31 + scripts/site/main/output/screenshots.en.inc | 67 + scripts/site/main/output/settings.en.inc | 136 + scripts/site/main/output/side-box.en.inc | 21 + scripts/site/main/output/vac_cancel.en.inc | 22 + scripts/site/main/output/vac_leave.en.inc | 23 + scripts/site/main/output/vac_start.en.inc | 28 + scripts/site/main/page.inc | 85 + scripts/ticks.php | 113 + site/beta4/Rank.html | 15 + site/beta4/Rankings.php | 35 + site/beta4/Rankswitch.html | 14 + site/beta4/background.jpg | Bin 0 -> 2979 bytes site/beta4/images/Legacy.jpg | Bin 0 -> 3709 bytes site/beta4/images/background.jpg | Bin 0 -> 2979 bytes site/beta4/legacyfront.gif | Bin 0 -> 88435 bytes site/beta4/legacyfront.jpg | Bin 0 -> 122546 bytes site/beta4/red.css | 102 + .../LegacyWorlds-Dashboard-latest.zip | Bin 0 -> 81235 bytes site/index.php | 35 + site/static/beta5/css/blue.css | 55 + site/static/beta5/css/fonts0.css | 7 + site/static/beta5/css/fonts1.css | 7 + site/static/beta5/css/fonts2.css | 7 + site/static/beta5/css/fonts3.css | 7 + site/static/beta5/css/fonts4.css | 7 + site/static/beta5/css/green.css | 53 + site/static/beta5/css/grey.css | 54 + site/static/beta5/css/main.css | 124 + site/static/beta5/css/pg_alliance.css | 97 + site/static/beta5/css/pg_allies.css | 10 + site/static/beta5/css/pg_empire.css | 5 + site/static/beta5/css/pg_enemylist.css | 4 + site/static/beta5/css/pg_fleets.css | 235 + site/static/beta5/css/pg_forums.css | 177 + site/static/beta5/css/pg_manual.css | 40 + site/static/beta5/css/pg_map.css | 130 + site/static/beta5/css/pg_market.css | 120 + site/static/beta5/css/pg_message.css | 226 + site/static/beta5/css/pg_money.css | 56 + site/static/beta5/css/pg_overview.css | 5 + site/static/beta5/css/pg_planet.css | 102 + site/static/beta5/css/pg_planets.css | 67 + site/static/beta5/css/pg_preferences.css | 16 + site/static/beta5/css/pg_probes.css | 21 + site/static/beta5/css/pg_rank.css | 16 + site/static/beta5/css/pg_research.css | 17 + site/static/beta5/css/purple.css | 54 + site/static/beta5/css/red.css | 54 + site/static/beta5/css/thm_classic.css | 52 + site/static/beta5/css/thm_cripes.css | 90 + site/static/beta5/css/thm_default-ie.css | 93 + site/static/beta5/css/thm_default.css | 100 + site/static/beta5/css/thm_invert-ie.css | 93 + site/static/beta5/css/thm_invert.css | 100 + site/static/beta5/css/yellow.css | 54 + site/static/beta5/js/main-en.js | 37 + site/static/beta5/js/main.js | 1399 ++ site/static/beta5/js/pg_alliance-en.js | 1682 +++ site/static/beta5/js/pg_alliance.js | 2062 +++ site/static/beta5/js/pg_allies-en.js | 129 + site/static/beta5/js/pg_allies.js | 408 + site/static/beta5/js/pg_comms-en.js | 40 + site/static/beta5/js/pg_comms.js | 144 + site/static/beta5/js/pg_diplomacy-en.js | 157 + site/static/beta5/js/pg_diplomacy.js | 62 + site/static/beta5/js/pg_empire-en.js | 62 + site/static/beta5/js/pg_empire.js | 118 + site/static/beta5/js/pg_enemylist-en.js | 40 + site/static/beta5/js/pg_enemylist.js | 167 + site/static/beta5/js/pg_fleets-en.js | 992 ++ site/static/beta5/js/pg_fleets.js | 2704 ++++ site/static/beta5/js/pg_forums-en.js | 53 + site/static/beta5/js/pg_forums.js | 10 + site/static/beta5/js/pg_map-en.js | 79 + site/static/beta5/js/pg_map.js | 824 ++ site/static/beta5/js/pg_market-en.js | 411 + site/static/beta5/js/pg_market.js | 1731 +++ site/static/beta5/js/pg_message-en.js | 377 + site/static/beta5/js/pg_message.js | 943 ++ site/static/beta5/js/pg_money-en.js | 161 + site/static/beta5/js/pg_money.js | 133 + site/static/beta5/js/pg_overview-en.js | 247 + site/static/beta5/js/pg_overview.js | 267 + site/static/beta5/js/pg_planet-en.js | 658 + site/static/beta5/js/pg_planet.js | 672 + site/static/beta5/js/pg_planets-en.js | 315 + site/static/beta5/js/pg_planets.js | 530 + site/static/beta5/js/pg_probes-en.js | 53 + site/static/beta5/js/pg_probes.js | 520 + site/static/beta5/js/pg_rank-en.js | 81 + site/static/beta5/js/pg_rank.js | 237 + site/static/beta5/js/pg_research-en.js | 654 + site/static/beta5/js/pg_research.js | 655 + site/static/beta5/js/pg_techtrade-en.js | 78 + site/static/beta5/js/pg_techtrade.js | 1570 +++ site/static/beta5/js/pg_ticks-en.js | 6 + site/static/beta5/js/pg_ticks.js | 177 + site/static/beta5/js/pg_universe-en.js | 20 + site/static/beta5/js/pg_universe.js | 172 + site/static/beta5/js/rpc-en.js | 57 + site/static/beta5/js/rpc.js | 369 + site/static/beta5/js/thm_classic.js | 103 + site/static/beta5/js/thm_cripes.js | 107 + site/static/beta5/js/thm_default-en.js | 1 + site/static/beta5/js/thm_default.js | 178 + site/static/beta5/js/thm_invert-en.js | 1 + site/static/beta5/js/thm_invert.js | 179 + site/static/beta5/js/tooltips.js | 616 + site/static/beta5/js/tt_classic_blue.js | 8 + site/static/beta5/js/tt_classic_green.js | 8 + site/static/beta5/js/tt_classic_grey.js | 8 + site/static/beta5/js/tt_classic_purple.js | 8 + site/static/beta5/js/tt_classic_red.js | 8 + site/static/beta5/js/tt_classic_yellow.js | 8 + site/static/beta5/js/tt_default_blue.js | 8 + site/static/beta5/js/tt_default_green.js | 8 + site/static/beta5/js/tt_default_grey.js | 8 + site/static/beta5/js/tt_default_purple.js | 8 + site/static/beta5/js/tt_default_red.js | 8 + site/static/beta5/js/tt_default_yellow.js | 8 + site/static/beta5/js/tt_invert_blue.js | 8 + site/static/beta5/js/tt_invert_green.js | 8 + site/static/beta5/js/tt_invert_grey.js | 8 + site/static/beta5/js/tt_invert_purple.js | 8 + site/static/beta5/js/tt_invert_red.js | 8 + site/static/beta5/js/tt_invert_yellow.js | 8 + site/static/beta5/pics/add1_blue.gif | Bin 0 -> 94 bytes site/static/beta5/pics/add1_green.gif | Bin 0 -> 94 bytes site/static/beta5/pics/add1_grey.gif | Bin 0 -> 94 bytes site/static/beta5/pics/add1_purple.gif | Bin 0 -> 94 bytes site/static/beta5/pics/add1_red.gif | Bin 0 -> 94 bytes site/static/beta5/pics/add1_yellow.gif | Bin 0 -> 94 bytes site/static/beta5/pics/add2_blue.gif | Bin 0 -> 112 bytes site/static/beta5/pics/add2_green.gif | Bin 0 -> 112 bytes site/static/beta5/pics/add2_grey.gif | Bin 0 -> 112 bytes site/static/beta5/pics/add2_purple.gif | Bin 0 -> 112 bytes site/static/beta5/pics/add2_red.gif | Bin 0 -> 112 bytes site/static/beta5/pics/add2_yellow.gif | Bin 0 -> 112 bytes site/static/beta5/pics/background.jpg | Bin 0 -> 9532 bytes site/static/beta5/pics/dec1_blue.gif | Bin 0 -> 94 bytes site/static/beta5/pics/dec1_green.gif | Bin 0 -> 94 bytes site/static/beta5/pics/dec1_grey.gif | Bin 0 -> 94 bytes site/static/beta5/pics/dec1_purple.gif | Bin 0 -> 94 bytes site/static/beta5/pics/dec1_red.gif | Bin 0 -> 94 bytes site/static/beta5/pics/dec1_yellow.gif | Bin 0 -> 94 bytes site/static/beta5/pics/dec2_blue.gif | Bin 0 -> 112 bytes site/static/beta5/pics/dec2_green.gif | Bin 0 -> 112 bytes site/static/beta5/pics/dec2_grey.gif | Bin 0 -> 112 bytes site/static/beta5/pics/dec2_purple.gif | Bin 0 -> 112 bytes site/static/beta5/pics/dec2_red.gif | Bin 0 -> 112 bytes site/static/beta5/pics/dec2_yellow.gif | Bin 0 -> 112 bytes site/static/beta5/pics/down_blue.gif | Bin 0 -> 64 bytes site/static/beta5/pics/down_green.gif | Bin 0 -> 176 bytes site/static/beta5/pics/down_grey.gif | Bin 0 -> 64 bytes site/static/beta5/pics/down_purple.gif | Bin 0 -> 180 bytes site/static/beta5/pics/down_red.gif | Bin 0 -> 64 bytes site/static/beta5/pics/down_yellow.gif | Bin 0 -> 64 bytes site/static/beta5/pics/icons/alliance.gif | Bin 0 -> 1581 bytes site/static/beta5/pics/icons/alliance.png | Bin 0 -> 1590 bytes site/static/beta5/pics/icons/fleets.gif | Bin 0 -> 488 bytes site/static/beta5/pics/icons/fleets.png | Bin 0 -> 532 bytes site/static/beta5/pics/icons/logout.gif | Bin 0 -> 1370 bytes site/static/beta5/pics/icons/logout.png | Bin 0 -> 1345 bytes site/static/beta5/pics/icons/map.gif | Bin 0 -> 1530 bytes site/static/beta5/pics/icons/map.png | Bin 0 -> 1529 bytes site/static/beta5/pics/icons/message.gif | Bin 0 -> 290 bytes site/static/beta5/pics/icons/planets.gif | Bin 0 -> 342 bytes site/static/beta5/pics/icons/planets.png | Bin 0 -> 411 bytes site/static/beta5/pics/left_blue.gif | Bin 0 -> 64 bytes site/static/beta5/pics/left_green.gif | Bin 0 -> 64 bytes site/static/beta5/pics/left_grey.gif | Bin 0 -> 64 bytes site/static/beta5/pics/left_purple.gif | Bin 0 -> 64 bytes site/static/beta5/pics/left_red.gif | Bin 0 -> 64 bytes site/static/beta5/pics/left_yellow.gif | Bin 0 -> 64 bytes site/static/beta5/pics/lock_blue.gif | Bin 0 -> 1142 bytes site/static/beta5/pics/lock_green.gif | Bin 0 -> 737 bytes site/static/beta5/pics/lock_grey.gif | Bin 0 -> 727 bytes site/static/beta5/pics/lock_purple.gif | Bin 0 -> 737 bytes site/static/beta5/pics/lock_red.gif | Bin 0 -> 1180 bytes site/static/beta5/pics/lock_yellow.gif | Bin 0 -> 737 bytes site/static/beta5/pics/lw-tot-tech-blue.gif | Bin 0 -> 4176 bytes site/static/beta5/pics/lw-tot-tech-blue.png | Bin 0 -> 8048 bytes site/static/beta5/pics/lw-tot-tech-green.gif | Bin 0 -> 4179 bytes site/static/beta5/pics/lw-tot-tech-green.png | Bin 0 -> 7916 bytes site/static/beta5/pics/lw-tot-tech-grey.gif | Bin 0 -> 5310 bytes site/static/beta5/pics/lw-tot-tech-grey.png | Bin 0 -> 6366 bytes site/static/beta5/pics/lw-tot-tech-purple.gif | Bin 0 -> 4194 bytes site/static/beta5/pics/lw-tot-tech-purple.png | Bin 0 -> 8141 bytes site/static/beta5/pics/lw-tot-tech-red.gif | Bin 0 -> 4190 bytes site/static/beta5/pics/lw-tot-tech-red.png | Bin 0 -> 8124 bytes site/static/beta5/pics/lw-tot-tech-yellow.gif | Bin 0 -> 4291 bytes site/static/beta5/pics/lw-tot-tech-yellow.png | Bin 0 -> 8251 bytes site/static/beta5/pics/msgr.gif | Bin 0 -> 551 bytes site/static/beta5/pics/msgrep.gif | Bin 0 -> 536 bytes site/static/beta5/pics/msgu.gif | Bin 0 -> 509 bytes site/static/beta5/pics/nebula1.png | Bin 0 -> 36635 bytes site/static/beta5/pics/nebula2.png | Bin 0 -> 35807 bytes site/static/beta5/pics/nebula3.png | Bin 0 -> 47734 bytes site/static/beta5/pics/nebula4.png | Bin 0 -> 41500 bytes site/static/beta5/pics/prem_l.png | Bin 0 -> 6729 bytes site/static/beta5/pics/prem_s.png | Bin 0 -> 1568 bytes site/static/beta5/pics/read.gif | Bin 0 -> 911 bytes site/static/beta5/pics/read_sticky.gif | Bin 0 -> 967 bytes site/static/beta5/pics/right_blue.gif | Bin 0 -> 65 bytes site/static/beta5/pics/right_green.gif | Bin 0 -> 65 bytes site/static/beta5/pics/right_grey.gif | Bin 0 -> 65 bytes site/static/beta5/pics/right_purple.gif | Bin 0 -> 65 bytes site/static/beta5/pics/right_red.gif | Bin 0 -> 65 bytes site/static/beta5/pics/right_yellow.gif | Bin 0 -> 65 bytes .../beta5/pics/that-other-theme-blue.png | Bin 0 -> 66374 bytes .../beta5/pics/that-other-theme-green.png | Bin 0 -> 60799 bytes .../beta5/pics/that-other-theme-grey.png | Bin 0 -> 36938 bytes .../beta5/pics/that-other-theme-purple.png | Bin 0 -> 76154 bytes .../beta5/pics/that-other-theme-red.png | Bin 0 -> 64565 bytes .../beta5/pics/that-other-theme-yellow.png | Bin 0 -> 57605 bytes .../static/beta5/pics/ttl/def/blue/menubg.png | Bin 0 -> 134 bytes .../beta5/pics/ttl/def/en/blue/alliance.gif | Bin 0 -> 4934 bytes .../beta5/pics/ttl/def/en/blue/allies.gif | Bin 0 -> 7399 bytes .../beta5/pics/ttl/def/en/blue/comms.gif | Bin 0 -> 8477 bytes .../beta5/pics/ttl/def/en/blue/diplomacy.gif | Bin 0 -> 6436 bytes .../beta5/pics/ttl/def/en/blue/empire.gif | Bin 0 -> 4651 bytes .../beta5/pics/ttl/def/en/blue/enemylist.gif | Bin 0 -> 5006 bytes .../beta5/pics/ttl/def/en/blue/fleets.gif | Bin 0 -> 3927 bytes .../beta5/pics/ttl/def/en/blue/forums.gif | Bin 0 -> 4494 bytes .../beta5/pics/ttl/def/en/blue/manual.gif | Bin 0 -> 4797 bytes .../pics/ttl/def/en/blue/manual_notfound.gif | Bin 0 -> 4797 bytes .../pics/ttl/def/en/blue/manual_search.gif | Bin 0 -> 4797 bytes .../static/beta5/pics/ttl/def/en/blue/map.gif | Bin 0 -> 4015 bytes .../beta5/pics/ttl/def/en/blue/market.gif | Bin 0 -> 7223 bytes .../beta5/pics/ttl/def/en/blue/message.gif | Bin 0 -> 5988 bytes .../beta5/pics/ttl/def/en/blue/money.gif | Bin 0 -> 4559 bytes .../beta5/pics/ttl/def/en/blue/nplanet.gif | Bin 0 -> 4565 bytes .../beta5/pics/ttl/def/en/blue/overview.gif | Bin 0 -> 5339 bytes .../beta5/pics/ttl/def/en/blue/planet.gif | Bin 0 -> 4565 bytes .../beta5/pics/ttl/def/en/blue/planetnf.gif | Bin 0 -> 4565 bytes .../beta5/pics/ttl/def/en/blue/planets.gif | Bin 0 -> 4565 bytes .../pics/ttl/def/en/blue/preferences.gif | Bin 0 -> 6348 bytes .../beta5/pics/ttl/def/en/blue/probes.gif | Bin 0 -> 4336 bytes .../beta5/pics/ttl/def/en/blue/rank.gif | Bin 0 -> 6030 bytes .../beta5/pics/ttl/def/en/blue/research.gif | Bin 0 -> 5376 bytes .../beta5/pics/ttl/def/en/blue/ticks.gif | Bin 0 -> 3651 bytes .../beta5/pics/ttl/def/en/blue/universe.gif | Bin 0 -> 5123 bytes .../beta5/pics/ttl/def/en/green/alliance.gif | Bin 0 -> 4680 bytes .../beta5/pics/ttl/def/en/green/allies.gif | Bin 0 -> 7105 bytes .../beta5/pics/ttl/def/en/green/comms.gif | Bin 0 -> 8243 bytes .../beta5/pics/ttl/def/en/green/diplomacy.gif | Bin 0 -> 6210 bytes .../beta5/pics/ttl/def/en/green/empire.gif | Bin 0 -> 4439 bytes .../beta5/pics/ttl/def/en/green/enemylist.gif | Bin 0 -> 4900 bytes .../beta5/pics/ttl/def/en/green/fleets.gif | Bin 0 -> 3795 bytes .../beta5/pics/ttl/def/en/green/forums.gif | Bin 0 -> 4346 bytes .../beta5/pics/ttl/def/en/green/manual.gif | Bin 0 -> 4660 bytes .../pics/ttl/def/en/green/manual_notfound.gif | Bin 0 -> 4660 bytes .../pics/ttl/def/en/green/manual_search.gif | Bin 0 -> 4660 bytes .../beta5/pics/ttl/def/en/green/map.gif | Bin 0 -> 3880 bytes .../beta5/pics/ttl/def/en/green/market.gif | Bin 0 -> 7038 bytes .../beta5/pics/ttl/def/en/green/message.gif | Bin 0 -> 5755 bytes .../beta5/pics/ttl/def/en/green/money.gif | Bin 0 -> 4325 bytes .../beta5/pics/ttl/def/en/green/nplanet.gif | Bin 0 -> 4355 bytes .../beta5/pics/ttl/def/en/green/overview.gif | Bin 0 -> 5214 bytes .../beta5/pics/ttl/def/en/green/planet.gif | Bin 0 -> 4355 bytes .../beta5/pics/ttl/def/en/green/planetnf.gif | Bin 0 -> 4355 bytes .../beta5/pics/ttl/def/en/green/planets.gif | Bin 0 -> 4355 bytes .../pics/ttl/def/en/green/preferences.gif | Bin 0 -> 6156 bytes .../beta5/pics/ttl/def/en/green/probes.gif | Bin 0 -> 4265 bytes .../beta5/pics/ttl/def/en/green/rank.gif | Bin 0 -> 5732 bytes .../beta5/pics/ttl/def/en/green/research.gif | Bin 0 -> 5193 bytes .../beta5/pics/ttl/def/en/green/ticks.gif | Bin 0 -> 3487 bytes .../beta5/pics/ttl/def/en/green/universe.gif | Bin 0 -> 4949 bytes .../beta5/pics/ttl/def/en/grey/alliance.gif | Bin 0 -> 5355 bytes .../beta5/pics/ttl/def/en/grey/allies.gif | Bin 0 -> 8008 bytes .../beta5/pics/ttl/def/en/grey/comms.gif | Bin 0 -> 9350 bytes .../beta5/pics/ttl/def/en/grey/diplomacy.gif | Bin 0 -> 6847 bytes .../beta5/pics/ttl/def/en/grey/empire.gif | Bin 0 -> 4972 bytes .../beta5/pics/ttl/def/en/grey/enemylist.gif | Bin 0 -> 5421 bytes .../beta5/pics/ttl/def/en/grey/fleets.gif | Bin 0 -> 4139 bytes .../beta5/pics/ttl/def/en/grey/forums.gif | Bin 0 -> 4852 bytes .../beta5/pics/ttl/def/en/grey/manual.gif | Bin 0 -> 5232 bytes .../pics/ttl/def/en/grey/manual_notfound.gif | Bin 0 -> 5232 bytes .../pics/ttl/def/en/grey/manual_search.gif | Bin 0 -> 5232 bytes .../static/beta5/pics/ttl/def/en/grey/map.gif | Bin 0 -> 4337 bytes .../beta5/pics/ttl/def/en/grey/market.gif | Bin 0 -> 7834 bytes .../beta5/pics/ttl/def/en/grey/message.gif | Bin 0 -> 6509 bytes .../beta5/pics/ttl/def/en/grey/money.gif | Bin 0 -> 4925 bytes .../beta5/pics/ttl/def/en/grey/nplanet.gif | Bin 0 -> 4942 bytes .../beta5/pics/ttl/def/en/grey/overview.gif | Bin 0 -> 5750 bytes .../beta5/pics/ttl/def/en/grey/planet.gif | Bin 0 -> 4942 bytes .../beta5/pics/ttl/def/en/grey/planetnf.gif | Bin 0 -> 4942 bytes .../beta5/pics/ttl/def/en/grey/planets.gif | Bin 0 -> 4942 bytes .../pics/ttl/def/en/grey/preferences.gif | Bin 0 -> 6974 bytes .../beta5/pics/ttl/def/en/grey/probes.gif | Bin 0 -> 4581 bytes .../beta5/pics/ttl/def/en/grey/rank.gif | Bin 0 -> 6495 bytes .../beta5/pics/ttl/def/en/grey/research.gif | Bin 0 -> 5884 bytes .../beta5/pics/ttl/def/en/grey/ticks.gif | Bin 0 -> 3931 bytes .../beta5/pics/ttl/def/en/grey/universe.gif | Bin 0 -> 5527 bytes .../beta5/pics/ttl/def/en/purple/alliance.gif | Bin 0 -> 4937 bytes .../beta5/pics/ttl/def/en/purple/allies.gif | Bin 0 -> 7344 bytes .../beta5/pics/ttl/def/en/purple/comms.gif | Bin 0 -> 8488 bytes .../pics/ttl/def/en/purple/diplomacy.gif | Bin 0 -> 6482 bytes .../beta5/pics/ttl/def/en/purple/empire.gif | Bin 0 -> 4662 bytes .../pics/ttl/def/en/purple/enemylist.gif | Bin 0 -> 5045 bytes .../beta5/pics/ttl/def/en/purple/fleets.gif | Bin 0 -> 3857 bytes .../beta5/pics/ttl/def/en/purple/forums.gif | Bin 0 -> 4538 bytes .../beta5/pics/ttl/def/en/purple/manual.gif | Bin 0 -> 4797 bytes .../ttl/def/en/purple/manual_notfound.gif | Bin 0 -> 4797 bytes .../pics/ttl/def/en/purple/manual_search.gif | Bin 0 -> 4797 bytes .../beta5/pics/ttl/def/en/purple/map.gif | Bin 0 -> 4083 bytes .../beta5/pics/ttl/def/en/purple/market.gif | Bin 0 -> 7227 bytes .../beta5/pics/ttl/def/en/purple/message.gif | Bin 0 -> 6033 bytes .../beta5/pics/ttl/def/en/purple/money.gif | Bin 0 -> 4610 bytes .../beta5/pics/ttl/def/en/purple/nplanet.gif | Bin 0 -> 4629 bytes .../beta5/pics/ttl/def/en/purple/overview.gif | Bin 0 -> 5361 bytes .../beta5/pics/ttl/def/en/purple/planet.gif | Bin 0 -> 4629 bytes .../beta5/pics/ttl/def/en/purple/planetnf.gif | Bin 0 -> 4629 bytes .../beta5/pics/ttl/def/en/purple/planets.gif | Bin 0 -> 4629 bytes .../pics/ttl/def/en/purple/preferences.gif | Bin 0 -> 6409 bytes .../beta5/pics/ttl/def/en/purple/probes.gif | Bin 0 -> 4340 bytes .../beta5/pics/ttl/def/en/purple/rank.gif | Bin 0 -> 6005 bytes .../beta5/pics/ttl/def/en/purple/research.gif | Bin 0 -> 5499 bytes .../beta5/pics/ttl/def/en/purple/ticks.gif | Bin 0 -> 3689 bytes .../beta5/pics/ttl/def/en/purple/universe.gif | Bin 0 -> 5151 bytes .../beta5/pics/ttl/def/en/red/alliance.gif | Bin 0 -> 4948 bytes .../beta5/pics/ttl/def/en/red/allies.gif | Bin 0 -> 7551 bytes .../beta5/pics/ttl/def/en/red/comms.gif | Bin 0 -> 8724 bytes .../beta5/pics/ttl/def/en/red/diplomacy.gif | Bin 0 -> 6618 bytes .../beta5/pics/ttl/def/en/red/empire.gif | Bin 0 -> 4689 bytes .../beta5/pics/ttl/def/en/red/enemylist.gif | Bin 0 -> 5201 bytes .../beta5/pics/ttl/def/en/red/fleets.gif | Bin 0 -> 3943 bytes .../beta5/pics/ttl/def/en/red/forums.gif | Bin 0 -> 4648 bytes .../beta5/pics/ttl/def/en/red/manual.gif | Bin 0 -> 4958 bytes .../pics/ttl/def/en/red/manual_notfound.gif | Bin 0 -> 4958 bytes .../pics/ttl/def/en/red/manual_search.gif | Bin 0 -> 4958 bytes site/static/beta5/pics/ttl/def/en/red/map.gif | Bin 0 -> 4107 bytes .../beta5/pics/ttl/def/en/red/market.gif | Bin 0 -> 7492 bytes .../beta5/pics/ttl/def/en/red/message.gif | Bin 0 -> 6089 bytes .../beta5/pics/ttl/def/en/red/money.gif | Bin 0 -> 4650 bytes .../beta5/pics/ttl/def/en/red/nplanet.gif | Bin 0 -> 4657 bytes .../beta5/pics/ttl/def/en/red/overview.gif | Bin 0 -> 5428 bytes .../beta5/pics/ttl/def/en/red/planet.gif | Bin 0 -> 4657 bytes .../beta5/pics/ttl/def/en/red/planetnf.gif | Bin 0 -> 4657 bytes .../beta5/pics/ttl/def/en/red/planets.gif | Bin 0 -> 4657 bytes .../beta5/pics/ttl/def/en/red/preferences.gif | Bin 0 -> 6515 bytes .../beta5/pics/ttl/def/en/red/probes.gif | Bin 0 -> 4413 bytes .../static/beta5/pics/ttl/def/en/red/rank.gif | Bin 0 -> 6216 bytes .../beta5/pics/ttl/def/en/red/research.gif | Bin 0 -> 5442 bytes .../beta5/pics/ttl/def/en/red/ticks.gif | Bin 0 -> 3749 bytes .../beta5/pics/ttl/def/en/red/universe.gif | Bin 0 -> 5226 bytes .../beta5/pics/ttl/def/en/yellow/alliance.gif | Bin 0 -> 4912 bytes .../beta5/pics/ttl/def/en/yellow/allies.gif | Bin 0 -> 7206 bytes .../beta5/pics/ttl/def/en/yellow/comms.gif | Bin 0 -> 8261 bytes .../pics/ttl/def/en/yellow/diplomacy.gif | Bin 0 -> 6336 bytes .../beta5/pics/ttl/def/en/yellow/empire.gif | Bin 0 -> 4560 bytes .../pics/ttl/def/en/yellow/enemylist.gif | Bin 0 -> 4991 bytes .../beta5/pics/ttl/def/en/yellow/fleets.gif | Bin 0 -> 3842 bytes .../beta5/pics/ttl/def/en/yellow/forums.gif | Bin 0 -> 4407 bytes .../beta5/pics/ttl/def/en/yellow/manual.gif | Bin 0 -> 4774 bytes .../ttl/def/en/yellow/manual_notfound.gif | Bin 0 -> 4774 bytes .../pics/ttl/def/en/yellow/manual_search.gif | Bin 0 -> 4774 bytes .../beta5/pics/ttl/def/en/yellow/map.gif | Bin 0 -> 3517 bytes .../beta5/pics/ttl/def/en/yellow/market.gif | Bin 0 -> 6970 bytes .../beta5/pics/ttl/def/en/yellow/message.gif | Bin 0 -> 5828 bytes .../beta5/pics/ttl/def/en/yellow/money.gif | Bin 0 -> 4544 bytes .../beta5/pics/ttl/def/en/yellow/nplanet.gif | Bin 0 -> 4547 bytes .../beta5/pics/ttl/def/en/yellow/overview.gif | Bin 0 -> 5268 bytes .../beta5/pics/ttl/def/en/yellow/planet.gif | Bin 0 -> 4547 bytes .../beta5/pics/ttl/def/en/yellow/planetnf.gif | Bin 0 -> 4547 bytes .../beta5/pics/ttl/def/en/yellow/planets.gif | Bin 0 -> 4547 bytes .../pics/ttl/def/en/yellow/preferences.gif | Bin 0 -> 6143 bytes .../beta5/pics/ttl/def/en/yellow/probes.gif | Bin 0 -> 4265 bytes .../beta5/pics/ttl/def/en/yellow/rank.gif | Bin 0 -> 5851 bytes .../beta5/pics/ttl/def/en/yellow/research.gif | Bin 0 -> 5301 bytes .../beta5/pics/ttl/def/en/yellow/ticks.gif | Bin 0 -> 3554 bytes .../beta5/pics/ttl/def/en/yellow/universe.gif | Bin 0 -> 5048 bytes .../beta5/pics/ttl/def/green/menubg.png | Bin 0 -> 218 bytes .../static/beta5/pics/ttl/def/grey/menubg.png | Bin 0 -> 213 bytes .../beta5/pics/ttl/def/purple/menubg.png | Bin 0 -> 135 bytes site/static/beta5/pics/ttl/def/red/menubg.png | Bin 0 -> 137 bytes .../beta5/pics/ttl/def/yellow/menubg.png | Bin 0 -> 137 bytes site/static/beta5/pics/unlock_blue.gif | Bin 0 -> 1124 bytes site/static/beta5/pics/unlock_green.gif | Bin 0 -> 721 bytes site/static/beta5/pics/unlock_grey.gif | Bin 0 -> 712 bytes site/static/beta5/pics/unlock_purple.gif | Bin 0 -> 721 bytes site/static/beta5/pics/unlock_red.gif | Bin 0 -> 1165 bytes site/static/beta5/pics/unlock_yellow.gif | Bin 0 -> 721 bytes site/static/beta5/pics/unread.gif | Bin 0 -> 1388 bytes site/static/beta5/pics/unread_sticky.gif | Bin 0 -> 1441 bytes site/static/beta5/pics/up_blue.gif | Bin 0 -> 66 bytes site/static/beta5/pics/up_green.gif | Bin 0 -> 176 bytes site/static/beta5/pics/up_grey.gif | Bin 0 -> 66 bytes site/static/beta5/pics/up_purple.gif | Bin 0 -> 179 bytes site/static/beta5/pics/up_red.gif | Bin 0 -> 66 bytes site/static/beta5/pics/up_yellow.gif | Bin 0 -> 66 bytes site/static/main/css/account.css | 69 + site/static/main/css/annoy.css | 34 + site/static/main/css/b6pp.css | 47 + site/static/main/css/content.css | 72 + site/static/main/css/create.css | 89 + site/static/main/css/credits.css | 37 + site/static/main/css/home.css | 89 + site/static/main/css/main.css | 149 + site/static/main/css/manual.css | 153 + site/static/main/css/rankings.css | 3 + site/static/main/css/screenshots.css | 49 + site/static/main/css/sidebox.css | 134 + site/static/main/css/text.css | 49 + site/static/main/js/account.js | 29 + site/static/main/js/adapt.js | 31 + site/static/main/js/annoy.js | 14 + site/static/main/js/jquery.js | 32 + site/static/main/js/manual.js | 13 + site/static/main/js/sidebox.js | 30 + site/static/main/pics/mp-content-b0.png | Bin 0 -> 32007 bytes site/static/main/pics/mp-content-b1.png | Bin 0 -> 31317 bytes site/static/main/pics/mp-content-b2.png | Bin 0 -> 30310 bytes site/static/main/pics/mp-content-b3.png | Bin 0 -> 25647 bytes site/static/main/pics/mp-content-b4.png | Bin 0 -> 22337 bytes site/static/main/pics/mp-content-b5.png | Bin 0 -> 22034 bytes site/static/main/pics/mp-content-b6.png | Bin 0 -> 19169 bytes site/static/main/pics/mp-content.jpg | Bin 0 -> 91318 bytes site/static/main/pics/mp-tab-1.png | Bin 0 -> 3456 bytes site/static/main/pics/mp-tab-2.png | Bin 0 -> 3495 bytes site/static/main/pics/mp-title-b0.png | Bin 0 -> 40322 bytes site/static/main/pics/mp-title-b1.png | Bin 0 -> 38279 bytes site/static/main/pics/mp-title-b2.png | Bin 0 -> 15559 bytes site/static/main/pics/mp-title-b3.png | Bin 0 -> 38014 bytes site/static/main/pics/mp-title-b4.png | Bin 0 -> 17605 bytes site/static/main/pics/mp-title-b5.png | Bin 0 -> 38810 bytes site/static/main/pics/mp-title-b6.png | Bin 0 -> 26923 bytes site/static/main/pics/mp-title.jpg | Bin 0 -> 103487 bytes site/static/main/pics/smiles/icon_arrow.gif | Bin 0 -> 170 bytes site/static/main/pics/smiles/icon_biggrin.gif | Bin 0 -> 172 bytes .../static/main/pics/smiles/icon_confused.gif | Bin 0 -> 171 bytes site/static/main/pics/smiles/icon_cool.gif | Bin 0 -> 172 bytes site/static/main/pics/smiles/icon_cry.gif | Bin 0 -> 498 bytes site/static/main/pics/smiles/icon_eek.gif | Bin 0 -> 170 bytes site/static/main/pics/smiles/icon_evil.gif | Bin 0 -> 236 bytes site/static/main/pics/smiles/icon_exclaim.gif | Bin 0 -> 236 bytes site/static/main/pics/smiles/icon_frown.gif | Bin 0 -> 171 bytes site/static/main/pics/smiles/icon_idea.gif | Bin 0 -> 176 bytes site/static/main/pics/smiles/icon_lol.gif | Bin 0 -> 336 bytes site/static/main/pics/smiles/icon_mad.gif | Bin 0 -> 174 bytes site/static/main/pics/smiles/icon_mrgreen.gif | Bin 0 -> 349 bytes site/static/main/pics/smiles/icon_neutral.gif | Bin 0 -> 171 bytes .../static/main/pics/smiles/icon_question.gif | Bin 0 -> 248 bytes site/static/main/pics/smiles/icon_razz.gif | Bin 0 -> 176 bytes site/static/main/pics/smiles/icon_redface.gif | Bin 0 -> 650 bytes .../static/main/pics/smiles/icon_rolleyes.gif | Bin 0 -> 485 bytes site/static/main/pics/smiles/icon_sad.gif | Bin 0 -> 171 bytes site/static/main/pics/smiles/icon_smile.gif | Bin 0 -> 174 bytes .../main/pics/smiles/icon_surprised.gif | Bin 0 -> 174 bytes site/static/main/pics/smiles/icon_twisted.gif | Bin 0 -> 238 bytes site/static/main/pics/smiles/icon_wink.gif | Bin 0 -> 170 bytes site/static/main/screens/b4-fleets-m.jpg | Bin 0 -> 26758 bytes site/static/main/screens/b4-fleets-s.jpg | Bin 0 -> 3828 bytes site/static/main/screens/b4-fleets.jpg | Bin 0 -> 153584 bytes site/static/main/screens/b4-map-m.jpg | Bin 0 -> 29635 bytes site/static/main/screens/b4-map-s.jpg | Bin 0 -> 3818 bytes site/static/main/screens/b4-map.jpg | Bin 0 -> 174207 bytes site/static/main/screens/b4-money-m.jpg | Bin 0 -> 23374 bytes site/static/main/screens/b4-money-s.jpg | Bin 0 -> 3087 bytes site/static/main/screens/b4-money.jpg | Bin 0 -> 142550 bytes site/static/main/screens/b4-ov-m.jpg | Bin 0 -> 17759 bytes site/static/main/screens/b4-ov-s.jpg | Bin 0 -> 2425 bytes site/static/main/screens/b4-ov.jpg | Bin 0 -> 103026 bytes site/static/main/screens/b4-planets-m.jpg | Bin 0 -> 21265 bytes site/static/main/screens/b4-planets-s.jpg | Bin 0 -> 3009 bytes site/static/main/screens/b4-planets.jpg | Bin 0 -> 126237 bytes site/static/main/screens/b4-ranking-m.jpg | Bin 0 -> 18815 bytes site/static/main/screens/b4-ranking-s.jpg | Bin 0 -> 2815 bytes site/static/main/screens/b4-ranking.jpg | Bin 0 -> 112690 bytes site/static/main/screens/b4-ticks-m.jpg | Bin 0 -> 16867 bytes site/static/main/screens/b4-ticks-s.jpg | Bin 0 -> 2389 bytes site/static/main/screens/b4-ticks.jpg | Bin 0 -> 107840 bytes site/static/main/screens/b5-allies-m.jpg | Bin 0 -> 11224 bytes site/static/main/screens/b5-allies-s.jpg | Bin 0 -> 2281 bytes site/static/main/screens/b5-allies.jpg | Bin 0 -> 55083 bytes site/static/main/screens/b5-fleets-m.jpg | Bin 0 -> 19792 bytes site/static/main/screens/b5-fleets-s.jpg | Bin 0 -> 3762 bytes site/static/main/screens/b5-fleets.jpg | Bin 0 -> 100179 bytes site/static/main/screens/b5-manual-m.jpg | Bin 0 -> 27331 bytes site/static/main/screens/b5-manual-s.jpg | Bin 0 -> 4822 bytes site/static/main/screens/b5-manual.jpg | Bin 0 -> 147048 bytes site/static/main/screens/b5-map-m.jpg | Bin 0 -> 25690 bytes site/static/main/screens/b5-map-s.jpg | Bin 0 -> 4309 bytes site/static/main/screens/b5-map.jpg | Bin 0 -> 111761 bytes site/static/main/screens/b5-market-m.jpg | Bin 0 -> 12001 bytes site/static/main/screens/b5-market-s.jpg | Bin 0 -> 2643 bytes site/static/main/screens/b5-market.jpg | Bin 0 -> 54610 bytes site/static/main/screens/b5-messages-m.jpg | Bin 0 -> 25789 bytes site/static/main/screens/b5-messages-s.jpg | Bin 0 -> 4248 bytes site/static/main/screens/b5-messages.jpg | Bin 0 -> 123154 bytes site/static/main/screens/b5-money-m.jpg | Bin 0 -> 23999 bytes site/static/main/screens/b5-money-s.jpg | Bin 0 -> 4473 bytes site/static/main/screens/b5-money.jpg | Bin 0 -> 111610 bytes site/static/main/screens/b5-ov-m.jpg | Bin 0 -> 16928 bytes site/static/main/screens/b5-ov-s.jpg | Bin 0 -> 3201 bytes site/static/main/screens/b5-ov.jpg | Bin 0 -> 84739 bytes site/static/main/screens/b5-planet-m.jpg | Bin 0 -> 18601 bytes site/static/main/screens/b5-planet-s.jpg | Bin 0 -> 3452 bytes site/static/main/screens/b5-planet.jpg | Bin 0 -> 91451 bytes site/static/main/screens/b5-planets-m.jpg | Bin 0 -> 17622 bytes site/static/main/screens/b5-planets-s.jpg | Bin 0 -> 3170 bytes site/static/main/screens/b5-planets.jpg | Bin 0 -> 87533 bytes site/static/main/screens/b5-ranking-m.jpg | Bin 0 -> 22033 bytes site/static/main/screens/b5-ranking-s.jpg | Bin 0 -> 3806 bytes site/static/main/screens/b5-ranking.jpg | Bin 0 -> 120073 bytes site/static/main/screens/b5-research-m.jpg | Bin 0 -> 19648 bytes site/static/main/screens/b5-research-s.jpg | Bin 0 -> 3379 bytes site/static/main/screens/b5-research.jpg | Bin 0 -> 101233 bytes sql/00-init.sql | 70 + sql/01-inheritance.sql | 1373 ++ sql/10-main.sql | 19 + sql/11-main-enums.sql | 71 + sql/12-main-tables.sql | 223 + sql/13-main-donations.sql | 70 + sql/13-main-forums.sql | 149 + sql/13-main-links.sql | 69 + sql/13-main-manual.sql | 66 + sql/13-main-proxy.sql | 25 + sql/18-main-functions.sql | 34 + sql/19-main-values.sql | 140 + sql/20-credits.sql | 18 + sql/25-ctf-maps.sql | 57 + sql/25-death-of-rats.sql | 161 + sql/25-predefined-alliances.sql | 42 + sql/30-beta5.sql | 13 + sql/50-beta6-planet-pictures.sql | 33 + sql/INSTALL.sql | 25 + sql/beta5/00-beta5.sql | 46 + sql/beta5/10-beta5-b5.sql | 56 + sql/beta5/11-beta5-b5m0.sql | 56 + sql/beta5/beta5-ctf.sql | 61 + sql/beta5/beta5-match.sql | 60 + sql/beta5/beta5-round.sql | 61 + sql/beta5/data/game.sql | 15 + sql/beta5/data/match.sql | 602 + sql/beta5/data/standard.sql | 801 ++ sql/beta5/structure/00-ecm-eccm.sql | 28 + sql/beta5/structure/00-gdata.sql | 18 + sql/beta5/structure/00-player-table.sql | 42 + sql/beta5/structure/00-rule-base.sql | 25 + sql/beta5/structure/00-system.sql | 26 + sql/beta5/structure/01-alliance.sql | 121 + sql/beta5/structure/01-message-base.sql | 45 + sql/beta5/structure/01-planet.sql | 139 + sql/beta5/structure/01-player-dipl.sql | 89 + sql/beta5/structure/01-player-rules.sql | 40 + sql/beta5/structure/01-research-base.sql | 59 + sql/beta5/structure/02-alliance-forums.sql | 100 + sql/beta5/structure/02-alliance-tech.sql | 81 + sql/beta5/structure/02-orders.sql | 57 + sql/beta5/structure/02-warehouse.sql | 70 + sql/beta5/structure/03-fleets.sql | 38 + sql/beta5/structure/04-sales.sql | 101 + sql/beta5/structure/05-message-player.sql | 71 + sql/beta5/structure/06-message-battle.sql | 56 + sql/beta5/structure/06-message-internal.sql | 341 + sql/beta5/structure/07-beacons.sql | 58 + sql/beta5/structure/07-message-admin.sql | 41 + sql/beta5/structure/10-ctf-tables.sql | 99 + sql/beta5/structure/10-prot-tables.sql | 89 + sql/beta5/structure/99-player-fk.sql | 15 + sql/beta5/structure/finalise.sql | 15 + 1377 files changed, 123808 insertions(+) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 COPYING.GPL create mode 100644 COPYING.MIT create mode 100644 README create mode 100644 TODO create mode 100644 admin/as_log.inc create mode 100644 admin/as_manager.inc create mode 100644 admin/bot.php create mode 100644 admin/cg_done.php create mode 100644 admin/cg_operation.inc create mode 100644 admin/cg_step0.php create mode 100644 admin/cg_step1.php create mode 100644 admin/cg_step10.php create mode 100644 admin/cg_step11.php create mode 100644 admin/cg_step12.php create mode 100644 admin/cg_step13.php create mode 100644 admin/cg_step2.php create mode 100644 admin/cg_step3.php create mode 100644 admin/cg_step4.php create mode 100644 admin/cg_step5.php create mode 100644 admin/cg_step6.php create mode 100644 admin/cg_step7.php create mode 100644 admin/cg_step8.php create mode 100644 admin/cg_step9.php create mode 100644 admin/cg_ticks_schedule.inc create mode 100644 admin/cg_user_dsp_1.inc create mode 100644 admin/cg_user_dsp_2.inc create mode 100644 admin/cg_user_dsp_3.inc create mode 100644 admin/cg_user_dsp_3ctf.inc create mode 100644 admin/cg_user_dsp_4.inc create mode 100644 admin/cg_user_dsp_5.inc create mode 100644 admin/cg_user_dsp_6.inc create mode 100644 admin/cg_user_dsp_7.inc create mode 100644 admin/cg_user_hdl_1.inc create mode 100644 admin/cg_user_hdl_2.inc create mode 100644 admin/cg_user_hdl_3.inc create mode 100644 admin/cg_user_hdl_3ctf.inc create mode 100644 admin/cg_user_hdl_4.inc create mode 100644 admin/cg_user_hdl_5.inc create mode 100644 admin/cg_user_hdl_6.inc create mode 100644 admin/cg_user_hdl_7.inc create mode 100644 admin/cg_user_hdr.inc create mode 100644 admin/config.inc create mode 100644 admin/create.php create mode 100644 admin/ctf_map.inc create mode 100644 admin/deathofrats.php create mode 100644 admin/game_status.php create mode 100644 admin/index.html create mode 100644 admin/maintenance.php create mode 100644 admin/map_edit.js create mode 100644 admin/maps.php create mode 100644 admin/proxy.php create mode 100644 admin/set_default.php create mode 100644 admin/ticks.php create mode 100644 ircbot/bot.conf create mode 100644 ircbot/bot.php create mode 100644 ircbot/chat.php create mode 100644 ircbot/command_reference.txt create mode 100644 ircbot/connection.php create mode 100644 ircbot/databases/ini.php create mode 100644 ircbot/databases/mysql.php create mode 100644 ircbot/databases/postgre.php create mode 100644 ircbot/databases/postgres.php create mode 100644 ircbot/dcc.php create mode 100644 ircbot/defines.php create mode 100644 ircbot/error.php create mode 100644 ircbot/file.php create mode 100644 ircbot/function.conf create mode 100644 ircbot/irc.php create mode 100644 ircbot/module.php create mode 100644 ircbot/modules/default/ads.ini create mode 100644 ircbot/modules/default/dcc_mod.conf create mode 100644 ircbot/modules/default/dcc_mod.php create mode 100644 ircbot/modules/default/priv_mod.conf create mode 100644 ircbot/modules/default/priv_mod.php create mode 100644 ircbot/modules/lw/lw_mod.conf create mode 100644 ircbot/modules/lw/lw_mod.php create mode 100644 ircbot/modules/more_mods.txt create mode 100644 ircbot/modules/seen/seen_mod.conf create mode 100644 ircbot/modules/seen/seen_mod.php create mode 100644 ircbot/modules/template.txt create mode 100644 ircbot/parser.php create mode 100644 ircbot/queue.php create mode 100644 ircbot/readme.txt create mode 100644 ircbot/remote.php create mode 100644 ircbot/socket.php create mode 100644 ircbot/timers.php create mode 100644 ircbot/typedefs.conf create mode 100644 manual/beta5/en/account_games.lwdoc create mode 100644 manual/beta5/en/accounts.lwdoc create mode 100644 manual/beta5/en/alliance.lwdoc create mode 100644 manual/beta5/en/alliance_create.lwdoc create mode 100644 manual/beta5/en/alliance_manage.lwdoc create mode 100644 manual/beta5/en/allies.lwdoc create mode 100644 manual/beta5/en/background.lwdoc create mode 100644 manual/beta5/en/battles.lwdoc create mode 100644 manual/beta5/en/communications.lwdoc create mode 100644 manual/beta5/en/communications_page.lwdoc create mode 100644 manual/beta5/en/diplomacy.lwdoc create mode 100644 manual/beta5/en/diplomacy_page.lwdoc create mode 100644 manual/beta5/en/empire.lwdoc create mode 100644 manual/beta5/en/empire_overview.lwdoc create mode 100644 manual/beta5/en/enemies.lwdoc create mode 100644 manual/beta5/en/fleets.lwdoc create mode 100644 manual/beta5/en/fleets_actions.lwdoc create mode 100644 manual/beta5/en/fleets_moving.lwdoc create mode 100644 manual/beta5/en/fleets_page.lwdoc create mode 100644 manual/beta5/en/fleets_splitting.lwdoc create mode 100644 manual/beta5/en/forums.lwdoc create mode 100644 manual/beta5/en/game_interface.lwdoc create mode 100644 manual/beta5/en/game_overview.lwdoc create mode 100644 manual/beta5/en/game_rules.lwdoc create mode 100644 manual/beta5/en/general_game.lwdoc create mode 100644 manual/beta5/en/general_rules.lwdoc create mode 100644 manual/beta5/en/glossary.lwdoc create mode 100644 manual/beta5/en/good_practices.lwdoc create mode 100644 manual/beta5/en/home_page.lwdoc create mode 100644 manual/beta5/en/law_discrimination.lwdoc create mode 100644 manual/beta5/en/law_spam.lwdoc create mode 100644 manual/beta5/en/law_unauthorised_access.lwdoc create mode 100644 manual/beta5/en/legal.lwdoc create mode 100644 manual/beta5/en/main.lwdoc create mode 100644 manual/beta5/en/maps.lwdoc create mode 100644 manual/beta5/en/market_buy.lwdoc create mode 100644 manual/beta5/en/market_sell.lwdoc create mode 100644 manual/beta5/en/marketplace.lwdoc create mode 100644 manual/beta5/en/messages.lwdoc create mode 100644 manual/beta5/en/mod_rules.lwdoc create mode 100644 manual/beta5/en/money.lwdoc create mode 100644 manual/beta5/en/overview.lwdoc create mode 100644 manual/beta5/en/overview_page.lwdoc create mode 100644 manual/beta5/en/planet.lwdoc create mode 100644 manual/beta5/en/planets.lwdoc create mode 100644 manual/beta5/en/preferences.lwdoc create mode 100644 manual/beta5/en/probes.lwdoc create mode 100644 manual/beta5/en/rankings.lwdoc create mode 100644 manual/beta5/en/ships.lwdoc create mode 100644 manual/beta5/en/tech_1.lwdoc create mode 100644 manual/beta5/en/tech_10.lwdoc create mode 100644 manual/beta5/en/tech_11.lwdoc create mode 100644 manual/beta5/en/tech_12.lwdoc create mode 100644 manual/beta5/en/tech_13.lwdoc create mode 100644 manual/beta5/en/tech_14.lwdoc create mode 100644 manual/beta5/en/tech_15.lwdoc create mode 100644 manual/beta5/en/tech_16.lwdoc create mode 100644 manual/beta5/en/tech_17.lwdoc create mode 100644 manual/beta5/en/tech_18.lwdoc create mode 100644 manual/beta5/en/tech_2.lwdoc create mode 100644 manual/beta5/en/tech_20.lwdoc create mode 100644 manual/beta5/en/tech_21.lwdoc create mode 100644 manual/beta5/en/tech_22.lwdoc create mode 100644 manual/beta5/en/tech_23.lwdoc create mode 100644 manual/beta5/en/tech_24.lwdoc create mode 100644 manual/beta5/en/tech_25.lwdoc create mode 100644 manual/beta5/en/tech_26.lwdoc create mode 100644 manual/beta5/en/tech_27.lwdoc create mode 100644 manual/beta5/en/tech_28.lwdoc create mode 100644 manual/beta5/en/tech_29.lwdoc create mode 100644 manual/beta5/en/tech_3.lwdoc create mode 100644 manual/beta5/en/tech_30.lwdoc create mode 100644 manual/beta5/en/tech_31.lwdoc create mode 100644 manual/beta5/en/tech_32.lwdoc create mode 100644 manual/beta5/en/tech_33.lwdoc create mode 100644 manual/beta5/en/tech_34.lwdoc create mode 100644 manual/beta5/en/tech_35.lwdoc create mode 100644 manual/beta5/en/tech_36.lwdoc create mode 100644 manual/beta5/en/tech_37.lwdoc create mode 100644 manual/beta5/en/tech_38.lwdoc create mode 100644 manual/beta5/en/tech_39.lwdoc create mode 100644 manual/beta5/en/tech_4.lwdoc create mode 100644 manual/beta5/en/tech_40.lwdoc create mode 100644 manual/beta5/en/tech_41.lwdoc create mode 100644 manual/beta5/en/tech_42.lwdoc create mode 100644 manual/beta5/en/tech_43.lwdoc create mode 100644 manual/beta5/en/tech_44.lwdoc create mode 100644 manual/beta5/en/tech_45.lwdoc create mode 100644 manual/beta5/en/tech_46.lwdoc create mode 100644 manual/beta5/en/tech_48.lwdoc create mode 100644 manual/beta5/en/tech_49.lwdoc create mode 100644 manual/beta5/en/tech_5.lwdoc create mode 100644 manual/beta5/en/tech_50.lwdoc create mode 100644 manual/beta5/en/tech_51.lwdoc create mode 100644 manual/beta5/en/tech_52.lwdoc create mode 100644 manual/beta5/en/tech_53.lwdoc create mode 100644 manual/beta5/en/tech_54.lwdoc create mode 100644 manual/beta5/en/tech_55.lwdoc create mode 100644 manual/beta5/en/tech_56.lwdoc create mode 100644 manual/beta5/en/tech_57.lwdoc create mode 100644 manual/beta5/en/tech_58.lwdoc create mode 100644 manual/beta5/en/tech_59.lwdoc create mode 100644 manual/beta5/en/tech_6.lwdoc create mode 100644 manual/beta5/en/tech_60.lwdoc create mode 100644 manual/beta5/en/tech_61.lwdoc create mode 100644 manual/beta5/en/tech_62.lwdoc create mode 100644 manual/beta5/en/tech_63.lwdoc create mode 100644 manual/beta5/en/tech_64.lwdoc create mode 100644 manual/beta5/en/tech_65.lwdoc create mode 100644 manual/beta5/en/tech_68.lwdoc create mode 100644 manual/beta5/en/tech_69.lwdoc create mode 100644 manual/beta5/en/tech_7.lwdoc create mode 100644 manual/beta5/en/tech_70.lwdoc create mode 100644 manual/beta5/en/tech_71.lwdoc create mode 100644 manual/beta5/en/tech_72.lwdoc create mode 100644 manual/beta5/en/tech_73.lwdoc create mode 100644 manual/beta5/en/tech_74.lwdoc create mode 100644 manual/beta5/en/tech_75.lwdoc create mode 100644 manual/beta5/en/tech_76.lwdoc create mode 100644 manual/beta5/en/tech_77.lwdoc create mode 100644 manual/beta5/en/tech_78.lwdoc create mode 100644 manual/beta5/en/tech_79.lwdoc create mode 100644 manual/beta5/en/tech_8.lwdoc create mode 100644 manual/beta5/en/tech_80.lwdoc create mode 100644 manual/beta5/en/tech_81.lwdoc create mode 100644 manual/beta5/en/tech_82.lwdoc create mode 100644 manual/beta5/en/tech_83.lwdoc create mode 100644 manual/beta5/en/tech_84.lwdoc create mode 100644 manual/beta5/en/tech_85.lwdoc create mode 100644 manual/beta5/en/tech_86.lwdoc create mode 100644 manual/beta5/en/tech_87.lwdoc create mode 100644 manual/beta5/en/tech_88.lwdoc create mode 100644 manual/beta5/en/tech_89.lwdoc create mode 100644 manual/beta5/en/tech_9.lwdoc create mode 100644 manual/beta5/en/tech_90.lwdoc create mode 100644 manual/beta5/en/tech_exchange.lwdoc create mode 100644 manual/beta5/en/tech_list.lwdoc create mode 100644 manual/beta5/en/technology.lwdoc create mode 100644 manual/beta5/en/ticks.lwdoc create mode 100644 manual/beta5/en/topics.lwdoc create mode 100644 manual/beta5/en/tutorial.lwdoc create mode 100644 manual/beta5/en/universe.lwdoc create mode 100644 manual/beta5/en/universe_page.lwdoc create mode 100644 manual/beta5/en/vacation_mode.lwdoc create mode 100755 misc/Legacy Worlds.wdgt/Default.png create mode 100755 misc/Legacy Worlds.wdgt/Icon.png create mode 100755 misc/Legacy Worlds.wdgt/Info.plist create mode 100755 misc/Legacy Worlds.wdgt/LW.css create mode 100755 misc/Legacy Worlds.wdgt/LW.html create mode 100755 misc/Legacy Worlds.wdgt/images/blue.png create mode 100644 misc/Legacy Worlds.wdgt/lib/Base.js create mode 100644 misc/Legacy Worlds.wdgt/lib/Base/Browser.js create mode 100644 misc/Legacy Worlds.wdgt/lib/Base/Comp.js create mode 100644 misc/Legacy Worlds.wdgt/lib/Base/Comp/Evt.js create mode 100644 misc/Legacy Worlds.wdgt/lib/Base/Comp/Slot.js create mode 100644 misc/Legacy Worlds.wdgt/lib/Base/Log.js create mode 100644 misc/Legacy Worlds.wdgt/lib/Base/Timer.js create mode 100644 misc/Legacy Worlds.wdgt/lib/Base/Util.js create mode 100644 misc/Legacy Worlds.wdgt/lib/Base/Util/Hashtable.js create mode 100644 misc/Legacy Worlds.wdgt/lib/Base/XMLLoader.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Cash.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Fleets.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Forums.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Msg.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Planets.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Player.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Techs.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Ticks.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Data.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Data/Leaf.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Data/Loader.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Data/Node.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Debug.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Game.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Game/Page.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/GameDisplay.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/GameSelector.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Login.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Main.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Menu.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Menu/Command.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Menu/Entry.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Menu/Item.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Menu/Mode.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Menu/Text.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/NewVersion.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Page.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/TextPage.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/VersionCheck.js create mode 100644 misc/Legacy Worlds.wdgt/lib/LWWidget/Versions.js create mode 100644 misc/forums-branch.patch create mode 100755 planetgen/generate.pl create mode 100755 planetgen/planetmaker.pl create mode 100644 planetgen/template.pov create mode 100644 planetgen/template2.pov create mode 100644 scripts/config.inc create mode 100755 scripts/control.pl create mode 100644 scripts/game/admin/beta5/library.inc create mode 100644 scripts/game/beta5/actions.inc create mode 100644 scripts/game/beta5/actions/addTrustedAlly.inc create mode 100644 scripts/game/beta5/actions/banTrustingAlly.inc create mode 100644 scripts/game/beta5/actions/getCommsOverview.inc create mode 100644 scripts/game/beta5/actions/getEmpireOverview.inc create mode 100644 scripts/game/beta5/actions/getOverview.inc create mode 100644 scripts/game/beta5/actions/getTrustedAllies.inc create mode 100644 scripts/game/beta5/actions/getUniverseOverview.inc create mode 100644 scripts/game/beta5/actions/removeTrustingAllies.inc create mode 100644 scripts/game/beta5/alliance/library.inc create mode 100644 scripts/game/beta5/alliance/library/acceptRequest.inc create mode 100644 scripts/game/beta5/alliance/library/addCandidate.inc create mode 100644 scripts/game/beta5/alliance/library/cancelRequest.inc create mode 100644 scripts/game/beta5/alliance/library/changeRank.inc create mode 100644 scripts/game/beta5/alliance/library/create.inc create mode 100644 scripts/game/beta5/alliance/library/createForum.inc create mode 100644 scripts/game/beta5/alliance/library/createRank.inc create mode 100644 scripts/game/beta5/alliance/library/deleteForum.inc create mode 100644 scripts/game/beta5/alliance/library/deleteRank.inc create mode 100644 scripts/game/beta5/alliance/library/get.inc create mode 100644 scripts/game/beta5/alliance/library/getCandidates.inc create mode 100644 scripts/game/beta5/alliance/library/getForums.inc create mode 100644 scripts/game/beta5/alliance/library/getForumsComplete.inc create mode 100644 scripts/game/beta5/alliance/library/getId.inc create mode 100644 scripts/game/beta5/alliance/library/getKeepers.inc create mode 100644 scripts/game/beta5/alliance/library/getMembers.inc create mode 100644 scripts/game/beta5/alliance/library/getMilitary.inc create mode 100644 scripts/game/beta5/alliance/library/getPlanets.inc create mode 100644 scripts/game/beta5/alliance/library/getPrivileges.inc create mode 100644 scripts/game/beta5/alliance/library/getRankPrivileges.inc create mode 100644 scripts/game/beta5/alliance/library/getRankSize.inc create mode 100644 scripts/game/beta5/alliance/library/getRanks.inc create mode 100644 scripts/game/beta5/alliance/library/getRequests.inc create mode 100644 scripts/game/beta5/alliance/library/getTechList.inc create mode 100644 scripts/game/beta5/alliance/library/getTechOrder.inc create mode 100644 scripts/game/beta5/alliance/library/getTechOrders.inc create mode 100644 scripts/game/beta5/alliance/library/getTechRequests.inc create mode 100644 scripts/game/beta5/alliance/library/getTechSubmission.inc create mode 100644 scripts/game/beta5/alliance/library/getVoters.inc create mode 100644 scripts/game/beta5/alliance/library/kick.inc create mode 100644 scripts/game/beta5/alliance/library/leave.inc create mode 100644 scripts/game/beta5/alliance/library/modifyForum.inc create mode 100644 scripts/game/beta5/alliance/library/modifyRank.inc create mode 100644 scripts/game/beta5/alliance/library/moveForum.inc create mode 100644 scripts/game/beta5/alliance/library/rejectRequest.inc create mode 100644 scripts/game/beta5/alliance/library/removeCandidate.inc create mode 100644 scripts/game/beta5/alliance/library/sendRequest.inc create mode 100644 scripts/game/beta5/alliance/library/setDemocratic.inc create mode 100644 scripts/game/beta5/alliance/library/setForumAccess.inc create mode 100644 scripts/game/beta5/alliance/library/setSuccessor.inc create mode 100644 scripts/game/beta5/alliance/library/setTechRequests.inc create mode 100644 scripts/game/beta5/alliance/library/setTechTradeMode.inc create mode 100644 scripts/game/beta5/alliance/library/stepDown.inc create mode 100644 scripts/game/beta5/alliance/library/submitTechList.inc create mode 100644 scripts/game/beta5/alliance/library/submitTechOrders.inc create mode 100644 scripts/game/beta5/alliance/library/takePresidency.inc create mode 100644 scripts/game/beta5/alliance/library/updateRequests.inc create mode 100644 scripts/game/beta5/alliance/library/updateVictory.inc create mode 100644 scripts/game/beta5/bq/library.inc create mode 100644 scripts/game/beta5/bq/library/append.inc create mode 100644 scripts/game/beta5/bq/library/flush.inc create mode 100644 scripts/game/beta5/bq/library/get.inc create mode 100644 scripts/game/beta5/bq/library/getReplacementCost.inc create mode 100644 scripts/game/beta5/bq/library/remove.inc create mode 100644 scripts/game/beta5/bq/library/reorder.inc create mode 100644 scripts/game/beta5/bq/library/replace.inc create mode 100644 scripts/game/beta5/ctf/library.inc create mode 100644 scripts/game/beta5/ctf/library/assign.inc create mode 100644 scripts/game/beta5/ctf/library/checkTargets.inc create mode 100644 scripts/game/beta5/ctf/library/joinMessage.inc create mode 100644 scripts/game/beta5/ctf/library/resetGame.inc create mode 100644 scripts/game/beta5/ecm/library.inc create mode 100644 scripts/game/beta5/fleet/library.inc create mode 100644 scripts/game/beta5/fleet/library/arrival.inc create mode 100644 scripts/game/beta5/fleet/library/autoSplit.inc create mode 100644 scripts/game/beta5/fleet/library/disband.inc create mode 100644 scripts/game/beta5/fleet/library/get.inc create mode 100644 scripts/game/beta5/fleet/library/getLocation.inc create mode 100644 scripts/game/beta5/fleet/library/getPlayerLocations.inc create mode 100644 scripts/game/beta5/fleet/library/getPower.inc create mode 100644 scripts/game/beta5/fleet/library/getStats.inc create mode 100644 scripts/game/beta5/fleet/library/getUpkeep.inc create mode 100644 scripts/game/beta5/fleet/library/merge.inc create mode 100644 scripts/game/beta5/fleet/library/sendMoveMessages.inc create mode 100644 scripts/game/beta5/fleet/library/setOrders.inc create mode 100644 scripts/game/beta5/fleet/library/split.inc create mode 100644 scripts/game/beta5/fleet/library/switchStatus.inc create mode 100644 scripts/game/beta5/forums/library.inc create mode 100644 scripts/game/beta5/forums/library/deletePost.inc create mode 100644 scripts/game/beta5/forums/library/deleteTopic.inc create mode 100644 scripts/game/beta5/forums/library/edit.inc create mode 100644 scripts/game/beta5/forums/library/getLatest.inc create mode 100644 scripts/game/beta5/forums/library/getPost.inc create mode 100644 scripts/game/beta5/forums/library/getPosts.inc create mode 100644 scripts/game/beta5/forums/library/getStructure.inc create mode 100644 scripts/game/beta5/forums/library/getTopic.inc create mode 100644 scripts/game/beta5/forums/library/getTopics.inc create mode 100644 scripts/game/beta5/forums/library/moveTopic.inc create mode 100644 scripts/game/beta5/forums/library/newTopic.inc create mode 100644 scripts/game/beta5/forums/library/reply.inc create mode 100644 scripts/game/beta5/forums/library/searchPosts.inc create mode 100644 scripts/game/beta5/forums/library/searchTopics.inc create mode 100644 scripts/game/beta5/forums/library/updateLast.inc create mode 100644 scripts/game/beta5/library.inc create mode 100644 scripts/game/beta5/library/checkPlanetName.inc create mode 100644 scripts/game/beta5/library/getPlayerCount.inc create mode 100644 scripts/game/beta5/library/getPlayerStatus.inc create mode 100644 scripts/game/beta5/library/investigate.inc create mode 100644 scripts/game/beta5/library/isFinished.inc create mode 100644 scripts/game/beta5/library/leaveGame.inc create mode 100644 scripts/game/beta5/library/leaveVacation.inc create mode 100644 scripts/game/beta5/library/listing.inc create mode 100644 scripts/game/beta5/library/preJoin.inc create mode 100644 scripts/game/beta5/library/preRegister.inc create mode 100644 scripts/game/beta5/library/register.inc create mode 100644 scripts/game/beta5/library/startVacation.inc create mode 100644 scripts/game/beta5/library/updateRankings.inc create mode 100644 scripts/game/beta5/map/library.inc create mode 100644 scripts/game/beta5/moving/library.inc create mode 100644 scripts/game/beta5/moving/library/cloneObject.inc create mode 100644 scripts/game/beta5/moving/library/computeTrajectory.inc create mode 100644 scripts/game/beta5/moving/library/getLocation.inc create mode 100644 scripts/game/beta5/moving/library/getTrajectory.inc create mode 100644 scripts/game/beta5/moving/library/newObject.inc create mode 100644 scripts/game/beta5/moving/library/redirect.inc create mode 100644 scripts/game/beta5/moving/library/stop.inc create mode 100644 scripts/game/beta5/msg/library.inc create mode 100644 scripts/game/beta5/msg/library/get.inc create mode 100644 scripts/game/beta5/msg/library/getHeaders.inc create mode 100644 scripts/game/beta5/msg/library/send.inc create mode 100644 scripts/game/beta5/msg/library/sendInAlliance.inc create mode 100644 scripts/game/beta5/msg/library/sendToPlanet.inc create mode 100644 scripts/game/beta5/msg/library/sendToPlayer.inc create mode 100644 scripts/game/beta5/msgformat/en/abandon.inc create mode 100644 scripts/game/beta5/msgformat/en/admin.inc create mode 100644 scripts/game/beta5/msgformat/en/alint.inc create mode 100644 scripts/game/beta5/msgformat/en/alliance.inc create mode 100644 scripts/game/beta5/msgformat/en/battle.inc create mode 100644 scripts/game/beta5/msgformat/en/bid.inc create mode 100644 scripts/game/beta5/msgformat/en/cash.inc create mode 100644 scripts/game/beta5/msgformat/en/ctf.inc create mode 100644 scripts/game/beta5/msgformat/en/detect.inc create mode 100644 scripts/game/beta5/msgformat/en/endprotection.inc create mode 100644 scripts/game/beta5/msgformat/en/flmove.inc create mode 100644 scripts/game/beta5/msgformat/en/flswitch.inc create mode 100644 scripts/game/beta5/msgformat/en/hsloss.inc create mode 100644 scripts/game/beta5/msgformat/en/kfleet.inc create mode 100644 scripts/game/beta5/msgformat/en/kimpr.inc create mode 100644 scripts/game/beta5/msgformat/en/leave.inc create mode 100644 scripts/game/beta5/msgformat/en/ownerch.inc create mode 100644 scripts/game/beta5/msgformat/en/pkwarning.inc create mode 100644 scripts/game/beta5/msgformat/en/planet.inc create mode 100644 scripts/game/beta5/msgformat/en/plsc.inc create mode 100644 scripts/game/beta5/msgformat/en/rename.inc create mode 100644 scripts/game/beta5/msgformat/en/resdipl.inc create mode 100644 scripts/game/beta5/msgformat/en/revdmg.inc create mode 100644 scripts/game/beta5/msgformat/en/revolt.inc create mode 100644 scripts/game/beta5/msgformat/en/sale.inc create mode 100644 scripts/game/beta5/msgformat/en/std.inc create mode 100644 scripts/game/beta5/msgformat/en/warnname.inc create mode 100644 scripts/game/beta5/msgformat/en/whsn.inc create mode 100644 scripts/game/beta5/planet/library.inc create mode 100644 scripts/game/beta5/planet/library/buildFactories.inc create mode 100644 scripts/game/beta5/planet/library/checkBuildFactories.inc create mode 100644 scripts/game/beta5/planet/library/checkDestroyFactories.inc create mode 100644 scripts/game/beta5/planet/library/destroyTurrets.inc create mode 100644 scripts/game/beta5/planet/library/detectFleets.inc create mode 100644 scripts/game/beta5/planet/library/getIncome.inc create mode 100644 scripts/game/beta5/planet/library/getPower.inc create mode 100644 scripts/game/beta5/planet/library/getStats.inc create mode 100644 scripts/game/beta5/planet/library/ownerChange.inc create mode 100644 scripts/game/beta5/planet/library/rename.inc create mode 100644 scripts/game/beta5/planet/library/restoreNeutral.inc create mode 100644 scripts/game/beta5/planet/library/updateHappiness.inc create mode 100644 scripts/game/beta5/planet/library/updateMaxPopulation.inc create mode 100644 scripts/game/beta5/planet/library/updateMilStatus.inc create mode 100644 scripts/game/beta5/player/library.inc create mode 100644 scripts/game/beta5/player/library/addEnemyAlliance.inc create mode 100644 scripts/game/beta5/player/library/assign.inc create mode 100644 scripts/game/beta5/player/library/breakProtection.inc create mode 100644 scripts/game/beta5/player/library/checkAllies.inc create mode 100644 scripts/game/beta5/player/library/get.inc create mode 100644 scripts/game/beta5/player/library/getAllies.inc create mode 100644 scripts/game/beta5/player/library/getDiploSummary.inc create mode 100644 scripts/game/beta5/player/library/getEnemies.inc create mode 100644 scripts/game/beta5/player/library/getEnemyAlliances.inc create mode 100644 scripts/game/beta5/player/library/getFleets.inc create mode 100644 scripts/game/beta5/player/library/getName.inc create mode 100644 scripts/game/beta5/player/library/getPlanetCount.inc create mode 100644 scripts/game/beta5/player/library/getPlanets.inc create mode 100644 scripts/game/beta5/player/library/getPlayerId.inc create mode 100644 scripts/game/beta5/player/library/getPower.inc create mode 100644 scripts/game/beta5/player/library/getProtectionLevel.inc create mode 100644 scripts/game/beta5/player/library/getRealPlanetCount.inc create mode 100644 scripts/game/beta5/player/library/getTAListBans.inc create mode 100644 scripts/game/beta5/player/library/isAllyOf.inc create mode 100644 scripts/game/beta5/player/library/isOnVacation.inc create mode 100644 scripts/game/beta5/player/library/isOnline.inc create mode 100644 scripts/game/beta5/player/library/isRestrained.inc create mode 100644 scripts/game/beta5/player/library/lastOnline.inc create mode 100644 scripts/game/beta5/player/library/makeEnemies.inc create mode 100644 scripts/game/beta5/player/library/moveAllyDown.inc create mode 100644 scripts/game/beta5/player/library/moveAllyUp.inc create mode 100644 scripts/game/beta5/player/library/reassign.inc create mode 100644 scripts/game/beta5/player/library/reorderAllies.inc create mode 100644 scripts/game/beta5/player/library/transferFunds.inc create mode 100644 scripts/game/beta5/prot/library.inc create mode 100644 scripts/game/beta5/prot/library/checkSystem.inc create mode 100644 scripts/game/beta5/prot/library/isPlayerMarked.inc create mode 100644 scripts/game/beta5/prot/library/updateFleets.inc create mode 100644 scripts/game/beta5/rules/library.inc create mode 100644 scripts/game/beta5/rules/library/change.inc create mode 100644 scripts/game/beta5/rules/library/get.inc create mode 100644 scripts/game/beta5/sale/library.inc create mode 100644 scripts/game/beta5/sale/library/bid.inc create mode 100644 scripts/game/beta5/sale/library/buy.inc create mode 100644 scripts/game/beta5/sale/library/cancel.inc create mode 100644 scripts/game/beta5/sale/library/cancelTransfer.inc create mode 100644 scripts/game/beta5/sale/library/decline.inc create mode 100644 scripts/game/beta5/sale/library/getDirectSales.inc create mode 100644 scripts/game/beta5/sale/library/getFleetSale.inc create mode 100644 scripts/game/beta5/sale/library/getPlanetSale.inc create mode 100644 scripts/game/beta5/sale/library/getPublicSales.inc create mode 100644 scripts/game/beta5/sale/library/getSentOffers.inc create mode 100644 scripts/game/beta5/sale/library/sell.inc create mode 100644 scripts/game/beta5/standby/library.inc create mode 100644 scripts/game/beta5/tech/library.inc create mode 100644 scripts/game/beta5/tech/library/acceptOffer.inc create mode 100644 scripts/game/beta5/tech/library/checkDependencies.inc create mode 100644 scripts/game/beta5/tech/library/createTree.inc create mode 100644 scripts/game/beta5/tech/library/declineOffer.inc create mode 100644 scripts/game/beta5/tech/library/getAvailableTechs.inc create mode 100644 scripts/game/beta5/tech/library/getBudget.inc create mode 100644 scripts/game/beta5/tech/library/getLaws.inc create mode 100644 scripts/game/beta5/tech/library/getOffers.inc create mode 100644 scripts/game/beta5/tech/library/getPoints.inc create mode 100644 scripts/game/beta5/tech/library/getTopicData.inc create mode 100644 scripts/game/beta5/tech/library/getTopics.inc create mode 100644 scripts/game/beta5/tech/library/getTree.inc create mode 100644 scripts/game/beta5/tech/library/has.inc create mode 100644 scripts/game/beta5/tech/library/implement.inc create mode 100644 scripts/game/beta5/tech/library/makeOffer.inc create mode 100644 scripts/game/beta5/tech/library/switchLaw.inc create mode 100644 scripts/game/beta5/ticks/battle/library.inc create mode 100644 scripts/game/beta5/ticks/cash/library.inc create mode 100644 scripts/game/beta5/ticks/day/library.inc create mode 100644 scripts/game/beta5/ticks/hour/library.inc create mode 100644 scripts/game/beta5/ticks/move/library.inc create mode 100644 scripts/game/beta5/ticks/punishment/library.inc create mode 100644 scripts/game/beta5/ticks/quit/library.inc create mode 100644 scripts/game/beta5/ticks/sales/library.inc create mode 100644 scripts/game/beta5/ticks/universe/library.inc create mode 100644 scripts/game/main/account/library.inc create mode 100644 scripts/game/main/account/library/createAccount.inc create mode 100644 scripts/game/main/account/library/getAccounts.inc create mode 100644 scripts/game/main/account/library/getKickList.inc create mode 100644 scripts/game/main/account/library/getLanguage.inc create mode 100644 scripts/game/main/account/library/getQuitCountdown.inc create mode 100644 scripts/game/main/account/library/getUserName.inc create mode 100644 scripts/game/main/account/library/isLeech.inc create mode 100644 scripts/game/main/account/library/log.inc create mode 100644 scripts/game/main/account/library/requestKick.inc create mode 100644 scripts/game/main/account/library/setQuitCountdown.inc create mode 100644 scripts/game/main/account/library/terminate.inc create mode 100644 scripts/game/main/actions.inc create mode 100644 scripts/game/main/actions/joinGame.inc create mode 100644 scripts/game/main/actions/lostPassword.inc create mode 100644 scripts/game/main/forums/library.inc create mode 100644 scripts/game/main/forums/library/deletePost.inc create mode 100644 scripts/game/main/forums/library/deleteTopic.inc create mode 100644 scripts/game/main/forums/library/edit.inc create mode 100644 scripts/game/main/forums/library/get.inc create mode 100644 scripts/game/main/forums/library/getAdministrator.inc create mode 100644 scripts/game/main/forums/library/getCategories.inc create mode 100644 scripts/game/main/forums/library/getCategory.inc create mode 100644 scripts/game/main/forums/library/getForums.inc create mode 100644 scripts/game/main/forums/library/getModerator.inc create mode 100644 scripts/game/main/forums/library/getPost.inc create mode 100644 scripts/game/main/forums/library/getPosts.inc create mode 100644 scripts/game/main/forums/library/getTopic.inc create mode 100644 scripts/game/main/forums/library/getTopics.inc create mode 100644 scripts/game/main/forums/library/move.inc create mode 100644 scripts/game/main/forums/library/newTopic.inc create mode 100644 scripts/game/main/forums/library/reply.inc create mode 100644 scripts/game/main/forums/library/signature.inc create mode 100644 scripts/game/main/forums/library/substitute.inc create mode 100644 scripts/game/main/forums/library/updateLast.inc create mode 100644 scripts/game/main/library.inc create mode 100644 scripts/game/main/library/getTick.inc create mode 100644 scripts/game/main/library/getTicks.inc create mode 100644 scripts/game/main/library/isGameRunning.inc create mode 100644 scripts/game/main/library/preJoin.inc create mode 100644 scripts/game/main/library/requestGenPlanets.inc create mode 100644 scripts/game/main/library/sendMail.inc create mode 100644 scripts/game/main/links/library.inc create mode 100644 scripts/game/main/mail/mail-change-pass-conf.en.txt create mode 100644 scripts/game/main/mail/mail-change-pass.en.txt create mode 100644 scripts/game/main/mail/mail-kick-inactive.en.txt create mode 100644 scripts/game/main/mail/mail-quit.en.txt create mode 100644 scripts/game/main/mail/mail-reg.en.txt create mode 100644 scripts/game/main/mail/mail-restart.en.txt create mode 100644 scripts/game/main/mail/mail-warn-inactive.en.txt create mode 100644 scripts/game/main/manual/library.inc create mode 100644 scripts/game/main/manual/library/getFirstPage.inc create mode 100644 scripts/game/main/manual/library/getNavLinks.inc create mode 100644 scripts/game/main/manual/library/getPageId.inc create mode 100644 scripts/game/main/manual/library/getSectionsIn.inc create mode 100644 scripts/game/main/manual/library/getStructure.inc create mode 100644 scripts/game/main/manual/library/readXMLFile.inc create mode 100644 scripts/game/main/manual/library/search.inc create mode 100644 scripts/game/main/manual/library/updateSections.inc create mode 100644 scripts/game/main/paypal/library.inc create mode 100644 scripts/game/main/rankings/library.inc create mode 100644 scripts/game/main/rankings/library/append.inc create mode 100644 scripts/game/main/rankings/library/delete.inc create mode 100644 scripts/game/main/rankings/library/getAll.inc create mode 100644 scripts/game/main/rankings/library/update.inc create mode 100644 scripts/game/main/ticks/day/library.inc create mode 100644 scripts/game/main/ticks/deathofrats/library.inc create mode 100644 scripts/game/main/ticks/mark/library.inc create mode 100644 scripts/game/main/ticks/session/library.inc create mode 100644 scripts/game/main/ticks/vacation/library.inc create mode 100644 scripts/game/main/vacation/library.inc create mode 100644 scripts/game/main/vacation/library/canSet.inc create mode 100644 scripts/game/main/vacation/library/leave.inc create mode 100644 scripts/game/main/vacation/library/start.inc create mode 100644 scripts/legacyworlds.xml create mode 100644 scripts/lib/account.inc create mode 100644 scripts/lib/actions.inc create mode 100644 scripts/lib/ajax.inc create mode 100644 scripts/lib/classloader.inc create mode 100644 scripts/lib/config.inc create mode 100644 scripts/lib/data_tree.inc create mode 100644 scripts/lib/db.inc create mode 100644 scripts/lib/db_accessor.inc create mode 100644 scripts/lib/db_connection.inc create mode 100644 scripts/lib/db_copy.inc create mode 100644 scripts/lib/engine.inc create mode 100644 scripts/lib/engines/css.inc create mode 100644 scripts/lib/engines/js.inc create mode 100644 scripts/lib/engines/page.inc create mode 100644 scripts/lib/engines/redirect.inc create mode 100644 scripts/lib/engines/rpc.inc create mode 100644 scripts/lib/engines/template.inc create mode 100644 scripts/lib/engines/xml.inc create mode 100644 scripts/lib/game.inc create mode 100644 scripts/lib/handler.inc create mode 100644 scripts/lib/input.inc create mode 100644 scripts/lib/library.inc create mode 100644 scripts/lib/log.inc create mode 100644 scripts/lib/output.inc create mode 100644 scripts/lib/pcheck.inc create mode 100644 scripts/lib/pcheck_manager.inc create mode 100644 scripts/lib/pcheck_thread.inc create mode 100644 scripts/lib/prefs.inc create mode 100644 scripts/lib/resource.inc create mode 100644 scripts/lib/session.inc create mode 100644 scripts/lib/tick.inc create mode 100644 scripts/lib/tick_manager.inc create mode 100644 scripts/lib/tracking.inc create mode 100644 scripts/lib/version.inc create mode 100644 scripts/lib/xml_config.inc create mode 100644 scripts/loader.inc create mode 100644 scripts/proxycheck.php create mode 100644 scripts/site/beta5/handlers/admin.inc create mode 100644 scripts/site/beta5/handlers/alliance.inc create mode 100644 scripts/site/beta5/handlers/allies.inc create mode 100644 scripts/site/beta5/handlers/comms.inc create mode 100644 scripts/site/beta5/handlers/diplomacy.inc create mode 100644 scripts/site/beta5/handlers/empire.inc create mode 100644 scripts/site/beta5/handlers/enemylist.inc create mode 100644 scripts/site/beta5/handlers/fleets.inc create mode 100644 scripts/site/beta5/handlers/forums.inc create mode 100644 scripts/site/beta5/handlers/manual.inc create mode 100644 scripts/site/beta5/handlers/map.inc create mode 100644 scripts/site/beta5/handlers/market.inc create mode 100644 scripts/site/beta5/handlers/message.inc create mode 100644 scripts/site/beta5/handlers/money.inc create mode 100644 scripts/site/beta5/handlers/norealloc.inc create mode 100644 scripts/site/beta5/handlers/nplanet.inc create mode 100644 scripts/site/beta5/handlers/overview.inc create mode 100644 scripts/site/beta5/handlers/planet.inc create mode 100644 scripts/site/beta5/handlers/planetnf.inc create mode 100644 scripts/site/beta5/handlers/planets.inc create mode 100644 scripts/site/beta5/handlers/play.inc create mode 100644 scripts/site/beta5/handlers/preferences.inc create mode 100644 scripts/site/beta5/handlers/probes.inc create mode 100644 scripts/site/beta5/handlers/rank.inc create mode 100644 scripts/site/beta5/handlers/rat.inc create mode 100644 scripts/site/beta5/handlers/research.inc create mode 100644 scripts/site/beta5/handlers/techtrade.inc create mode 100644 scripts/site/beta5/handlers/ticks.inc create mode 100644 scripts/site/beta5/handlers/universe.inc create mode 100644 scripts/site/beta5/layout/classic/ajax.inc create mode 100644 scripts/site/beta5/layout/classic/footer.en.inc create mode 100644 scripts/site/beta5/layout/classic/header.en.inc create mode 100644 scripts/site/beta5/layout/cripes/ajax.inc create mode 100644 scripts/site/beta5/layout/cripes/footer.en.inc create mode 100644 scripts/site/beta5/layout/cripes/header.en.inc create mode 100644 scripts/site/beta5/layout/default/ajax.inc create mode 100644 scripts/site/beta5/layout/default/footer.en.inc create mode 100644 scripts/site/beta5/layout/default/header.en.inc create mode 100644 scripts/site/beta5/layout/invert/ajax.inc create mode 100644 scripts/site/beta5/layout/invert/footer.en.inc create mode 100644 scripts/site/beta5/layout/invert/header.en.inc create mode 100644 scripts/site/beta5/output/admin.en.inc create mode 100644 scripts/site/beta5/output/admin/en/acmgmt.inc create mode 100644 scripts/site/beta5/output/admin/en/kicklst.inc create mode 100644 scripts/site/beta5/output/admin/en/kickreq.inc create mode 100644 scripts/site/beta5/output/admin/en/lkcat.inc create mode 100644 scripts/site/beta5/output/admin/en/lklist.inc create mode 100644 scripts/site/beta5/output/admin/en/lklk.inc create mode 100644 scripts/site/beta5/output/admin/en/lkrep.inc create mode 100644 scripts/site/beta5/output/admin/en/lksadd.inc create mode 100644 scripts/site/beta5/output/admin/en/lksub.inc create mode 100644 scripts/site/beta5/output/admin/en/main.inc create mode 100644 scripts/site/beta5/output/admin/en/pnlist.inc create mode 100644 scripts/site/beta5/output/admin/en/spam.inc create mode 100644 scripts/site/beta5/output/alliance.en.inc create mode 100644 scripts/site/beta5/output/allies.en.inc create mode 100644 scripts/site/beta5/output/comms.en.inc create mode 100644 scripts/site/beta5/output/diplomacy.en.inc create mode 100644 scripts/site/beta5/output/empire.en.inc create mode 100644 scripts/site/beta5/output/enemylist.en.inc create mode 100644 scripts/site/beta5/output/fleets.en.inc create mode 100644 scripts/site/beta5/output/forums.en.inc create mode 100644 scripts/site/beta5/output/forums/en/category.inc create mode 100644 scripts/site/beta5/output/forums/en/catnotfound.inc create mode 100644 scripts/site/beta5/output/forums/en/forum.inc create mode 100644 scripts/site/beta5/output/forums/en/forumnopost.inc create mode 100644 scripts/site/beta5/output/forums/en/forumnotfound.inc create mode 100644 scripts/site/beta5/output/forums/en/latest.inc create mode 100644 scripts/site/beta5/output/forums/en/overview.inc create mode 100644 scripts/site/beta5/output/forums/en/post.inc create mode 100644 scripts/site/beta5/output/forums/en/postnotfound.inc create mode 100644 scripts/site/beta5/output/forums/en/search.inc create mode 100644 scripts/site/beta5/output/forums/en/sresposts.inc create mode 100644 scripts/site/beta5/output/forums/en/srestopics.inc create mode 100644 scripts/site/beta5/output/forums/en/topic.inc create mode 100644 scripts/site/beta5/output/forums/en/topicnotfound.inc create mode 100644 scripts/site/beta5/output/manual.en.inc create mode 100644 scripts/site/beta5/output/manual/en/header.inc create mode 100644 scripts/site/beta5/output/manual/en/notfound.inc create mode 100644 scripts/site/beta5/output/manual/en/page.inc create mode 100644 scripts/site/beta5/output/manual/en/search.inc create mode 100644 scripts/site/beta5/output/map.en.inc create mode 100644 scripts/site/beta5/output/market.en.inc create mode 100644 scripts/site/beta5/output/message.en.inc create mode 100644 scripts/site/beta5/output/money.en.inc create mode 100644 scripts/site/beta5/output/norealloc.en.inc create mode 100644 scripts/site/beta5/output/nplanet.en.inc create mode 100644 scripts/site/beta5/output/overview.en.inc create mode 100644 scripts/site/beta5/output/planet.en.inc create mode 100644 scripts/site/beta5/output/planetnf.en.inc create mode 100644 scripts/site/beta5/output/planets.en.inc create mode 100644 scripts/site/beta5/output/preferences.en.inc create mode 100644 scripts/site/beta5/output/probes.en.inc create mode 100644 scripts/site/beta5/output/rank.en.inc create mode 100644 scripts/site/beta5/output/rat.en.inc create mode 100644 scripts/site/beta5/output/research.en.inc create mode 100644 scripts/site/beta5/output/techtrade.inc create mode 100644 scripts/site/beta5/output/ticks.en.inc create mode 100644 scripts/site/beta5/output/universe.en.inc create mode 100644 scripts/site/beta5/page.inc create mode 100644 scripts/site/main/handlers/about.inc create mode 100644 scripts/site/main/handlers/b6pp.inc create mode 100644 scripts/site/main/handlers/confirm.inc create mode 100644 scripts/site/main/handlers/contrib.inc create mode 100644 scripts/site/main/handlers/create.inc create mode 100644 scripts/site/main/handlers/credits.inc create mode 100644 scripts/site/main/handlers/css.inc create mode 100644 scripts/site/main/handlers/disclaimer.inc create mode 100644 scripts/site/main/handlers/donate.inc create mode 100644 scripts/site/main/handlers/index.inc create mode 100644 scripts/site/main/handlers/js.inc create mode 100644 scripts/site/main/handlers/links.inc create mode 100644 scripts/site/main/handlers/login.inc create mode 100644 scripts/site/main/handlers/logout.inc create mode 100644 scripts/site/main/handlers/lostpass.inc create mode 100644 scripts/site/main/handlers/macwidget.inc create mode 100644 scripts/site/main/handlers/manual.inc create mode 100644 scripts/site/main/handlers/notfound.inc create mode 100644 scripts/site/main/handlers/pcheck.inc create mode 100644 scripts/site/main/handlers/play.inc create mode 100644 scripts/site/main/handlers/ppipn.inc create mode 100644 scripts/site/main/handlers/rankings.inc create mode 100644 scripts/site/main/handlers/restart.inc create mode 100644 scripts/site/main/handlers/screenshots.inc create mode 100644 scripts/site/main/handlers/settings.inc create mode 100644 scripts/site/main/layout/actual-header.en.inc create mode 100644 scripts/site/main/layout/footer.inc create mode 100644 scripts/site/main/layout/header.inc create mode 100644 scripts/site/main/layout/lbox.en.inc create mode 100644 scripts/site/main/layout/players.en.inc create mode 100644 scripts/site/main/maintenance.inc create mode 100644 scripts/site/main/output/about.en.inc create mode 100644 scripts/site/main/output/account.en.inc create mode 100644 scripts/site/main/output/annoy.en.inc create mode 100644 scripts/site/main/output/b6pp.en.inc create mode 100644 scripts/site/main/output/back_confirm.en.inc create mode 100644 scripts/site/main/output/confirm.en.inc create mode 100644 scripts/site/main/output/contrib.en.inc create mode 100644 scripts/site/main/output/create.en.inc create mode 100644 scripts/site/main/output/created.en.inc create mode 100644 scripts/site/main/output/credits.en.inc create mode 100644 scripts/site/main/output/disclaimer.en.inc create mode 100644 scripts/site/main/output/donate.en.inc create mode 100644 scripts/site/main/output/index.en.inc create mode 100644 scripts/site/main/output/kicked.en.inc create mode 100644 scripts/site/main/output/links.en.inc create mode 100644 scripts/site/main/output/login.en.inc create mode 100644 scripts/site/main/output/logout.en.inc create mode 100644 scripts/site/main/output/lostpass.en.inc create mode 100644 scripts/site/main/output/manual-box.en.inc create mode 100644 scripts/site/main/output/manual.en.inc create mode 100644 scripts/site/main/output/manual_notfound.en.inc create mode 100644 scripts/site/main/output/manual_search.en.inc create mode 100644 scripts/site/main/output/menu.en.inc create mode 100644 scripts/site/main/output/notfound.en.inc create mode 100644 scripts/site/main/output/notregistered.en.inc create mode 100644 scripts/site/main/output/pcheck.inc create mode 100644 scripts/site/main/output/play.en.inc create mode 100644 scripts/site/main/output/ppipn.en.inc create mode 100644 scripts/site/main/output/quit_confirm.en.inc create mode 100644 scripts/site/main/output/rankings.en.inc create mode 100644 scripts/site/main/output/restart.en.inc create mode 100644 scripts/site/main/output/screenshots.en.inc create mode 100644 scripts/site/main/output/settings.en.inc create mode 100644 scripts/site/main/output/side-box.en.inc create mode 100644 scripts/site/main/output/vac_cancel.en.inc create mode 100644 scripts/site/main/output/vac_leave.en.inc create mode 100644 scripts/site/main/output/vac_start.en.inc create mode 100644 scripts/site/main/page.inc create mode 100644 scripts/ticks.php create mode 100644 site/beta4/Rank.html create mode 100644 site/beta4/Rankings.php create mode 100644 site/beta4/Rankswitch.html create mode 100644 site/beta4/background.jpg create mode 100644 site/beta4/images/Legacy.jpg create mode 100644 site/beta4/images/background.jpg create mode 100644 site/beta4/legacyfront.gif create mode 100644 site/beta4/legacyfront.jpg create mode 100644 site/beta4/red.css create mode 100644 site/downloads/LegacyWorlds-Dashboard-latest.zip create mode 100644 site/index.php create mode 100644 site/static/beta5/css/blue.css create mode 100644 site/static/beta5/css/fonts0.css create mode 100644 site/static/beta5/css/fonts1.css create mode 100644 site/static/beta5/css/fonts2.css create mode 100644 site/static/beta5/css/fonts3.css create mode 100644 site/static/beta5/css/fonts4.css create mode 100644 site/static/beta5/css/green.css create mode 100644 site/static/beta5/css/grey.css create mode 100644 site/static/beta5/css/main.css create mode 100644 site/static/beta5/css/pg_alliance.css create mode 100644 site/static/beta5/css/pg_allies.css create mode 100644 site/static/beta5/css/pg_empire.css create mode 100644 site/static/beta5/css/pg_enemylist.css create mode 100644 site/static/beta5/css/pg_fleets.css create mode 100644 site/static/beta5/css/pg_forums.css create mode 100644 site/static/beta5/css/pg_manual.css create mode 100644 site/static/beta5/css/pg_map.css create mode 100644 site/static/beta5/css/pg_market.css create mode 100644 site/static/beta5/css/pg_message.css create mode 100644 site/static/beta5/css/pg_money.css create mode 100644 site/static/beta5/css/pg_overview.css create mode 100644 site/static/beta5/css/pg_planet.css create mode 100644 site/static/beta5/css/pg_planets.css create mode 100644 site/static/beta5/css/pg_preferences.css create mode 100644 site/static/beta5/css/pg_probes.css create mode 100644 site/static/beta5/css/pg_rank.css create mode 100644 site/static/beta5/css/pg_research.css create mode 100644 site/static/beta5/css/purple.css create mode 100644 site/static/beta5/css/red.css create mode 100644 site/static/beta5/css/thm_classic.css create mode 100644 site/static/beta5/css/thm_cripes.css create mode 100644 site/static/beta5/css/thm_default-ie.css create mode 100644 site/static/beta5/css/thm_default.css create mode 100644 site/static/beta5/css/thm_invert-ie.css create mode 100644 site/static/beta5/css/thm_invert.css create mode 100644 site/static/beta5/css/yellow.css create mode 100644 site/static/beta5/js/main-en.js create mode 100644 site/static/beta5/js/main.js create mode 100644 site/static/beta5/js/pg_alliance-en.js create mode 100644 site/static/beta5/js/pg_alliance.js create mode 100644 site/static/beta5/js/pg_allies-en.js create mode 100644 site/static/beta5/js/pg_allies.js create mode 100644 site/static/beta5/js/pg_comms-en.js create mode 100644 site/static/beta5/js/pg_comms.js create mode 100644 site/static/beta5/js/pg_diplomacy-en.js create mode 100644 site/static/beta5/js/pg_diplomacy.js create mode 100644 site/static/beta5/js/pg_empire-en.js create mode 100644 site/static/beta5/js/pg_empire.js create mode 100644 site/static/beta5/js/pg_enemylist-en.js create mode 100644 site/static/beta5/js/pg_enemylist.js create mode 100644 site/static/beta5/js/pg_fleets-en.js create mode 100644 site/static/beta5/js/pg_fleets.js create mode 100644 site/static/beta5/js/pg_forums-en.js create mode 100644 site/static/beta5/js/pg_forums.js create mode 100644 site/static/beta5/js/pg_map-en.js create mode 100644 site/static/beta5/js/pg_map.js create mode 100644 site/static/beta5/js/pg_market-en.js create mode 100644 site/static/beta5/js/pg_market.js create mode 100644 site/static/beta5/js/pg_message-en.js create mode 100644 site/static/beta5/js/pg_message.js create mode 100644 site/static/beta5/js/pg_money-en.js create mode 100644 site/static/beta5/js/pg_money.js create mode 100644 site/static/beta5/js/pg_overview-en.js create mode 100644 site/static/beta5/js/pg_overview.js create mode 100644 site/static/beta5/js/pg_planet-en.js create mode 100644 site/static/beta5/js/pg_planet.js create mode 100644 site/static/beta5/js/pg_planets-en.js create mode 100644 site/static/beta5/js/pg_planets.js create mode 100644 site/static/beta5/js/pg_probes-en.js create mode 100644 site/static/beta5/js/pg_probes.js create mode 100644 site/static/beta5/js/pg_rank-en.js create mode 100644 site/static/beta5/js/pg_rank.js create mode 100644 site/static/beta5/js/pg_research-en.js create mode 100644 site/static/beta5/js/pg_research.js create mode 100644 site/static/beta5/js/pg_techtrade-en.js create mode 100644 site/static/beta5/js/pg_techtrade.js create mode 100644 site/static/beta5/js/pg_ticks-en.js create mode 100644 site/static/beta5/js/pg_ticks.js create mode 100644 site/static/beta5/js/pg_universe-en.js create mode 100644 site/static/beta5/js/pg_universe.js create mode 100644 site/static/beta5/js/rpc-en.js create mode 100644 site/static/beta5/js/rpc.js create mode 100644 site/static/beta5/js/thm_classic.js create mode 100644 site/static/beta5/js/thm_cripes.js create mode 100644 site/static/beta5/js/thm_default-en.js create mode 100644 site/static/beta5/js/thm_default.js create mode 100644 site/static/beta5/js/thm_invert-en.js create mode 100644 site/static/beta5/js/thm_invert.js create mode 100644 site/static/beta5/js/tooltips.js create mode 100644 site/static/beta5/js/tt_classic_blue.js create mode 100644 site/static/beta5/js/tt_classic_green.js create mode 100644 site/static/beta5/js/tt_classic_grey.js create mode 100644 site/static/beta5/js/tt_classic_purple.js create mode 100644 site/static/beta5/js/tt_classic_red.js create mode 100644 site/static/beta5/js/tt_classic_yellow.js create mode 100644 site/static/beta5/js/tt_default_blue.js create mode 100644 site/static/beta5/js/tt_default_green.js create mode 100644 site/static/beta5/js/tt_default_grey.js create mode 100644 site/static/beta5/js/tt_default_purple.js create mode 100644 site/static/beta5/js/tt_default_red.js create mode 100644 site/static/beta5/js/tt_default_yellow.js create mode 100644 site/static/beta5/js/tt_invert_blue.js create mode 100644 site/static/beta5/js/tt_invert_green.js create mode 100644 site/static/beta5/js/tt_invert_grey.js create mode 100644 site/static/beta5/js/tt_invert_purple.js create mode 100644 site/static/beta5/js/tt_invert_red.js create mode 100644 site/static/beta5/js/tt_invert_yellow.js create mode 100644 site/static/beta5/pics/add1_blue.gif create mode 100644 site/static/beta5/pics/add1_green.gif create mode 100644 site/static/beta5/pics/add1_grey.gif create mode 100644 site/static/beta5/pics/add1_purple.gif create mode 100644 site/static/beta5/pics/add1_red.gif create mode 100644 site/static/beta5/pics/add1_yellow.gif create mode 100644 site/static/beta5/pics/add2_blue.gif create mode 100644 site/static/beta5/pics/add2_green.gif create mode 100644 site/static/beta5/pics/add2_grey.gif create mode 100644 site/static/beta5/pics/add2_purple.gif create mode 100644 site/static/beta5/pics/add2_red.gif create mode 100644 site/static/beta5/pics/add2_yellow.gif create mode 100644 site/static/beta5/pics/background.jpg create mode 100644 site/static/beta5/pics/dec1_blue.gif create mode 100644 site/static/beta5/pics/dec1_green.gif create mode 100644 site/static/beta5/pics/dec1_grey.gif create mode 100644 site/static/beta5/pics/dec1_purple.gif create mode 100644 site/static/beta5/pics/dec1_red.gif create mode 100644 site/static/beta5/pics/dec1_yellow.gif create mode 100644 site/static/beta5/pics/dec2_blue.gif create mode 100644 site/static/beta5/pics/dec2_green.gif create mode 100644 site/static/beta5/pics/dec2_grey.gif create mode 100644 site/static/beta5/pics/dec2_purple.gif create mode 100644 site/static/beta5/pics/dec2_red.gif create mode 100644 site/static/beta5/pics/dec2_yellow.gif create mode 100644 site/static/beta5/pics/down_blue.gif create mode 100644 site/static/beta5/pics/down_green.gif create mode 100644 site/static/beta5/pics/down_grey.gif create mode 100644 site/static/beta5/pics/down_purple.gif create mode 100644 site/static/beta5/pics/down_red.gif create mode 100644 site/static/beta5/pics/down_yellow.gif create mode 100644 site/static/beta5/pics/icons/alliance.gif create mode 100644 site/static/beta5/pics/icons/alliance.png create mode 100644 site/static/beta5/pics/icons/fleets.gif create mode 100644 site/static/beta5/pics/icons/fleets.png create mode 100644 site/static/beta5/pics/icons/logout.gif create mode 100644 site/static/beta5/pics/icons/logout.png create mode 100644 site/static/beta5/pics/icons/map.gif create mode 100644 site/static/beta5/pics/icons/map.png create mode 100644 site/static/beta5/pics/icons/message.gif create mode 100644 site/static/beta5/pics/icons/planets.gif create mode 100644 site/static/beta5/pics/icons/planets.png create mode 100644 site/static/beta5/pics/left_blue.gif create mode 100644 site/static/beta5/pics/left_green.gif create mode 100644 site/static/beta5/pics/left_grey.gif create mode 100644 site/static/beta5/pics/left_purple.gif create mode 100644 site/static/beta5/pics/left_red.gif create mode 100644 site/static/beta5/pics/left_yellow.gif create mode 100644 site/static/beta5/pics/lock_blue.gif create mode 100644 site/static/beta5/pics/lock_green.gif create mode 100644 site/static/beta5/pics/lock_grey.gif create mode 100644 site/static/beta5/pics/lock_purple.gif create mode 100644 site/static/beta5/pics/lock_red.gif create mode 100644 site/static/beta5/pics/lock_yellow.gif create mode 100644 site/static/beta5/pics/lw-tot-tech-blue.gif create mode 100644 site/static/beta5/pics/lw-tot-tech-blue.png create mode 100644 site/static/beta5/pics/lw-tot-tech-green.gif create mode 100644 site/static/beta5/pics/lw-tot-tech-green.png create mode 100644 site/static/beta5/pics/lw-tot-tech-grey.gif create mode 100644 site/static/beta5/pics/lw-tot-tech-grey.png create mode 100644 site/static/beta5/pics/lw-tot-tech-purple.gif create mode 100644 site/static/beta5/pics/lw-tot-tech-purple.png create mode 100644 site/static/beta5/pics/lw-tot-tech-red.gif create mode 100644 site/static/beta5/pics/lw-tot-tech-red.png create mode 100644 site/static/beta5/pics/lw-tot-tech-yellow.gif create mode 100644 site/static/beta5/pics/lw-tot-tech-yellow.png create mode 100644 site/static/beta5/pics/msgr.gif create mode 100644 site/static/beta5/pics/msgrep.gif create mode 100644 site/static/beta5/pics/msgu.gif create mode 100644 site/static/beta5/pics/nebula1.png create mode 100644 site/static/beta5/pics/nebula2.png create mode 100644 site/static/beta5/pics/nebula3.png create mode 100644 site/static/beta5/pics/nebula4.png create mode 100644 site/static/beta5/pics/prem_l.png create mode 100644 site/static/beta5/pics/prem_s.png create mode 100644 site/static/beta5/pics/read.gif create mode 100644 site/static/beta5/pics/read_sticky.gif create mode 100644 site/static/beta5/pics/right_blue.gif create mode 100644 site/static/beta5/pics/right_green.gif create mode 100644 site/static/beta5/pics/right_grey.gif create mode 100644 site/static/beta5/pics/right_purple.gif create mode 100644 site/static/beta5/pics/right_red.gif create mode 100644 site/static/beta5/pics/right_yellow.gif create mode 100644 site/static/beta5/pics/that-other-theme-blue.png create mode 100644 site/static/beta5/pics/that-other-theme-green.png create mode 100644 site/static/beta5/pics/that-other-theme-grey.png create mode 100644 site/static/beta5/pics/that-other-theme-purple.png create mode 100644 site/static/beta5/pics/that-other-theme-red.png create mode 100644 site/static/beta5/pics/that-other-theme-yellow.png create mode 100644 site/static/beta5/pics/ttl/def/blue/menubg.png create mode 100644 site/static/beta5/pics/ttl/def/en/blue/alliance.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/allies.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/comms.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/diplomacy.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/empire.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/enemylist.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/fleets.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/forums.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/manual.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/manual_notfound.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/manual_search.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/map.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/market.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/message.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/money.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/nplanet.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/overview.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/planet.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/planetnf.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/planets.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/preferences.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/probes.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/rank.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/research.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/ticks.gif create mode 100644 site/static/beta5/pics/ttl/def/en/blue/universe.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/alliance.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/allies.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/comms.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/diplomacy.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/empire.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/enemylist.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/fleets.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/forums.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/manual.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/manual_notfound.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/manual_search.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/map.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/market.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/message.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/money.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/nplanet.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/overview.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/planet.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/planetnf.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/planets.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/preferences.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/probes.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/rank.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/research.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/ticks.gif create mode 100644 site/static/beta5/pics/ttl/def/en/green/universe.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/alliance.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/allies.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/comms.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/diplomacy.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/empire.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/enemylist.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/fleets.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/forums.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/manual.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/manual_notfound.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/manual_search.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/map.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/market.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/message.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/money.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/nplanet.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/overview.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/planet.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/planetnf.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/planets.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/preferences.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/probes.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/rank.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/research.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/ticks.gif create mode 100644 site/static/beta5/pics/ttl/def/en/grey/universe.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/alliance.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/allies.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/comms.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/diplomacy.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/empire.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/enemylist.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/fleets.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/forums.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/manual.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/manual_notfound.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/manual_search.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/map.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/market.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/message.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/money.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/nplanet.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/overview.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/planet.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/planetnf.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/planets.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/preferences.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/probes.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/rank.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/research.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/ticks.gif create mode 100644 site/static/beta5/pics/ttl/def/en/purple/universe.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/alliance.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/allies.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/comms.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/diplomacy.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/empire.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/enemylist.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/fleets.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/forums.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/manual.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/manual_notfound.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/manual_search.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/map.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/market.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/message.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/money.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/nplanet.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/overview.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/planet.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/planetnf.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/planets.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/preferences.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/probes.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/rank.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/research.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/ticks.gif create mode 100644 site/static/beta5/pics/ttl/def/en/red/universe.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/alliance.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/allies.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/comms.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/diplomacy.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/empire.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/enemylist.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/fleets.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/forums.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/manual.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/manual_notfound.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/manual_search.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/map.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/market.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/message.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/money.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/nplanet.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/overview.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/planet.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/planetnf.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/planets.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/preferences.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/probes.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/rank.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/research.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/ticks.gif create mode 100644 site/static/beta5/pics/ttl/def/en/yellow/universe.gif create mode 100644 site/static/beta5/pics/ttl/def/green/menubg.png create mode 100644 site/static/beta5/pics/ttl/def/grey/menubg.png create mode 100644 site/static/beta5/pics/ttl/def/purple/menubg.png create mode 100644 site/static/beta5/pics/ttl/def/red/menubg.png create mode 100644 site/static/beta5/pics/ttl/def/yellow/menubg.png create mode 100644 site/static/beta5/pics/unlock_blue.gif create mode 100644 site/static/beta5/pics/unlock_green.gif create mode 100644 site/static/beta5/pics/unlock_grey.gif create mode 100644 site/static/beta5/pics/unlock_purple.gif create mode 100644 site/static/beta5/pics/unlock_red.gif create mode 100644 site/static/beta5/pics/unlock_yellow.gif create mode 100644 site/static/beta5/pics/unread.gif create mode 100644 site/static/beta5/pics/unread_sticky.gif create mode 100644 site/static/beta5/pics/up_blue.gif create mode 100644 site/static/beta5/pics/up_green.gif create mode 100644 site/static/beta5/pics/up_grey.gif create mode 100644 site/static/beta5/pics/up_purple.gif create mode 100644 site/static/beta5/pics/up_red.gif create mode 100644 site/static/beta5/pics/up_yellow.gif create mode 100644 site/static/main/css/account.css create mode 100644 site/static/main/css/annoy.css create mode 100644 site/static/main/css/b6pp.css create mode 100644 site/static/main/css/content.css create mode 100644 site/static/main/css/create.css create mode 100644 site/static/main/css/credits.css create mode 100644 site/static/main/css/home.css create mode 100644 site/static/main/css/main.css create mode 100644 site/static/main/css/manual.css create mode 100644 site/static/main/css/rankings.css create mode 100644 site/static/main/css/screenshots.css create mode 100644 site/static/main/css/sidebox.css create mode 100644 site/static/main/css/text.css create mode 100644 site/static/main/js/account.js create mode 100644 site/static/main/js/adapt.js create mode 100644 site/static/main/js/annoy.js create mode 100644 site/static/main/js/jquery.js create mode 100644 site/static/main/js/manual.js create mode 100644 site/static/main/js/sidebox.js create mode 100644 site/static/main/pics/mp-content-b0.png create mode 100644 site/static/main/pics/mp-content-b1.png create mode 100644 site/static/main/pics/mp-content-b2.png create mode 100644 site/static/main/pics/mp-content-b3.png create mode 100644 site/static/main/pics/mp-content-b4.png create mode 100644 site/static/main/pics/mp-content-b5.png create mode 100644 site/static/main/pics/mp-content-b6.png create mode 100644 site/static/main/pics/mp-content.jpg create mode 100644 site/static/main/pics/mp-tab-1.png create mode 100644 site/static/main/pics/mp-tab-2.png create mode 100644 site/static/main/pics/mp-title-b0.png create mode 100644 site/static/main/pics/mp-title-b1.png create mode 100644 site/static/main/pics/mp-title-b2.png create mode 100644 site/static/main/pics/mp-title-b3.png create mode 100644 site/static/main/pics/mp-title-b4.png create mode 100644 site/static/main/pics/mp-title-b5.png create mode 100644 site/static/main/pics/mp-title-b6.png create mode 100644 site/static/main/pics/mp-title.jpg create mode 100644 site/static/main/pics/smiles/icon_arrow.gif create mode 100644 site/static/main/pics/smiles/icon_biggrin.gif create mode 100644 site/static/main/pics/smiles/icon_confused.gif create mode 100644 site/static/main/pics/smiles/icon_cool.gif create mode 100644 site/static/main/pics/smiles/icon_cry.gif create mode 100644 site/static/main/pics/smiles/icon_eek.gif create mode 100644 site/static/main/pics/smiles/icon_evil.gif create mode 100644 site/static/main/pics/smiles/icon_exclaim.gif create mode 100644 site/static/main/pics/smiles/icon_frown.gif create mode 100644 site/static/main/pics/smiles/icon_idea.gif create mode 100644 site/static/main/pics/smiles/icon_lol.gif create mode 100644 site/static/main/pics/smiles/icon_mad.gif create mode 100644 site/static/main/pics/smiles/icon_mrgreen.gif create mode 100644 site/static/main/pics/smiles/icon_neutral.gif create mode 100644 site/static/main/pics/smiles/icon_question.gif create mode 100644 site/static/main/pics/smiles/icon_razz.gif create mode 100644 site/static/main/pics/smiles/icon_redface.gif create mode 100644 site/static/main/pics/smiles/icon_rolleyes.gif create mode 100644 site/static/main/pics/smiles/icon_sad.gif create mode 100644 site/static/main/pics/smiles/icon_smile.gif create mode 100644 site/static/main/pics/smiles/icon_surprised.gif create mode 100644 site/static/main/pics/smiles/icon_twisted.gif create mode 100644 site/static/main/pics/smiles/icon_wink.gif create mode 100644 site/static/main/screens/b4-fleets-m.jpg create mode 100644 site/static/main/screens/b4-fleets-s.jpg create mode 100644 site/static/main/screens/b4-fleets.jpg create mode 100644 site/static/main/screens/b4-map-m.jpg create mode 100644 site/static/main/screens/b4-map-s.jpg create mode 100644 site/static/main/screens/b4-map.jpg create mode 100644 site/static/main/screens/b4-money-m.jpg create mode 100644 site/static/main/screens/b4-money-s.jpg create mode 100644 site/static/main/screens/b4-money.jpg create mode 100644 site/static/main/screens/b4-ov-m.jpg create mode 100644 site/static/main/screens/b4-ov-s.jpg create mode 100644 site/static/main/screens/b4-ov.jpg create mode 100644 site/static/main/screens/b4-planets-m.jpg create mode 100644 site/static/main/screens/b4-planets-s.jpg create mode 100644 site/static/main/screens/b4-planets.jpg create mode 100644 site/static/main/screens/b4-ranking-m.jpg create mode 100644 site/static/main/screens/b4-ranking-s.jpg create mode 100644 site/static/main/screens/b4-ranking.jpg create mode 100644 site/static/main/screens/b4-ticks-m.jpg create mode 100644 site/static/main/screens/b4-ticks-s.jpg create mode 100644 site/static/main/screens/b4-ticks.jpg create mode 100644 site/static/main/screens/b5-allies-m.jpg create mode 100644 site/static/main/screens/b5-allies-s.jpg create mode 100644 site/static/main/screens/b5-allies.jpg create mode 100644 site/static/main/screens/b5-fleets-m.jpg create mode 100644 site/static/main/screens/b5-fleets-s.jpg create mode 100644 site/static/main/screens/b5-fleets.jpg create mode 100644 site/static/main/screens/b5-manual-m.jpg create mode 100644 site/static/main/screens/b5-manual-s.jpg create mode 100644 site/static/main/screens/b5-manual.jpg create mode 100644 site/static/main/screens/b5-map-m.jpg create mode 100644 site/static/main/screens/b5-map-s.jpg create mode 100644 site/static/main/screens/b5-map.jpg create mode 100644 site/static/main/screens/b5-market-m.jpg create mode 100644 site/static/main/screens/b5-market-s.jpg create mode 100644 site/static/main/screens/b5-market.jpg create mode 100644 site/static/main/screens/b5-messages-m.jpg create mode 100644 site/static/main/screens/b5-messages-s.jpg create mode 100644 site/static/main/screens/b5-messages.jpg create mode 100644 site/static/main/screens/b5-money-m.jpg create mode 100644 site/static/main/screens/b5-money-s.jpg create mode 100644 site/static/main/screens/b5-money.jpg create mode 100644 site/static/main/screens/b5-ov-m.jpg create mode 100644 site/static/main/screens/b5-ov-s.jpg create mode 100644 site/static/main/screens/b5-ov.jpg create mode 100644 site/static/main/screens/b5-planet-m.jpg create mode 100644 site/static/main/screens/b5-planet-s.jpg create mode 100644 site/static/main/screens/b5-planet.jpg create mode 100644 site/static/main/screens/b5-planets-m.jpg create mode 100644 site/static/main/screens/b5-planets-s.jpg create mode 100644 site/static/main/screens/b5-planets.jpg create mode 100644 site/static/main/screens/b5-ranking-m.jpg create mode 100644 site/static/main/screens/b5-ranking-s.jpg create mode 100644 site/static/main/screens/b5-ranking.jpg create mode 100644 site/static/main/screens/b5-research-m.jpg create mode 100644 site/static/main/screens/b5-research-s.jpg create mode 100644 site/static/main/screens/b5-research.jpg create mode 100644 sql/00-init.sql create mode 100644 sql/01-inheritance.sql create mode 100644 sql/10-main.sql create mode 100644 sql/11-main-enums.sql create mode 100644 sql/12-main-tables.sql create mode 100644 sql/13-main-donations.sql create mode 100644 sql/13-main-forums.sql create mode 100644 sql/13-main-links.sql create mode 100644 sql/13-main-manual.sql create mode 100644 sql/13-main-proxy.sql create mode 100644 sql/18-main-functions.sql create mode 100644 sql/19-main-values.sql create mode 100644 sql/20-credits.sql create mode 100644 sql/25-ctf-maps.sql create mode 100644 sql/25-death-of-rats.sql create mode 100644 sql/25-predefined-alliances.sql create mode 100644 sql/30-beta5.sql create mode 100644 sql/50-beta6-planet-pictures.sql create mode 100644 sql/INSTALL.sql create mode 100644 sql/beta5/00-beta5.sql create mode 100644 sql/beta5/10-beta5-b5.sql create mode 100644 sql/beta5/11-beta5-b5m0.sql create mode 100644 sql/beta5/beta5-ctf.sql create mode 100644 sql/beta5/beta5-match.sql create mode 100644 sql/beta5/beta5-round.sql create mode 100644 sql/beta5/data/game.sql create mode 100644 sql/beta5/data/match.sql create mode 100644 sql/beta5/data/standard.sql create mode 100644 sql/beta5/structure/00-ecm-eccm.sql create mode 100644 sql/beta5/structure/00-gdata.sql create mode 100644 sql/beta5/structure/00-player-table.sql create mode 100644 sql/beta5/structure/00-rule-base.sql create mode 100644 sql/beta5/structure/00-system.sql create mode 100644 sql/beta5/structure/01-alliance.sql create mode 100644 sql/beta5/structure/01-message-base.sql create mode 100644 sql/beta5/structure/01-planet.sql create mode 100644 sql/beta5/structure/01-player-dipl.sql create mode 100644 sql/beta5/structure/01-player-rules.sql create mode 100644 sql/beta5/structure/01-research-base.sql create mode 100644 sql/beta5/structure/02-alliance-forums.sql create mode 100644 sql/beta5/structure/02-alliance-tech.sql create mode 100644 sql/beta5/structure/02-orders.sql create mode 100644 sql/beta5/structure/02-warehouse.sql create mode 100644 sql/beta5/structure/03-fleets.sql create mode 100644 sql/beta5/structure/04-sales.sql create mode 100644 sql/beta5/structure/05-message-player.sql create mode 100644 sql/beta5/structure/06-message-battle.sql create mode 100644 sql/beta5/structure/06-message-internal.sql create mode 100644 sql/beta5/structure/07-beacons.sql create mode 100644 sql/beta5/structure/07-message-admin.sql create mode 100644 sql/beta5/structure/10-ctf-tables.sql create mode 100644 sql/beta5/structure/10-prot-tables.sql create mode 100644 sql/beta5/structure/99-player-fk.sql create mode 100644 sql/beta5/structure/finalise.sql diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..d73ec65 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,11 @@ +Legacy Worlds Beta 5 authors +----------------------------- + +Legacy Worlds Beta 4: + Chris Wicks + +Legacy Worlds Beta 5: + Emmanuel Benoît + Julie Bourbeillon + Douglas Rodgers + Dave Timmermans diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..9356c42 --- /dev/null +++ b/COPYING @@ -0,0 +1,32 @@ +Licensing information +---------------------- + +The IRC bot code is based on PHP-IRC (http://www.phpbots.org/) with some +changes for LW integration. It is distributed under the GNU GPL license. + +The main LW code (admin and scripts directories), as well as the CSS and JS +code (with the exception of the jquery library) in site/static, the planet +generator and the SQL database structure and associated functions are +distributed under the GNU GPL license. Same goes for whatever is in the +misc/ directory. + +The outdated copy of the JQuery (http://www.jquery.org) library found in the +site/static/main/js/ directory is distributed under the MIT license. + +Icons used in the site's appearance have been taken and modified from various +sources. Their respective licenses are mostly unknown. + +The game data (including the manual and all files in the sql subdirectory +which contain actual values - sql/beta5/data/ most notably) remain the +exclusive property of Deep Clone Development. This data may be copied, +distributed or used as long as: +1) the distribution is free, +2) the distribution is complete and unmodified (it should match the +contents of the official SVN repository), +3) no profits are made from using this data. +Feel free to replace this data with something else entirely to get rid of +these licensing terms. + + +A copy of the GPLv2 is included in COPYING.GPL ; the MIT license may be found +in COPYING.MIT. diff --git a/COPYING.GPL b/COPYING.GPL new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/COPYING.GPL @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/COPYING.MIT b/COPYING.MIT new file mode 100644 index 0000000..89de354 --- /dev/null +++ b/COPYING.MIT @@ -0,0 +1,17 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README b/README new file mode 100644 index 0000000..dfedea1 --- /dev/null +++ b/README @@ -0,0 +1,96 @@ +Legacy Worlds Beta 5 source code +--------------------------------- + + +1. Introduction + +This file contains information regarding the source code release for Legacy +Worlds Beta 5 - how the source code is organised and how it may be used. + +For licensing information, please read the COPYING file. For the list of +contributors, please read the AUTHORS file. For stuff that could be done +considering one had a lot of time to waste, please read the TODO file. + +The official SVN repository for the LWB5 code can be found at: +* https://lwb5.svn.deepclone.co.uk (IPv6 users) +* https://secure.nocternity.net/deepclone.co.uk/svn/lwb5/ (IPv4 users) + + +2. Organisation of the source code + +... admin/ Administration interface scripts +... ircbot/ IRC bot +... ircbot/modules/lw/ LWB5-specific code for the IRC bot +... manual/ Source code (as XML) of the LWB5 manual +... misc/ Various optional elements +... planetgen/ Planet generator source code +... scripts/ Scripts that implement most of the game +... scripts/game/ Back-end scripts +... scripts/lib/ Core library +... scripts/site/ Web front-end +... site/ Root directory of the public part of the site +... site/static/ Images, CSS and JavaScript elements +... sql/ Database creation scripts + + +3. Requirements + +In order to run Legacy Worlds, you will need the following software: + +* Apache + PHP 5.2 +* (optional) a PHP opcode cache +* PostgreSQL 8.3 or higher +* the POVRay raytracer (used for planet generation) + + +4. Installing and running LWB5 + +These instructions may be erroneous on some points, as I didn't bother to try +and re-install it myself. + +* Set the database users' passwords in sql/00-init.sql + +* (optional) Modify sql/30-beta5.sql to select the games that will be created +on initialisation. + +* Create the database by running sql/INSTALL.sql + +* Create an administrative user in the main.account table + +* Create some system users for the various parts of the LWB5 backend: +** user "lwbot"; when su'd to, this user should start the IRC bot, +** user "lwticks"; when su'd to, this user should start scripts/ticks.php +** user "lwproxy"; when su'd to, this user should start scripts/proxycheck.php + +* In addition to the above users, the planet picture generator should run with +privileges allowing it to write to subdirectories of site/static/beta5/pics/pl/ + +* Modify scripts/config.inc and scripts/legacyworlds.xml +** Also modify ircbot/bot.conf to have the bot connect to a server + +* Set up the web server: +** the main site should use the site/ directory, +** the administration interface should use the admin/ directory; it should be +protected using HTTP authentication. + +Once the installation is complete, it is possible to start (most of) the game +by running scripts/control.pl with the --start command line argument. However, +the planet generator script has to be started manually. + + +5. The misc/ subdirectory + +The misc/ subdirectory contains two things: +* the source code of the MacOS dashboard widget, +* the "forums branch" patch. + +The forums branch patch has been created by doing a very quick and dirty merge +of an old development branch that was mostly abandoned; it contains some +improvements on the core library - notably a class to prepare database queries +and some other improvements in the DB query API. Of course, it also includes an +incomplete rewrite of the in-game forums. + +Because I had to merge the branches to generate the patch, and because I didn't +bother with testing it, the patch is likely to break a lot of things. However +it may be a good starting point for improving the LWB5 code. See the TODO file +for more information. diff --git a/TODO b/TODO new file mode 100644 index 0000000..f5d612e --- /dev/null +++ b/TODO @@ -0,0 +1,47 @@ +This file lists stuff that could be done to improve the current LWB5 source +code. It's mostly stuff that I had the intention of doing at some point but +didn't have the time to work on or finish. Of course, this is just a list of +ideas - feel free to ignore it altogether. + + +Back-end clean up: + +* A lot of the game and site functions use older APIs that are deprecated. +** Calls to gameAction() +** Calls to logText() +** There's more, but I can't remember... + +* Some of the improvements to the core library found in the forums branch +patch should be included - most importantly the SQL query stuff. Of course +this also implies modifying a lot of the code that actually uses SQL queries. + +* While some of it has been removed, there is still a lot of SQL in the +various web handlers; it should be moved to the library. + +* Internal messages are sent by inserting into the database directly in many +of the game's functions; this should be changed as there is an API for that. + + +Front-end clean up: + +* When LWB5 was first written, I hadn't planned on using that much JavaScript. +In addition, JS libraries were not as common then as they are now. Therefore +most of the JS code is a terrible, ugly heap of spaghetti code and copy pasta. +It should be rewritten. + + +Improvements and missing features: + +* Finishing the forums rewrite (started in the forums branch patch). + +* Rewrite fleet handling - the database structure is brain dead, normalisation +is clearly required, and it'd reduce the various checks performed wherever +fleets are accessed. + +* Fleet trajectory is buggy, it contains a relatively easily exploitable bug. +Needs redesign. + +* Probes never made it into the final version. + +* The IRC bot's source code should be upgraded to the latest version of +PHP-IRC. diff --git a/admin/as_log.inc b/admin/as_log.inc new file mode 100644 index 0000000..01c8fd6 --- /dev/null +++ b/admin/as_log.inc @@ -0,0 +1,20 @@ + diff --git a/admin/as_manager.inc b/admin/as_manager.inc new file mode 100644 index 0000000..8507d48 --- /dev/null +++ b/admin/as_manager.inc @@ -0,0 +1,24 @@ + 22) { + return false; + } + + return $pid; +} + + +?> diff --git a/admin/bot.php b/admin/bot.php new file mode 100644 index 0000000..a69d4ca --- /dev/null +++ b/admin/bot.php @@ -0,0 +1,80 @@ + + + + LegacyWorlds Beta 5 > Administration > IRC bot + + +

LWB5 > Administration > IRC bot

+

Operation in progress...

+

+ A system operation is in progress. Please wait, the page will update in a few seconds. +

+ + + + + + + LegacyWorlds Beta 5 > Administration > IRC bot + + +

LWB5 > Administration > IRC bot

+ +

+ IRC bot is (probably) running. Kill bot +

+ +

+ IRC bot is not running. Start bot +

+ + + diff --git a/admin/cg_done.php b/admin/cg_done.php new file mode 100644 index 0000000..9a73b66 --- /dev/null +++ b/admin/cg_done.php @@ -0,0 +1,56 @@ + + + + Legacy Worlds Beta 5 > Administration > Create game + + + + + + + + + + + + + +
  + + + + + + + + + + + + + + + +
LegacyWorlds Beta 5
 
Game creation complete!
  + +
 
 
100% complete
 
 
 
  + The new game has been created; however, it is not visible from the interface yet, you will have to + make it visible
+
+ The server is still in maintenance mode, you will have to disable it manually.
+
+ Main admin page +
 
+ + + diff --git a/admin/cg_operation.inc b/admin/cg_operation.inc new file mode 100644 index 0000000..40c0e2b --- /dev/null +++ b/admin/cg_operation.inc @@ -0,0 +1,45 @@ + + + Legacy Worlds Beta 5 > Administration > Create game + + + + + + + +
  + + + + + + + + + + + + + + + +
LegacyWorlds Beta 5
Game creation in progress
 
  + "; +} elseif ($op['pc'] == 100) { + print ""; +} else { + print "" + . ""; +} + +?> +
   
 
% complete
 
 
+ + + diff --git a/admin/cg_step0.php b/admin/cg_step0.php new file mode 100644 index 0000000..51f370b --- /dev/null +++ b/admin/cg_step0.php @@ -0,0 +1,24 @@ + 0, + "text" => "Initialising ...", + "delay" => 1, + "to" => "cg_step1.php" +); + +include('cg_operation.inc'); + +?> diff --git a/admin/cg_step1.php b/admin/cg_step1.php new file mode 100644 index 0000000..8fae89e --- /dev/null +++ b/admin/cg_step1.php @@ -0,0 +1,30 @@ + 5, + "text" => $text, + "delay" => 1, + "to" => $next +); + +include('cg_operation.inc'); + +?> diff --git a/admin/cg_step10.php b/admin/cg_step10.php new file mode 100644 index 0000000..45014b6 --- /dev/null +++ b/admin/cg_step10.php @@ -0,0 +1,28 @@ + 85, + "text" => "Adding silent admins", + "delay" => 1, + "to" => "cg_step11.php" +); + +include('cg_operation.inc'); + +?> diff --git a/admin/cg_step11.php b/admin/cg_step11.php new file mode 100644 index 0000000..7321fc0 --- /dev/null +++ b/admin/cg_step11.php @@ -0,0 +1,60 @@ + 90, + "text" => "Restarting ticks", + "delay" => 1, + "to" => "cg_step12.php" + ); +} else { + $op = array( + "pc" => 100, + "text" => "Cleaning up", + "delay" => 1, + "to" => "cg_done.php" + ); +} + +include('cg_operation.inc'); + +?> diff --git a/admin/cg_step12.php b/admin/cg_step12.php new file mode 100644 index 0000000..865f366 --- /dev/null +++ b/admin/cg_step12.php @@ -0,0 +1,35 @@ + 100, + "text" => "Cleaning up", + "delay" => 1, + "to" => "cg_done.php" + ); +} else { + touch($config['cachedir'] . "/start_ticks"); + + $op = array( + "pc" => 95, + "text" => "Waiting for ticks to restart", + "delay" => 1, + "to" => "cg_step13.php" + ); +} + +include('cg_operation.inc'); + +?> diff --git a/admin/cg_step13.php b/admin/cg_step13.php new file mode 100644 index 0000000..e4948e5 --- /dev/null +++ b/admin/cg_step13.php @@ -0,0 +1,33 @@ + 95, + "text" => "Waiting for ticks to restart", + "delay" => 1, + "to" => "cg_step13.php" + ); +} else { + $op = array( + "pc" => 100, + "text" => "Cleaning up", + "delay" => 1, + "to" => "cg_done.php" + ); +} + +include('cg_operation.inc'); + +?> diff --git a/admin/cg_step2.php b/admin/cg_step2.php new file mode 100644 index 0000000..4149c74 --- /dev/null +++ b/admin/cg_step2.php @@ -0,0 +1,31 @@ + time() + 15 * 60, + "reason" => "Creating game " . $_SESSION['lw_new_game']['name'] +); + +include("../scripts/config.inc"); +$f = fopen($config['cachedir'] . '/maintenance.ser', "w"); +fwrite($f, serialize($maintenance)); +fclose($f); + +$_SESSION['lw_new_game']['menable'] = time(); +$op = array( + "pc" => 10, + "text" => "Waiting ...", + "delay" => 1, + "to" => "cg_step3.php" +); + +include('cg_operation.inc'); + +?> diff --git a/admin/cg_step3.php b/admin/cg_step3.php new file mode 100644 index 0000000..91761bc --- /dev/null +++ b/admin/cg_step3.php @@ -0,0 +1,29 @@ + 10 + floor($t/2), + "text" => $text, + "delay" => 1, + "to" => $next +); + +include('cg_operation.inc'); + +?> diff --git a/admin/cg_step4.php b/admin/cg_step4.php new file mode 100644 index 0000000..dbc29f9 --- /dev/null +++ b/admin/cg_step4.php @@ -0,0 +1,41 @@ + 25, + "text" => "Inserting data for ID $gameType$minId", + "delay" => 1, + "to" => "cg_step5.php" +); + +include('cg_operation.inc'); + +?> diff --git a/admin/cg_step5.php b/admin/cg_step5.php new file mode 100644 index 0000000..73c209d --- /dev/null +++ b/admin/cg_step5.php @@ -0,0 +1,205 @@ +\n"; + if ($state == 0) { + // Remove comments + $line = preg_replace('/\s*--.*$/', '', $line); + if ($line == '') { + continue; + } + + // \c ? + if (preg_match('/^\\\\c\s+[^\s]+\s([^\s]+)\s*$/', $line, $match)) { + if ($match[1] == "legacyworlds_admin") { + //echo "CONNECT AS ADMIN
"; + array_push(self::$instructions, array("CADM")); + } else { + //echo "CONNECT AS USER
"; + array_push(self::$instructions, array("CUSR")); + } + } + // \i ? + elseif (preg_match('/^\\\\i\s+([^\s]+)\s*$/', $line, $match)) { + self::parseFile($match[1]); + } + // COPY table FROM STDIN ? + elseif (preg_match('/^COPY\s+([^\s]+)\s+FROM\s+STDIN\s*;\s*$/i', $line, $match)) { + //echo "COPY DATA INTO {$match[1]}: "; + array_push(self::$instructions, array("COPY", $match[1])); + $state = 1; + $copyArray = array(); + } + // Other commands + else { + $rv = self::parseCommand($line); + if ($rv[0]) { + //echo "EXECUTE QUERY " . htmlentities($rv[1]) . "
\n"; + array_push(self::$instructions, array("QUERY", $rv[1])); + } else { + $buffer = $rv[1]; + $state = 2; + } + } + } elseif ($state == 1) { + if ($line == "\\.") { + //echo count($copyArray) . " LINE(S) OF DATA
\n"; + self::$instructions[count(self::$instructions) - 1][2] = $copyArray; + $state = 0; + } else { + array_push($copyArray, "$line\n"); + } + } elseif ($state == 2) { + $rv = self::parseCommand($line, $buffer); + if ($rv[0]) { + //echo "EXECUTE QUERY " . htmlentities($rv[1]) . "
\n"; + array_push(self::$instructions, array("QUERY", $rv[1])); + $state = 0; + } else { + $buffer = $rv[1]; + } + } + } + } + + private static function parseFile($fileName) { + $file = fopen("../sql/$fileName", "r"); + $text = ""; + while (($line = fgets($file)) !== FALSE) { + $text .= $line; + } + fclose($file); + self::parse($text); + } + + private static function parseCommand($line, $buffer = "") { + $state = 0; + for ($i = 0; $i < strlen($line); $i ++) { + if ($state == 0) { + if ($line{$i} == ';') { + return array(true, $buffer); + } + $buffer .= $line{$i}; + if ($line{$i} == "'") { + $state = 1; + } + } else { + $buffer .= $line{$i}; + if ($line{$i} == "'") { + $state = 0; + } + } + } + return array(false, "$buffer "); + } + + public static function connect() { + self::$uConnection = __dbConnect(false); + if (!self::$uConnection) { + $argh = "Error while accessing the database in user mode"; + include('cg_argh.inc'); + exit(1); + } + + self::$aConnection = __dbConnect(true); + if (!self::$aConnection) { + $argh = "Error while accessing the database in admin mode"; + include('cg_argh.inc'); + exit(1); + } + } + + public static function execute($schema) { + foreach (self::$instructions as $instr) { + $iType = array_shift($instr); + + if ($iType == 'CADM') { + self::setConnection(self::$aConnection); + } elseif ($iType == 'CUSR') { + self::setConnection(self::$aConnection); + } elseif ($iType == 'QUERY') { + if (!pg_query(self::$cConnection, $instr[0])) { + $argh = "Could not execute query {$instr[0]}"; + pg_query(self::$aConnection, "DROP SCHEMA \"$schema\" CASCADE"); + include('cg_argh.inc'); + exit(1); + } + } elseif ($iType == 'COPY') { + if (!pg_copy_from(self::$cConnection, $instr[0], $instr[1])) { + $argh = "Copy failed for table {$instr[0]}"; + pg_query(self::$aConnection, "DROP SCHEMA \"$schema\" CASCADE"); + include('cg_argh.inc'); + exit(1); + } + } + } + } + + public static function setConnection($cnx) { + self::$cConnection = $cnx; + } +} + +$gtData = array( + "r" => array('round', 'r'), + "m" => array('match', 'm'), + "c" => array('ctf', 'm'), +); +$gData = $gtData[$_SESSION['lw_new_game']['game_type']]; + +$substStr = "b5" . $gData[1] . "X"; + +// Load base match script +$fileName = "../sql/beta5/beta5-" . $gData[0] . ".sql"; +$baseFile = fopen($fileName, "r"); +$base = ""; +while (($line = fgets($baseFile)) !== FALSE) { + $base .= preg_replace("/$substStr/", $_SESSION['lw_new_game']['found_id'], $line); +} +fclose($baseFile); + +dbParser::parse($base); +dbParser::connect(); +dbParser::execute($_SESSION['lw_new_game']['found_id']); + +if (__isManagerRunning()) { + $op = array( + "pc" => 50, + "text" => "Stopping all ticks", + "delay" => 1, + "to" => "cg_step6.php" + ); +} else { + $op = array( + "pc" => 60, + "text" => "Generating configuration", + "delay" => 1, + "to" => "cg_step8.php" + ); +} + +include('cg_operation.inc'); + +?> diff --git a/admin/cg_step6.php b/admin/cg_step6.php new file mode 100644 index 0000000..cedc6ae --- /dev/null +++ b/admin/cg_step6.php @@ -0,0 +1,35 @@ + 60, + "text" => "Generating configuration", + "delay" => 1, + "to" => "cg_step8.php" + ); +} else { + touch($config['cachedir'] . "/stop_ticks"); + + $op = array( + "pc" => 55, + "text" => "Waiting for ticks to stop", + "delay" => 1, + "to" => "cg_step7.php" + ); +} + +include('cg_operation.inc'); + +?> diff --git a/admin/cg_step7.php b/admin/cg_step7.php new file mode 100644 index 0000000..a8916b2 --- /dev/null +++ b/admin/cg_step7.php @@ -0,0 +1,33 @@ + 55, + "text" => "Waiting for ticks to stop", + "delay" => 1, + "to" => "cg_step7.php" + ); +} else { + $op = array( + "pc" => 60, + "text" => "Generating configuration", + "delay" => 1, + "to" => "cg_step8.php" + ); +} + +include('cg_operation.inc'); + +?> diff --git a/admin/cg_step8.php b/admin/cg_step8.php new file mode 100644 index 0000000..7ee1a49 --- /dev/null +++ b/admin/cg_step8.php @@ -0,0 +1,109 @@ +DATABASE CONNECTION ERROR"; + exit(1); + } + $query = pg_query("SELECT * FROM main.ctf_map_def ORDER BY name"); + if (!$query) { + print "DATABASE ERROR"; + exit(1); + } + + $ctfMaps = array(); + while ($r = pg_fetch_assoc($query)) { + $ctfMaps[$r['id']] = $r; + $query2 = pg_query("SELECT COUNT(*) FROM main.ctf_map_layout WHERE map={$r['id']} AND spawn_here"); + if (!$query2) { + print "DATABASE ERROR"; + exit(1); + } + list($ctfMaps[$r['id']]['players']) = pg_fetch_array($query2); + pg_free_result($query2); + } + pg_free_result($query); + pg_close($cnx); + + $map = $ctfMaps[$_SESSION['lw_new_game']['ctfmap']]; + $cParams = $_SESSION['lw_new_game']['ctfparams']; + + $params = array( + 'usemap' => $map['id'], + 'maxplayers' => $map['players'], + 'norealloc' => 1, + 'partialtechs' => 0, + 'lockalliances' => $map['alliances'], + 'alliancecap' => 0, + 'victory' => 2, + 'novacation' => 1 + ); + + foreach ($cParams as $p => $v) { + $params[$p] = $v; + } + + return $params; +} + + +$ns = $_SESSION['lw_new_game']['found_id']; +$newConfig = "\t\t\n" + . "\t\t\n" + . "\t\t\t\n"; + +if ($_SESSION['lw_new_game']['game_type'] == 'c') { + $_SESSION['lw_new_game']['params'] = makeCTFParams(); +} + +foreach ($_SESSION['lw_new_game']['params'] as $p => $v) { + $newConfig .= "\t\t\t\n"; +} +$newConfig .= "\n" + . "\t\t\t\n" + . "\t\t\t\n" + . "\t\t\t\t" . $_SESSION['lw_new_game']['descr'] . "\n" + . "\t\t\t\n\n" + . "\t\t\t\n\n"; + +require_once("cg_ticks_schedule.inc"); +$ticks = __computeTicks($_SESSION['lw_new_game']['ft_y'], + $_SESSION['lw_new_game']['ft_m'], $_SESSION['lw_new_game']['ft_d'], + $_SESSION['lw_new_game']['speed'], $_SESSION['lw_new_game']['shift_ticks']); + +foreach ($ticks as $tid => $data) { + $time = $data[1]; + $time = ($time - ($secs = $time % 60)) / 60; + $time = ($time - ($mins = $time % 60)) / 60; + $newConfig .= "\t\t\t\n" + . "\t\t\t\n"; +} +$newConfig .= "\t\t\n"; + +$file = fopen($aConfig['ctrlPath'] . "/config.$ns.xml", "w"); +fwrite($file, $newConfig); +fclose($file); + +$op = array( + "pc" => 70, + "text" => "Merging configuration", + "delay" => 1, + "to" => "cg_step9.php" +); + +include('cg_operation.inc'); + +?> diff --git a/admin/cg_step9.php b/admin/cg_step9.php new file mode 100644 index 0000000..710a382 --- /dev/null +++ b/admin/cg_step9.php @@ -0,0 +1,27 @@ + 75, + "text" => "Initialising universe", + "delay" => 1, + "to" => "cg_step10.php" +); + +include('cg_operation.inc'); + +?> diff --git a/admin/cg_ticks_schedule.inc b/admin/cg_ticks_schedule.inc new file mode 100644 index 0000000..7f53509 --- /dev/null +++ b/admin/cg_ticks_schedule.inc @@ -0,0 +1,86 @@ + array( + 'now' => false, + 'change' => true, + 'interval' => 60 * 60, + 'desync' => 60 * 60 + ), + "battle" => array( + 'now' => false, + 'change' => true, + 'interval' => 4 * 60 * 60, + 'desync' => 55 * 60, + ), + "cash" => array( + 'now' => false, + 'change' => true, + 'interval' => 12 * 60 * 60, + 'desync' => (12 * 60 + 5) * 60 + ), + "day" => array( + 'now' => false, + 'change' => true, + 'interval' => 24 * 60 * 60, + 'desync' => (24 * 60 + 10) * 60 + ), + "move" => array( + 'now' => false, + 'change' => true, + 'interval' => 60, + 'desync' => 60 * 60 + 27 + ), + + // Immediate ticks + 'universe' => array( + 'now' => true, + 'change' => false, + 'interval' => 60 * 5, + 'desync' => 57 + ), + 'punishment' => array( + 'now' => true, + 'change' => false, + 'interval' => 39, + 'desync' => 28 + ), + + // Immutable ticks + 'quit' => array( + 'now' => false, + 'change' => false, + 'interval' => 50, + 'desync' => 7 + ), + 'sales' => array( + 'now' => false, + 'change' => false, + 'interval' => 21, + 'desync' => 13 + ), + ); + + $startTime = gmmktime(7, 0, 0, $month, $day, $year); + $now = time(); + $results = array(); + foreach ($tickDefs as $tid => $def) { + $interval = $def['change'] ? ceil($def['interval']/$speed) : $def['interval']; + if ($def['now']) { + $first = $startTime + $def['desync']; + while ($first > $now) { + $first -= $def['interval']; + } + } else { + $first = $startTime + ($def['change'] ? ceil($def['desync']/$speed) : $def['desync']); + } + $results[$tid] = array($first + $shift * 60, $interval); + } + + return $results; +} + +?> diff --git a/admin/cg_user_dsp_1.inc b/admin/cg_user_dsp_1.inc new file mode 100644 index 0000000..e7e079c --- /dev/null +++ b/admin/cg_user_dsp_1.inc @@ -0,0 +1,12 @@ +

Step 1 / 6 : game type

+

Please select the type of game you want to create from the form below.

+
+

+ />
+ />
+ /> +

+

+ +

+
diff --git a/admin/cg_user_dsp_2.inc b/admin/cg_user_dsp_2.inc new file mode 100644 index 0000000..6d3715c --- /dev/null +++ b/admin/cg_user_dsp_2.inc @@ -0,0 +1,30 @@ +

Step 2 / 6 : game description

+

Please type in the name and description of the new game.

+
+

+ Game name: Too short!\n"; break; + case 2: print " Invalid characters!\n"; break; +endswitch; + +?>
+ This is the name that will appear in menus and lists. +

+

+ Description: Too short!\n"; break; + case 2: print " Invalid characters!\n"; break; +endswitch; + +?>
+ This is the description that will appear next to the name on people's account pages. +

+

+ + +

+
diff --git a/admin/cg_user_dsp_3.inc b/admin/cg_user_dsp_3.inc new file mode 100644 index 0000000..1c73a77 --- /dev/null +++ b/admin/cg_user_dsp_3.inc @@ -0,0 +1,111 @@ + 'maxplayers', + 'title' => 'Maximum amount of players', + 'notes' => '0 for unlimited, -1 for "1/system"' + ), + array( + 'id' => 'minsystems', + 'title' => 'Minimum amount of free systems', + 'notes' => 'Also influences the initial universe' + ), + array( + 'id' => 'maxsystems', + 'title' => 'Maximum amount of systems', + 'notes' => '0 for unlimited' + ), + array( + 'id' => 'partialtechs', + 'title' => 'Partial tech graph', + 'notes' => '0 for complete tech graph, 1 for partial' + ), + array( + 'id' => 'initialcash', + 'title' => 'Initial amount of money', + 'notes' => '0 for default (20,000)' + ), + array( + 'id' => 'zonesize', + 'title' => 'Universe generator: zone size', + 'notes' => '0 = default (5x5); 1 for 3x3, 2 for 5x5, etc...' + ), + array( + 'id' => 'nebulaprob', + 'title' => 'Probability of a nebula', + 'notes' => '0 .. 20, -1 = default' + ), + array( + 'id' => 'norealloc', + 'title' => 'Prevent planet reallocation', + 'notes' => '0 to allow players to get new planets' + ), + array( + 'id' => 'novacation', + 'title' => 'Disable vacation mode', + 'notes' => '0 = normal / 1 = disable' + ), + array( + 'id' => 'victory', + 'title' => 'Victory conditions', + 'notes' => '0 = none / 1 = alliance holds 75% of the planets for 1 week' + ), + array( + 'id' => 'lockalliances', + 'title' => 'Use predefined alliances', + 'notes' => 'Disabled if smaller than 2; maximum value = 8' + ), + array( + 'id' => 'alliancecap', + 'title' => 'Alliance capping %', + 'notes' => '% of players; 0 to disable (no effect on predefined alliances)' + ), + array( + 'id' => 'prot_after', + 'title' => 'Protection after', + 'notes' => 'Day ticks. Only applies to rounds.' + ), + array( + 'id' => 'prot_duration', + 'title' => 'Protection duration', + 'notes' => 'Day ticks. Only applies to rounds.' + ) +); + +?> +

Step 3 / 6 : parameters

+

Define the new game's parameters here.

+
+ +"; +} + +?> + +
" . htmlentities($p['title']) . " : " + . "" + . htmlentities($p['notes']) . "
+

+ + +

+
+ diff --git a/admin/cg_user_dsp_3ctf.inc b/admin/cg_user_dsp_3ctf.inc new file mode 100644 index 0000000..139ecf7 --- /dev/null +++ b/admin/cg_user_dsp_3ctf.inc @@ -0,0 +1,137 @@ +DATABASE CONNECTION ERROR"; + exit(1); + } + $query = pg_query("SELECT * FROM main.ctf_map_def ORDER BY name"); + if (!$query) { + print "DATABASE ERROR"; + exit(1); + } + + $ctfMaps = array(); + while ($r = pg_fetch_assoc($query)) { + $ctfMaps[$r['id']] = $r; + $query2 = pg_query("SELECT COUNT(*) FROM main.ctf_map_layout WHERE map={$r['id']} AND spawn_here"); + if (!$query2) { + print "DATABASE ERROR"; + exit(1); + } + list($ctfMaps[$r['id']]['players']) = pg_fetch_array($query2); + pg_free_result($query2); + } + pg_free_result($query); + + pg_close($cnx); +} + +if (!is_array($errors)) { + $errors = array(); +} + +$params = array( + array( + 'id' => 'initialcash', + 'title' => 'Initial amount of money', + 'notes' => '0 for default (20,000)' + ), + array( + 'id' => 'v2time', + 'title' => 'Time for victory', + 'notes' => 'Number of hours an alliance must hold the targets to win' + ), + array( + 'id' => 'v2grace', + 'title' => 'Grace period', + 'notes' => 'Number of hours an alliance has to retake the targets. Set to 0 to disable.' + ), + array( + 'id' => 'v2points', + 'title' => 'Points per victory', + 'notes' => 'Number of points an alliance gains at each victory. Must be > 0 and < 100' + ) +); + +?> +

Step 3 / 6 : parameters

+ +

Define the new game's parameters here.

+
+
 
+ + + + + +"; +} + +?> +
'> + Map to use: + + +
" . htmlentities($p['title']) . " : " + . "" + . htmlentities($p['notes']) . "
+

+ + +

+
diff --git a/admin/cg_user_dsp_4.inc b/admin/cg_user_dsp_4.inc new file mode 100644 index 0000000..880af0d --- /dev/null +++ b/admin/cg_user_dsp_4.inc @@ -0,0 +1,54 @@ + "-- select speed --", + "1" => "Normal", + "2" => "2x", + "3" => "3x", + "4" => "4x" +); + +?> +

Step 4 / 6 : ticks configuration

+

Please indicate the speed of the game and the day the game will begin on.

+
+

+ Game speed: + (4x games = bad for the server!) +

+

+ First tick on (YYYY-MM-DD): + - + - +
+Invalid date! Must be in the future."; +} else { + print "Ticks will start on the specified day, at the first hour tick after 7:00 AM"; +} + +?> +

+

+ Shift ticks by minutes +

+

+ + +

+
+ diff --git a/admin/cg_user_dsp_5.inc b/admin/cg_user_dsp_5.inc new file mode 100644 index 0000000..741b8ea --- /dev/null +++ b/admin/cg_user_dsp_5.inc @@ -0,0 +1,43 @@ +DATABASE CONNECTION ERROR"; + exit(1); + } + $query = pg_query("SELECT id,name FROM main.account WHERE admin AND NOT name LIKE 'AI>%' ORDER BY name"); + if (!$query) { + print "DATABASE CONNECTION ERROR"; + exit(1); + } + + $allAdmins = array(); + while ($r = pg_fetch_row($query)) { + $allAdmins[$r[0]] = $r[1]; + } + + pg_close($cnx); +} + +?> +

Step 5 / 6 : silent admins

+

"Silent admins" are admins that will join the game for administrative purposes but won't play it. Please beware with this function, as a silent admin will no longer be able to join the game as a player.

+
+

+ $name) { +?> + />
+ +

+

+ + +

+
diff --git a/admin/cg_user_dsp_6.inc b/admin/cg_user_dsp_6.inc new file mode 100644 index 0000000..5190370 --- /dev/null +++ b/admin/cg_user_dsp_6.inc @@ -0,0 +1,213 @@ +DATABASE CONNECTION ERROR"; + exit(1); +} +$query = pg_query("SELECT id,name FROM main.account WHERE admin AND NOT name LIKE 'AI>%' ORDER BY name"); +if (!$query) { + print "DATABASE CONNECTION ERROR"; + exit(1); +} + +$allAdmins = array(); +while ($r = pg_fetch_row($query)) { + $allAdmins[$r[0]] = $r[1]; +} + +$query = pg_query("SELECT * FROM main.ctf_map_def ORDER BY name"); +if (!$query) { + print "DATABASE ERROR"; + exit(1); +} + +$ctfMaps = array(); +while ($r = pg_fetch_assoc($query)) { + $ctfMaps[$r['id']] = $r; + $query2 = pg_query("SELECT COUNT(*) FROM main.ctf_map_layout WHERE map={$r['id']} AND spawn_here"); + if (!$query2) { + print "DATABASE ERROR"; + exit(1); + } + list($ctfMaps[$r['id']]['players']) = pg_fetch_array($query2); + pg_free_result($query2); +} +pg_free_result($query); + +pg_close($cnx); + + +function __start($title) { +?> +

+ + + + + + +\n"; +} + +$params = array( + array( + 'id' => 'maxplayers', + 'title' => 'Maximum amount of players', + ), + array( + 'id' => 'minsystems', + 'title' => 'Minimum amount of free systems', + ), + array( + 'id' => 'maxsystems', + 'title' => 'Maximum amount of systems', + ), + array( + 'id' => 'partialtechs', + 'title' => 'Partial tech graph', + ), + array( + 'id' => 'initialcash', + 'title' => 'Initial amount of money', + ), + array( + 'id' => 'zonesize', + 'title' => 'Universe generator: zone size', + ), + array( + 'id' => 'nebulaprob', + 'title' => 'Probability of a nebula', + ), + array( + 'id' => 'norealloc', + 'title' => 'Prevent planet reallocation', + ), + array( + 'id' => 'novacation', + 'title' => 'Disable vacation mode', + ), + array( + 'id' => 'victory', + 'title' => 'Victory conditions', + ), + array( + 'id' => 'lockalliances', + 'title' => 'Use predefined alliances', + ), + array( + 'id' => 'alliancecap', + 'title' => 'Alliance capping %', + ) +); + +$ctfParams = array( + array( + 'id' => 'initialcash', + 'title' => 'Initial amount of money', + ), + array( + 'id' => 'v2time', + 'title' => 'Time for victory', + ), + array( + 'id' => 'v2grace', + 'title' => 'Grace period', + ), + array( + 'id' => 'v2points', + 'title' => 'Points per victory', + ) +); + +?> +

Step 6 / 6 : recap

+

Please check the information below CAREFULLY.

+ + 'Match', + 'r' => 'Round', + 'c' => 'Kings of the Hill' +); + +__start("Main information"); +__line("Game type", $gTypes[$_SESSION['lw_new_game']['game_type']]); +__line("Game name", $_SESSION['lw_new_game']['name']); +__line("Description", $_SESSION['lw_new_game']['descr']); +__end(); + +__start("Parameters"); +if ($_SESSION['lw_new_game']['game_type'] == 'c') { + $map = $ctfMaps[$_SESSION['lw_new_game']['ctfmap']]; + __line("Map name", $map['name']); + if ($map['description'] != '') { + __line("Description", $map['description']); + } + __line("Players", $map['players']); + __line("Teams", $map['alliances']); + __line("Size", $map['width'] . "x" . $map['height']); + foreach ($ctfParams as $p) { + __line($p['title'], $_SESSION['lw_new_game']['ctfparams'][$p['id']]); + } +} else { + foreach ($params as $p) { + __line($p['title'], $_SESSION['lw_new_game']['params'][$p['id']]); + } +} +__end(); + +$speeds = array("Normal", "2x", "3x", "4x"); +__start("Ticks configuration"); +__line("Speed", $speeds[$_SESSION['lw_new_game']['speed'] - 1]); +__line("First tick (YYYY-MM-DD)", $_SESSION['lw_new_game']['ft_y'] . "-" . + $_SESSION['lw_new_game']['ft_m'] . "-" . $_SESSION['lw_new_game']['ft_d']); +__end(); + +$ticks = __computeTicks($_SESSION['lw_new_game']['ft_y'], + $_SESSION['lw_new_game']['ft_m'], $_SESSION['lw_new_game']['ft_d'], + $_SESSION['lw_new_game']['speed'], $_SESSION['lw_new_game']['shift_ticks']); + +__start("Ticks schedule"); +foreach ($ticks as $tid => $data) { + __line("\"$tid\" / first tick", gmstrftime("%H:%M:%S on %Y-%m-%d", $data[0])); + $time = $data[1]; + $time = ($time - ($secs = $time % 60)) / 60; + $time = ($time - ($mins = $time % 60)) / 60; + __line("\"$tid\" / interval", "{$time}h, {$mins}min, {$secs}s"); + __line("\"$tid\" / numeric values", $data[0] . " / " . $data[1]); +} +__end(); + +__start("Silent admins"); +foreach ($allAdmins as $id => $name) { + __line($name, in_array($id, $_SESSION['lw_new_game']['silent']) ? "YES" : "no"); +} +__end(); + +__start("Server information"); +__line("Maintenance mode", is_null($config['maintenance']) ? "No - will enable automatically (DANGEROUS)" : "yes"); +__line("Ticks manager", __isManagerRunning() ? "running" : "stopped"); +__end(); + +?> + +

+ + +

+ diff --git a/admin/cg_user_dsp_7.inc b/admin/cg_user_dsp_7.inc new file mode 100644 index 0000000..c57f05d --- /dev/null +++ b/admin/cg_user_dsp_7.inc @@ -0,0 +1,23 @@ + +


+
+


+

LAST CHANCE

+

+ +

+ This is your last chance to get back.
+ You are about to create a new LegacyWorlds game, which is no small operation.
+
+ Please, be reasonable and stop now, while you can. Any client crash during the operation could cause the server to be unusable.
+
+
+
+ Ok, you're still reading. That's bad news.
+ Scroll down to access the next step. +

+ +

































































+

Alea jacta est.

diff --git a/admin/cg_user_hdl_1.inc b/admin/cg_user_hdl_1.inc new file mode 100644 index 0000000..f6d634a --- /dev/null +++ b/admin/cg_user_hdl_1.inc @@ -0,0 +1,8 @@ + diff --git a/admin/cg_user_hdl_2.inc b/admin/cg_user_hdl_2.inc new file mode 100644 index 0000000..91637c0 --- /dev/null +++ b/admin/cg_user_hdl_2.inc @@ -0,0 +1,27 @@ + diff --git a/admin/cg_user_hdl_3.inc b/admin/cg_user_hdl_3.inc new file mode 100644 index 0000000..a1e14e8 --- /dev/null +++ b/admin/cg_user_hdl_3.inc @@ -0,0 +1,73 @@ + array( + 'maxplayers' => -1, + 'minsystems' => 30, + 'maxsystems' => 30, + 'norealloc' => 1, + 'partialtechs' => 0, + 'initialcash' => 40000, + 'zonesize' => 1, + 'nebulaprob' => 15, + 'lockalliances' => 4, + 'alliancecap' => 0, + 'victory' => 1, + 'novacation' => 1, + 'prot_after' => 0, + 'prot_duration' => 0, + ), + 'r' => array( + 'maxplayers' => 0, + 'minsystems' => 40, + 'maxsystems' => 0, + 'norealloc' => 0, + 'partialtechs' => 1, + 'initialcash' => 0, + 'zonesize' => 1, + 'nebulaprob' => -1, + 'lockalliances' => 0, + 'alliancecap' => 20, + 'victory' => 0, + 'novacation' => 0, + 'prot_after' => 20, + 'prot_duration' => 20, + ) +); +$pList = array_keys($pDefaults['m']); + + +if (!is_array($_SESSION['lw_new_game']['params'])) { + $_SESSION['lw_new_game']['params'] = array(); +} + +if ($_POST['gtdef']) { + $_SESSION['lw_new_game']['params'] = $pDefaults[$_SESSION['lw_new_game']['game_type']]; +} elseif ($_POST['go']) { + $errors = array(); + foreach ($pList as $pid) { + $val = $_POST["p_$pid"]; + if (strcmp($val, (string)(int)$val)) { + array_push($errors, $pid); + } + $_SESSION['lw_new_game']['params'][$pid] = $val; + } + if (! count($errors)) { + $_SESSION['lw_new_game']['step'] = 4; + } +} elseif ($_POST['back']) { + foreach ($pList as $pid) { + $_SESSION['lw_new_game']['params'][$pid] = $_POST["p_$pid"]; + } + $_SESSION['lw_new_game']['step'] = 2; +} + +?> diff --git a/admin/cg_user_hdl_3ctf.inc b/admin/cg_user_hdl_3ctf.inc new file mode 100644 index 0000000..d11e7fd --- /dev/null +++ b/admin/cg_user_hdl_3ctf.inc @@ -0,0 +1,66 @@ +DATABASE CONNECTION ERROR"; + exit(1); +} +$query = pg_query("SELECT * FROM main.ctf_map_def ORDER BY name"); +if (!$query) { + print "DATABASE ERROR"; + exit(1); +} + +$ctfMaps = array(); +while ($r = pg_fetch_assoc($query)) { + $ctfMaps[$r['id']] = $r; + $query2 = pg_query("SELECT COUNT(*) FROM main.ctf_map_layout WHERE map={$r['id']} AND spawn_here"); + if (!$query2) { + print "DATABASE ERROR"; + exit(1); + } + list($ctfMaps[$r['id']]['players']) = pg_fetch_array($query2); + pg_free_result($query2); +} +pg_free_result($query); +pg_close($cnx); + + +$pList = array('initialcash', 'v2time','v2grace','v2points'); + + + +if (!is_array($_SESSION['lw_new_game']['ctfparams'])) { + $_SESSION['lw_new_game']['ctfparams'] = array(); +} + +if ($_POST['go']) { + $errors = array(); + + $mapID = (int) $_POST['map']; + if (!array_key_exists($mapID, $ctfMaps)) { + $errors[0] = 'map'; + } else { + $_SESSION['lw_new_game']['ctfmap'] = $mapID; + } + + foreach ($pList as $pid) { + $val = $_POST["p_$pid"]; + if (strcmp($val, (string)(int)$val)) { + array_push($errors, $pid); + } + $_SESSION['lw_new_game']['ctfparams'][$pid] = $val; + } + if (! count($errors)) { + $_SESSION['lw_new_game']['step'] = 4; + } +} elseif ($_POST['back']) { + foreach ($pList as $pid) { + $_SESSION['lw_new_game']['ctfparams'][$pid] = $_POST["p_$pid"]; + } + $_SESSION['lw_new_game']['ctfmap'] = $_POST['map']; + $_SESSION['lw_new_game']['step'] = 2; +} + +?> diff --git a/admin/cg_user_hdl_4.inc b/admin/cg_user_hdl_4.inc new file mode 100644 index 0000000..b452f09 --- /dev/null +++ b/admin/cg_user_hdl_4.inc @@ -0,0 +1,30 @@ + diff --git a/admin/cg_user_hdl_5.inc b/admin/cg_user_hdl_5.inc new file mode 100644 index 0000000..63ef5b1 --- /dev/null +++ b/admin/cg_user_hdl_5.inc @@ -0,0 +1,44 @@ +DATABASE CONNECTION ERROR"; + exit(1); +} +$query = pg_query("SELECT id,name FROM main.account WHERE admin AND NOT name LIKE 'AI>%' ORDER BY name"); +if (!$query) { + print "DATABASE CONNECTION ERROR"; + exit(1); +} + +$allAdmins = array(); +while ($r = pg_fetch_row($query)) { + $allAdmins[$r[0]] = $r[1]; +} + +pg_close($cnx); + + +$sa = $_POST['sa']; +if (!is_array($sa)) { + $sa = array(); +} +if ($_POST['go'] || $_POST['back']) { + $_SESSION['lw_new_game']['silent'] = array(); + foreach ($sa as $saId) { + if (array_key_exists($saId, $allAdmins)) { + array_push($_SESSION['lw_new_game']['silent'], $saId); + } + } +} elseif (!is_array($_SESSION['lw_new_game']['silent'])) { + $_SESSION['lw_new_game']['silent'] = array(); +} + +if ( $_POST['back']) { + $_SESSION['lw_new_game']['step'] = 4; +} elseif ($_POST['go']) { + $_SESSION['lw_new_game']['step'] = 6; +} + +?> diff --git a/admin/cg_user_hdl_6.inc b/admin/cg_user_hdl_6.inc new file mode 100644 index 0000000..31abeab --- /dev/null +++ b/admin/cg_user_hdl_6.inc @@ -0,0 +1,10 @@ + diff --git a/admin/cg_user_hdl_7.inc b/admin/cg_user_hdl_7.inc new file mode 100644 index 0000000..142f6fb --- /dev/null +++ b/admin/cg_user_hdl_7.inc @@ -0,0 +1,8 @@ + diff --git a/admin/cg_user_hdr.inc b/admin/cg_user_hdr.inc new file mode 100644 index 0000000..b3c3b32 --- /dev/null +++ b/admin/cg_user_hdr.inc @@ -0,0 +1,9 @@ + + + LegacyWorlds Beta 5 > Administration > Create game + + +

LWB5 > Administration > Create game

+

+ This administrative tools allows you to create new LegacyWorlds games. +

diff --git a/admin/config.inc b/admin/config.inc new file mode 100644 index 0000000..349120e --- /dev/null +++ b/admin/config.inc @@ -0,0 +1,39 @@ + 'legacyworlds', + "password" => 'password for the standard database user', + "adminUser" => 'legacyworlds_admin', + "adminPassword" => 'password for the administrative database user', + "ctrlFifo" =>'/tmp/.lwFifo', + "ctrlPath" =>'/tmp/.lwControl' +); + + +function __dbConnect($admin = false) { + global $aConfig; + $u = $aConfig[$admin ? 'adminUser' : 'user']; + $p = $aConfig[$admin ? 'adminPassword' : 'password']; + $cString = "dbname='legacyworlds' host='127.0.0.1' sslmode='prefer' user='$u' password='$p'"; + return pg_connect($cString); +} + + +function __sendControl($command) { + global $aConfig; + $fName = $aConfig['ctrlFifo']; + if (!file_exists($fName)) { + return false; + } + + $fifo = @fopen($fName, "w"); + if ($fifo === false) { + return false; + } + fwrite($fifo, "$command\n"); + fclose($fifo); + return true; +} + + +?> diff --git a/admin/create.php b/admin/create.php new file mode 100644 index 0000000..265a012 --- /dev/null +++ b/admin/create.php @@ -0,0 +1,19 @@ + 1 + ); +} + +include("cg_user_hdl_" . $_SESSION['lw_new_game']['step'] . ".inc"); + +include("cg_user_hdr.inc"); +include("cg_user_dsp_" . $_SESSION['lw_new_game']['step'] . ".inc"); + +?> + + diff --git a/admin/ctf_map.inc b/admin/ctf_map.inc new file mode 100644 index 0000000..13ca036 --- /dev/null +++ b/admin/ctf_map.inc @@ -0,0 +1,425 @@ +x = $x; + $this->y = $y; + $this->type = $type; + $this->alloc = $alloc; + if (is_bool($spawn)) { + $this->spawn = $spawn; + } elseif (is_string($spawn)) { + $this->spawn = ($spawn == 't'); + } else { + $this->spawn = null; + } + } + + public function setType($type) { + if (is_null($type) || !in_array((string) $type, array('S', '1', '2', '3', '4'))) { + throw new Exception('Invalid type'); + } + + $this->type = $type; + if ($this->type != 'S') { + $this->alloc = $this->spawn = null; + } elseif (is_null($this->alloc)) { + $this->alloc = 0; + $this->spawn = null; + } + } + + public function setAllocation($alloc) { + if ($alloc < 0 || $this->type != 'S') { + throw new Exception('Invalid allocation type'); + } + $this->alloc = $alloc; + + if ($this->alloc != 0 && is_null($this->spawn)) { + $this->spawn = false; + } elseif ($this->alloc = 0) { + $this->spawn = null; + } + } + + public function setSpawn($spawn) { + if (is_null($this->alloc) || $this->alloc == 0 || !is_bool($spawn)) { + throw new Exception("Invalid spawning value"); + } + $this->spawn = $spawn; + } + + + public function getType() { + return $this->type; + } + + public function getAllocation() { + return $this->alloc; + } + + public function getSpawn() { + return $this->spawn; + } + + public function getX() { + return $this->x; + } + + public function getY() { + return $this->y; + } + + + public function store($db, $map) { + $q = pg_query_params($db, "INSERT INTO main.ctf_map_layout(map,sys_x,sys_y,sys_type,alloc_for,spawn_here)" + . " VALUES ($map,$1,$2,$3,$4,$5)", array( + $this->x, $this->y, $this->type, $this->alloc, + ($this->type == 'S' && $this->alloc > 0) ? ($this->spawn ? 't' : 'f') : null + )); + if (!$q) { + throw new Exception("Failed to store system at {$this->x};{$this->y}"); + } + } +} + + +class ctf_map { + + private $dbID; + private $name; + private $description; + private $width; + private $height; + private $alliances; + private $map; + + public static function allMaps() { + $db = __dbConnect(); + if (! $db) { + throw new Exception('Unable to connect to the database'); + } + + $result = pg_query($db, "SELECT id FROM main.ctf_map_def ORDER BY id"); + if (!$result) { + throw new Exception("Database error"); + } + + $IDs = array(); + while ($row = pg_fetch_array($result)) { + array_push($IDs, $row[0]); + } + + pg_free_result($result); + pg_close($db); + + $maps = array(); + foreach ($IDs as $id) { + $maps[$id] = new ctf_map($id); + } + return $maps; + } + + public function __construct($dbID = null) { + if (is_null($dbID)) { + $this->dbID = $this->name = $this->description = null; + $this->alliances = 2; + $this->width = $this->height = 3; + } else { + $db = __dbConnect(); + if (! $db) { + throw new Exception("Unable to connect to the database"); + } + + $result = pg_query($db, "SELECT * FROM main.ctf_map_def WHERE id = $dbID"); + if (!($result && pg_num_rows($result) == 1)) { + pg_close($db); + throw new Exception("Definition '$dbID' not found"); + } + + $row = pg_fetch_assoc($result); + pg_free_result($result); + pg_close($db); + + $this->dbID = $dbID; + $this->name = $row['name']; + $this->description = $row['description']; + $this->width = $row['width']; + $this->height = $row['height']; + $this->alliances = $row['alliances']; + } + $this->map = null; + } + + + public function getID() { + return $this->dbID; + } + + public function getName() { + return $this->name; + } + + public function getDescription() { + return $this->description; + } + + public function getWidth() { + return $this->width; + } + + public function getHeight() { + return $this->height; + } + + public function getAlliances() { + return $this->alliances; + } + + + public function setName($value) { + $this->name = $value; + } + + public function setDescription($value) { + $this->description = ($value == '') ? null : $value; + } + + public function setAlliances($value) { + $this->alliances = $value; + } + + + public function setWidth($value) { + if (is_null($this->map) && !is_null($this->dbID)) { + $this->load(); + } + + $this->width = $value; + if (is_array($this->map)) { + $this->initBlankMap(); + } + } + + public function setHeight($value) { + if (is_null($this->map) && !is_null($this->dbID)) { + $this->load(); + } + + $this->height = $value; + if (is_array($this->map)) { + $this->initBlankMap(); + } + } + + + public function getMapInfo($x, $y) { + if (! is_array($this->map)) { + if ($this->dbID) { + $this->load(); + } else { + return null; + } + } + + $offset = $this->getOffset($x, $y); + return $this->map[$offset]; + } + + + public function setSystemType($x, $y, $t) { + if (! is_array($this->map)) { + if ($this->dbID) { + $this->load(); + } else { + $this->initBlankMap(); + } + } + + $offset = $this->getOffset($x, $y); + $v = $this->map[$offset]; + + if (is_null($v)) { + $v = $this->map[$offset] = new ctf_map_sys($x, $y, $t); + } else { + $v->setType($t); + } + } + + public function setSystemAlloc($x, $y, $a) { + if (! is_array($this->map)) { + if ($this->dbID) { + $this->load(); + } else { + $this->initBlankMap(); + } + } + + $offset = $this->getOffset($x, $y); + $v = $this->map[$offset]; + + if (is_null($v)) { + $v = $this->map[$offset] = new ctf_map_sys($x, $y, 'S'); + } + if ($v->getType() != 'S') { + throw new Exception("Can't set allocation type for a nebula"); + } + if ($a < 0 || $a > $this->alliances) { + throw new Exception("Invalid allocation type $a"); + } + $v->setAllocation($a); + } + + public function setSystemSpawn($x, $y, $s) { + if (! is_array($this->map)) { + if ($this->dbID) { + $this->load(); + } else { + $this->initBlankMap(); + } + } + + $offset = $this->getOffset($x, $y); + $v = $this->map[$offset]; + + if (is_null($v)) { + $v = $this->map[$offset] = new ctf_map_sys($x, $y, 'S', 1, $s); + } else { + if ($v->getType() != 'S' || $v->getAllocation() < 1) { + throw new Exception("Can't set spawning point"); + } + $v->setSpawn($s); + } + } + + + public function jsDump() { + $parts = array(); + + if (is_null($this->map)) { + if ($this->dbID) { + $this->load(); + } else { + $this->initBlankMap(); + } + } + + foreach ($this->map as $s) { + if (is_null($s)) { + continue; + } + $str = '[' . $s->getX() . ',' . $s->getY() . ',"' . $s->getType() . '",'; + if ($s->getType() != 'S') { + $str .= 'null,null'; + } else { + $str .= $s->getAllocation() . ','; + if ($s->getAllocation()) { + $str .= $s->getSpawn() ? 'true' : 'false'; + } else { + $str .= 'null'; + } + } + array_push($parts, "$str]"); + } + + return join(',', $parts); + } + + + public function save() { + $db = __dbConnect(); + if (!$db) { + throw new Exception("Database error"); + } + + pg_query($db, "BEGIN TRANSACTION"); + pg_query($db, "SET search_path = public, main"); + if ($this->dbID) { + $id = $this->dbID; + pg_query_params($db, "UPDATE main.ctf_map_def SET name=$2, description=$3, alliances=$4, " + . "width=$5, height=$6 WHERE id=$1", array( + $id, $this->name, $this->description, $this->alliances, + $this->width, $this->height + )); + pg_query($db, "DELETE FROM main.ctf_map_layout WHERE map=$id"); + } else { + pg_query_params($db, "INSERT INTO main.ctf_map_def(name,description,alliances,width,height) " + . "VALUES ($1, $2, $3, $4, $5)", array( + $this->name, $this->description, $this->alliances, + $this->width, $this->height + )); + $q = pg_query($db, "SELECT last_inserted('ctf_map_def')"); + if (!($q && pg_num_rows($q))) { + throw new Exception("Database error"); + } + list($id) = pg_fetch_array($q); + } + + foreach ($this->map as $sys) { + $sys->store($db, $id); + } + + pg_query($db, "COMMIT"); + pg_close($db); + + $this->dbID = $id; + } + + public function destroy() { + if (! $this->dbID) { + return; + } + $db = __dbConnect(); + if (!$db) { + throw new Exception("Database error"); + } + + pg_query($db, "DELETE FROM main.ctf_map_def WHERE id={$this->dbID}"); + pg_close($db); + } + + + private function load() { + $db = __dbConnect(); + if (!$db) { + throw new Exception("Database error"); + } + $result = pg_query($db, "SELECT * FROM main.ctf_map_layout WHERE map = {$this->dbID}"); + if (!($result && pg_num_rows($result) == $this->height * $this->width)) { + throw new Exception("Unable to load map"); + } + + $map = array(); + while ($row = pg_fetch_assoc($result)) { + $offset = $this->getOffset($row['sys_x'], $row['sys_y']); + $map[$offset] = new ctf_map_sys($row['sys_x'], $row['sys_y'], + $row['sys_type'], $row['alloc_for'], $row['spawn_here']); + } + $this->map = $map; + + pg_free_result($result); + pg_close($db); + } + + private function initBlankMap() { + $map = array(); + for ($i = 0; $i < $this->width * $this->height; $i ++) { + $map[$i] = null; + } + $this->map = $map; + } + + private function getOffset($x, $y) { + return $this->width * (floor($this->height / 2) + $y) + floor($this->width / 2) + $x; + } +} + +?> diff --git a/admin/deathofrats.php b/admin/deathofrats.php new file mode 100644 index 0000000..a6c0b20 --- /dev/null +++ b/admin/deathofrats.php @@ -0,0 +1,494 @@ + + + LegacyWorlds Beta 5 > Administration > Death of Rats + + +

LWB5 > Administration > Death of Rats

+

+ The Death of Rats is LegacyWorlds' experimental multi detection tool.
+ Select page: + array('s', 'Current status', 'showStatus'), + 'actions' => array('al', 'Actions taken', 'showActionLog'), + 'finalpoints' => array('fp', 'Decision points', 'showFinalPoints'), + 'ingamelog' => array('il', 'In-game checks', 'showInGameChecks'), + 'multipoints' => array('mp', 'Multiplayer points', 'showMultiPoints'), + 'multilog' => array('ml', 'Multiplayer log', 'showMultiLog'), + 'signlepoints' => array('sp', 'Single player points', 'showSinglePoints'), + 'singlelog' => array('sl', 'Single player events', 'showSingleLog'), + 'execution' => array('e', 'Execution log', 'showExecLog'), +); + +foreach ($pages as $pName => $data) { + if ($pName == $page) { + echo ""; + } else { + echo ""; + } + echo $data[1]; + if ($pName == $page) { + echo " "; + } else { + echo " "; + } +} +echo "

"; + +$func = $pages[$page][2]; +$func(); + + +function showStatus() { +?> +

Current status

+

+ The Death of Rats is still in an experimental stage at this time, and no actual action is taken. What + it would do if it were fully enabled is logged on the "Actions taken" page nonetheless.
+ Most of the information it provides can be trusted, tho; however, if you suspect it sent a warning or "punished" a + player for no good reason, manual checks should be performed. +

+

+ The following checks are performed: +

+
    +
  • use of open proxies
  • +
  • cookie deletion
  • +
  • trying to log on with a banned account
  • +
  • passwords shared between multiple accounts
  • +
  • simple multiing, as well as pass sharing
  • +
  • "vicious" multiing, implying that the cookies are cleared between each use
  • +
  • in-game checks for donations, tech exchanges, gifts and sales, and planet retake after abandon
  • +
+

+ Checks for concurrent session from the same IP as well as more in-game checks (alliance, posts, messages, + TA list, battles) are still missing. +

+

About the different pages

+

+ This tool consists in a few different pages which give different information about the Death of Rats' current status. + These pages are: +

+
    +
  • + Actions taken: the log of all actions the Death of Rats performed. This includes sending + warnings and deciding to punish players. +
  • +
  • + Decision points: the current amount of points for each pair of players that has been investigated thoroughly + by the Death of Rats. Pairs with over 100 points will cause the Death of Rats to act. +
  • +
  • + In-game checks log: the latest 400 entries of the checks performed in-game on suspicious players as well + as the results of these checks. +
  • +
  • + Multiplayer points: the current amount of points for each pair of accounts that could be multiing or + abusing pass-sharing. +
  • +
  • + Multiplayer log: the latest 200 entries of the suspicious events detected between pairs of accounts. +
  • +
  • + Single player points: the current amount of points for each account that has performed suspicious actions. + While these points are normally not a problem, they influence multiplayer scores. +
  • +
  • + Single player log: the latest 200 suspicious events detected for single accounts. +
  • +
  • + Execution log: the latest 200 runs of the Death of Rats. +
  • +
+ +

Previous 200 runs of the Death of Rats

+

+ This page shows the log of the previous 200 executions of the Death of Rats tick. The Changes column indicates + the amount of changes (connection records, password updates) examined; the Events column indicates the amount + of entries added to either the single player log or the multiplayer log. +

+
:
+ + + + + + + + + + + + +
Time & dateChangesEvents
" : ""?>" : ""?>" : ""?>" : ""?>" : ""?>" : ""?>
+ +

Previous 200 single player log entries

+

+ This page shows the log of the previous 200 log entries generated for single players. +

+ + + + + + + "Tried to log on using a banned account", + "PROXY" => "Currently using an open proxy", + "CLCOOK-SIP" => "Cleared cookies from the same IP", + "CLCOOK-DIP" => "Cleared cookies from a different (but close) IP", + ); + + foreach ($entries as $entry) { +?> + + + + + +
Time & dateAccountMessage
+
+ +

Single player "badness points"

+

+ These points correspond to recent suspicious activities from active accounts. +

+ + + + + + + + + + + +
AccountPoints
+ +

Latest 400 in-game checks log entries

+

+ This page shows the log of the latest 400 log entries generated by in-game checks on players. Events logged here + belong to different categories: +

+
    +
  • Somewhat suspicious events: donations of less than €100,000; rejected tech offers
  • +
  • + Suspicious events: donations of less than €1,000,000; pending tech offers; accepted techs offer with + a price greater than €1,000; planets taken by a player within 5 days of being abandonned by the other; sales + of planets or fleets. +
  • +
  • + Highly suspicious events: donations of less than €10,000,000; gifts; tech offers with a price lower + than €1,000. +
  • +
  • + Extremely suspicious events: donations of more than €10,000,000. +
  • +
+ + + + + + + + + + "Verifying accounts", + "VHSE" => "Extremely suspicious in-game events", + "HSE" => "Highly suspicious in-game events", + "SE" => "Suspicious in-game events", + "LSE" => "Somewhat suspicious in-game events" + ); + + foreach ($entries as $entry) { + list($message, $count) = explode('-', $entry['message']); + if ($message == 'CHECK') { + $count = "N/A"; + } +?> + + + + + + + + +
Time & dateGame IDAccount 1Account 2MessageCount
+
+ +

Previous 200 multiplayer log entries

+

+ This page shows the log of the previous 200 log entries generated for pairs of players. +

+ + + + + + + + "Simple multiing / open pass sharing detected", + "SIMPLE-10" => "Simple multiing / open pass sharing detected (within 10 seconds!)", + "PASS" => "Accounts are using the same password", + "NOPASS" => "Accounts are no longer using the same password", + "VICIOUS-LP" => "Potential attempt to conceal pass-sharing", + "VICIOUS-MP" => "Probable attempt to conceal pass-sharing", + "VICIOUS-HP" => "Highly probable attempt to conceal pass-sharing", + ); + + foreach ($entries as $entry) { + $id = explode(',', $entry['id']); + sort($id); + $id = join(',', $id) . "-" . $entry['ts'] . "-" . $entry['message']; + if (in_array($id, $displayed)) { + continue; + } + $displayed[] = $id; +?> + + + + + + +
Time & dateAccount 1Account 2Message
+
+ +

Final decision points

+

+ This page displays the current amount of points for each pair of players that has been investigated thoroughly + by the Death of Rats. Pairs will over 100 points will cause the Death of Rats to act. +

+ + + + + + + + + + + + + +
Account 1Account 2Points
+ +

Multiplayer "badness points"

+

+ This page shows the list of "badness points" between pairs of accounts. The higher the badness points, the more likely + the accounts are multis. +

+ + + + + + + + + + + + + +
Account 1Account 2Points
+ +

Actions performed by the Death of Rats

+

+ This page lists all the actions the Death of Rats has performed. +

+ + + + + + + + "Warned player", + "PUNISH" => "Slaughtered player with a rat-sized scythe" + ); + + foreach ($entries as $entry) { +?> + + + + + + +
Time & dateAccount 1Account 2Message
+
+ + + diff --git a/admin/game_status.php b/admin/game_status.php new file mode 100644 index 0000000..a1bdb17 --- /dev/null +++ b/admin/game_status.php @@ -0,0 +1,242 @@ + + + + LegacyWorlds Beta 5 > Administration > Game status + + +

LWB5 > Administration > Game status

+

Operation in progress...

+

+ A system operation is in progress. Please wait, the page will update in 5 seconds. +

+ + + + array('yellow', 'red'), + "READY" => array('red', 'yellow'), + "RUNNING" => array('white', 'green'), + "VICTORY" => array('yellow', 'blue'), + "ENDING" => array('black', 'yellow'), + "FINISHED" => array("white", "black") + ); + + print "$status"; +} + +function sendFifo($command) { + global $aConfig; + + $fName = $aConfig['ctrlFifo']; + if (!file_exists($fName)) { + return false; + } + + $fifo = fopen($fName, "w"); + fwrite($fifo, "$command\n"); + fclose($fifo); + + redirect(); +} + +function handleCommand($command, $game) { + if ($command == 'mv' && $game->status() == 'PRE') { + __logAdmin("is making game {$game->name} visible"); + sendFifo("READY {$game->name}"); + } elseif ($command == 'te' && $game->status() == 'READY' && $game->firstTick() - time() > 24 * 60 * 60 + 30) { + __logAdmin("made game {$game->name} start 24h earlier"); + sendFifo("START {$game->name} EARLY"); + } elseif ($command == 'tl' && $game->status() == 'READY') { + __logAdmin("made game {$game->name} start 24h later"); + sendFifo("START {$game->name} LATE"); + } elseif ($command == 'en' && ($game->status() == 'RUNNING' || $game->status() == "VICTORY")) { + __logAdmin("terminated game {$game->name}"); + sendFifo("SETEND {$game->name} 0"); + } elseif ($command == 'e24' && $game->status() == 'RUNNING') { + __logAdmin("set game {$game->name} to end in 24h"); + sendFifo("SETEND {$game->name} 24"); + } elseif ($command == 'kr' && $game->status() == 'ENDING') { + __logAdmin("prevented game {$game->name} from ending"); + sendFifo("NOEND {$game->name}"); + } elseif ($command == 'ee' && $game->status() == 'ENDING' && $game->lastTick() - time() > 24 * 60 * 60 + 30) { + __logAdmin("made game {$game->name} end 24h earlier"); + sendFifo("END {$game->name} EARLY"); + } elseif ($command == 'el' && $game->status() == 'ENDING') { + __logAdmin("made game {$game->name} end 24h later"); + sendFifo("END {$game->name} LATE"); + } elseif ($command == 'en' && $game->status() == 'ENDING') { + __logAdmin("terminated game {$game->name}"); + sendFifo("END {$game->name} NOW"); + } +} + + +// Load the list of games +$oldDir = getcwd(); +chdir("../scripts"); +$__logPrefix = "lwControl"; +$__loader = array( + 'log', 'classloader', + 'version', 'game', 'tick', 'config', + 'db_connection', 'db_accessor', 'db', + 'library' +); +require_once("loader.inc"); +chdir($oldDir); + +$games = config::getGames(); +dbConnect(); + + +// Handle commands +if ($_GET['c'] != '') { + $cGame = $_GET['g']; + if (array_key_exists($cGame, $games) && $cGame != 'main') { + handleCommand($_GET['c'], $games[$cGame]); + } +} + + +?> + + + LegacyWorlds Beta 5 > Administration > Game status + + +

LWB5 > Administration > Game status

+ +

Game list

+

+ WARNING: make sure you know what you're doing here - there are no second chances on this page; + if you click something, that "something" will happen at once. +

+ + + + + + + + + $game) { + if ($name == 'main') { + continue; + } + + $status = $game->status(); + $firstTick = $game->firstTick(); + $lastTick = $game->lastTick(); + + print " \n \n"; + printStatus($status); + print " \n"; + print " \n \n \n"; +} + +?> +
IDStatusNameTicksCommands
$name" . htmlentities($game->text) . ""; + if ($firstTick > time()) { + print "Starting at " . gmstrftime('%H:%M:%S on %Y-%m-%d', $firstTick); + } elseif ($lastTick == 0) { + print "Running since " . gmstrftime('%H:%M:%S on %Y-%m-%d', $firstTick); + } elseif ($lastTick > time()) { + print "Running until " . gmstrftime('%H:%M:%S on %Y-%m-%d', $lastTick); + } else { + print "Stopped since " . gmstrftime('%H:%M:%S on %Y-%m-%d', $lastTick); + } + + if ($status == 'PRE') { + $cmd = array( + array('mv', 'Make visible') + ); + } elseif ($status == 'READY') { + $cmd = array( + array('tl', 'Start 24h later') + ); + if ($firstTick - time() > 24 * 60 * 60 + 30) { + array_push($cmd, array('te', 'Start 24h earlier')); + } + } elseif ($status == 'RUNNING') { + $cmd = array( + array('e24', 'End in 24h'), + array('en', 'End now') + ); + } elseif ($status == 'VICTORY') { + $cmd = array( + array('en', 'End game') + ); + } elseif ($status == 'ENDING') { + $cmd = array( + array('el', 'Postpone by 24h') + ); + if ($lastTick - time() > 24 * 60 * 60 + 30) { + array_push($cmd, array('ee', 'End 24h earlier')); + } + array_push($cmd, array('en', 'End now')); + array_push($cmd, array('kr', 'Keep running')); + } else { + $cmd = array(); + } + + print ""; + if (count($cmd)) { + $lk = array(); + foreach ($cmd as $c) { + array_push($lk, "" . $c[1] . ""); + } + print join(' - ', $lk); + } else { + print " "; + } + + print "
+ +

About game status

+

Games can have the following status:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
The game is configured, but is hidden for now
The game is visible, but ticks have not started
The game is running normally
The game is still running but someone reached victory
The game is still available but is about to end.
The game is no longer running and only visible through the rankings page
+ + + diff --git a/admin/index.html b/admin/index.html new file mode 100644 index 0000000..c5b7c43 --- /dev/null +++ b/admin/index.html @@ -0,0 +1,25 @@ + + + LegacyWorlds Beta 5 > Administration + + +

LW Beta 5 administration tools

+ +

Game management

+ + +

Server management

+
    +
  • Ticks: enable / disable the tick manager, run ticks manually
  • +
  • Proxy detector: control the detector's status and manually check for open proxies
  • +
  • Maintenance: activate / de-activate maintenance mode
  • +
  • Maps management: manage maps used for Kings of the Hill games
  • +
  • IRC bot: control the IRC bot
  • +
+ + diff --git a/admin/maintenance.php b/admin/maintenance.php new file mode 100644 index 0000000..170a7f1 --- /dev/null +++ b/admin/maintenance.php @@ -0,0 +1,79 @@ + + + LegacyWorlds Beta 5 > Administration > Maintenance mode + + +

LWB5 > Administration > Maintenance mode

+ time() + $duration * 60, + "reason" => $reason + ); + + $f = fopen($config['cachedir'] . '/maintenance.ser', "w"); + fwrite($f, serialize($maintenance)); + fclose($f); + include('../scripts/config.inc'); + + __logAdmin("put the server in maintenance mode for reason: $reason"); + } +} + +if (is_null($config['maintenance'])) { +?> +

+ Maintenance mode is currently inactive. Please use the form below to activate it. +

+
+

+ Reason for maintenance:
+ Maintenance mode duration: minutes +

+$err

\n"; + } +?> +

+ +

+
+ +

+ Maintenance mode is currently active. +

+

+ Reason:
+ Until: + (current: ). +

+

Disable maintenance mode

+ + + diff --git a/admin/map_edit.js b/admin/map_edit.js new file mode 100644 index 0000000..2453430 --- /dev/null +++ b/admin/map_edit.js @@ -0,0 +1,703 @@ +// Generic text input component +TextInput = function (id, title, multi, min, max, init) { + + this.id = id; + this.title = title; + this.multi = multi; + var value = init; + this.min = min; + this.max = max; + this.valid = false; + + this.onChange = null; + + var readValue = function () { + var e = document.getElementById(id + '-input'); + if (e) { + value = e.value; + } + }; + + this.getText = function () { + readValue(); + return value; + }; + + this.draw = function () { + readValue(); + + var str = '' + + '
' + + this.title + ':'; + var jsStr = '="TextInput.byId[\'' + this.id + '\'].__change(); return true"'; + + if (this.multi) { + str += ''; + } else { + str += ''; + } + + str += '
'; + document.getElementById(this.id).innerHTML = str; + document.getElementById(this.id + '-input').value = value; + this.checkValidity(); + }; + + this.checkValidity = function () { + this.valid = (this.min == 0 && this.max == 0); + if (!this.valid) { + this.valid = (this.min > 0 && value.length >= this.min || this.min == 0) + && (this.max > 0 && value.length <= this.max || this.max == 0); + } + + with (document.getElementById(this.id + '-input').style) { + color = this.valid ? 'black' : 'white'; + backgroundColor = this.valid ? 'white' : 'red'; + } + }; + + this.__change = function () { + readValue(); + this.checkValidity(); + if (this.onChange) { + this.onChange(value); + } + }; + + TextInput.byId[this.id] = this; +}; +TextInput.byId = {}; + +// Generic numeric control for size and amount of alliances +NumericControl = function (id, title, min, max, init) { + this.id = id; + this.title = title; + this.min = min; + this.max = max; + this.value = init; + + this.onChange = null; + + this.draw = function () { + var str = '' + + '' + + '
' + this.title + + ':' + this.value + + '' + + ' ' + + '
'; + document.getElementById(this.id).innerHTML = str; + }; + + this.increase = function () { + if (this.value == this.max) { + return; + } + + this.value ++; + this.draw(); + if (this.onChange) { + this.onChange(this.value); + } + }; + this.decrease = function () { + if (this.value == this.min) { + return; + } + + this.value --; + this.draw(); + if (this.onChange) { + this.onChange(this.value); + } + }; + + NumericControl.byId[this.id] = this; +}; +NumericControl.byId = {}; + + +// A system on the map +MapLocation = function (type, alloc, spawn) { + this.type = type; + this.alloc = alloc; + this.spawn = spawn; +}; + + +// The map itself +Map = function (initFrom) { + // Copy basic map information + this.name = initFrom.name; + this.description = initFrom.description; + this.alliances = initFrom.alliances; + this.width = initFrom.width; + this.height = initFrom.height; + + // Copy the map + this.map = { }; + for (var i in initFrom.map) { + var ma = initFrom.map[i]; + this.map['x' + ma[0] + 'y' + ma[1]] = new MapLocation(ma[2], ma[3], ma[4]); + } + + // Function that computes min/max x/y + this.updateCoordinates = function () { + this.minX = - Math.floor(this.width / 2); + this.maxX = this.minX + this.width - 1; + + this.minY = - Math.floor(this.height / 2); + this.maxY = this.minY + this.height - 1; + }; + + // Creates a nebula area + this.setNebula = function (x, y, opacity) { + if (this.map['x' + x + 'y' + y]) { + this.map['x' + x + 'y' + y].type = opacity; + } else { + this.map['x' + x + 'y' + y] = new MapLocation(opacity, null, null); + } + }; + + // Creates a target system + this.setTarget = function (x, y) { + if (this.map['x' + x + 'y' + y]) { + this.map['x' + x + 'y' + y].type = 'S'; + this.map['x' + x + 'y' + y].alloc = 0; + } else { + this.map['x' + x + 'y' + y] = new MapLocation('S', 0, null); + } + }; + + // Creates an alliance-controlled system + this.setAlliance = function (x, y, alliance) { + var sys = this.map['x' + x + 'y' + y]; + if (sys) { + sys.type = 'S'; + sys.alloc = alliance; + sys.spawn = false; + } else { + this.map['x' + x + 'y' + y] = new MapLocation('S', alliance, false); + } + }; + + // Switches spawning status for an alliance-controlled system + this.switchSpawnPoint = function (x, y) { + var sys = this.map['x' + x + 'y' + y]; + if (sys && sys.type == 'S' && sys.alloc > 0) { + sys.spawn = !sys.spawn; + } + }; + + // Removes systems allocated to alliances which no longer exist + this.removeExtraAlliances = function () { + for (var i in this.map) { + var sys = this.map[i]; + if (sys.type == 'S' && sys.alloc > this.alliances) { + this.map[i] = null; + } + } + }; + + this.updateCoordinates(); +}; + + +// The editor's grid +Grid = function (id, map) { + this.id = id; + this.map = map; + this.cX = 0; + this.cY = 0; + + this.onClick = null; + + // This function draws the grid in which the map is displayed + this.draw = function () { + var i, j; + var str = '' + + '' + + '' + + '' + + '' + + ''; + + for (i = 0; i < 11; i ++) { + str += ''; + } + str += '' + + '' + + ''; + + for (i = 0; i < 11; i ++) { + str += ''; + for (j = 0; j < 11; j ++) { + str += ''; + } + str += ''; + } + + str += ''; + for (i = 0; i < 11; i ++) { + str += ''; + } + + str += '' + + '' + + '' + + '' + + '
   
     
   
   
   
'; + document.getElementById(this.id).innerHTML = str; + this.drawMap(); + }; + + // This function draws the map on the grid + this.drawMap = function () { + var i, j, str, x, y; + + // Scroll buttons + if (this.cX - 5 > this.map.minX) { + str = ''; + } else { + str = ' '; + } + document.getElementById('map-left').innerHTML = str; + if (this.cX + 5 < this.map.maxX) { + str = ''; + } else { + str = ' '; + } + document.getElementById('map-right').innerHTML = str; + if (this.cY - 5 > this.map.minY) { + str = ''; + } else { + str = ' '; + } + document.getElementById('map-down').innerHTML = str; + if (this.cY + 5 < this.map.maxY) { + str = ''; + } else { + str = ' '; + } + document.getElementById('map-up').innerHTML = str; + + // Draw X coordinates + for (i = 0; i < 11; i ++) { + x = this.cX + i - 5; + if (x < this.map.minX || x > this.map.maxX) { + str = ' '; + } else { + str = '' + x + ''; + } + document.getElementById('top-x-' + i).innerHTML = + document.getElementById('bottom-x-' + i).innerHTML = str; + } + + // Draw Y coordinates + for (i = 0; i < 11; i ++) { + y = this.cY - i + 5; + if (y < this.map.minY || y > this.map.maxY) { + str = ' '; + } else { + str = '' + y + ''; + } + document.getElementById('left-y-' + i).innerHTML = + document.getElementById('right-y-' + i).innerHTML = str; + } + + // Draw contents + for (i = 0; i < 11; i ++) { + x = this.cX + i - 5; + for (j = 0; j < 11; j ++) { + y = this.cY - j + 5; + var cell = document.getElementById('map-' + i + '-' + j); + + if (y < this.map.minY || y > this.map.maxY || x < this.map.minX || x > this.map.maxX) { + cell.innerHTML = ' '; + cell.style.backgroundColor = 'black'; + continue; + } + + var sys = this.map.map['x' + x + 'y' + y]; + if (!sys) { + cell.innerHTML = ' '; + cell.style.backgroundColor = '#3f3f3f'; + } else if (sys.type != 'S') { + cell.innerHTML = '' + sys.type + ''; + cell.style.backgroundColor = '#afafaf'; + cell.style.color = Grid.nebulaColours[parseInt(sys.type, 10) - 1]; + } else if (sys.alloc == 0) { + cell.innerHTML = 'T'; + cell.style.backgroundColor = 'black'; + cell.style.color = 'white'; + } else { + cell.style.color = 'black'; + cell.style.backgroundColor = Grid.allianceColours[sys.alloc - 1]; + cell.innerHTML = sys.spawn ? 'X' : ' '; + } + } + } + }; + + // This function handle clicks on the map + this.handleClick = function(i, j) { + x = this.cX + i - 5; + y = this.cY - j + 5; + + if (x >= this.map.minX && x <= this.map.maxX + && y >= this.map.minY && y <= this.map.maxY && this.onClick) { + this.onClick(x, y); + } + }; +}; +Grid.allianceColours = [ + 'ff0000', '00ff00', '0000ff', '007f7f', '7f007f', 'afaf00', 'ffaf3f', '003f7f' +]; +Grid.nebulaColours = [ + '#ff0000', '#ff2f00', '#ff5f00', '#ff7f00' +]; + + +// Toolbox +Toolbox = function (id, map) { + this.id = id; + this.map = map; + this.currentTool = null; + + var drawToolCell = function (id, title, ctxt, cbg, cfg) { + return str = '' + + '' + + '' + + '
' + ctxt + '' + title + '
'; + }; + + this.draw = function () { + var i, str = '' + + '' + + ''; + + for (i = 1; i < 5; i ++) { + str += drawToolCell('n' + i, 'Class ' + i + ' nebula', '' + i + '', + '#afafaf', Grid.nebulaColours[i - 1]); + } + + str += '' + + drawToolCell('tg', 'Target system', 'T', 'black', 'white') + + drawToolCell('sp', 'Spawing point', 'X', 'white', 'black') + + ''; + + for (i = 1; i <= this.map.alliances; i ++) { + str += drawToolCell('a' + i, 'Alliance ' + i, ' ', Grid.allianceColours[i - 1], 'black'); + } + + str += '
Map drawing tools
Nebulae
Misc
Alliances
'; + + document.getElementById(this.id).innerHTML = str; + if (this.currentTool) { + this.selectTool(this.currentTool); + } + }; + + this.selectTool = function(tool) { + if (this.currentTool && document.getElementById('tool-' + this.currentTool)) { + document.getElementById('tool-' + this.currentTool).style.borderStyle = 'none'; + } + + if (! document.getElementById('tool-' + tool)) { + this.currentTool = null; + return; + } + + with (document.getElementById('tool-' + tool).style) { + borderColor = 'red'; + borderWidth = '2px'; + borderStyle = 'solid'; + } + this.currentTool = tool; + }; +}; + + +// Validity check / submit button +CheckAndSubmit = function (id, map) { + this.id = id; + this.map = map; + this.error = "---"; + + var mapCheck = function (map) { + var ax = []; + var tgc = 0; + + for (var i = 0; i < map.alliances; i ++) { + ax[i] = [0, 0]; + } + + for (var i = map.minX; i <= map.maxX; i ++) { + for (var j = map.minY; j <= map.maxY; j ++) { + var m = map.map['x' + i + 'y' + j]; + if (! m) { + return 1; + } + if (m.type != 'S') { + continue; + } + if (m.alloc == 0) { + tgc ++; + continue; + } + ax[m.alloc - 1][0] ++; + if (m.spawn) { + ax[m.alloc - 1][1] ++; + } + } + } + + if (tgc == 0) { + return 2; + } + + var min = -1; + for (var i = 0; i < map.alliances; i ++) { + if (ax[i][0] == 0) { + return 3 + i; + } + if (min == -1) { + min = ax[i][0]; + } else if (ax[i][0] < min) { + return 19; + } else if (ax[i][0] > min) { + return 19 + i; + } + } + + min = -1; + for (var i = 0; i < map.alliances; i ++) { + if (ax[i][1] == 0) { + return 11 + i; + } + + if (min == -1) { + min = ax[i][1]; + } else if (ax[i][1] < min) { + return 27; + } else if (ax[i][1] > min) { + return 27 + i; + } + } + + return 0; + }; + + this.draw = function () { + if (this.error == '---') { + this.check(); + } + + var str; + if (this.error == "") { + str = ''; + } else { + str = '' + this.error + ''; + } + document.getElementById(this.id).innerHTML = str; + }; + + this.check = function () { + var error = ""; + + if (map.name.length < 4) { + error = "Name too short"; + } else if (map.name.length > 32) { + error = "Name too long"; + } else { + var i = mapCheck(this.map); + switch (i) { + case 0: break; + case 1: error = "Map has undefined areas"; break; + case 2: error = "Map has no target areas"; break; + default: + if (i < 11) { + i -= 2; + error = "Alliance " + i + " has no systems."; + } else if (i < 19) { + i -= 10; + error = "Alliance " + i + " has no spawning points."; + } else if (i < 27) { + i -= 18; + error = "Alliance " + i + " has too many systems."; + } else if (i < 35) { + i -= 26; + error = "Alliance " + i + " has too many spawning points."; + } + break; + } + } + + if (this.error != error) { + this.error = error; + this.draw(); + } + }; +}; + + +// Main editor object +Editor = function (initFrom) { + var map = new Map(initFrom); + var components = []; + + var cSub = new CheckAndSubmit('check-and-send', map); + var tools = new Toolbox('tools', map); + components.push(tools); + + var grid = new Grid('grid', map); + grid.onClick = function(x, y) { + var tool = tools.currentTool; + if (!tool) { + return; + } + + if (tool.match(/^n[1-4]$/)) { + map.setNebula(x, y, tool.charAt(1)); + } else if (tool == 'tg') { + map.setTarget(x, y); + } else if (tool.match(/^a[1-8]$/)) { + map.setAlliance(x, y, parseInt(tool.charAt(1), 10)); + } else if (tool == 'sp') { + map.switchSpawnPoint(x, y); + } + + grid.drawMap(); + cSub.check(); + }; + components.push(grid); + + var c; + c = new TextInput('name', 'Map name', false, 4, 32, map.name); + c.onChange = function (value) { + map.name = value; + cSub.check(); + }; + components.push(c); + + c = new TextInput('desc', 'Description', true, 0, 0, map.description); + c.onChange = function (value) { + map.description = value; + }; + components.push(c); + + c = new NumericControl('n-alliances', 'Alliances', 2, 8, map.alliances); + c.onChange = function (value) { + var old = map.alliances; + map.alliances = value; + tools.draw(); + if (value < old) { + map.removeExtraAlliances(); + grid.drawMap(); + } + cSub.check(); + }; + components.push(c); + + c = new NumericControl('m-width', 'Map width', 3, 41, map.width); + c.onChange = function (value) { + map.width = value; + map.updateCoordinates(); + grid.drawMap(); + cSub.check(); + }; + components.push(c); + + c = new NumericControl('m-height', 'Map height', 3, 41, map.height); + c.onChange = function (value) { + map.height = value; + map.updateCoordinates(); + grid.drawMap(); + cSub.check(); + }; + components.push(c); + components.push(cSub); + + this.draw = function () { + var str = '

Parameters

' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
 
 
   
 
' + + '

Map

' + + '
 
 
'; + document.getElementById('mapedit').innerHTML = str; + + for (var i in components) { + components[i].draw(); + } + }; + + this.scroll = function (dx, dy) { + grid.cX += dx; grid.cY += dy; + grid.drawMap(); + }; + + this.mapClick = function(x,y) { + grid.handleClick(x, y); + }; + + this.selectTool = function(tool) { + tools.selectTool(tool); + }; + + this.submit = function () { + document.getElementById('sf-name').value = map.name; + document.getElementById('sf-desc').value = map.description; + document.getElementById('sf-width').value = map.width; + document.getElementById('sf-height').value = map.height; + document.getElementById('sf-alliances').value = map.alliances; + + var ma = []; + for (var j = map.minY; j <= map.maxY; j ++) { + var str = ''; + for (var i = map.minX; i <= map.maxX; i ++) { + var m = map.map['x' + i + 'y' + j]; + + str += m.type; + if (m.type != 'S') { + continue; + } + str += m.alloc; + if (m.alloc == 0) { + continue; + } + str += m.spawn ? '1' : '0'; + } + ma.push(str); + } + document.getElementById('sf-map').value = ma.join('#'); + + document.getElementById('sendform').submit(); + + }; +}; + + +Editor.editor = new Editor(initMap); +Editor.editor.draw(); diff --git a/admin/maps.php b/admin/maps.php new file mode 100644 index 0000000..d1d4a71 --- /dev/null +++ b/admin/maps.php @@ -0,0 +1,189 @@ +destroy(); + $op = ''; + } else if ($_GET['cancel']) { + $op = ""; + } else { + $op = 'd'; + } + } elseif ($_POST['c'] == 'ms' && $_SESSION['edit_map'] instanceof ctf_map) { + $map = $_SESSION['edit_map']; + $map->setName(stripslashes($_POST['name'])); + $map->setDescription(stripslashes($_POST['desc'])); + $map->setWidth((int) $_POST['width']); + $map->setHeight((int) $_POST['height']); + $map->setAlliances((int) $_POST['alliances']); + + $minY = -floor($map->getHeight() / 2); $maxY = $minY + $map->getHeight() - 1; + $minX = -floor($map->getWidth() / 2); $maxX = $minX + $map->getWidth() - 1; + + $layout = explode('#', $_POST['map']); + for ($y = $minY; $y <= $maxY; $y ++) { + $str = array_shift($layout); + for ($x = $minX; $x <= $maxX; $x ++) { + $type = $str{0}; + $map->setSystemType($x, $y, $type); + if ($type != 'S') { + $str = substr($str, 1); + continue; + } + $alloc = (int) $str{1}; + $map->setSystemAlloc($x, $y, $alloc); + if ($alloc == 0) { + $str = substr($str, 2); + continue; + } + $map->setSystemSpawn($x, $y, $str{2} == '1'); + $str = substr($str, 3); + } + } + $map->save(); + $_SESSION['edit_map'] = null; + $op = ''; + } else { + $op = ''; + } + return $op; +} + + + +function listMaps() { +?> +

Available maps

+ +

+ There are no maps on the server at this time. +

+ + + + + + + + + + + + + + + + +
Name & descriptionSizeAlliances 
" . htmlentities($map->getName()) . "" + . (is_null($map->getDescription()) ? "" : ("
" . htmlentities($map->getDescription())))?>
getWidth()?>xgetHeight()?>getAlliances()?>Edit - Delete
+ +

+ Create a map +

+ +

Map editor

+
+

Loading, please wait ...

+
+ + + + +

Map deletion

+

+ You are about to delete the map called getName())?>.
+ Please confirm. +

+
+ + + + +
+ + + + LegacyWorlds Beta 5 > Administration > Maps management + + +

LWB5 > Administration > Maps management

+

+ The purpose of this tool is to create new maps, edit existing ones or delete unused ones. +

+ + + diff --git a/admin/proxy.php b/admin/proxy.php new file mode 100644 index 0000000..76054f9 --- /dev/null +++ b/admin/proxy.php @@ -0,0 +1,128 @@ + + + + LegacyWorlds Beta 5 > Administration > Proxy detector + + +

LWB5 > Administration > Proxy detector

+

Operation in progress...

+

+ A system operation is in progress. Please wait, the page will update in 5 seconds. +

+ + + + 254) { + $status = "Invalid IP address"; + break; + } + } + if ($status == "") { + try { + $result = pcheck::check(array($ip)); + $status = "$ip - "; + switch ($result[$ip]) { + case -1: $status .= "detection failed"; break; + case 0: $status .= "no proxy detected"; break; + case 1: $status .= "OPEN PROXY DETECTED!"; break; + } + } catch (Exception $e) { + $status = $e->getMessage(); + } + } + } + } else { + $status = "Invalid IP address"; + } +} else { + $status = $ip = ""; +} + +?> + + + LegacyWorlds Beta 5 > Administration > Proxy detector + + +

LWB5 > Administration > Proxy detector

+ +

+ Proxy detector is running; process ID #. Stop detector +

+
+

+ Manually check address + + ' . $status . '') : ''?> +

+
+ +

+ Proxy detector is not running. Start detector +

+ + + diff --git a/admin/set_default.php b/admin/set_default.php new file mode 100644 index 0000000..742c09f --- /dev/null +++ b/admin/set_default.php @@ -0,0 +1,104 @@ + + + + LegacyWorlds Beta 5 > Administration > Default game + + +

LWB5 > Administration > Default game

+

Operation in progress...

+

+ A system operation is in progress. Please wait, the page will update in 2 seconds. +

+ + + +name != $_GET['d'] && array_key_exists($_GET['d'], $games) && $_GET['d'] != 'main') { + sendFifo("SETDEF {$_GET['d']}"); + redirect(); + } +} + +$games = config::getGames(); +$defGame = config::getDefaultGame(); + +?> + + + LegacyWorlds Beta 5 > Administration > Default game + + +

LWB5 > Administration > Default game

+

+ The default game is the game for which overall round rankings are displayed on the site's + main page. +

+
+

+ Current default game: + + +

+
+ + diff --git a/admin/ticks.php b/admin/ticks.php new file mode 100644 index 0000000..fe7a0ca --- /dev/null +++ b/admin/ticks.php @@ -0,0 +1,221 @@ + + + + LegacyWorlds Beta 5 > Administration > Ticks + + +

LWB5 > Administration > Ticks

+

Operation in progress...

+

+ A system operation is in progress. Please wait, the page will update in 5 seconds. +

+ + + +" . join("::", $__adminParams) . " run successfully") + : ("Error while running tick " . join("::", $__adminParams) . ":
$argh"); + + } else { + // Enable / disable ticks + if ($tActive === true && $_GET['c'] == 'dt') { + disableTicks(); + $tActive = 'pending'; + } elseif ($mRunning !== false && $tActive === false && $_GET['c'] == 'et') { + enableTicks(); + $tActive = 'pending'; + } + } +} + +// Load the list of games if no ticks were run +if (!class_exists('config')) { + $oldDir = getcwd(); + chdir("../scripts"); + + $__logPrefix = "lwControl"; + $__loader = array( + 'log', 'classloader', + 'version', 'game', 'tick', 'config' + ); + require_once("loader.inc"); + + chdir($oldDir); +} + +?> + + + LegacyWorlds Beta 5 > Administration > Ticks + + +

LWB5 > Administration > Ticks

+

Manager status

+

Tick manager status: +not running - Start manager + running, process ID # - Kill manager
+ Ticks status change pending; please reload the page.
+ This can take up to 20 seconds, be patient. + Ticks are active - Disable ticks + Ticks are inactive - Enable ticks + +

+

Manual controls

+$statusMessage

\n"; +} +?> +
+ + +

+ Engine tick: + + +

+
+ +
+ +

+ Tick + + for game + + +

+
+ + + diff --git a/ircbot/bot.conf b/ircbot/bot.conf new file mode 100644 index 0000000..357d3e3 --- /dev/null +++ b/ircbot/bot.conf @@ -0,0 +1,147 @@ +;+--------------------------------------------------------------------------- +;| PHP-IRC v2.2.1 Service Release +;| ======================================================= +;| by Manick +;| (c) 2001-2006 by http://www.phpbots.org/ +;| Contact: manick@manekian.com +;| irc: #manekian@irc.rizon.net +;| ======================================== +;| Special Contributions were made by: +;| cortex +;+--------------------------------------------------------------------------- +;| > Configuration File +;+--------------------------------------------------------------------------- +;| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +;+--------------------------------------------------------------------------- +;| Changes +;| =======------- +;| > If you wish to suggest or submit an update/change to the source +;| > code, email me at manick@manekian.com with the change, and I +;| > will look to adding it in as soon as I can. +;+--------------------------------------------------------------------------- + + +;====Bot config file + +;====Contains the nickname of the Bot +nick LW-Bot + +;====The nickserv password, if any (autoident).. +password ... + +;====Real name.. +realname Legacy Worlds Bot + +;====The server you are going to connect to +server localhost + +;====The server port +port 6667 + +;====The server password you are going to connect to +;serverpassword test + +;====NAT IP +;natip 192.168.1.101 + +;===BIND IP +;Specify the IP this bot should bind to when connecting to a server +;bind 127.0.0.1 + +;====DCC Range start for chats/files +;dccrangestart 6081 + +;====DCC Reverse Protocl +;This follows the mIRC DCC Server Protocol +;When this is set, you will connect to this port +;on the users machine, and send them the file, similar to +;sysreset's firewall workaround. +;This function is EXPERIMENTAL +;==== +;mircdccreverse 4000 + +;====Who on join? +;After joining a channel, populate the host lists +;with /WHO information? This probably isn't so good +;for larger channels... +;This function populates host/ident information for +;all users in a channel. Useful for running channel protection +;scripts and such. The DCC Reverse setting above using a WHOIS +;command to get user hosts, so this doesn't have to be set +;to use the above setting. +;==== +;populatewho + +;====Populate ban list? +;This will make php-irc run MODE #chan +b upon joining, populating +;the ban list. This is useful for running channel protection or ban +;rotating scripts. +;==== +;populatebans + +;====Flood lines +;when someone types text that is a trigger for the bot, how many times can they type +;different triggers (within a 10 second period) before the bot ignores them for spamming the bot +floodlines 500 + +;====Time to ban user in seconds for abusing the bots triggers +floodtime 60 + +;====Channels.. unlimited +;====i.e., channel #channel key +;====but you don't have to specify a key +channel #testing meeeh + +;====Your ident +ident lwbot + +;====Log file +logfile __CFG:cachedir__/ircbot.log + +;====uncomment the following lines to use a database +;either 'usedatabase mysql' or 'usedatabase postgre' +;usedatabase mysql + +;dbuser root +;dbpass none +;db question +;dbprefix bot_ +;dbhost localhost +;dbport + +;====Send queue timeout +;How often to send more text to the irc server. This handles the 'notice' +;and 'privMsg' commands sent from modules and such (in seconds) +queuetimeout 1 + +;====Send queue buffer +;How many bytes of text to send to the server every 'queuetimeout' period. +queuebuffer 225 + +;====Perm. Ignore (These hosts will always be ignored, forever) +;example: +;ignore *!*@* + +;====DCC Admin password //default is 'mypass' +;dccadminpass a029d0df84eb5549c641e04a9ef389e5 + +;====set this to your function file +functionfile function.conf + +;====whether this bot accepts sends from other people +upload no + +;====The place where uploaded files are put. +;uploaddir /home/my/dir/ diff --git a/ircbot/bot.php b/ircbot/bot.php new file mode 100644 index 0000000..1719a6a --- /dev/null +++ b/ircbot/bot.php @@ -0,0 +1,476 @@ + Main module +| > Module written by Manick +| > Module Version Number: 2.2.0 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +error_reporting(E_ALL); +set_time_limit(0); + + +$oldDir = getcwd(); +chdir("../scripts"); +$__logPrefix = "lwBot"; +$__loader = array( + 'log', 'classloader', 'version', 'game', 'tick', 'config', + 'db_connection', 'db_accessor', 'db', 'library' +); +require_once('loader.inc'); +chdir($oldDir); + +require('./defines.php'); +require('./queue.php'); +require('./module.php'); +require('./irc.php'); +require('./socket.php'); +require('./timers.php'); +require('./dcc.php'); +require('./chat.php'); +require('./file.php'); +require('./parser.php'); +require('./databases/ini.php'); +require('./error.php'); +require('./connection.php'); +require('./remote.php'); + + +final class bot { + + /* Global socket class used by all bots */ + private $socketClass; + + /* Global process Queue used by all bots, timers, dcc classes */ + private $procQueue; + + /* Whether we are running in background mode or not. (not sure if this is used anymore */ + private $background = 0; + + //contain all the bots + private $bots = array(); + + // save the only one instance of bot (singleton) + private static $_instance; + + public static function getInstance() + { + if (!isset (self :: $_instance)) + { + self :: $_instance = new bot(); + } + return self :: $_instance; + } + + + //Main Method + private function __construct() + { + + $this->socketClass = new socket(); + $this->procQueue = new processQueue(); + + $this->socketClass->setProcQueue($this->procQueue); + + $this->readConfig(); + } + + public function launch(){ + + foreach($this->bots as $bot) + { + $this->createBot($bot); + } + + try + { + + /* Main program loop */ + + while (1) + { + //Get data from sockets, and trigger procQueue's for new data to be read + $this->socketClass->handle(); + + //The bots main process loop. Run everything we need to! + $timeout = $this->procQueue->handle(); + + //Okay, set the socketclass timeout based on the next applicable process + if ($timeout !== true) + { + $this->socketClass->setTimeout($timeout); + } + + //echo $this->procQueue->getNumQueued() . "\n"; + //$this->procQueue->displayQueue(); + + //Aight, if we don't have any sockets open/in use, and + //we have no processes in the process queue, then there are + //obviously no bots running, so just exit! + if ($this->socketClass->getNumSockets() == 0 && $this->procQueue->getNumQueued() == 0) + { + break; + } + } + + } + catch (Exception $e) + { + $this->ircClass->log($e->_toString()); + } + + } + + + public static function addBot($filename){ + $bot = bot::getInstance(); + $config = bot::parseConfig($filename); + if ($config == false) + { + return false; + } + + $newBot = new botClass(); + $newBot->config = $config; + $newBot->configFilename = $filename; + + $bot->bots[] = $newBot; + $bot->createBot($newBot); + return true; + } + + + private function createBot($bot) + { + $this->connectToDatabase($bot); + + $bot->socketClass = $this->socketClass; + $bot->timerClass = new timers(); + $bot->parserClass = new parser(); + + $bot->dccClass = new dcc(); + $bot->ircClass = new irc(); + + $bot->ircClass->setConfig($bot->config, $bot->configFilename); + $bot->ircClass->setSocketClass($this->socketClass); + $bot->ircClass->setParserClass($bot->parserClass); + $bot->ircClass->setDccClass($bot->dccClass); + $bot->ircClass->setTimerClass($bot->timerClass); + $bot->ircClass->setProcQueue($this->procQueue); + + $bot->dccClass->setSocketClass($this->socketClass); + $bot->dccClass->setTimerClass($bot->timerClass); + $bot->dccClass->setParserClass($bot->parserClass); + $bot->dccClass->setProcQueue($this->procQueue); + + $bot->parserClass->setTimerClass($bot->timerClass); + $bot->parserClass->setSocketClass($this->socketClass); + $bot->parserClass->setDatabase($bot->db); + + $bot->timerClass->setIrcClass($bot->ircClass); + $bot->timerClass->setSocketClass($this->socketClass); + $bot->timerClass->setProcQueue($this->procQueue); + + $bot->parserClass->init(); + + //Okay, this function adds the connect timer and starts up this bot class. + $bot->ircClass->init(); + + bot::createChannelArray($bot->ircClass); + + } + + + private function readConfig() + { + global $argc, $argv; + + if ($argc < 2) { + $args = array(config::$main['scriptdir'] . "/../ircbot/bot.conf"); + } else { + $args = $argv; + array_shift($args); + } + + $isPasswordEncrypt = false; + + foreach ($args AS $filename) + { + if ($filename == "") + { + continue; + } + + if ($isPasswordEncrypt == true) + { + die("Encrypted Password: " . md5($filename) . "\nReplace this as 'dccadminpass' in bot.conf!"); + } + + if ($filename == "-c") + { + $isPasswordEncrypt = true; + continue; + } + + if ($filename == "-b" && $this->background != 1) + { + $this->background = 1; + $this->doBackground(); + continue; + } + + $config = bot::parseConfig($filename); + + if ($config == false) + { + echo "Could not spawn bot $filename"; + die(); + } + + $bot = new botClass(); + $bot->config = $config; + $bot->configFilename = $filename; + + $this->bots[] = $bot; + } + + if ($isPasswordEncrypt == true) { + die("No password submitted on command line! Syntax: bot.php -c \n"); + } + + if (! $this->background && PID != '') { + $file = fopen(PID, "w+"); + fwrite($file, getmypid()); + fclose($file); + } + } + + + + + private function connectToDatabase($bot) + { + if (isset($bot->config['usedatabase'])) + { + if (!file_exists("./databases/" . $bot->config['usedatabase']. ".php")) + { + die("Couldn't find the database file! Make sure it exists!"); + } + + require_once("./databases/" . $bot->config['usedatabase']. ".php"); + + $dbType = $bot->config['usedatabase']; + + if (!isset($bot->config['dbhost'])) + $bot->config['dbhost'] = "localhost"; + if (!isset($bot->config['dbuser'])) + $bot->config['dbuser'] = "root"; + if (!isset($bot->config['dbpass'])) + $bot->config['dbpass'] = ""; + if (!isset($bot->config['db'])) + $bot->config['db'] = "test"; + if (!isset($bot->config['dbprefix'])) + $bot->config['dbprefix'] = ""; + if (!isset($bot->config['dbport'])) + { + $bot->db = new $dbType($bot->config['dbhost'], + $bot->config['db'], + $bot->config['dbuser'], + $bot->config['dbpass'], + $bot->config['dbprefix']); + } + else + { + $bot->db = new $dbType($bot->config['dbhost'], + $bot->config['db'], + $bot->config['dbuser'], + $bot->config['dbpass'], + $bot->config['dbprefix'], + $bot->config['dbport']); + } + + if (!$bot->db->isConnected()) + { + die("Couldn't connect to database..."); + } + + } + } + + + public static function createChannelArray($ircClass) + { + $channels = $ircClass->getClientConf('channel'); + + if ($channels != "") + { + if (!is_array($channels)) + { + $channels = array($channels); + } + + foreach ($channels AS $channel) + { + $chan = $channel; + $key = ""; + + if (strpos($channel, chr(32)) !== false) + { + $channelVars = explode(chr(32), $channel); + $chan = $channelVars[0]; + $key = $channelVars[1]; + } + + $ircClass->maintainChannel($chan, $key); + } + } + } + + public static function parseConfig($filename) + { + $configFPtr = @fopen($filename, "rt"); + + if ($configFPtr == null) + { + // echo "Could not find config file '".$filename."'\n"; + return false; + } + + $configRaw = ""; + + try + { + + while (!feof($configFPtr)) + { + $configRaw .= fgets($configFPtr, 1024); + } + + fclose($configFPtr); + + } + catch (Exception $e) + { + // echo "A fatal IO Exception occured."; + return false; + } + + $config = array(); + + $configRaw = str_replace("\r", "", $configRaw); + + $confLines = explode("\n", $configRaw); + + foreach ($confLines AS $line) + { + $line = trim($line); + + if ($line == "" || substr($line, 0, 1) == ";") + { + continue; + } + + $offsetA = strpos($line, chr(32)); + + if ($offsetA != false) + { + $confVar = substr($line, 0, $offsetA); + $confParams = substr($line, $offsetA + 1); + } + else + { + $confVar = $line; + $confParams = ""; + } + + if (preg_match('/__CFG:([a-zA-Z_]+)__/', $confParams, $matches)) { + $src = array(); $dst = array(); + array_shift($matches); + foreach ($matches as $cfgSubst) { + array_push($src, "/__CFG:{$cfgSubst}__/"); + array_push($dst, config::$main[$cfgSubst]); + } + $confParams = preg_replace($src, $dst, $confParams); + } + + if (isset($config[$confVar])) + { + if (!is_array($config[$confVar])) + { + $prevParam = $config[$confVar]; + $config[$confVar] = array(); + $config[$confVar][] = $prevParam; + } + + $config[$confVar][] = $confParams; + + } + else + { + $config[$confVar] = $confParams; + } + } + + return $config; + } + + + private function doBackground() + { + $pid = pcntl_fork(); + if ($pid == -1) { + die("Error: could not fork\n"); + } else if ($pid) { + if (PID != "") { + $file = fopen(PID, "w+"); + fwrite($file, $pid); + fclose($file); + } + + exit(); // Parent + } + + if (!posix_setsid()) { + die("Error: Could not detach from terminal\n"); + } + + fclose(STDIN); + fclose(STDOUT); + fclose(STDERR); + } + +} + +$ircBot = bot::getInstance(); +$ircBot->launch(); + +?> diff --git a/ircbot/chat.php b/ircbot/chat.php new file mode 100644 index 0000000..cd5abaf --- /dev/null +++ b/ircbot/chat.php @@ -0,0 +1,364 @@ + dcc chat module +| > Module written by Manick +| > Module Version Number: 2.2.1 beta ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +class chat { + + /* Chat specific Data */ + public $id; + public $status; + public $sockInt; + public $isAdmin; + public $timeConnected; + public $verified; + public $readQueue; + public $floodQueue; + public $floodQueueTime; + public $port; + public $type; + public $nick; + public $timeOutLevel; + public $removed; + public $connection; + + public $handShakeSent; + public $handShakeTime; + public $reverse; + public $connectHost; + + /* Classes */ + private $dccClass; + private $parserClass; + private $ircClass; + private $socketClass; + private $timerClass; + + //class handler + private $handler; + + /* Constructor */ + public function __construct($id, $nick, $admin, $sockInt, $host, $port, $handler, $reverse) + { + $this->id = $id; + $this->handler = $handler; + $this->nick = $nick; + $this->isAdmin = $admin; + $this->sockInt = $sockInt; + $this->port = $port; + $this->connectHost = $host; + $this->reverse = $reverse; + $this->handShakeSent = false; + + $this->sendQueue = array(); + $this->sendQueueCount = 0; + } + + public function setIrcClass($class) + { + $this->ircClass = $class; + } + + public function setDccClass($class) + { + $this->dccClass = $class; + } + + public function setSocketClass($class) + { + $this->socketClass = $class; + } + + public function setParserClass($class) + { + $this->parserClass = $class; + } + + public function setTimerClass($class) + { + $this->timerClass = $class; + } + + private function sendUserGreeting() + { + if ($this->verified == true) + { + return; + } + + $this->dccSend("Welcome to " . $this->ircClass->getNick()); + $this->dccSend("PHP-iRC v". VERSION ." [". VERSION_DATE ."]"); + $time = $this->ircClass->timeFormat($this->ircClass->getRunTime(), "%d days, %h hrs, %m min, %s sec"); + $this->dccSend("running " . $time); + $this->dccSend("You are currently in the dcc chat interface. Type 'help' to begin."); + } + + private function sendAdminGreeting() + { + if ($this->verified == true) + { + return; + } + + $this->dccSend("Welcome to " . $this->ircClass->getNick()); + $this->dccSend("PHP-iRC v". VERSION ." [". VERSION_DATE ."]"); + $time = $this->ircClass->timeFormat($this->ircClass->getRunTime(), "%d days, %h hrs, %m min, %s sec"); + $this->dccSend("running " . $time); + $this->dccSend("Enter Your Password:"); + } + + public function dccSend($data, $to = null) + { + if ($this->status != DCC_CONNECTED) + { + return; + } + + if ($to == null) + { + $to = $this; + } + + $this->dccClass->dccSend($to, "--> " . $data . "\n"); + } + + public function dccSendRaw($data, $to = null) + { + if ($this->status != DCC_CONNECTED) + { + return; + } + + if ($to == null) + { + $to = $this; + } + + $this->dccClass->dccSend($to, $data); + } + + + public function disconnect($msg = "") + { + + $msg = str_replace("\r", "", $msg); + $msg = str_replace("\n", "", $msg); + + if (is_object($this->handler) && $this->status == DCC_CONNECTED) + { + $this->handler->disconnected($this); + } + + $this->status = false; + + if ($msg != "") + { + $this->dccClass->dccInform("DCC: " . $this->nick . " closed DCC Chat (" . $msg . ")", $this); + $this->ircClass->notice($this->nick, "DCC session ended: " . $msg, 1); + } + else + { + $this->dccClass->dccInform("DCC: " . $this->nick . " closed DCC Chat", $this); + } + + $this->dccClass->disconnect($this); + + $this->connection = null; + + return true; + } + + + + private function doHandShake() + { + $this->dccSendRaw("100 ".$this->ircClass->getNick()."\n"); + $this->handShakeSent = true; + $this->timerClass->addTimer(irc::randomHash(), $this, "handShakeTimeout", "", 8); + } + + private function processHandShake() + { + if ($this->readQueue == "") + { + return; + } + + $response = $this->readQueue; + $this->readQueue = ""; + $responseArray = explode(chr(32), $response); + if ($responseArray[0] == "101") + { + $this->reverse = false; + $this->onConnect($this->connection); + return; + } + + $this->disconnect("DCC Client Server reported error on attempt to start chat"); + } + + public function handShakeTimeout() + { + if ($this->status != false) + { + if ($this->reverse == true) + { + $this->disconnect("DCC Reverse handshake timed out"); + } + } + return false; + } + + /* Main events */ + public function onTimeout($conn) + { + $this->disconnect("Connection transfer timed out"); + } + + public function onDead($conn) + { + $this->disconnect($this->connection->getErrorMsg()); + } + + public function onRead($conn) + { + if ($this->socketClass->hasLine($this->sockInt)) + { + $this->readQueue .= $this->socketClass->getQueueLine($this->sockInt); + } + + if ($this->status == DCC_CONNECTED) + { + if ($this->reverse != false) + { + if ($this->handShakeSent != false) + { + $this->processHandShake(); + } + } + else + { + if ($this->readQueue != "") + { + $this->parserClass->parseDcc($this, $this->handler); + } + } + + } + + if ($this->socketClass->hasLine($this->sockInt)) + { + return true; + } + } + + public function onWrite($conn) + { + //do nothing + } + + public function onAccept($oldConn, $newConn) + { + $this->dccClass->accepted($oldConn, $newConn); + $this->connection = $newConn; + $oldConn->disconnect(); + $this->sockInt = $newConn->getSockInt(); + $this->onConnect($newConn); + } + + public function onConnectTimeout($conn) + { + $this->disconnect("Connection attempt timed out"); + } + + public function onConnect($conn) + { + $this->status = DCC_CONNECTED; + + if ($this->reverse != false) + { + $this->dccClass->dccInform("DCC CHAT: " . $this->nick . " handling dcc server request"); + $this->doHandShake(); + return; + } + + $this->dccClass->dccInform("DCC CHAT: " . $this->nick . " connection established"); + + if ($this->handler === false || $this->handler == null) + { + if ($this->isAdmin == true) + { + $this->sendAdminGreeting(); + } + else + { + $this->sendUserGreeting(); + } + } + else + { + + if (is_object($this->handler)) + { + $this->handler->connected($this); + } + } + + } + + + public function initialize() + { + + $this->dccClass->dccInform("DCC: " . $this->nick . " is attempting to login"); + + if ($this->status == DCC_LISTENING) + { + $this->ircClass->privMsg($this->nick, "\1DCC CHAT chat " . $this->ircClass->getClientIP(1) . " " . $this->port . "\1", 0); + $this->ircClass->notice($this->nick, "DCC Chat (" . $this->ircClass->getClientIP(0) . ")", 0); + } + + $this->timeConnected = time(); + $this->timeOutLevel = 0; + $this->verified = 0; + $this->readQueue = ""; + $this->floodQueue = ""; + $this->floodQueueTime = 0; + $this->type = CHAT; + } + + +} + +?> diff --git a/ircbot/command_reference.txt b/ircbot/command_reference.txt new file mode 100644 index 0000000..72611e2 --- /dev/null +++ b/ircbot/command_reference.txt @@ -0,0 +1,870 @@ ++--------------------------------------------------------------------------- +| PHP-IRC v2.2.1 Service Release +| ======================================================== +| by Manick +| (c) 2001-2006 by http://www.phpbots.org/ +| Contact: manick@manekian.net +| irc: #manekian@irc.rizon.net +| ======================================================== ++--------------------------------------------------------------------------- +| > Module Command Reference ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- + +The following file is a quickly put together command reference of all the functions that you can use to complement the php scripting language while writing your modules. + +=============== +Quick Reference (See below the quick reference for function descriptions) +=============== + +irc.php (Accessed with $this->ircClass->) +----------------------------------------- +array parseModes($modeString) +array getMaintainedChannels() +string getClientConf($var = "") +string getNick() +string getServerConf($var) +string getStatusString($status) +string getClientIP($long = 1) +class getChannelData($channel) +class getUserData($user, $channel = "") +void disconnect() +void setClientConfigVar($var, $value) +void joinChannel($chan) +void sendRaw($text, $force = false) +void privMsg($who, $msg, $queue = 1) +void action($who, $msg, $queue = 1) +void notice($who, $msg, $queue = 1) +void log($text) +void maintainChannel($channel, $key = "") +void removeMaintain($channel) +void addQuery($host, $port, $query, $line, $class, $function) +int getStatusRaw() +bool changeMode($chan, $act, $mode, $user) +bool isOnline($nick, $chan) +bool isMode($nick, $channel, $mode) +bool isChanMode($channel, $mode, $extra = "") +bool hasModeSet($chan, $user, $modes); +bool hostMasksMatch($mask1, $mask2) +bool checkIgnore($mask) + +socket.php (Accessed with $this->socketClass->) +----------------------------------------------- +string getHost($sockInt) +string getQueue($sockInt) +string getQueueLine($sockInt) +int sendSocket($sockInt, $data) +int getSockStatus($sockInt) +object getSockData($sockInt) +bool hasWriteQueue($sockInt) +bool hasLine($sockInt) + +timers.php (Accessed with $this->timerClass->) +---------------------------------------------- +class addTimer($name, $class, $function, $args, $interval, $runRightAway = false) +class getTimers() +void removeTimer($timer) (MUST be name) + +mysql/postgre.php (Accessed with $this->db->) +--------------------------------------------- +array queryFetch($query) +array fetchArray($object) +array fetchRow($object) +string getError() +bool isConnected() +int lastID() +int numRows($toFetch) +int numQueries() +void close() +object query($query) + +dcc.php (Accessed with $this->dccClass->) +----------------------------------------- +array getDccList() +int getDownloadCount() +int getUploadCount() +int getChatCount() +int getBytesDown() +int getBytesUp() +int addChat($nick, $host, $port, $admin, $handler) +int addFile($nick, $host, $port, $type, $filename, $size) +int sendFile($nick, $file) +class getDcc($dcc) +void dccInform($text, $from = null) + +chat.php +---------- +void disconnect($msg = "") +void dccSend($msg) + +file.php +---------- +void disconnect($msg = "") + +ini.php +---------- +bool getError() +bool sectionExists($section) +bool deleteSection($section) +bool deleteVar($section, $var) +bool setIniVal($section, $var, $val) +bool writeIni() +array getSections() +array getVars($section) +array getSection($section) +array randomSection($num = 1) +array searchSections($search, $type = EXACT_MATCH) +array searchVars($section, $search, $type = EXACT_MATCH) +array searchSectionsByVar($var, $search, $type = EXACT_MATCH) +array searchVals($section, $search, $type = EXACT_MATCH) +mixed randomVar($section, $num = 1) +mixed getIniVal($section, $var) +int numSections() +int numVars($section) + +Useful Static Routines +----------------------- +string socket::generatePostQuery($query, $host, $path, $httpVersion) +string socket::generateGetQuery($query, $host, $path, $httpVersion) +string irc::intToSizeString($size) +string irc::myStrToLower($text) +string irc::myStrToUpper($text) +string irc::timeFormat($time, $format) +string irc::randomHash() +array irc::multiLine($text) + + +========================= +Full Function Definitions +========================= + +irc.php (Accessed with $this->ircClass->) +----------------------------------------- + +------------------------------- +array parseModes($modeString) +------------------------------- + +Mode strings usually come from the $line['params'] variable. However, they look like this: + ++o-b Manick blah!*@* + +This is a real mess to parse. Thus, this function takes the line and returns an array of each mode being operated. The format is as follows: + +$modesArray = array( + + [0] = array( 'ACTION' => '+', + 'MODE' => 'o', + 'EXTRA' => 'Manick', + 'TYPE' => USER_MODE, + ) + + [1] = array( 'ACTION' => '-', + 'MODE' => 'b', + 'EXTRA' => 'blah!*@*', + 'TYPE' => CHANNEL_MODE, + ) +) + + +-------------------------------- +array getMaintainedChannels() +-------------------------------- +This will return an array of all the channels that php-irc will attempt to stay in. Every sixty seconds or so, the bot will attempt to join these channels if it is not currently in them. See 'maintainChannel()' and 'removeMaintain()' for more information. + + +-------------------------------- +string getClientConf($var = "") +-------------------------------- + +Will return the corresponding value for the setting of $var in bot.conf. If $var == "", the full configuration array is returned as an array. Also, if the setting does not exist in the configuration, this function will return an empty string, "". + + +-------------------------------- +string getNick() +-------------------------------- + +Returns the bots current, IRC recognized nick. + + +-------------------------------- +string getServerConf($var) +-------------------------------- + +When connecting to a server, it will send these lines upon connection: +NETWORK=Rizon STATUSMSG=@%+ MODES=4 CHANLIMIT=#:30 MAXCHANNELS=30 MAXLIST=beI:100 MAXBANS=100 MAXTARGETS=4 NICKLEN=30 TOPICLEN=400 KICKLEN=400 CHANNELLEN=50 AWAYLEN=90 are supported by this server +CHANTYPES=# KNOCK EXCEPTS INVEX PREFIX=(ohv)@%+ CHANMODES=eIb,k,l,cimnpstMNORZ CASEMAPPING=rfc1459 CALLERID WALLCHOPS FNC PENALTY ETRACE are supported by this server + +(These are just samples). So, you can retrieve the value for say, CHANMODES by doing this: + +getServerConf("CHANMODES"); + +This will return a string, "eIb,k,l,cimnpstMNORZ". + + +---------------------------------- +string getStatusString($status) +---------------------------------- + +After using getStatusRaw(), you can feed its value to this function, and it'll send back what it means, like "Connecting to Server..." + + +---------------------------------- +string getClientIP($long = 1) +---------------------------------- + +This function returns the IP that the bot is using. if you specify $long as 1, then the ip address converted to long integer form is returned. + + +---------------------------------- +class getChannelData($channel) +---------------------------------- + +This will search the channel database for the channel $channel, and then if it finds it, it will return that channels object. See the "channelLink" class in defines.php for member information. + + +---------------------------------- +class getUserData($user, $channel = "") +---------------------------------- + +If a channel is specified, this will search the channel for the specified user's data. If it is found, it will return it as an object. See the "memberLink" class in defines.php for member information. If no channel is specified, this will search the entire channel database until the member is found, and then return that object. + + +---------------------------------- +void disconnect() +---------------------------------- + +Disconnect the bot from the server... + + +---------------------------------- +void setClientConfigVar($var, $value) +---------------------------------- + +This will overwrite a configuration setting from bot.conf that the bot is currently using. Self-explanitory. + + +---------------------------------- +void joinChannel($chan) +---------------------------------- + +Join the channel $chan + + +---------------------------------- +void sendRaw($text, $force = false) +---------------------------------- + +Send the raw text $text to the server. If $force is true, then the data is sent RIGHT away, it totally bypasses the queue, everything. It goes right to the send function, even through the socket class. Use this if you need lightning fast responses, such as writing a channel guard script. + + +---------------------------------- +void privMsg($who, $msg, $queue = 1) +---------------------------------- + +$who = nick of user/name of channel + +Send text to a channel or user. If $queue is 1, then the data will be put at the end of the text queue, if it is 0, it'll be pushed onto the beginning of the queue. + +---------------------------------- +void action($who, $msg, $queue = 1) +---------------------------------- + +Same as privMsg... emulates the /me command from mIRC + + +---------------------------------- +void notice($who, $msg, $queue = 1) +---------------------------------- + +Same as privMsg, except a notice. + + +---------------------------------- +void log($text) +---------------------------------- + +Write something to the log file. + +---------------------------------- +void maintainChannel($channel, $key = "") +---------------------------------- + +Attempt to stay in channel $channel. The bot will attempt rejoining every 60 seconds. You may specify a $key or leave it blank. + +---------------------------------- +void removeMaintain($channel) +---------------------------------- + +No longer attempt to say in channel $channel. + +---------------------------------- +void addQuery($host, $port, $query, $line, $class, $function) +---------------------------------- + +Also make sure you read the text about this function in the readme.txt, "Querying remote servers". This function takes 6 arguments. + +$host = the host machine you want to connect to +$port = the port you want to connect to +$query = the raw data that will be sent to the server upon connection +$line = the $line argument that was passed to your module's function (just pass this right along with addQuery) +$class = usually '$this'.. +$function = the function to run after the query is complete. + +See sections 8 and 9 of readme.txt for information related to writing 'query' function types. + + +---------------------------------- +int getStatusRaw() +---------------------------------- + +Returns an integer corresponding to current server status. See defines.php for meanings + + +---------------------------------- +bool changeMode($chan, $act, $mode, $user) +---------------------------------- + +Changes the mode of $user on $chan + +$act = either "+" or "-" +$mode = some mode, like "o" (ops), or "b" (ban), or something else. +$user = whatever the mode is being applied to + + +---------------------------------- +bool isOnline($nick, $chan) +---------------------------------- + +Send back whether the nick is on the channel $chan (true or false) + + +---------------------------------- +bool isMode($nick, $channel, $mode) +---------------------------------- + +See if some $user has some $mode activated on him in $channel + +$user = nick of user +$mode = any mode, such as "o", "h", "v" + + +---------------------------------- +bool isChanMode($channel, $mode, $extra = "") +---------------------------------- + +Sends back whether a specific mode is set in a channel + +$mode = "b" or "s" or any other chan mode + +$extra = (if you're using "b" for $mode, specify mask here) + + +---------------------------------- +bool hasModeSet($chan, $nick, $modes) +---------------------------------- + +If any of the modes specified in "$modes" are set on a user in a channel, this will return true. +For instance: + +If Manick is +vo in channel #manekian, + +hasModeSet("#manekian", "Manick", "oh") + +will return true. I'm asking php-irc if Manick is either mode o or mode h. Which he's mode o, so it returns true. + + +---------------------------------- +bool hostMasksMatch($mask1, $mask2) +---------------------------------- + +Use this to determine whether two host masks match + + +---------------------------------- +bool checkIgnore($mask) +---------------------------------- +Check whether a hostmask is on the bot.conf ignore list. + + +socket.php (Accessed with $this->socketClass->) +----------------------------------------------- + +---------------------------------- +string getHost($sockInt) +---------------------------------- +Return the IP address of the current socket integer of a connection. You can use connection::getSockInt() to get the sock int. + + +---------------------------------- +string getQueue($sockInt) +---------------------------------- +Retrieve the current read queue for a socket. The queue is then purged. + + +---------------------------------- +string getQueueLine($sockInt) +---------------------------------- +Get the next line in the queue, purging it from the read buffer and returning it (without crlf) + + +---------------------------------- +int sendSocket($sockInt, $data) +---------------------------------- +Send data to a socket + + +---------------------------------- +int getSockStatus($sockInt) +---------------------------------- +Get the status of a socket. See defines.php for list of constants. + + +---------------------------------- +object getSockData($sockInt) +---------------------------------- +Return the object that retains all data about socket "$sockInt". This is a php-irc maintained list. + + +---------------------------------- +bool hasWriteQueue($sockInt) +---------------------------------- +Return true or false depending on whether the bot has data to write to the socket. If this is true, you should restrain yourself from sending more data to the socket. An onWrite() call will be sent every time data is written, and each time "hasWriteQueue()" should be called to see if the queue is empty before sending data. + + +---------------------------------- +bool hasLine($sockInt) +---------------------------------- +Return true or false depending on if there is a newline in the buffer. + + +timers.php (Accessed with $this->timerClass->) +---------------------------------------------- + +---------------------------------- +class addTimer($name, $class, $function, $args, $interval, $runRightAway = false) +---------------------------------- + +Add a timer. Please view the readme.txt documentation on this. + +---------------------------------- +class getTimers() +---------------------------------- + +This function will return a pointer to all the active timers. See the class "timer" in defines.php for member information. + + +---------------------------------- +void removeTimer($timer) (MUST be timer name) +---------------------------------- + +Remove a timer $timer <-- can only be the name of timer, unlike in 2.1.1 where it could be the object itself. + + +mysql/postgre.php (Accessed with $this->db->) +--------------------------------------------- +Some items for postgre aren't fully implemented or work yet... most notably queryFetch(), and some others. See postgre.php for more details. + +---------------------------------- +array queryFetch($query) +---------------------------------- + +Runs a query and returns the first row. Basically does query() and then mysql_fetch_array() + + +---------------------------------- +array fetchArray($object) +---------------------------------- + +Does mysql_fetch_array() (see php.net for help) + + +---------------------------------- +array fetchRow($object) +---------------------------------- + +Does mysql_fetch_row() + + +---------------------------------- +string getError() +---------------------------------- + +Does mysql_error(); + + +---------------------------------- +bool isConnected() +---------------------------------- + +Returns whether the database is connected + +---------------------------------- +int lastID() +---------------------------------- + +Gets the last inserted 'id' row from the database. + + +---------------------------------- +int numRows($toFetch) +---------------------------------- + +Does mysql_num_rows() + + +---------------------------------- +int numQueries() +---------------------------------- + +sends back an integer of all the queries since the database class was spawned. + + +---------------------------------- +void close() +---------------------------------- + +Closes the database connection + + +---------------------------------- +object query($query) +---------------------------------- + +Runs a query, and returns the result as an object. + + +dcc.php (Accessed with $this->dccClass->) +----------------------------------------- + +---------------------------------- +array getDccList() +---------------------------------- + +Returns an array of all current dcc's in progress (chat and file) + + +---------------------------------- +int getDownloadCount() +---------------------------------- + +Number of all current downloads + + +---------------------------------- +int getUploadCount() +---------------------------------- + +Number of all current uploads + + +---------------------------------- +int getChatCount() +---------------------------------- + +Number of all current dcc chat sessions + + +---------------------------------- +int getBytesDown() +---------------------------------- + +Returns number of bytes downloaded since bot was started + + +---------------------------------- +int getBytesUp() +---------------------------------- + +Returns number of bytes uploaded since bot was started + + +---------------------------------- +int addChat($nick, $host, $port, $admin, $handler) +---------------------------------- + +Use this to create a dcc chat session. + +$nick = nick of user to chat with +$host = host of user to chat with +$port = port to connect to +$admin = whether the user is admin (use admin dcc chat administration) +$handler = null, usually, unless you are creating a custom dcc chat handler. See readme.txt for more information. + +If $host or $port is null, then addChat will think you are setting up a listening connection, otherwise it will try to connect to the host/port specified. + + +---------------------------------- +int addFile($nick, $host, $port, $type, $filename, $size) +---------------------------------- + +Send a file to someone, or recieve a file. + +$nick = nick of person sending/receiving file +$host = host of person sending/receiving file +$port = port of transfer +$type = either UPLOAD or DOWNLOAD +$filename = full filename with full path to file +$size = size of the file, can be null if its an UPLOAD type. + +See readme.txt for more information regarding file transfers. + + +---------------------------------- +int sendFile($nick, $file) +---------------------------------- +Alias for addFile($nick, null, null, UPLOAD, $file, null)... basically just an easier way of sending files. + + +---------------------------------- +class getDcc($dcc) +---------------------------------- + +$dcc = sockInt of the transfer + +Returns dcc object for some sockInt, see fileserver.php for example. + + +---------------------------------- +void dccInform($text) +---------------------------------- + +Send a message to all administrators + + +chat.php +---------- + +---------------------------------- +void disconnect($msg = "") +---------------------------------- + +End the chat session + +---------------------------------- +void dccSend($msg) +---------------------------------- + +Send text to the user + + +file.php +---------- + +---------------------------------- +void disconnect($msg = "") +---------------------------------- + +End the dcc file transfer session + + +ini.php +---------- +Please see readme.txt for more information on create ini objects. + + +---------------------------------- +bool getError() +---------------------------------- +An ini object will not operate after a fatal error. You can use this to retrieve whether a fatal error has happened. + + +---------------------------------- +bool sectionExists($section) +---------------------------------- +Return true if the section exists, or false if it doesn't. + + +---------------------------------- +bool deleteSection($section) +---------------------------------- +Delete a section. + + +---------------------------------- +bool deleteVar($section, $var) +---------------------------------- +Delete a var in a section. + + +---------------------------------- +bool setIniVal($section, $var, $val) +---------------------------------- +Set the '$var' value '$val' in section $section. + + +---------------------------------- +bool writeIni() +---------------------------------- +Write all of the ini changes to a file + + +---------------------------------- +array getSections() +---------------------------------- +Return an array of all the section names. + + +---------------------------------- +array getVars($section) +---------------------------------- +Return all the vars/vals in an associative array for $section. + + +---------------------------------- +array getSection($section) +---------------------------------- +Alias for getVars() + + +---------------------------------- +array randomSection($num = 1) +---------------------------------- +Does same as getVars(), but returns random section. $num is the number of random sections to return. Default 1. + + +---------------------------------- +array searchSections($search, $type = EXACT_MATCH) +---------------------------------- +Search section names for $search, can do 4 types of searches: Exact, and, or, contains: + +Exact: the section and search match completely +And: the section name contains all componants of search (separated by space) +Or: the section name contains at least one of the componants of search (separated by space) +Contains: the section contains the search string within it + + +---------------------------------- +array searchVars($section, $search, $type = EXACT_MATCH) +---------------------------------- +Search the variable names of a section for $search, same as searchSections() except with vars. + + +---------------------------------- +array searchSectionsByVar($var, $search, $type = EXACT_MATCH) +---------------------------------- +In every section that has '$var' as a varaible, see if the var's value matches $search. + + +---------------------------------- +array searchVals($section, $search, $type = EXACT_MATCH) +---------------------------------- +Search all the values of a section for '$search'. Return list of vars. + + +---------------------------------- +mixed randomVar($section, $num = 1) +---------------------------------- +Return a random var in a section. + + +---------------------------------- +mixed getIniVal($section, $var) +---------------------------------- +Retrieve an ini value '$var' in section '$section'. False if non-existant. + + +---------------------------------- +int numSections() +---------------------------------- +Return number of sections. + + +---------------------------------- +int numVars($section) +---------------------------------- +Return number of vars in a section. + + +Useful Static Routines +----------------------- + +---------------------------------- +string socket::generatePostQuery($query, $host, $path, $httpVersion = "") +---------------------------------- + +Takes a query, like so: + +$query = "search=blah&somesetting=5"; + +And turns it into a POST query you can send off to addQuery + +$host = host of server you will be accessing, like "www.manekian.com" +$path = script + path of script you will be accessing, like "/search.php" or "/index.php", or just "/" +$httpVersion, you can ignore this, but sometimes you might want to set this to "1.0" + + +---------------------------------- +string socket::generateGetQuery($query, $host, $path, $httpVersion) +---------------------------------- + +Same as generatePostQuery except with Get String + + +---------------------------------- +string irc::intToSizeString($size) +---------------------------------- + +Takes a large size, and then changes it into MB, GB, KB, depending on the size. + + +---------------------------------- +string irc::myStrToLower($text) +---------------------------------- + +RFC1459 complient strtolower() + + +---------------------------------- +string irc::myStrToUpper($text) +---------------------------------- + +RFC1459 complient strtoupper() + + +---------------------------------- +string irc::timeFormat($time, $format) +---------------------------------- +Feed this thing a timestring, and a format, and it'll send back a user friendly representation of the timestamp. + +for example: + +timeFormat(5, "%d days, %h hours, %m minutes, %s seconds") + +This will return a string, "0 days, 0 hours, 0 minutes, 5 seconds". + + +---------------------------------- +string irc::randomHash() +---------------------------------- + +Generates a 32 char random md5 hash + + +---------------------------------- +array irc::multiLine($text) +---------------------------------- + +Feed this a huge text string, and it will split it up into 255 max char increment lines you can send to the irc server. It returns an array of all of these. + + + diff --git a/ircbot/connection.php b/ircbot/connection.php new file mode 100644 index 0000000..1a248df --- /dev/null +++ b/ircbot/connection.php @@ -0,0 +1,432 @@ + connection class module +| > Module written by Manick +| > Module Version Number: 2.1.2 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +/* Connection class. A Layer between dcc/irc class and socket class. */ +/* To use, simply create an object, call the calls listed under the constructor in order. */ +/* Damn I'm hungry... */ + +class connection { + + //External Classes + private $socketClass; + private $ircClass; + private $timerClass; + + //Internal variables + private $callbackClass; + private $host; + private $port; + private $connTimeout; + private $transTimeout; + private $sockInt; + + //Function specific + private $connected; + private $connStartTime; + private $lastTransTime; + + //If this is set to true, this connection class will no longer function. + private $error; + private $errorMsg; + + //Called first + function __construct($host, $port, $connTimeout) + { + $this->error = true; + $this->errorMsg = "Connection not initialized"; + $this->host = $host; + $this->port = $port; + $this->connTimeout = $connTimeout; + $this->transTimeout = 0; + $this->connected = false; + $this->sockInt = false; + } + + //Called second + public function setSocketClass($class) + { + $this->socketClass = $class; + } + + //Called third + public function setIrcClass($class) + { + $this->ircClass = $class; + } + + //Called fourth + public function setCallbackClass($class) + { + $this->callbackClass = $class; + } + + //Called fifth + public function setTimerClass($class) + { + $this->timerClass = $class; + } + + //Called sixth + public function init() + { + $this->error = false; + + if ($this->host != null) + { + if ($this->connTimeout <= 0) + { + $this->setError("Must set connection timeout > 0 for non-listening sockets"); + return; + } + } + else + { + if ($this->connTimeout < 0) + { + $this->setError("Must set connection timeout >= 0 for listening sockets"); + return; + } + } + + if (!is_object($this->callbackClass)) + { + $this->setError("Specified callback class is not an object"); + return; + } + + if (!is_object($this->socketClass)) + { + $this->setError("Specified socket class is not an object"); + return; + } + + if (!is_object($this->ircClass)) + { + $this->setError("Specified irc class is not an object"); + return; + } + + $sockInt = $this->socketClass->addSocket($this->host, $this->port); // add socket + if ($sockInt == false) + { + $this->setError("Could not create socket"); + return; + } + + $sockData = $this->socketClass->getSockData($sockInt); + + $this->socketClass->setHandler($sockInt, $this->ircClass, $this, "handle"); + + //Set internal variables + if ($this->port == NULL) + { + $this->port = $sockData->port; + } + $this->sockInt = $sockInt; + + return $this->port; + } + + public function bind($ip) + { + if ($this->error != false || $this->sockInt == false) + { + return; + } + + if ($this->connected == true) + { + return; + } + + $this->socketClass->bindIP($this->sockInt, $ip); + } + + //Called to listen, only called by onAccept() function in this class + public function listen() + { + $this->error = false; + $this->connected = true; + } + + //Called last, and only to start connection to another server + public function connect() + { + if ($this->error == true) + { + return false; + } + + if ($this->connTimeout > 0) + { + $this->timerClass->addTimer(irc::randomHash(), $this, "connTimeout", "", $this->connTimeout); + } + + $this->timerClass->addTimer(irc::randomHash(), $this->socketClass, "connectSocketTimer", $this->sockInt, 1); + + /* $this->socketClass->beginConnect($this->sockInt); */ + $this->connStartTime = time(); + } + + public function disconnect() + { + unset($this->callbackClass); + $this->socketClass->killSocket($this->sockInt); + $this->socketClass->removeSocket($this->sockInt); + $this->setError("Disconnected from server"); + } + + public function getSockInt() + { + return $this->sockInt; + } + + public function setSockInt($sockInt) + { + $this->sockInt = $sockInt; + } + + public function setTransTimeout($time) + { + $this->transTimeout = ($time < 0 ? 0 : $time); + } + + /* Timers */ + + public function connTimeout() + { + if ($this->connected == false) + { + $this->handle(CONN_CONNECT_TIMEOUT); + } + } + + public function transTimeout() + { + if ($this->error == true) + { + return false; + } + + if ($this->connected == false) + { + return true; + } + + if ($this->transTimeout > 0) + { + if (time() > $this->transTimeout + $this->lastTransTime) + { + $this->handle(CONN_TRANSFER_TIMEOUT); + } + } + + return true; + } + + //handle function, handles all calls from socket class, and calls appropriate + //functions in the callback class + public function handle($msg) + { + + if ($this->socketClass->getSockStatus($this->sockInt) === false) + { + return false; + } + + $stat = false; + + if ($this->error == true) + { + return false; + } + + switch ($msg) + { + case CONN_CONNECT: + $stat = $this->onConnect(); + break; + case CONN_READ: + $stat = $this->onRead(); + break; + case CONN_WRITE: + $stat = $this->onWrite(); + break; + case CONN_ACCEPT: + $stat = $this->onAccept(); + break; + case CONN_DEAD: + $stat = $this->onDead(); + break; + case CONN_TRANSFER_TIMEOUT: + $stat = $this->onTransferTimeout(); + break; + case CONN_CONNECT_TIMEOUT: + $stat = $this->onConnectTimeout(); + break; + default: + return false; + break; + } + + return $stat; + } + + /* Specific handling functions */ + + private function onTransferTimeout() + { + $this->callbackClass->onTransferTimeout($this); + } + + private function onConnectTimeout() + { + $this->callbackClass->onConnectTimeout($this); + } + + private function onConnect() + { + $this->connected = true; + + if ($this->transTimeout > 0) + { + $this->timerClass->addTimer(irc::randomHash(), $this, "transTimeout", "", $this->transTimeout); + } + + $this->callbackClass->onConnect($this); + return false; + } + + //For this function, true can be returned from onRead() to input more data. + private function onRead() + { + $this->lastTransTime = time(); + + $stat = $this->callbackClass->onRead($this); + if ($stat !== true) + { + $this->socketClass->clearReadSchedule($this->sockInt); + } + + return $stat; + } + + private function onWrite() + { + $this->socketClass->clearWriteSchedule($this->sockInt); + $this->lastTransTime = time(); + $this->callbackClass->onWrite($this); + return false; + } + + private function onAccept() + { + //Get the sockInt from the socketClass + $newSockInt = $this->socketClass->hasAccepted($this->sockInt); + + if ($newSockInt === false) + { + //False alarm.. just ignore it. + return false; + } + + //Create a new connection object for the new socket connection, then return it to onAccept + //We must assume that onAccept will handle the connection object. Otherwise it'll die, and the + //connection will be orphaned. + + $newConn = new connection(null, null, 0); + $newConn->setSocketClass($this->socketClass); + $newConn->setIrcClass($this->ircClass); + $newConn->setCallbackClass($this->callbackClass); //Can be overwritten in the onAccept function + $newConn->setTimerClass($this->timerClass); + $newConn->listen(); + //We don't need to call init(), we're already setup. + + //Setup our connection transmission timeout thing. + if ($this->transTimeout > 0) + { + $this->timerClass->addTimer(irc::randomHash(), $newConn, "transTimeout", "", $this->transTimeout); + } + + $newConn->setTransTimeout($this->transTimeout); + + //Set handler for our new sockInt to new connection class + $this->socketClass->setHandler($newSockInt, $this->ircClass, $newConn, "handle"); + + //Set our new sockInt + $newConn->setSockInt($newSockInt); + + //Call our callback function for accepting with old object, and new object. + $this->callbackClass->onAccept($this, $newConn); + return false; + } + + private function onDead() + { + if ($this->connected == true) + { + $this->setError("Connection is dead"); + } + else + { + $this->setError("Could not connect: " . $this->socketClass->getSockStringError($this->sockInt)); + } + $this->callbackClass->onDead($this); + return false; + } + + /* Error handling routines */ + + private function setError($msg) + { + $this->error = true; + $this->errorMsg = $msg; + } + + public function getError() + { + return $this->error; + } + + public function getErrorMsg() + { + return $this->errorMsg; + } + +} + +?> diff --git a/ircbot/databases/ini.php b/ircbot/databases/ini.php new file mode 100644 index 0000000..a2712f8 --- /dev/null +++ b/ircbot/databases/ini.php @@ -0,0 +1,515 @@ + ini-file database module +| > Module written by Manick +| > Module Version Number: 2.2.1 alpha ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +class ini { + + private $filename; + private $error; + private $ini = array(); + private $numSections; + + //used in isMatched() + private $search; + private $searchParts; + + + //Load ini into memory + public function __construct($filename) + { + $this->error = false; + $this->filename = $filename; + + $filePtr = @fopen($filename, "r"); + + if ($filePtr === false) + { + $filePtr = @fopen($filename, "a"); + + if ($filePtr === false) + { + $this->error = true; + return; + } + else + { + fclose($filePtr); + return; + } + } + + $fileData = ""; + + while (!feof($filePtr)) + { + $fileData .= fread($filePtr, 4096); + } + + fclose($filePtr); + + $fileData = str_replace("\r", "", $fileData); + + $lines = explode("\n", $fileData); + + $currSection = ""; + $this->numSections = 0; + + foreach($lines AS $line) + { + $line = trim($line); + + $offsetA = strpos($line, "["); + $offsetB = strpos($line, "]"); + + if ($offsetA === 0) + { + $currSection = substr($line, 1, $offsetB - 1); + $this->numSections++; + $this->ini[$currSection] = array(); + } + else + { + if ($currSection != "") + { + $offsetC = strpos($line, "="); + + if ($offsetC !== false) + { + $var = trim(substr($line, 0, $offsetC)); + $val = substr($line, $offsetC + 1); + + if ($var != "") + { + $this->ini[$currSection][$var] = $val; + } + } + else + { + $this->ini[$currSection][$line] = true; + } + } + } + } + } + + public function getError() + { + return $this->error; + } + + public function getSections() + { + $sections = array(); + + if ($this->numSections == 0) + { + return $sections; + } + + foreach ($this->ini AS $section => $vals) + { + $sections[] = $section; + } + + return $sections; + } + + public function getVars($section) + { + if (!isset($this->ini[$section])) + { + return false; + } + + return $this->ini[$section]; + } + + public function sectionExists($section) + { + if (isset($this->ini[$section]) && is_array($this->ini[$section])) + { + return true; + } + return false; + } + + public function getSection($section) + { + return $this->getVars($section); + } + + public function randomSection($num = 1) + { + if ($this->numSections == 0 || $num < 1 || $num > $this->numSections) + { + return false; + } + + return array_rand($this->ini, $num); + } + + public function randomVar($section, $num = 1) + { + if (!isset($this->ini[$section])) + { + return false; + } + + $count = count($this->ini[$section]); + + if ($count == 0 || $num < 1 || $num > $count) + { + return false; + } + + return array_rand($this->ini[$section], $num); + } + + + public function searchSections($search, $type = EXACT_MATCH) + { + $results = array(); + + if (trim($search) == "") + { + return $results; + } + + if ($this->numSections == 0) + { + return; + } + + foreach($this->ini AS $section => $vars) + { + if ($this->isMatched($search, $section, $type)) + { + $results[] = $section; + } + } + + return $results; + } + + public function searchVars($section, $search, $type = EXACT_MATCH) + { + $results = array(); + + if (trim($search) == "") + { + return $results; + } + + if (!isset($this->ini[$section])) + { + return $results; + } + + if ($this->numSections == 0) + { + return; + } + + if (count($this->ini[$section]) == 0) + { + return $results; + } + + foreach($this->ini[$section] AS $var => $val) + { + if ($this->isMatched($search, $var, $type)) + { + $results[] = $var; + } + } + + return $results; + } + + public function searchSectionsByVar($var, $search, $type = EXACT_MATCH) + { + $results = array(); + + if ($this->numSections == 0) + { + return $results; + } + + foreach($this->ini AS $section => $vars) + { + if (isset($vars[$var])) + { + if ($this->isMatched($search, $vars[$var], $type)) + { + $results[] = $section; + } + } + } + + return $results; + + } + + public function searchVals($section, $search, $type = EXACT_MATCH) + { + $results = array(); + + if (trim($search) == "") + { + return $results; + } + + if (!isset($this->ini[$section])) + { + return $results; + } + + if ($this->numSections == 0) + { + return; + } + + if (count($this->ini[$section]) == 0) + { + return $results; + } + + foreach($this->ini[$section] AS $var => $val) + { + if ($this->isMatched($search, $val, $type)) + { + $results[] = $var; + } + } + + return $results; + } + + private function isMatched(&$needle, &$haystack, $type = EXACT_MATCH) + { + + if ($type == EXACT_MATCH) + { + if ($haystack == $needle) + { + return true; + } + } + + if ($type == CONTAINS_MATCH) + { + if (strpos(strtolower($haystack), strtolower($needle)) !== false) + { + return true; + } + } + + if ($search != $this->search) + { + $this->searchParts = explode(chr(32), $search); + $this->search = $search; + } + + if ($type == AND_MATCH) + { + $foundAll = true; + + foreach($this->searchParts AS $part) + { + if (strpos($val, $part) === false) + { + $foundAll = false; + break; + } + } + + if ($foundAll == true) + { + return true; + } + } + else if ($type == OR_MATCH) + { + foreach($this->searchParts AS $part) + { + if (strpos($val, $part) !== false) + { + return true; + break; + } + } + } + + return false; + } + + + public function deleteSection($section) + { + if (isset($this->ini[$section])) + { + unset($this->ini[$section]); + return true; + } + + return false; + } + + public function deleteVar($section, $var) + { + if (isset($this->ini[$section])) + { + if (isset($this->ini[$section][$var])) + { + unset($this->ini[$section][$var]); + return true; + } + } + + return false; + } + + public function numSections() + { + return $this->numSections; + } + + public function numVars($section) + { + if (isset($this->ini[$section])) + { + return count($this->ini[$section]); + } + return 0; + } + + public function setIniVal($section, $var, $val) + { + if ($this->error == true) + { + return; + } + + if (!isset($this->ini[$section])) + { + $this->numSections++; + $this->ini[$section] = array(); + } + + if (strpos($var, "=") !== false) + { + return false; + } + + $this->ini[$section][$var] = $val; + + return true; + } + + public function getIniVal($section, $var) + { + if ($this->error == true) + { + return; + } + + if (isset($this->ini[$section]) + && isset($this->ini[$section][$var])) + { + return $this->ini[$section][$var]; + } + else + { + return false; + } + } + + //Update and write ini to file + public function writeIni() + { + if ($this->error == true) + { + return; + } + + if ($this->numSections == 0) + { + return; + } + + $output = ""; + + foreach ($this->ini AS $section => $vars) + { + + $output .= "[" . $section . "]\n"; + + if (count($vars)) + { + foreach ($vars AS $var => $val) + { + $output .= $var . "=" . $val . "\n"; + } + } + } + + $filePtr = fopen($this->filename, "at"); + + if ($filePtr === false) + { + $this->error = true; + return false; + } + + flock($filePtr, LOCK_EX); + + ftruncate($filePtr, 0); + + if (fwrite($filePtr, $output) === FALSE) + { + $this->error = true; + } + + flock($filePtr, LOCK_UN); + + fclose($filePtr); + + return !$this->error; + } + +} + +?> diff --git a/ircbot/databases/mysql.php b/ircbot/databases/mysql.php new file mode 100644 index 0000000..7ffaa3a --- /dev/null +++ b/ircbot/databases/mysql.php @@ -0,0 +1,169 @@ + database module +| > Module written by Manick +| > Module Version Number: 2.2.0 alpha1 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +class mysql { + + private $dbIndex; + private $prefix; + private $queries = 0; + private $isConnected = false; + + private $user; + private $pass; + private $database; + private $host; + private $port; + + public function __construct($host, $database, $user, $pass, $prefix, $port = 3306) + { + $this->user = $user; + $this->pass = $pass; + $this->host = $host; + $this->database = $database; + $this->port = $port; + + $db = mysql_connect($host . ":" . $port, $user, $pass); + + if (!$db) + { + return; + } + + $dBase = mysql_select_db($database, $db); + + if (!$dBase) + { + return; + } + + $this->prefix = $prefix; + $this->dbIndex = $db; + $this->isConnected = true; + } + + public function getError() + { + return (@mysql_error($this->dbIndex)); + } + + public function isConnected() + { + return $this->isConnected; + } + + //Call by reference switched to function declaration, 05/13/05 + private function fixVar($id, &$values) + { + return mysql_real_escape_string($values[intval($id)-1], $this->dbIndex); + } + + public function query($query, $values = array()) + { + + if (!is_array($values)) + $values = array($values); + + $query = preg_replace('/\[([0-9]+)]/e', "\$this->fixVar(\\1, \$values)", $query); + + $this->queries++; + + $data = mysql_query($query, $this->dbIndex); + + if (!$data) + { + return false; + } + + return $data; + } + + + public function queryFetch($query, $values = array()) + { + + if (!is_array($values)) + $values = array($values); + + $query = preg_replace('/\[([0-9]+)]/e', "\$this->fixVar(\\1, &\$values)", $query); + + $this->queries++; + + $data= mysql_query($query, $this->dbIndex); + + if (!$data) + { + return false; + } + + return mysql_fetch_array($data); + } + + + public function fetchArray($toFetch) + { + return mysql_fetch_array($toFetch); + } + + public function fetchRow($toFetch) + { + return mysql_fetch_row($toFetch); + } + + public function close() + { + @mysql_close($this->dbIndex); + } + + public function lastID() + { + return mysql_insert_id(); + } + + public function numRows($toFetch) + { + return mysql_num_rows($toFetch); + } + + public function numQueries() + { + return $this->queries; + } + +} + +?> + diff --git a/ircbot/databases/postgre.php b/ircbot/databases/postgre.php new file mode 100644 index 0000000..1bb3e51 --- /dev/null +++ b/ircbot/databases/postgre.php @@ -0,0 +1,162 @@ + database module +| > Module written by Manick +| > Module Version Number: 2.1.1 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +class postgre { + + private $dbIndex; + private $prefix; + private $queries = 0; + private $isConnected = false; + private $error; + + public function __construct($host, $database, $user, $pass, $prefix, $port = 5432) + { + $this->error = true; + + $connect = "host=" . $host . " ". + "port=" . $port . " ". + "dbname=" . $database . " ". + "user=" . $user . " ". + "password=" . $pass; + + $this->error = pg_connect($connect); + + if (!$this->error) + { + return; + } + + $this->prefix = $prefix; + $this->dbIndex = $this->error; + $this->isConnected = true; + } + + public function getError() + { + return $this->error === false ? true : false; + //return (@mysql_error($this->dbIndex)); + } + + public function isConnected() + { + return $this->isConnected; + } + + private function fixVar($id, $values) + { + return pg_escape_string($values[intval($id)-1]); + } + + public function query($query, $values = array()) + { + + if (!is_array($values)) + $values = array($values); + + $query = preg_replace('/\[([0-9]+)]/e', "\$this->fixVar(\\1, &\$values)", $query); + + $this->queries++; + + $data = pg_query($this->dbIndex, $query); + + if (!$data) + { + $this->error = $data; + return false; + } + + return $data; + } + + + public function queryFetch($query, $values = array()) + { + + if (!is_array($values)) + $values = array($values); + + $query = preg_replace('/\[([0-9]+)]/e', "\$this->fixVar(\\1, &\$values)", $query); + + $this->queries++; + + $data = pg_query($query, $this->dbIndex); + + if (!$data) + { + $this->error = false; + return false; + } + + return pg_fetch_array($data); + } + + + public function fetchArray($toFetch) + { + return pg_fetch_array($toFetch); + } + + public function fetchRow($toFetch) + { + return pg_fetch_row($toFetch); + } + + public function close() + { + @pg_close($this->dbIndex); + } + + public function lastID() + { + //ehhh. don't use this. + return null; + } + + public function numRows($toFetch) + { + return pg_num_rows($toFetch); + } + + public function numQueries() + { + return $this->queries; + } + +} + +?> + diff --git a/ircbot/databases/postgres.php b/ircbot/databases/postgres.php new file mode 100644 index 0000000..92264c2 --- /dev/null +++ b/ircbot/databases/postgres.php @@ -0,0 +1,148 @@ + database module +| > Module written by Manick +| > Module Version Number: 2.1.1 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +// *** Modified by Nemesis128_at_atarax_dot_org + +class postgresql { + + private $dbRes; + private $prefix; + private $numQueries = 0; + private $isConnected = false; + private $error = false; + + private $user; + private $pswd; + private $dbase; + private $host; + private $port; + + public function __construct ($user,$pswd,$dbase,$prefix,$host = null,$port = 5432) { + + $this->user = $user; + $this->pswd = $pswd; + $this->dbase = $dbase; + $this->prefix = $prefix; + $this->host = $host; + $this->port = $port; + + $conn_str = ''; + + if (!is_null($host)) { // connect thru TCP/IP + $conn_str .= 'host='.$host; + $conn_str .= ' port='.$port; + } // else thru intern sockets + $conn_str .= ' user='.$user; + $conn_str .= ' password='.$pswd; + $conn_str .= ' dbname='.$dbase; + + $this->dbRes = pg_connect($conn_str); + + if (!is_resource($this->dbRes)) { + $this->error = 'PgSQL Connection error'; + return; + } + + $this->isConnected = true; + } + + public function getError () { + if ($this->error) { + $err = $this->error."\n\n"; + return ($err.@pg_last_error($this->dbIndex)); + } else { + return null; + } + } + + public function isConnected () { + return $this->isConnected; + } + + public static function esc ( $var ) { + return pg_escape_string ( $var ); + } + + public function query ( $query_str ) { + + if (pg_connection_status($this->dbRes) === PGSQL_CONNECTION_BAD) { + if (!pg_connection_reset($this->dbRes)) { + $this->error = 'Connection lost'; + $this->isConnected = false; + return false; + } + } + + $this->numQueries++; + + $res = @pg_query($this->dbRes,$query_str); + + if (!$res) { + $this->error = 'Query failed: '.pg_last_error().' ('.$query_str.')'; + return false; + } + + return $res; + } + + public function fetchArray ( $toFetch ) { + return pg_fetch_assoc($toFetch); + } + + public function fetchObject ( $toFetch ) { + return pg_fetch_object($toFetch); + } + + public function fetchRow ( $toFetch ) { + return pg_fetch_row($toFetch); + } + + public function numRows ( $toFetch ) { + return pg_num_rows($toFetch); + } + + public function numQueries () { + return $this->numQueries; + } + + public function close () { + @pg_close($this->dbRes); + } + +} + +?> \ No newline at end of file diff --git a/ircbot/dcc.php b/ircbot/dcc.php new file mode 100644 index 0000000..5ec3282 --- /dev/null +++ b/ircbot/dcc.php @@ -0,0 +1,579 @@ + dcc module +| > Module written by Manick +| > Module Version Number: 2.2.0 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +class dcc { + + private $dccList = array(); //holds all connected sockets + private $dccChangedList = array(); + private $dccChangedCount = 0; + + private $fileDlCount = 0; + private $fileUlCount = 0; + private $chatCount = 0; + + //Classes + private $timerClass; + private $socketClass; + private $ircClass; + private $parserClass; + + //Bytes Transferred + private $bytesUp = 0; + private $bytesDown = 0; + + //Process Queue + private $procQueue; + + public function __construct() + { + // do nothing... + } + + public function setProcQueue($class) + { + $this->procQueue = $class; + } + + public function setIrcClass($class) + { + $this->ircClass = $class; + } + + public function setParserClass($class) + { + $this->parserClass = $class; + $this->parserClass->setDccClass($this); + } + + public function setSocketClass($class) + { + $this->socketClass = $class; + } + + public function setTimerClass($class) + { + $this->timerClass = $class; + } + + public function getDownloadCount() + { + return $this->fileDlCount; + } + + public function getUploadCount() + { + return $this->fileUlCount; + } + + public function getChatCount() + { + return $this->chatCount; + } + + public function closeAll() + { + foreach ($this->dccList AS $dcc) + { + $dcc->disconnect("Owner Requsted Close"); + } + } + + public function addBytesUp($num) + { + $this->bytesUp += $num; + } + + public function addBytesDown($num) + { + $this->bytesDown += $num; + } + + public function getBytesDown() + { + return $this->bytesDown; + } + + public function getBytesUp() + { + return $this->bytesUp; + } + + + public function getDcc($someDcc) + { + if (isset($this->dccList[$someDcc])) + { + return $this->dccList[$someDcc]; + } + + return false; + } + + + public function dccInform($data, $from = null) + { + foreach ($this->dccList AS $dcc) + { + if ($dcc->type == CHAT && $dcc->verified == true && $dcc->isAdmin == true) + { + if ($from != null) + { + if ($dcc->sockInt != $from->sockInt) + { + $dcc->dccSend($data); + } + } + else + { + $dcc->dccSend($data); + } + + } + + } + } + + //Works in conjunction with $oldConn->connected + public function accepted($oldConn, $newConn) + { + $sockInt = $oldConn->getSockInt(); + $hasAccepted = $newConn->getSockInt(); + + $dcc = $this->dccList[$sockInt]; + $this->dccList[$hasAccepted] = $dcc; + $dcc->status = DCC_CONNECTED; + $dcc->sockInt = $hasAccepted; + unset($this->dccList[$sockInt]); + } + + //CheckDccTimeout function + public function checkDccTimeout($dcc) + { + if (!is_object($dcc) || $dcc->removed == true) + { + return false; + } + + if ($dcc->status != DCC_LISTENING) + { + return false; + } + + switch ($dcc->timeOutLevel) + { + case 0: + $dcc->timeOutLevel++; + break; + case 1: + $dcc->timeOutLevel++; + $this->ircClass->notice($dcc->nick, "You have a DCC session pending. Set your client to connect. 60 seconds before timeout.", 1); + break; + case 2: + $dcc->timeOutLevel++; + $this->ircClass->notice($dcc->nick, "You have a DCC session pending. Set your client to connect. 30 seconds before timeout.", 1); + break; + case 3: + $dcc->timeOutLevel = 0; + $dcc->disconnect("DCC Session timed out (90 Seconds)"); + return false; + break; + default: + break; + } + + return true; + + } + + public function getDccList() + { + return $this->dccList; + } + + private function removeDcc($dcc) + { + $sockInt = $dcc->sockInt; + unset($this->dccList[$sockInt]); + } + + public function dccSend($to, $data) + { + if (($len = $this->socketClass->sendSocket($to->sockInt, $data)) === false) + { + $to->disconnect(); + } + return $len; + } + + + private function highestId() + { + $highest = 0; + + foreach ($this->dccList AS $index => $dcc) + { + $highest = ($dcc->id > $highest ? $dcc->id : $highest); + } + + return $highest + 1; + } + + + public function addChat($nick, $host, $port, $admin, $handler, $fromTimer = false) + { + $lnick = irc::myStrToLower($nick); + + foreach ($this->dccList AS $index => $dcc) + { + if ($dcc->type == CHAT) + { + if (irc::myStrToLower($dcc->nick) == $lnick) + { + $dcc->disconnect(); + break; + } + } + } + + $reverse = false; + + if ($this->ircClass->getClientConf("mircdccreverse") != "" && $fromTimer == false) + { + $port = intval($this->ircClass->getClientConf("mircdccreverse")); + if ($port == 0) + { + return NULL; + } + + $args = new argClass; + + $args->arg1 = $nick; + $args->arg2 = $host; + $args->arg3 = $port; + $args->arg4 = $admin; + $args->arg5 = $handler; + $args->arg7 = time(); + $args->arg8 = CHAT; + + $this->ircClass->notice($nick, "DCC: NOTICE: This server is using the mIRC Chat Server Protocol. Please use ' /dccserver +c on " . $this->ircClass->getClientConf("mircdccreverse") . " ' to chat with me! Starting 6 second delay...", 0); + + $this->ircClass->sendRaw("WHOIS " . $nick); + $this->timerClass->addTimer(irc::randomHash(), $this, "reverseTimer", $args, 6, false); + return; + } + + if ($fromTimer == true) + { + $reverse = DCC_REVERSE; // using mIRC dcc reverse protocol + } + + if ($host == NULL || $port == NULL) + { + $conn = new connection(null, null, 0); + $listening = true; + $status = DCC_LISTENING; + } + else + { + $conn = new connection($host, $port, CONNECT_TIMEOUT); + $listening = false; + $status = DCC_CONNECTING; + } + + $conn->setSocketClass($this->socketClass); + $conn->setIrcClass($this->ircClass); + $conn->setCallbackClass($this); + $conn->setTimerClass($this->timerClass); + $port = $conn->init(); + + if ($conn->getError()) + { + $this->ircClass->log("Start Chat Error: " . $conn->getErrorMsg()); + return false; + } + + $sockInt = $conn->getSockInt(); + + $this->chatCount++; + + $id = $this->highestId(); + + $chat = new chat($id, $nick, $admin, $sockInt, $host, $port, $handler, $reverse); + $chat->setIrcClass($this->ircClass); + $chat->setDccClass($this); + $chat->setParserClass($this->parserClass); + $chat->setSocketClass($this->socketClass); + $chat->setTimerClass($this->timerClass); + $chat->connection = $conn; + $chat->status = $status; + $chat->removed = false; + + $this->dccList[$sockInt] = $chat; + + $chat->initialize(); + + $conn->setCallbackClass($chat); + + if ($listening == true) + { + $this->timerClass->addTimer(irc::randomHash(), $this, "checkDccTimeout", $chat, 30, true); + } + else + { + $conn->connect(); + } + + return $port; + } + + public function addFile($nick, $host, $port, $type, $filename, $size, $fromTimer = false) // <-- ignore fromTimer, it is sent by reverseTimer() above + { + $reverse = false; + + if ($this->ircClass->getClientConf("mircdccreverse") != "" && $fromTimer == false && $type != DOWNLOAD) + { + $port = intval($this->ircClass->getClientConf("mircdccreverse")); + if ($port == 0) + { + return NULL; + } + + $args = new argClass; + + $args->arg1 = $nick; + $args->arg2 = $host; + $args->arg3 = $port; + $args->arg4 = $type; + $args->arg5 = $filename; + $args->arg6 = $size; + $args->arg7 = time(); + $args->arg8 = FILE; + + $this->ircClass->notice($nick, "DCC: NOTICE: This server is using the mIRC File Server Protocol. Please use ' /dccserver +s on " . $this->ircClass->getClientConf("mircdccreverse") . " ' to recieve files from me! Starting 6 second delay...", 0); + + $this->ircClass->sendRaw("WHOIS " . $nick); + $this->timerClass->addTimer(irc::randomHash(), $this, "reverseTimer", $args, 6); + return; + } + + if ($fromTimer == true) + { + $reverse = DCC_REVERSE; // using mIRC dcc reverse protocol + } + + if ($host == NULL || $port == NULL) + { + $conn = new connection(null, null, 0); + $listening = true; + $status = DCC_LISTENING; + } + else + { + $conn = new connection($host, $port, CONNECT_TIMEOUT); + $listening = false; + $status = DCC_CONNECTING; + } + + $conn->setSocketClass($this->socketClass); + $conn->setIrcClass($this->ircClass); + $conn->setCallbackClass($this); + $conn->setTransTimeout(30); + $conn->setTimerClass($this->timerClass); + $port = $conn->init(); + + if ($conn->getError()) + { + $this->ircClass->log("File transfer start error: " . $conn->getErrorMsg()); + return false; + } + + $sockInt = $conn->getSockInt(); + + $id = $this->highestId(); + + $file = new file($id, $nick, $sockInt, $host, $port, $type, $reverse); + + if ($file->transferType == UPLOAD) + { + $this->fileUlCount++; + } + else + { + $this->fileDlCount++; + } + + $file->setIrcClass($this->ircClass); + $file->setDccClass($this); + $file->setSocketClass($this->socketClass); + $file->setProcQueue($this->procQueue); + $file->setTimerClass($this->timerClass); + $file->connection = $conn; + $file->status = $status; + $file->removed = false; + + $this->dccList[$sockInt] = $file; + + $file->initialize($filename, $size); + + $conn->setCallbackClass($file); + + if ($listening == true) + { + $this->timerClass->addTimer(irc::randomHash(), $this, "checkDccTimeout", $file, 30, true); + } + + if ($reverse == true) + { + $conn->connect(); + } + + return $port; + } + + + public function alterSocket($sockInt, $level, $opt, $val) + { + return $this->socketClass->alterSocket($sockInt, $level, $opt, $val); + } + + + public function reverseTimer($args) + { + $memData = $this->ircClass->getUserData($args->arg1); + + if ($memData == NULL || ($memData->host == "" || $memData->host == NULL)) + { + $this->ircClass->notice($args->arg1, "DCC: ERROR: Couldn't resolve your hostname. Try again?"); + } + else + { + + if ($args->arg8 == FILE) + { + $this->addFile( $args->arg1, + $memData->host, + $this->ircClass->getClientConf("mircdccreverse"), + $args->arg4, + $args->arg5, + $args->arg6, + true); + } + else + { + $this->addChat( $args->arg1, + $memData->host, + $this->ircClass->getClientConf("mircdccreverse"), + $args->arg4, + $args->arg5, + true); + } + } + + return false; + } + + public function sendFile($nick, $file) + { + return $this->addFile($nick, null, null, UPLOAD, $file, NULL); + } + + + public function dccResume($port, $size) + { + foreach ($this->dccList AS $dcc) + { + if ($dcc->type == FILE) + { + if ($dcc->port == $port) + { + $dcc->resume($size); + break; + } + } + } + } + + public function dccAccept($port) + { + foreach ($this->dccList AS $dcc) + { + if ($dcc->type == FILE) + { + if ($dcc->port == $port) + { + $dcc->accepted(); + break; + } + } + } + } + + public function disconnect($dcc) + { + + switch ($dcc->type) + { + case CHAT: + $this->chatCount--; + break; + case FILE: + switch($dcc->transferType) + { + case UPLOAD: + $this->fileUlCount--; + break; + case DOWNLOAD: + $this->fileDlCount--; + break; + } + break; + default: + break; + } + + $dcc->removed = true; + + $dcc->connection->disconnect(); + $this->removeDcc($dcc); + + return true; + } + + +} + +?> diff --git a/ircbot/defines.php b/ircbot/defines.php new file mode 100644 index 0000000..15d283e --- /dev/null +++ b/ircbot/defines.php @@ -0,0 +1,314 @@ + defines module +| > Module written by Manick +| > Module Version Number: 2.2.0 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +// Debug Mode +define('DEBUG', 1); + +// PID file +define('PID', config::$main['cs_path'] . "/ircbot.pid"); + +// OS Type (windows/unix/linux/freebsd/unknown/auto) +define('OS', 'auto'); + +//YOU SHOULD NOT HAVE TO EDIT BELOW THIS POINT UNLESS YOU SPECIFY "unknown" AS OS! + +if (OS == "auto") +{ + switch (PHP_OS) + { + case "Windows NT": + $OS = "windows"; + break; + case "Linux": + $OS = "linux"; + break; + case "FreeBSD": + $OS = "freebsd"; + break; + case "Unix": + $OS = "unix"; + break; + //Thx OrochiTux for below + case "Darwin": + $OS = "freebsd"; + break; + default: + $OS = "windows"; + break; + } +} +else +{ + $OS = OS; +} + +if ($OS == 'unknown') +{ + define('EAGAIN', 0); /* Try again */ + define('EISCONN', 0); /* Transport endpoint is already connected */ + define('EALREADY', 0); /* Operation already in progress */ + define('EINPROGRESS', 0); /* Operation now in progress */ +} +else if ($OS == 'windows') +{ + //http://developer.novell.com/support/winsock/doc/appenda.htm + define('EAGAIN', 10035); //EWOULDBLOCK.. kinda like EAGAIN in windows? + define('EISCONN', 10056); /* Transport endpoint is already connected */ + define('EALREADY', 10037); /* Operation already in progress */ + define('EINPROGRESS', 10036); /* Operation now in progress */ +} +else if ($OS == 'freebsd') +{ + //Thanks to ryguy@efnet + ///usr/include/errno.h (freebsd) + define('EAGAIN', 35); /* Try again */ + define('EISCONN', 56); /* Transport endpoint is already connected */ + define('EALREADY', 37); /* Operation already in progress */ + define('EINPROGRESS', 36); /* Operation now in progress */ +} +else if ($OS == 'linux') +{ + ///usr/include/sys/errno.h (sparc) + define('EAGAIN', 11); /* Try again */ + define('EISCONN', 106); /* Transport endpoint is already connected */ + define('EALREADY', 114); /* Operation already in progress */ + define('EINPROGRESS', 115); /* Operation now in progress */ +} +else if ($OS == 'unix') +{ + ///usr/include/asm/errno.h (mandrake 9.0) + define('EAGAIN', 11); /* Try again */ + define('EISCONN', 133); /* Transport endpoint is already connected */ + define('EALREADY', 149); /* Operation already in progress */ + define('EINPROGRESS', 150); /* Operation now in progress */ +} + +// Version Definition +define('VERSION', '2.2.1'); +define('VERSION_DATE', '04/08/06'); + +// Timer declarations +define('NICK_CHECK_TIMEOUT', 120); //seconds +define('CHAN_CHECK_TIMEOUT', 60); //seconds +define('PING_TIMEOUT', 130); //seconds (check every 130 seconds if we're still connected) + +// Parser definitions +define('MAX_ARGS', 4); + +// Status definitions +define('STATUS_IDLE', 0); +define('STATUS_ERROR', 1); +define('STATUS_CONNECTING', 2); +define('STATUS_CONNECTED', 3); +define('STATUS_CONNECTED_SENTREGDATA', 4); +define('STATUS_CONNECTED_REGISTERED', 5); + +// Constant Definitions +define('ERROR_TIMEOUT', 60); +define('CONNECT_TIMEOUT', 45); +define('REGISTRATION_TIMEOUT', 60); +define('TIMEOUT_CHECK_TIME', 85); //85 + +//Constants for Channel Modes +define('BY_MASK', 0); +define('BY_STRING', 1); +define('BY_INT', 2); +define('BY_NONE', 3); + +//Used with $ircClass->parseMode +define('USER_MODE', 0); +define('CHANNEL_MODE', 1); + +//Random Vars +define('STATUS_JUST_BANNED', 1); +define('STATUS_ALREADY_BANNED', 2); +define('STATUS_NOT_BANNED', 3); + +//Socket Class defines +define('SOCK_DEAD', 1); +define('SOCK_CONNECTING', 2); +define('SOCK_LISTENING', 3); +define('SOCK_ACCEPTED', 4); +define('SOCK_ACCEPTING', 5); +define('SOCK_CONNECTED', 6); +define('HIGHEST_PORT', 1000); // this is tcpRangeStart + HIGHEST_PORT + +//DCC Class defines +define('FILE', 0); +define('CHAT', 1); +define('DCC_WAITING', 3); +define('DCC_REVERSE', 4); +define('DCC_CONNECTING', 0); +define('DCC_CONNECTED', 1); +define('DCC_LISTENING', 2); + +//Connection class defines +define('CONN_READ', 0); +define('CONN_WRITE', 1); +define('CONN_ACCEPT', 2); +define('CONN_CONNECT', 3); +define('CONN_DEAD', 4); +define('CONN_CONNECT_TIMEOUT', 5); +define('CONN_TRANSFER_TIMEOUT', 6); + +//Parser Class defines +define('BRIGHT', chr(3) . "13"); +define('DARK', chr(3) . "03"); +define('NORMAL', chr(16)); +define('BOLD', chr(2)); +define('UNDERLINE', chr(31)); +define('PRIV', 1); +define('DCC', 2); + +//File Class defines +define('UPLOAD', 0); +define('DOWNLOAD', 1); + +//Used with $ircClass->addQuery +define('QUERY_SUCCESS', 0); +define('QUERY_ERROR', 1); + +//Used in ini +define('EXACT_MATCH', 0); +define('AND_MATCH', 1); +define('OR_MATCH', 2); +define('CONTAINS_MATCH', 3); + +//Used in socket class to keep track of sockets + +class socketInfo { + public $socket; + public $status; + public $readQueue; + public $readLength; + public $writeQueue; + public $writeLength; + public $host; + public $port; + public $newSockInt; + public $listener; + public $owner; + public $class; + public $func; + public $readScheduled; //Used so we don't add infinite queues to the process queue. + public $writeScheduled; +} + +//Channel and Username Linked List (Links) Definitions + +class channelLink { + public $name; + public $count; + public $memberList = array(); + public $banList = array(); + public $whoComplete; + public $banComplete; + public $modes; + public $created; + public $topic; + public $topicBy; +} + +class memberLink { + public $nick; + public $realNick; + public $host; + public $ident; + public $banned; + public $bantime; + public $status; + public $ignored; +} + +// Used in timer class + +class timer { + public $name; + public $class; + public $args; + public $interval; + public $lastTimeRun; + public $nextRunTime; + public $func; +} + +class usageLink { + public $isBanned; + public $timeBanned; + public $lastTimeUsed; + public $timesUsed; +} + + +// Useful for sending arguments with timers +class argClass +{ + public $arg1; + public $arg2; + public $arg3; + public $arg4; + public $arg5; + public $arg6; + public $arg7; + public $arg8; + public $timer; +} + +// Used to instantiate a bot +class botClass { + public $timerClass; + public $ircClass; + public $dccClass; + public $parserClass; + public $socketClass; + public $configFilename; + public $db; + public $config; +} + +// Used with processQueue +class queueItem { + public $owner; //IRC Class of owner + public $callBack_class; //CALL BACK class/function to use + public $callBack_function; + public $nextRunTime; //The next getMicroTime() time to run + public $removed; + public $next; + public $prev; +} + +?> diff --git a/ircbot/error.php b/ircbot/error.php new file mode 100644 index 0000000..789b7fb --- /dev/null +++ b/ircbot/error.php @@ -0,0 +1,107 @@ + error module +| > Module written by Manick +| > Module Version Number: 2.2.0 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +class ConnectException extends Exception { + + private $exceptionTime = 0; + + function __construct($message) + { + parent::__construct($message); + $this->exceptionTime = time(); + } + + function getTime() + { + return $this->exceptionTime; + } +} + + +class SendDataException extends Exception { + + private $exceptionTime = 0; + + function __construct($message) + { + parent::__construct($message); + $this->exceptionTime = time(); + } + + function getTime() + { + return $this->exceptionTime; + } +} + + + +class ConnectionTimeout extends Exception { + + private $exceptionTime = 0; + + function __construct($message) + { + parent::__construct($message); + $this->exceptionTime = time(); + } + + function getTime() + { + return $this->exceptionTime; + } +} + + + +class ReadException extends Exception { + + private $exceptionTime = 0; + + function __construct($message) + { + parent::__construct($message); + $this->exceptionTime = time(); + } + + function getTime() + { + return $this->exceptionTime; + } +} + +?> diff --git a/ircbot/file.php b/ircbot/file.php new file mode 100644 index 0000000..0b7f84c --- /dev/null +++ b/ircbot/file.php @@ -0,0 +1,663 @@ + dcc chat module +| > Module written by Manick +| > Module Version Number: 2.2.0 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +class file { + + /* Chat specific Data */ + public $id; + public $status; + public $sockInt; + public $timeConnected; + public $readQueue; + public $port; + public $dccString; + public $type; + public $transferType; + public $nick; + public $timeOutLevel; + public $removed; + public $connection; + + public $reverse; // reverse dcc? + private $handShakeSent; + private $handShakeTime; + + public $filename; + public $filenameNoDir; + public $filePointer; + public $filesize; + public $bytesTransfered; + public $resumedSize; + public $completed; + public $reportedRecieved; + + public $connectHost; + + //private $resumed; + private $started; + + private $sendQueue; + private $sendQueueCount; + + //keep track of speed; + private $speed_sec_add; + public $speed_lastavg; + private $speed_starttime; + + /* Classes */ + private $dccClass; + private $ircClass; + private $socketClass; + private $procQueue; + private $timerClass; + + /* Constructor */ + public function __construct($id, $nick, $sockInt, $host, $port, $type, $reverse) + { + $this->id = $id; + $this->nick = $nick; + $this->sockInt = $sockInt; + $this->connectHost = $host; + $this->port = $port; + $this->transferType = $type; + $this->filesize = 0; + $this->bytesTransfered = 0; + $this->resumedSize = 0; + $this->started = false; + $this->status = DCC_WAITING; + $this->reverse = $reverse; + $this->handShakeSent = false; + + $this->speed_sec_add = 0; + $this->speed_lastavg = 0; + $this->speed_starttime = 0; + + if ($type == UPLOAD) + { + $this->dccString = "DCC UPLOAD[".$this->id."]: "; + } + else + { + $this->dccString = "DCC DOWNLOAD[".$this->id."]: "; + } + + } + + public function setProcQueue($class) + { + $this->procQueue = $class; + } + + public function setSocketClass($class) + { + $this->socketClass = $class; + } + + public function setIrcClass($class) + { + $this->ircClass = $class; + } + + public function setTimerClass($class) + { + $this->timerClass = $class; + } + + public function setDccClass($class) + { + $this->dccClass = $class; + } + + public function dccSend($data) + { + return $this->dccClass->dccSend($this, $data); + } + + public function disconnect($msg = "") + { + + $msg = str_replace("\r", "", $msg); + $msg = str_replace("\n", "", $msg); + + if ($this->started == true) + { + fclose($this->filePointer); + } + + if ($msg != "") + { + $this->dccClass->dccInform($this->dccString . "DCC session ended with " . $this->nick . " (" . $msg . ")", $this); + $this->ircClass->notice($this->nick, "DCC session ended: " . $msg, 1); + } + else + { + $this->dccClass->dccInform($this->dccString . "DCC session ended with " . $this->nick, $this); + } + + $this->status = false; + + $this->dccClass->disconnect($this); + + $this->connection = null; + + return true; + } + + function xferUpload() + { + + while ($this->readQueue != "") + { + $unsignedLong = substr($this->readQueue, 0, 4); + + if (strlen($unsignedLong) < 4) + { + break; + } + + $sizeArray = unpack("N", $unsignedLong); + + $this->reportedRecieved = $sizeArray[1]; + + $this->readQueue = substr($this->readQueue, 4); + } + + if ($this->completed == 1) + { + if ($this->reportedRecieved >= $this->filesize) + { + $avgspeed = ""; + if ($this->speed_lastavg != 0) + { + $size = irc::intToSizeString($this->speed_lastavg); + $avgspeed = " (" . $size . "/s)"; + } + + $totalTime = $this->ircClass->timeFormat(time() - $this->timeConnected, "%h hrs, %m min, %s sec"); + $size = irc::intToSizeString($this->bytesTransfered - $this->resumedSize); + $this->disconnect("Transfer Completed, Sent " . $size . " in " . $totalTime . $avgspeed); + } + return; + } + + if ($this->status != DCC_CONNECTED) + { + return; + } + + if ($this->socketClass->hasWriteQueue($this->sockInt)) + { + return; + } + + if ($this->bytesTransfered >= $this->filesize) + { + $this->completed = 1; + return; + } + + if (time() >= $this->speed_starttime + 3) + { + $this->speed_lastavg = $this->speed_sec_add / 3.0; + $this->speed_sec_add = 0; + $this->speed_starttime = time(); + } + + if (!is_resource($this->filePointer)) + { + $this->disconnect("File pointer is not a resource"); + return; + } + + for ($i = 0; $i < 30; $i++) + { + if (($data = fread($this->filePointer, 4096)) === false) + { + $this->disconnect("Read error: Could not access file"); + return; + } + + $this->dccSend($data); + + $dataSize = strlen($data); + + $this->bytesTransfered += $dataSize; + $this->dccClass->addBytesUp($dataSize); + $this->speed_sec_add += $dataSize; + + if ($this->socketClass->hasWriteQueue($this->sockInt)) + { + break; + } + } + + } + + function xferDownload() + { + + if ($this->status != DCC_CONNECTED) + { + return; + } + + $readQueueSize = strlen($this->readQueue); + + if ($readQueueSize <= 0) + { + return; + } + + if (fwrite($this->filePointer, $this->readQueue, $readQueueSize) === false) + { + $this->disconnect("Write error: Could not access file"); + } + + $this->speed_sec_add += $readQueueSize; + $this->dccClass->addBytesDown($readQueueSize); + $this->bytesTransfered += $readQueueSize; + $this->readQueue = ""; + + $this->dccSend(pack("N", $this->bytesTransfered)); + + if ($this->bytesTransfered >= $this->filesize) + { + $avgspeed = ""; + if ($this->speed_lastavg != 0) + { + $size = irc::intToSizeString($this->speed_lastavg); + $avgspeed = " (" . $size . "/s)"; + } + + $totalTime = $this->ircClass->timeFormat(time() - $this->timeConnected, "%h hrs, %m min, %s sec"); + $size = irc::intToSizeString($this->bytesTransfered - $this->resumedSize); + $this->disconnect("Transfer Completed, Recieved " . $size . " in " . $totalTime . $avgspeed); + } + + if (time() >= $this->speed_starttime + 3) + { + $this->speed_lastavg = $this->speed_sec_add / 3.0; + $this->speed_sec_add = 0; + $this->speed_starttime = time(); + } + } + + + private function doHandShake() + { + $this->dccSend("120 ".$this->ircClass->getNick()." ".$this->filesize." ".$this->filenameNoDir."\n"); + $this->handShakeSent = true; + $this->timerClass->addTimer(irc::randomHash(), $this, "handShakeTimeout", "", 8); + } + + private function processHandShake() + { + if ($this->readQueue == "") + { + return; + } + + $response = $this->readQueue; + $this->readQueue = ""; + $responseArray = explode(chr(32), $response); + if ($responseArray[0] == "121") + { + $this->resumedSize = intval($responseArray[2]); + $this->reverse = false; + $this->onConnect($conn); + return; + } + + $this->disconnect("DCC Client Server reported error on attempt to send file"); + } + + public function handShakeTimeout() + { + if ($this->status != false) + { + if ($this->reverse == true) + { + $this->disconnect("DCC Reverse handshake timed out"); + } + } + return false; + } + + + + /* Main events */ + public function onDead($conn) + { + if ($this->completed == 1) + { + $avgspeed = ""; + if ($this->speed_lastavg != 0) + { + $size = irc::intToSizeString($this->speed_lastavg); + $avgspeed = " (" . $size . "/s)"; + } + + $totalTime = $this->ircClass->timeFormat(time() - $this->timeConnected, "%h hrs, %m min, %s sec"); + $size = irc::intToSizeString($this->bytesTransfered - $this->resumedSize); + $this->disconnect("Transfer Completed, Sent " . $size . " in " . $totalTime . $avgspeed); + } + else + { + $this->disconnect($this->connection->getErrorMsg()); + } + } + + public function onRead($conn) + { + + $this->readQueue .= $this->socketClass->getQueue($this->sockInt); + + if ($this->status == DCC_CONNECTED) + { + + if ($this->transferType == UPLOAD) + { + if ($this->reverse != false) + { + if ($this->handShakeSent != false) + { + $this->processHandShake(); + } + } + } + else + { + $this->xferDownload(); + } + } + return false; + } + + public function onWrite($conn) + { + if ($this->status == DCC_CONNECTED && $this->reverse == false) + { + $this->xferUpload(); + } + } + + public function onAccept($oldConn, $newConn) + { + $this->dccClass->accepted($oldConn, $newConn); + $this->connection = $newConn; + $oldConn->disconnect(); + $this->sockInt = $newConn->getSockInt(); + $this->onConnect($newConn); + } + + public function onTransferTimeout($conn) + { + $this->disconnect("Transfer timed out"); + } + + public function onConnectTimeout($conn) + { + $this->disconnect("Connection attempt timed out"); + } + + public function onConnect($conn) + { + $this->status = DCC_CONNECTED; + + $this->dccClass->dccInform($this->dccString . $this->nick . " connection established"); + + if ($this->reverse != false) + { + $this->doHandShake(); + return; + } + + if ($this->transferType == UPLOAD) + { + $this->dccClass->alterSocket($this->sockInt, SOL_SOCKET, SO_SNDBUF, 32768); + + $this->filePointer = fopen($this->filename, "rb"); + + if ($this->filePointer === false) + { + $this->disconnect("Error opening local file for reading"); + return; + } + + if ($this->resumedSize > 0) + { + if (fseek($this->filePointer, $this->resumedSize, SEEK_SET) == -1) + { + $this->disconnect("Error seeking to resumed file position in file"); + return; + } + } + + $this->xferUpload(); + + } + else + { + $this->dccClass->alterSocket($this->sockInt, SOL_SOCKET, SO_RCVBUF, 32768); + + $this->filePointer = fopen($this->filename, "ab"); + + $this->ircClass->notice($this->nick, "DCC: Upload connection established", 0); + + if ($this->filePointer === false) + { + $this->disconnect("Error opening local file for writing"); + return; + } + + } + + $this->started = true; + $this->speed_starttime = time(); + + } + + + public function initialize($filename, $size = 0) + { + $this->reportedRecieved = 0; + $this->completed = 0; + $this->filesize = $size; + $this->timeConnected = time(); + $this->timeOutLevel = 0; + $this->readQueue = ""; + $this->type = FILE; + + if ($this->transferType == UPLOAD) + { + $this->filename = $filename; + + if (strpos($filename, "/") !== false) + { + $filenameArray = explode("/", $filename); + $this->filenameNoDir = $filenameArray[count($filenameArray) - 1]; + } + else if (strpos($filename, "\\") !== false) + { + $filenameArray = explode("\\", $filename); + $this->filenameNoDir = $filenameArray[count($filenameArray) - 1]; + } + else + { + $this->filenameNoDir = $filename; + } + + $this->filenameNoDir = $this->cleanFilename($this->filenameNoDir); + + $this->dccClass->dccInform($this->dccString . "Initiating file transfer of (".$this->filenameNoDir.") to " . $this->nick); + + if (!$this->file_exists($this->filename)) + { + $this->disconnect("File does not exist"); + return; + } + + $fileSize = $this->filesize($this->filename); + if ($fileSize === false) + { + $this->disconnect("File does not exist"); + return; + } + + $this->filesize = $fileSize; + + + $kbSize = irc::intToSizeString($fileSize); + + if ($this->reverse == false) + { + $this->ircClass->privMsg($this->nick, "\1DCC SEND " . $this->filenameNoDir . " " . $this->ircClass->getClientIP(1) . " " . $this->port . " " . $fileSize . "\1", 0); + } + + $this->ircClass->notice($this->nick, "DCC: Sending you (\"" . $this->filenameNoDir . "\") which is " . $kbSize . " (resume supported)", 0); + + } + else + { + $uldir = $this->ircClass->getClientConf('uploaddir'); + + $lastChar = substr($uldir, strlen($uldir) - 1, 1); + + if ($lastChar != "\\" || $lastChar != "/") + { + $uldir .= "/"; + } + + $filename = $this->cleanFilename($filename); + + $this->filename = $uldir . $filename; + $this->dccClass->dccInform($this->dccString . "Initiating file transfer of (".$filename.") from " . $this->nick); + + if ($this->file_exists($this->filename)) + { + $bytesFinished = $this->filesize($this->filename); + if ($bytesFinished >= $this->filesize) + { + $this->disconnect("Connection aborted. I already have that file."); + return; + } + else + { + $this->status = DCC_WAITING; + $this->bytesTransfered = $bytesFinished; + $this->resumedSize = $bytesFinished; + $this->ircClass->privMsg($this->nick, "\1DCC RESUME file.ext " . $this->port . " " . $bytesFinished . "\1", 0); + } + return; + } + + $this->ircClass->notice($this->nick, "DCC: Upload accepted, connecting to you (" . $this->connectHost . ") on port " . $this->port . ".",0); + + + $this->status = DCC_CONNECTING; + $this->connection->connect(); + + } + + } + + public function accepted() + { + $this->status = DCC_CONNECTING; + $this->connection->connect(); + } + + public function resume($size) + { + $this->resumedSize = $size; + $this->bytesTransfered = $size; + + $resumePlace = round($size / 1000, 0); + $this->dccClass->dccInform($this->dccString . "Resumed at " . $resumePlace . "K"); + $this->ircClass->privMsg($this->nick, "\1DCC ACCEPT file.ext " . $this->port . " " . $size . "\1", 0); + } + + public static function cleanFilename($filename) + { + $filename = str_replace("..", "__", $filename); + $filename = str_replace(chr(47), "_", $filename); + $filename = str_replace(chr(92), "_", $filename); + $filename = str_replace(chr(58), "_", $filename); + $filename = str_replace(chr(63), "_", $filename); + $filename = str_replace(chr(34), "_", $filename); + $filename = str_replace(chr(62), "_", $filename); + $filename = str_replace(chr(60), "_", $filename); + $filename = str_replace(chr(124), "_", $filename); + $filename = str_replace(chr(32), "_", $filename); + + return $filename; + } + + private function file_exists($filename) + { + $fp = @fopen($filename, "rb"); + if ($fp === false) + { + return false; + } + else + { + fclose($fp); + return true; + } + } + + private function filesize($filename) + { + + $fp = @fopen($filename, "rb"); + if ($fp === false) + { + return false; + } + else + { + $fstat = fstat($fp); + fclose($fp); + return $fstat['size']; + } + + } + +} + +?> diff --git a/ircbot/function.conf b/ircbot/function.conf new file mode 100644 index 0000000..c940125 --- /dev/null +++ b/ircbot/function.conf @@ -0,0 +1,58 @@ +;+--------------------------------------------------------------------------- +;| PHP-IRC v2.2.1 Service Release +;| ======================================================= +;| by Manick +;| (c) 2001-2006 by http://www.phpbots.org/ +;| Contact: manick@manekian.com +;| irc: #manekian@irc.rizon.net +;| ======================================== +;| Special Contributions were made by: +;| cortex +;+--------------------------------------------------------------------------- +;| > Function File +;+--------------------------------------------------------------------------- +;| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +;+--------------------------------------------------------------------------- +;| Changes +;| =======------- +;| > If you wish to suggest or submit an update/change to the source +;| > code, email me at manick@manekian.com with the change, and I +;| > will look to adding it in as soon as I can. +;+--------------------------------------------------------------------------- + +; some notes: +; the ~ operater spans definitions over multiple lines. the ; operator +; denotes a comment. it can be placed anywhere. you must escape +; your single quotes in quoted entries with a backslash \. the first +; type of every statement is explained + +; NOTE: as of 2.2.0, all function configuration was moved to specific files using +; the 'include' function. General type definitions can be found in typedefs.conf + +include typedefs.conf + +include modules/lw/lw_mod.conf +;include modules/default/priv_mod.conf +;include modules/default/dcc_mod.conf +;include modules/bad_words/bad_words.conf +;include modules/peak_mod/peak_mod.conf +;include modules/seen/seen_mod.conf +;include modules/news/news_mod.conf +;include modules/imdb/imdb_mod.conf +;include modules/quotes_ini/quote_mod.conf +;include modules/quotes_sql/quote_mod.conf +;include modules/httpd/httpd_mod.conf +;include modules/fileserver/fileserver.conf +;include modules/bash/bash_mod.conf diff --git a/ircbot/irc.php b/ircbot/irc.php new file mode 100644 index 0000000..432dca5 --- /dev/null +++ b/ircbot/irc.php @@ -0,0 +1,2501 @@ + irc module +| > Module written by Manick +| > Module Version Number: 2.2.0 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +class irc { + + // Config Vars + private $clientConfig = array(); + private $configFilename = ""; + // nick, realname, localhost, remotehost, ident, host, port + private $serverConfig = array(); + private $nick = ""; + private $tempNick = ""; + // only used if our nick is taken initially + private $clientIP; + private $clientLongIP; + // Above address related determined on runtime.. + private $startTime = 0; //when the bot was started + + // Status Vars + private $status = STATUS_IDLE; + private $exception; + private $reportedStatus = STATUS_IDLE; + + //Classes + private $timerClass = null; + private $socketClass = null; + private $dccClass = null; + private $parserClass = null; + + //Socket Vars + private $sockInt; //old pre 2.1.2 method + private $conn; //new 2.1.2 method + private $connectStartTime = 0; + private $lagTime = 0; + private $timeConnected = 0; + + //Queue Vars + private $textQueueTime = 0; + private $textQueue = array(); + private $textQueueLength = 0; + + /* This variable will be set when new text is sent to be sent to the irc server, its so we don't + have to call addQueue() more than once. */ + private $textQueueAdded = false; + private $modeQueueAdded = false; + + private $modeQueue = array(); + private $modeQueueLength = 0; + private $modeTimerAdded = false; + + //Stats Var + private $stats = array(); + + //Parsing Vars + private $lVars = array(); + private $modeArray; + private $prefixArray; + + //Channel and User Linked Lists + private $chanData = array(); + private $maintainChannels = array(); +/* private $chanData = null; */ + + private $usageList = array(); + + //Timers + private $timeoutTimer = 0; + private $lastPing = 0; + private $lastPingTime = 0; + private $nickTimer = 0; + private $checkChansTimer = 0; + + //Kill + private $setKill = false; + + //My process id + private $pid; + + //Process Queue + private $procQueue; + + function __construct() + { + + $this->startTime = time(); + + // Initialize the stats array + $this->stats = array( 'BYTESUP' => 0, + 'BYTESDOWN' => 0, + ); + + $this->pid = getmypid(); + + return; + + } + + public function init() + { + $this->socketClass->setTcpRange( $this->getClientConf('dccrangestart') ); + + /* Add other timers */ + $this->timerClass->addTimer("check_nick_timer", $this, "checkNick", "", NICK_CHECK_TIMEOUT); + $this->timerClass->addTimer("check_channels_timer", $this, "checkChans", "", CHAN_CHECK_TIMEOUT); + $this->timerClass->addTimer("check_ping_timeout_timer", $this, "checkPingTimeout", "", PING_TIMEOUT+1); + + /* Timer that makes sure we're connected every 1:15 minutes */ + $this->reconnect(); + //$this->timerClass->addTimer("check_connection", $this, "checkConnection", "", 75, true); + } + + public function pid() + { + return $this->pid; + } + + public function setConfig($config, $filename = "") + { + $this->clientConfig = $config; + $this->configFilename = $filename; + $this->nick = $config['nick']; + } + + public function setProcQueue($class) + { + $this->procQueue = $class; + } + + public function setDccClass($class) + { + $this->dccClass = $class; + $this->dccClass->setIrcClass($this); + } + + public function setParserClass($class) + { + $this->parserClass = $class; + $this->parserClass->setIrcClass($this); + } + + public function setSocketClass($class) + { + $this->socketClass = $class; + } + + public function setTimerClass($class) + { + $this->timerClass = $class; + } + + public function setClientConfigVar($var, $value) + { + $this->clientConfig[$this->myStrToLower($var)] = $value; + } + + public function getNick() + { + return $this->nick; + } + + public function getMaintainedChannels() + { + return $this->maintainChannels; + } + + public function getServerConf($var) + { + if (isset($this->serverConfig[$this->myStrToUpper($var)])) + { + return $this->serverConfig[$this->myStrToUpper($var)]; + } + + return ""; + } + + + public function getConfigFilename() + { + return $this->configFilename; + } + + public function getClientConf($var = "") + { + + if ($var != "") + { + if (isset($this->clientConfig[$var])) + { + return $this->clientConfig[$var]; + } + } + else + { + return $this->clientConfig; + } + + return ""; + + } + + + //debug only! + public function displayUsers() + { + $toReturn = ""; + + foreach($this->chanData AS $chanPtr) + { + + $toReturn .= $chanPtr->name . "\n"; + + foreach($chanPtr->memberList AS $memPtr) + { + $toReturn .= $memPtr->realNick . " -- "; + $toReturn .= $memPtr->status . "\n"; + } + + } + + return trim($toReturn); + } + + + + private function addMember($channel, $nick, $ident, $status, $host = "") + { + $channel = $this->myStrToLower($channel); + $realNick = trim($nick); + $nick = trim($this->myStrToLower($nick)); + + $newMember = new memberLink; + if ($host != "") + { + $newMember->host = $host; + } + $newMember->nick = $nick; + $newMember->realNick = $realNick; + $newMember->ident = $ident; + $newMember->status = $status; + + if (!isset($this->chanData[$channel])) + { + $chanPtr = new channelLink; + $chanPtr->count = 1; + $chanPtr->memberList = NULL; + $chanPtr->banComplete = 1; + $chanPtr->name = $channel; + $this->chanData[$channel] = $chanPtr; + } + else + { + $chanPtr = $this->chanData[$channel]; + + if (!isset($chanPtr->memberList[$nick])) + { + $chanPtr->count++; + } + } + + $chanPtr->memberList[$nick] = $newMember; + + return $chanPtr->count; + + } + + public function &getChannelData($channel = "") + { + + if ($channel == "") + { + return $this->chanData; + } + + $channel = $this->myStrToLower($channel); + + if (isset($this->chanData[$channel])) + { + return $this->chanData[$channel]; + } + + return NULL; + + } + + + public function setMemberData($nick, $ident, $host) + { + $nick = $this->myStrToLower($nick); + + foreach($this->chanData AS $chanPtr) + { + if (isset($chanPtr->memberList[$nick])) + { + $chanPtr->memberList[$nick]->ident = $ident; + $chanPtr->memberList[$nick]->host = $host; + } + } + + } + + + public function getUserData($user, $channel = "") + { + if ($user == "") + { + return NULL; + } + + $channel = $this->myStrToLower($channel); + $user = $this->myStrToLower($user); + + if ($channel == "") + { + foreach ($this->chanData AS $chanPtr) + { + if (isset($chanPtr->memberList[$user])) + { + return $chanPtr->memberList[$user]; + } + } + return NULL; + } + + if (isset($this->chanData[$channel])) + { + if (isset($this->chanData[$channel]->memberList[$user])) + { + return $this->chanData[$channel]->memberList[$user]; + } + return NULL; + } + } + + + + + + + private function changeMember( $channel, $oldNick, $newNick, $ident, + $newStatus, $action, $newHost = "") + { + + $channel = $this->myStrToLower($channel); + $ident = trim($ident); + $oldNick = trim($this->myStrToLower($oldNick)); + $realNick = trim($newNick); + $newNick = trim($this->myStrToLower($newNick)); + + // See if we have a valid usermode + + if ($newStatus != "") + { + if (strpos($this->getServerConf('PREFIX'), $newStatus) === false) + { + $newStatus = ""; + $action = ""; + } + } + + //Find our channel, also change user name if no channel name given + + if ($channel == "") + { + foreach($this->chanData AS $chanPtr) + { + if (isset($chanPtr->memberList[$oldNick])) + { + $memPtr = $chanPtr->memberList[$oldNick]; + + if ($newHost != "") + { + $memPtr->host = $newHost; + } + if ($ident != "") + { + $memPtr->ident = $ident; + } + $memPtr->nick = $newNick; + $memPtr->realNick = $realNick; + + $chanPtr->memberList[$newNick] = $memPtr; + unset($chanPtr->memberList[$oldNick]); + } + } + return; + } + + if (isset($this->chanData[$channel])) + { + $chanPtr = $this->chanData[$channel]; + + if (isset($chanPtr->memberList[$oldNick])) + { + $memPtr = $chanPtr->memberList[$oldNick]; + + if ($newHost != "") + { + $memPtr->host = $newHost; + } + if ($ident != "") + { + $memPtr->ident = $ident; + } + + if ($newStatus != "") + { + if ($action == "+") + { + if (strpos($memPtr->status, $newStatus) === false) + { + $memPtr->status .= $newStatus; + } + } + else + { + $memPtr->status = str_replace($newStatus, "", $memPtr->status); + } + } + + } + + } + + + } + + private function setChannelData($channel, $item, $val) + { + $channel = $this->myStrToLower($channel); + $item = $this->myStrToLower($item); + + if (isset($this->chanData[$channel])) + { + $chanPtr = $this->chanData[$channel]; + + switch ($item) + { + case "topicby": + $chanPtr->topicBy = $val; + break; + + case "topic": + $chanPtr->topic = $val; + break; + + case "bancomplete": + $chanPtr->banComplete = $val; + break; + + case "created": + $chanPtr->created = $val; + break; + + case "modes": + unset($chanPtr->modes); + $chanPtr->modes = array(); + $chanPtr->modes['MODE_STRING'] = substr($val, 1); + break; + } + + } + + } + + + private function clearBanList($channel) + { + $channel = $this->myStrToLower($channel); + + if (isset($this->chanData[$channel])) + { + unset($this->chanData[$channel]->banList); + $this->chanData[$channel]->banList = array(); + } + } + + private function changeChannel($channel, $action, $newStatus, $extraStatus = "") + { + + $channel = $this->myStrToLower($channel); + $newStatus = trim($newStatus); // i.e, l, b + + if (!is_array($extraStatus)) + { + $extraStatus = trim($extraStatus); // i.e, 50, *!*@* + } + + + + //ex. CHANMODES=eIb,k,l,cimnpstMNORZ + + if ($newStatus == "" || $action == "") + { + return; + } + + if (!isset($this->modeArray[$newStatus])) + { + return; + } + + $type = $this->modeArray[$newStatus]; + + if ($type != BY_NONE && $extraStatus == "") + { + return; + } + + if (strpos($this->getServerConf('CHANMODES'), $newStatus) === false) + { + return; + } + + //Find our channel + + if (isset($this->chanData[$channel])) + { + $chanPtr = $this->chanData[$channel]; + + if (!is_array($chanPtr->modes)) + { + unset($chanPtr->modes); + $chanPtr->modes = array(); + } + + if (!isset($chanPtr->modes['MODE_STRING'])) + { + $chanPtr->modes['MODE_STRING'] = ""; + } + + if ($type == BY_MASK) + { + + if ($newStatus == "b") + { + if ($action == "+") + { + if (is_array($extraStatus)) + { + $ban = $extraStatus[0]; + $time = $extraStatus[1]; + } + else + { + $ban = $extraStatus; + $time = time(); + } + + $chanPtr->banList[] = array( 'HOST' => $ban, + 'TIME' => $time, + ); + + + } + else + { + foreach ($chanPtr->banList AS $index => $data) + { + if ($data['HOST'] == $extraStatus) + { + unset($chanPtr->banList[$index]); + break; + } + } + } + } + } + else + { + if ($action == "+") + { + if (strpos($chanPtr->modes['MODE_STRING'], $newStatus) === false) + { + $chanPtr->modes['MODE_STRING'] .= $newStatus; + if ($type != BY_NONE) + { + $chanPtr->modes[$newStatus] = $extraStatus; + } + } + } + else + { + $chanPtr->modes['MODE_STRING'] = str_replace($newStatus, "", $chanPtr->modes['MODE_STRING']); + if ($type != BY_NONE) + { + unset($chanPtr->modes[$newStatus]); + } + } + } + + } + + + } + + private function purgeChanList() + { + foreach ($this->chanData AS $cIndex => $chanPtr) + { + foreach ($chanPtr->memberList AS $mIndex => $memPtr) + { + unset($chanPtr->memberList[$mIndex]); + } + + unset($this->chanData[$cIndex]); + } + + unset($this->chanData); + $this->chanData = array(); + } + + private function removeMember($channel, $nick) + { + + $channel = $this->myStrToLower($channel); + $nick = trim($this->myStrToLower($nick)); + + if ($channel == "") + { + foreach($this->chanData AS $chanPtr) + { + if (isset($chanPtr->memberList[$nick])) + { + unset($chanPtr->memberList[$nick]); + $chanPtr->count--; + } + } + + } + else + { + if (isset($this->chanData[$channel])) + { + if (isset($this->chanData[$channel]->memberList[$nick])) + { + unset($this->chanData[$channel]->memberList[$nick]); + $this->chanData[$channel]->count--; + } + } + } + + } + + private function removeChannel($channel) + { + $channel = $this->myStrToLower($channel); + + if (isset($this->chanData[$channel])) + { + $chanPtr = $this->chanData[$channel]; + + foreach ($chanPtr->memberList AS $mIndex => $memPtr) + { + $this->removeMember($channel, $mIndex); + } + + unset($chanPtr->banList); + unset($chanPtr->modes); + + unset($this->chanData[$channel]); + } + } + + public function isChanMode($channel, $mode, $extra = "") + { + $channel = $this->myStrToLower($channel); + $extra = $this->myStrToLower($extra); + + if (!isset($this->chanData[$channel])) + { + return; + } + + $chanPtr = $this->chanData[$channel]; + + if (!isset($this->modeArray[$mode])) + { + return false; + } + + $type = $this->modeArray[$mode]; + + if ($type == BY_MASK) + { + if ($mode == "b") + { + + foreach ($chanPtr->banList AS $index => $ban) + { + + if ($this->hostMasksMatch($ban['HOST'], $extra)) + { + return true; + } + + } + } + } + else + { + if (strpos($chanPtr->modes['MODE_STRING'], $mode) !== false) + { + return true; + } + } + + return false; + } + + public function hasModeSet($chan, $nick, $modes) + { + $channel = $this->myStrToLower($chan); + $nick = $this->myStrToLower($nick); + + if (isset($this->chanData[$channel])) + { + if (isset($this->chanData[$channel]->memberList[$nick])) + { + $memPtr = $this->chanData[$channel]->memberList[$nick]; + + while ($modes != "") + { + $mode = substr($modes, 0, 1); + $modes = substr($modes, 1); + + if (strpos($memPtr->status, $mode) !== false) + { + return true; + } + } + + } + } + + return false; + + } + + public function isMode($nick, $channel, $mode) + { + $channel = $this->myStrToLower($channel); + $nick = $this->myStrToLower($nick); + + if (isset($this->chanData[$channel])) + { + if (isset($this->chanData[$channel]->memberList[$nick])) + { + $memPtr = $this->chanData[$channel]->memberList[$nick]; + + if ($mode == "online") + { + return true; + } + else + { + if (strpos($memPtr->status, $mode) !== false) + { + return true; + } + } + } + } + + return false; + } + + private function getHostMask($mask) + { + $offsetA = strpos($mask, "!"); + $offsetB = strpos($mask, "@"); + + $myMask = array(); + + $myMask['nick'] = $this->myStrToLower(substr($mask, 0, $offsetA)); + $myMask['ident'] = $this->myStrToLower(substr($mask, $offsetA + 1, $offsetB - $offsetA - 1)); + $myMask['host'] = $this->myStrToLower(substr($mask, $offsetB + 1)); + + return $myMask; + } + + public function hostMasksMatch($mask1, $mask2) + { + + $maskA = $this->getHostMask($mask1); + $maskB = $this->getHostMask($mask2); + + $ident = false; + $nick = false; + $host = false; + + if ($maskA['ident'] == $maskB['ident'] + || $maskA['ident'] == "*" || $maskB['ident'] == "*") + { + $ident = true; + } + + if ($maskA['nick'] == $maskB['nick'] + || $maskA['nick'] == "*" || $maskB['nick'] == "*") + { + $nick = true; + } + + if ($maskA['host'] == $maskB['host'] + || $maskA['host'] == "*" || $maskB['host'] == "*") + { + $host = true; + } + + if ($host && $nick && $ident) + { + return true; + } + else + { + return false; + } + + } + + + public function setToIdleStatus() + { + $this->status = STATUS_IDLE; + } + + + private function getStatus() + { + $this->reportedStatus = $this->status; + return $this->status; + } + + private function getStatusChange() + { + return ($this->reportedStatus != $this->status); + } + + private function getException() + { + return $this->exception; + } + + private function updateContext() + { + + if ($this->getStatusChange()) + { + $status = $this->getStatus(); + $statusStr = $this->getStatusString($status); + $this->log("STATUS: " . $statusStr); + $this->dccClass->dccInform("Status: " . $statusStr); + + switch($status) + { + case STATUS_ERROR: + $exception = $this->getException(); + $this->log("Error: " . $exception->getMessage()); + $this->dccClass->dccInform("Error: " . $exception->getMessage()); + break; + default: + break; + } + + } + + } + + public function reconnect() + { + $this->updateContext(); + + try { + + $this->connectStartTime = time(); + + $conn = new connection($this->getClientConf('server'), $this->getClientConf('port'), CONNECT_TIMEOUT); + + $this->conn = $conn; + + $conn->setSocketClass($this->socketClass); + $conn->setIrcClass($this); + $conn->setCallbackClass($this); + $conn->setTimerClass($this->timerClass); + $conn->init(); + + if ($conn->getError()) + { + throw new ConnectException($conn->getErrorMsg()); + } + + //Bind socket... + if ($this->getClientConf('bind') != "") + { + $conn->bind($this->getClientConf('bind')); + } + + $this->sockInt = $conn->getSockInt(); + $conn->connect(); + + + } + catch (ConnectException $e) + { + $this->beginReconnectTimer(); + $this->status = STATUS_ERROR; + $this->exception = $e; + return; + } + + $this->status = STATUS_CONNECTING; + + $this->updateContext(); + + return; + } + + public function onConnect($conn) + { + $this->status = STATUS_CONNECTED; + + $this->updateContext(); + + $this->timeoutTimer = time(); + $ip = ""; + if ($this->getClientConf('natip') != "") + { + $ip = $this->getClientConf('natip'); + } + $this->setClientIP($ip); + + $this->register(); + //TODO: Add registration timeout timer (with $conn to check identity) + + return false; + } + + public function onRead($conn) + { + $this->updateContext(); + + return $this->readInput(); + } + + public function onWrite($conn) + { + //Do nothing.. this function has no purpose for the ircClass + return false; + } + + public function onAccept($listenConn, $newConn) + { + //Do nothing.. this function has no purpose for the ircClass + return false; + } + + public function onDead($conn) + { + if ($conn->getError()) + { + $this->status = STATUS_ERROR; + $this->exception = new ConnectException($conn->getErrorMsg()); + } + + $this->disconnect(); + return false; + } + + public function onConnectTimeout($conn) + { + $this->status = STATUS_ERROR; + $this->exception = new ConnectException("Connection attempt timed out"); + $this->disconnect(); + } + + private function beginReconnectTimer() + { + $this->timerClass->addTimer(self::randomHash(), $this, "reconnectTimer", $this->conn, ERROR_TIMEOUT); + } + + public function reconnectTimer($conn) + { + //If curr connection is equal to the stored connection, then no forced + //connect was attempted, so attempt another. + + if ($this->conn === $conn) + { + $this->reconnect(); + } + + return false; + } + + + public function disconnect() + { + $this->conn->disconnect(); + + $this->updateContext(); + + $this->status = STATUS_ERROR; + $this->exception = new ConnectException("Disconnected from server"); + + $this->updateContext(); + + //reset all vars + $this->purgeChanList(); + $this->timeoutTimer = 0; + $this->lastPing = 0; + $this->lastPingTime = 0; + $this->nickTimer = 0; + $this->connectStartTime = 0; + $this->lagTime = 0; + $this->checkChansTimer = 0; + $this->purgeTextQueue(); + $this->nick = $this->getClientConf('nick'); + $this->tempNick = ""; + unset($this->modeArray); + unset($this->prefixArray); + unset($this->serverConfig); + $this->modeArray = array(); + $this->prefixArray = array(); + $this->serverConfig = array(); + + $this->beginReconnectTimer(); + + return; + } + + private function register() + { + $login_string = "NICK ".$this->getClientConf('nick')."\r\n"."USER ".$this->getClientConf('ident')." "."localhost"." ".$this->getClientConf('server')." :".$this->getClientConf('realname'); + if ($this->getClientConf('serverpassword') != "") + $login_string = "PASS ".$this->getClientConf('serverpassword')."\r\n".$login_string; + + $validate = $this->clientFormat($login_string); + + $this->timeConnected = time(); + $this->pushAfter($validate); + $this->status = STATUS_CONNECTED_SENTREGDATA; + + $this->timerClass->addTimer($this->randomHash(), $this, "regTimeout", $this->sockInt, REGISTRATION_TIMEOUT); + } + + + public function regTimeout($sockInt) + { + if ($sockInt != $this->sockInt) + { + return false; + } + + if ($this->status != STATUS_CONNECTED_SENTREGDATA) + { + return false; + } + + $this->disconnect(); + + $this->status = STATUS_ERROR; + $this->exception = new ConnectionTimeout("Session Authentication Timed out"); + + return false; + } + + //The following function kills this irc object, but ONLY if there is no send queue. + //A good way to use it is with a timer, say, ever second or so. + public function shutDown() + { + if (!$this->conn->getError()) + { + return; + } + + /* + if ($this->socketClass->hasWriteQueue($this->sockInt)) + { + return true; + } + + $this->disconnect(); + */ + $this->parserClass->destroyModules(); + $this->dccClass->closeAll(); + $this->procQueue->removeOwner($this); + $date = date("h:i:s a, m-d-y"); + $this->log("The bot successfully shutdown, $date"); + + //Okay.. now the bot is PSEUDO dead. It still exists, however it has no open sockets, it will not + //attempt to reconnect to the server, and all dcc sessions, modules, timers, and processes related to it + //have been destroyed. + + return false; + + } + + /* Some assorted timers */ + + public function checkNick() + { + if ($this->getStatusRaw() != STATUS_CONNECTED_REGISTERED) + { + return true; + } + + if ($this->nick != $this->getClientConf('nick')) + { + $this->changeNick($this->getClientConf('nick')); + } + + return true; + } + + public function checkPingTimeout() + { + if ($this->getStatusRaw() != STATUS_CONNECTED_REGISTERED) + { + return true; + } + + try { + if ($this->lastPing == 1) + { + $this->lastPing = 0; + throw new ConnectionTimeout("The connection with the server timed out."); + } + else + { + if (time() > $this->timeoutTimer + PING_TIMEOUT + $this->lagTime) + { + $this->pushBefore($this->clientFormat("PING :Lagtime")); + $this->lastPingTime = time(); + $this->lastPing = 1; + } + } + } + catch (ConnectionTimeout $e) + { + $this->disconnect(); + $this->status = STATUS_ERROR; + $this->exception = $e; + } + + return true; + } + + public function getStatusRaw() + { + return $this->status; + } + + + public function getLine() + { + return $this->lVars; + } + + private function readInput() + { + if ($this->status != STATUS_CONNECTED_REGISTERED && $this->status != STATUS_CONNECTED_SENTREGDATA) + { + return false; + } + + if ($this->socketClass->isDead($this->sockInt) && !$this->socketClass->hasLine($this->sockInt)) + { + $this->disconnect(); + $this->status = STATUS_ERROR; + $this->exception = new ReadException("Failed while reading from socket"); + return false; + } + + if ($this->socketClass->hasLine($this->sockInt)) + { + $this->timeoutTimer = time(); + + if ($this->lastPing == 1) + { + $this->lagTime = time() - $this->lastPingTime; + } + $this->lastPing = 0; + + $line = $this->socketClass->getQueueLine($this->sockInt); + + $this->stats['BYTESDOWN'] += strlen($line); + + $this->log($line); + + if (substr($line, 0, 1) != ":") + { + $line = ":Server " . $line; + } + + $line = substr($line, 1); + + $parts = explode(chr(32), $line); + + $params = substr($line, strlen($parts[0]) + strlen($parts[1]) + strlen($parts[2]) + 3); + if (strpos($params, " :")) + { + $params = substr($params, 0, strpos($params, " :")); + } + + $offset1 = strpos($parts[0], '!'); + $offset2 = $offset1 + 1; + $offset3 = strpos($parts[0], '@') + 1; + $offset4 = $offset3 - $offset2 - 1; + $offset5 = strpos($line, " :") + 2; + + unset($this->lVars); + + $this->lVars = array( 'from' => $parts[0], + 'fromNick' => substr($parts[0], 0, $offset1), + 'fromIdent' => substr($parts[0], $offset2, $offset4), + 'fromHost' => substr($parts[0], $offset3), + 'cmd' => $parts[1], + 'to' => $parts[2], + 'text' => substr($line, $offset5), + 'params' => trim($params), + 'raw' => ":" . $line, + ); + + if ($offset5 === false) + { + $line['text'] = ""; + } + + if (intval($this->lVars['cmd']) > 0) + { + $this->parseServerMsgs($this->lVars['cmd']); + } + else + { + $this->parseMsgs(); + } + + $this->parserClass->parseLine($this->lVars); + } + + if ($this->socketClass->hasQueue($this->sockInt)) + { + return true; + } + + return false; + } + + + private function parseServerMsgs($cmd) + { + switch ($cmd) + { + case 004: + $this->status = STATUS_CONNECTED_REGISTERED; + if ($this->tempNick != "") + { + $this->nick = $this->tempNick; + } + break; + + case 005: + $this->parseServerConfig(); + if (!isset($this->modeArray) || !is_array($this->modeArray) || count($this->modeArray) <= 0) + { + if ($this->getServerConf("CHANMODES") != "") + { + $this->createModeArray(); + $this->checkChans(); + } + } + break; + + case 311: + $params = explode(chr(32), $this->lVars['params']); + $this->setMemberData($params[0], $params[1], $params[2]); + + case 324: + $params = explode(chr(32), $this->lVars['params']); + $channel = $params[0]; + $query = substr($this->lVars['params'], strlen($channel) + 1); + $this->setChannelData($channel, "modes", $query); + break; + + case 329: + $params = explode(chr(32), $this->lVars['params']); + $channel = $params[0]; + $query = substr($this->lVars['params'], strlen($channel) + 1); + $this->setChannelData($channel, "created", $query); + break; + + case 332: + $this->setChannelData(trim($this->lVars['params']), "topic", $this->lVars['text']); + break; + + case 333: + $params = explode(chr(32), $this->lVars['params']); + $channel = $params[0]; + $query = substr($this->lVars['params'], strlen($channel) + 1); + $this->setChannelData($channel, "topicby", $query); + break; + + case 352: + $params = explode(chr(32), $this->lVars['params']); + $this->changeMember($params[0], $params[4], $params[4], $params[1], "", "", $params[2]); + break; + + case 353: + $channel = substr($this->lVars['params'], 2); + $this->updateOpList($channel); + break; + + case 367: + $params = explode(chr(32), $this->lVars['params']); + $data = $this->getChannelData($params[0]); + if ($data != NULL) + { + if ($data->banComplete == 1) + { + $this->clearBanList($params[0]); + $data->banComplete = 0; + } + + $this->changeChannel($params[0], "+", "b", array($params[1], $params[3])); + } + break; + + case 368: + $params = explode(chr(32), $this->lVars['params']); + $channel = $params[0]; + $this->setChannelData($channel, "bancomplete", 1); + break; + + case 401: + $this->removeQueues($this->lVars['params']); + break; + + case 433: + if ($this->getStatusRaw() != STATUS_CONNECTED_REGISTERED) + { + if ($this->nick == $this->getClientConf('nick')) + { + $this->changeNick($this->nick . rand() % 1000); + } + $this->nickTimer = time(); + } + break; + } + + } + + public function isOnline($nick, $chan) + { + return $this->isMode($nick, $chan, "online"); + } + + + private function updateOpList($channel) + { + $channel = $this->myStrToLower($channel); + $users = explode(chr(32), $this->lVars['text']); + + if (!isset($this->prefixArray) || count($this->prefixArray) <= 0) + { + $this->createPrefixArray(); + } + + foreach ($users AS $user) + { + if (trim($user) == "") + { + continue; + } + + $userModes = ""; + $userNick = ""; + + for ($currIndex = 0; $currIndex < strlen($user); $currIndex++) + { + $currChar = substr($user, $currIndex, 1); + + if (!isset($this->prefixArray[$currChar])) + { + $userNick = substr($user, $currIndex); + break; + } + + $userModes .= $currChar; + } + + if ($userNick != $this->nick) + { + $this->addmember($channel, $userNick, "", $this->convertUserModes($userModes)); + } + + } + + } + + + private function convertUserModes($modes) + { + $newModes = ""; + + for ($index = 0; $index < strlen($modes); $index++) + { + $newModes .= $this->prefixArray[$modes[$index]]; + } + + return $newModes; + } + + + private function createPrefixArray() + { + $modeSymbols = substr($this->getServerConf('PREFIX'), strpos($this->getServerConf('PREFIX'), ")") + 1); + + $leftParan = strpos($this->getServerConf('PREFIX'), "("); + $rightParan = strpos($this->getServerConf('PREFIX'), ")"); + $modeLetters = substr($this->getServerConf('PREFIX'), $leftParan + 1, $rightParan - $leftParan - 1); + + for ($index = 0; $index < strlen($modeLetters); $index++) + { + $this->prefixArray[$modeSymbols[$index]] = $modeLetters[$index]; + } + + } + + + public function doMode() + { + $this->modeQueueAdded = false; + + $currAct = ""; + $currChan = ""; + + $modeLineModes = ""; + $modeLineParams = ""; + + $maxModesPerLine = ($this->getServerConf('MODES') == "" ? 1 : $this->getServerConf('MODES')); + $currLineModes = 0; + + foreach($this->modeQueue AS $modeChange) + { + if ($modeLineModes != "" && ($currChan != $modeChange['CHANNEL'] || $currLineModes >= $maxModesPerLine)) + { + $this->pushAfter($this->clientFormat("MODE " . $currChan . " " . $modeLineModes . " " . trim($modeLineParams))); + $modeLineModes = ""; + $currAct = ""; + $currChan = ""; + $modeLineParams = ""; + $currLineModes = 0; + } + + if ($currAct != $modeChange['ACT']) + { + $modeLineModes .= $modeChange['ACT']; + } + + $modeLineModes .= $modeChange['MODE']; + + if ($modeChange['USER'] != "") + { + $modeLineParams .= $modeChange['USER'] . " "; + } + + $currLineModes++; + + $currAct = $modeChange['ACT']; + $currChan = $modeChange['CHANNEL']; + + } + + if ($modeLineModes != "") + { + $this->pushAfter($this->clientFormat("MODE " . $currChan . " " . $modeLineModes . " " . trim($modeLineParams))); + } + + unset($this->modeQueue); + $this->modeQueue = array(); + $this->modeQueueLength = 0; + + return false; + + } + + public function changeMode($chan, $act, $mode, $user) + { + $user = trim($user); + $chan = trim($chan); + $act = trim($act); + $mode = trim($mode); + + if ($chan == "" || $mode == "") + { + return false; + } + + if (!($act == "+" || $act == "-")) + { + return false; + } + + if (strlen($mode) > 1) + { + return false; + } + + if (!isset($this->modeArray[$mode])) + { + if ($user == "") + { + return false; + } + } + + if ($this->modeQueueAdded != true) + { + $this->timerClass->addTimer("mode_timer", $this, "doMode", "", 0, true); + $this->modeQueueAdded = true; + } + + $this->modeQueue[] = array('USER' => $user, 'CHANNEL' => $chan, 'ACT' => $act, 'MODE' => $mode); + $this->modeQueueLength++; + + return true; + + } + + public function parseModes($modeString) + { + $modeString .= " "; + + $offset = strpos($modeString, chr(32)); + $modes = substr($modeString, 0, $offset); + $users = substr($modeString, $offset + 1); + $userArray = explode(chr(32), $users); + + if (count($this->modeArray) <= 0) + { + $this->createModeArray(); + } + + $action = ""; + $returnModes = array(); + + while (trim($modes) != "") + { + $thisMode = substr($modes, 0, 1); + + $modes = substr($modes, 1); + + if ($thisMode == "-" || $thisMode == "+") + { + $action = $thisMode; + continue; + } + + if (strpos($this->getServerConf('CHANMODES'), $thisMode) !== false) + { + if (!isset($this->modeArray[$thisMode])) + { + return false; + } + + $type = $this->modeArray[$thisMode]; + $extra = ""; + if ($type != BY_NONE) + { + $extra = array_shift($userArray); + } + + $type = CHANNEL_MODE; + + } + else + { + $extra = array_shift($userArray); + $type = USER_MODE; + } + + $returnModes[] = array( 'ACTION' => $action, + 'MODE' => $thisMode, + 'EXTRA' => $extra, + 'TYPE' => $type, + ); + + } + + return $returnModes; + } + + public static function intToSizeString($size) + { + + $i = 20; + while ($size > pow(2, $i)) + { + $i += 10; + } + + switch ($i) + { + case 20: //kb + $num = $size / 1000; + $type = "KB"; + break; + case 30: //mb + $num = $size / 1000000; + $type = "MB"; + break; + case 40: //gb + $num = $size / 1000000000; + $type = "GB"; + break; + case 50: //tb + $num = $size / 1000000000000; + $type = "TB"; + break; + default: //pb + $num = $size / 1000000000000000; + $type = "PB"; + break; + } + + $stringSize = round($num, 2) . $type; + + return $stringSize; + + } + + + public function checkIgnore($mask) + { + $ignore = $this->getClientConf('ignore'); + + if ($ignore == "") + { + return false; + } + + if (!is_array($ignore)) + { + $ignore = array($ignore); + } + + foreach($ignore AS $ig) + { + $case = $this->hostMasksMatch($mask, $ig); + + if ($case) + { + return true; + } + } + + return false; + } + + + private function parseMsgs() + { + + switch($this->lVars['cmd']) + { + case "JOIN": + $chan = $this->lVars['to']; + if (substr($this->lVars['to'], 0, 1) == ":") + { + $chan = substr($this->lVars['to'], 1); + } + $this->addMember($chan, $this->lVars['fromNick'], $this->lVars['fromIdent'], "", $this->lVars['fromHost']); + if ($this->lVars['fromNick'] == $this->getNick()) + { + $this->sendRaw("MODE " . $chan); + + if (isset($this->clientConfig['populatebans'])) + { + $this->sendRaw("MODE " . $chan . " +b"); + } + + if (isset($this->clientConfig['populatewho'])) + { + $this->sendRaw("WHO " . $chan); + } + } + break; + + case "PART": + if ($this->lVars['fromNick'] == $this->nick) + { + $this->removeChannel($this->lVars['to']); + } + else + { + $this->removeMember($this->lVars['to'], $this->lVars['fromNick']); + } + break; + + case "QUIT": + if ($this->lVars['fromNick'] == $this->nick) + { + $this->purgeChanList(); + } + else + { + $this->removeMember("", $this->lVars['fromNick']); + } + break; + + case "NICK": + if ($this->lVars['fromNick'] == $this->nick) + { + $this->nick = $this->lVars['text']; + } + $this->changeMember("", $this->lVars['fromNick'], $this->lVars['text'], "", "", ""); + break; + + case "KICK": + if ($this->myStrToLower($this->lVars['params']) == $this->myStrToLower($this->nick)) + { + $this->removeChannel($this->lVars['to']); + $this->joinChannel($this->lVars['to']); + } + else + { + $this->removeMember($this->lVars['to'], $this->lVars['params']); + } + break; + + case "MODE": + $channel = $this->myStrToLower($this->lVars['to']); + if ($channel == $this->myStrToLower($this->nick)) + break; + $modes = $this->parseModes($this->lVars['params']); + foreach($modes AS $mode) + { + if ($mode['TYPE'] == CHANNEL_MODE) + { + $this->changeChannel($channel, $mode['ACTION'], $mode['MODE'], $mode['EXTRA']); + } + else + { + $this->changeMember($channel, $mode['EXTRA'], $mode['EXTRA'], "", $mode['MODE'], $mode['ACTION']); + } + unset($mode); + } + unset($modes); + break; + case "NOTICE": + if ($this->checkIgnore($this->lVars['from'])) + { + return; + } + if ($this->myStrToLower($this->lVars['fromNick']) == "nickserv") + { + if (strpos($this->myStrToLower($this->lVars['text']), "identify") !== false) + { + if ($this->getClientConf('password') != "") + { + $this->pushBefore($this->clientFormat("PRIVMSG HostServ :ON")); + $this->pushBefore($this->clientFormat("PRIVMSG NickServ :IDENTIFY " . $this->getClientConf('password'))); + $this->pushBefore($this->clientFormat("MODE " . $this->getClientConf('nick') . " +B")); + } + } + } + break; + case "PRIVMSG": + if ($this->checkIgnore($this->lVars['from'])) + { + return; + } + if (strpos($this->lVars['text'], chr(1)) !== false) + { + $this->parseCtcp(); + } + break; + case "TOPIC": + $this->setChannelData($this->lVars['to'], "topic", $this->lVars['text']); + break; + + case "PING": + if ($this->lVars['from'] == "Server") + { + $this->pushBefore($this->clientFormat("PONG :" . $this->lVars['text'])); + } + + default: + break; + + + } + + + + + } + + + + private function parseCtcp() + { + $cmd = str_replace(chr(1), "", $this->lVars['text']) . " "; + $query = trim(substr($cmd, strpos($cmd, chr(32)) + 1)); + $cmd = substr($this->myStrToLower($cmd), 0, strpos($cmd, chr(32))); + + $msg = ""; + + switch($cmd) + { + case "version": + // PLEASE DO NOT CHANGE THE FOLLOWING LINE OF CODE. It is the only way for people to know that this project + // exists. If you would like to change it, please leave the project name/version or url in there somewhere, + // so that others may find this project as you have. :) + $msg = "PHP-iRC v" . VERSION . " [".VERSION_DATE."] by Manick (visit http://www.phpbots.org/ to download)"; + $this->notice($this->lVars['fromNick'], chr(1) . $msg . chr(1)); + $msg = ""; + $this->showModules($this->lVars['fromNick']); + break; + + case "time": + $msg = "My current time is " . date("l, F jS, Y @ g:i a O", time()) . "."; + break; + + case "uptime": + $msg = "My uptime is " . $this->timeFormat($this->getRunTime(), "%d days, %h hours, %m minutes, and %s seconds."); + break; + + case "ping": + $msg = "PING " . $query; + + } + + if ($msg != "") + { + $this->notice($this->lVars['fromNick'], chr(1) . $msg . chr(1)); + } + + } + + //Split huge lines up by spaces 255 by default + public static function multiLine($text, $separator = " ") + { + $returnArray = array(); + $text = trim($text); + $strlen = strlen($text); + $sepSize = strlen($separator); + + while ($strlen > 0) + { + if (256 > $strlen) + { + $returnArray[] = $text; + break; + } + + for ($i = 255; $i > 0; $i--) + { + if (substr($text, $i, $sepSize) == $separator) + { + break; + } + } + + if ($i <= 0) + { + $returnArray[] = substr($text, 0, 255); + $text = substr($text, 254); + $strlen -= 255; + } + else + { + $returnArray[] = substr($text, 0, $i); + $text = substr($text, $i - 1); + $strlen -= $i; + } + } + + return $returnArray; + } + + + private function showModules($nick) + { + $cmdList = $this->parserClass->getCmdList(); + + if (isset($cmdList['file'])) + { + $mod = ""; + + foreach($cmdList['file'] AS $module) + { + $class = $module['class']; + + if (isset($class->dontShow) && $class->dontShow == true) + { + continue; + } + $mod .= "[" . $class->title . " " . $class->version . "] "; + } + + if ($mod != "") + { + $modArray = $this->multiLine("Running Modules: " . $mod); + + foreach ($modArray AS $myMod) + { + $this->notice($nick, chr(1) . $myMod . chr(1)); + } + } + } + unset($cmdList); + } + + + public function getRunTime() + { + return (time() - $this->startTime); + } + + + public static function timeFormat($time, $format) + { + + $days = 0; + $seconds = 0; + $minutes = 0; + $hours = 0; + + if (strpos($format, "%d") !== FALSE) + { + $days = (int) ($time / (3600 * 24)); + $time -= ($days * (3600 * 24)); + } + + if (strpos($format, "%h") !== FALSE) + { + $hours = (int) ($time / (3600)); + $time -= ($hours * (3600)); + } + + if (strpos($format, "%m") !== FALSE) + { + $minutes = (int) ( $time / (60)); + $time -= ($minutes * (60)); + } + + $seconds = $time; + + $format = str_replace("%d", $days, $format); + $format = str_replace("%s", $seconds, $format); + $format = str_replace("%m", $minutes, $format); + $format = str_replace("%h", $hours, $format); + + return $format; + + } + + + private function createModeArray() + { + $modeArray = explode(",", $this->getServerConf('CHANMODES')); + + for ($i = 0; $i < count($modeArray); $i++) + { + for ($j = 0; $j < strlen($modeArray[$i]); $j++) + { + $this->modeArray[$modeArray[$i][$j]] = $i; + } + + } + } + + public function checkChans() + { + if ($this->getStatusRaw() != STATUS_CONNECTED_REGISTERED) + { + return true; + } + + foreach ($this->maintainChannels AS $index => $channel) + { + if ($this->isOnline($this->nick, $channel['CHANNEL']) === false) + { + if ($channel['KEY'] != "") + { + $this->joinChannel($channel['CHANNEL'] . " " . $channel['KEY']); + } + else + { + $this->joinChannel($channel['CHANNEL']); + } + } + } + + return true; + } + + public function getStatusString($status) + { + $msg = ""; + + switch ($status) + { + case STATUS_IDLE: + $msg = "Idle"; + break; + case STATUS_ERROR: + $msg = "Error"; + break; + case STATUS_CONNECTING: + $msg = "Connecting to server..."; + break; + case STATUS_CONNECTED: + $msg = "Connected to server: " . $this->getClientConf('server') . " " . $this->getClientConf('port'); + break; + case STATUS_CONNECTED_SENTREGDATA: + $msg = "Sent registration data, awaiting reply..."; + break; + case STATUS_CONNECTED_REGISTERED: + $msg = "Authenticated"; + break; + default: + $msg = "Unknown"; + } + + return $msg; + } + + + public function purgeMaintainList() + { + unset($this->maintainChannels); + $this->maintainChannels = array(); + } + + + public function removeMaintain($channel) + { + $channel = $this->myStrToLower($channel); + + foreach ($this->maintainChannels AS $index => $chan) + { + if ($chan['CHANNEL'] == $channel) + { + unset($this->maintainChannels[$index]); + break; + } + } + } + + + public function maintainChannel($channel, $key = "") + { + $channel = $this->myStrToLower($channel); + $this->maintainChannels[] = array('CHANNEL' => $channel, 'KEY' => $key); + } + + + public function joinChannel($chan) + { + $this->pushBefore($this->clientFormat("JOIN " . $chan)); + } + + public function changeNick($nick) + { + $this->pushBefore($this->clientFormat("NICK " . $nick)); + $this->tempNick = $nick; + } + + + private function parseServerConfig() + { + $args = explode(chr(32), $this->lVars['params']); + + foreach ($args AS $arg) + { + + if (strpos($arg, "=") === false) + { + $arg .= "=1"; + } + + $argParts = explode("=", $arg); + + $this->serverConfig[$argParts[0]] = $argParts[1]; + + } + } + + private function clientFormat($text) + { + return array("USER" => "*", "TEXT" => $text); + } + + + + public function removeQueues($nick) + { + $nick = $this->myStrToLower($nick); + + foreach ($this->textQueue AS $index => $queue) + { + if ($this->myStrToLower($queue['USER']) == $nick) + { + unset($this->textQueue[$index]); + $this->textQueueLength--; + } + } + + + } + + public function getStats() + { + return $this->stats; + } + + public function doQueue() + { + if ($this->status < STATUS_CONNECTED) + { + $this->textQueueAdded = false; + return false; + } + + if ($this->socketClass->hasWriteQueue($this->sockInt) !== false) + { + return true; + } + + if ($this->textQueueLength < 0) + { + if (is_array($this->textQueue)) + { + unset($this->textQueue); + $this->textQueue = array(); + } + $this->textQueueAdded = false; + return false; + } + + $bufferSize = $this->getClientConf("queuebuffer"); + + $bufferSize = $bufferSize <= 0 ? 0 : $bufferSize; + + $sendData = ""; + + $nextItem = array_shift($this->textQueue); + + if (trim($nextItem['TEXT']) != "") + { + $sendData .= $nextItem['TEXT'] . "\r\n"; + } + + unset($nextItem); + + $this->textQueueLength--; + + while ($this->textQueueLength > 0 && ((strlen($this->textQueue[0]['TEXT']) + strlen($sendData)) < $bufferSize)) + { + + $nextItem = array_shift($this->textQueue); + + if (trim($nextItem['TEXT']) != "") + { + $sendData .= $nextItem['TEXT'] . "\r\n"; + unset($nextItem); + } + + $this->textQueueLength--; + + } + + $this->stats['BYTESUP'] += strlen($sendData); + + $this->writeToSocket($sendData); + + unset($sendData); + + return true; + } + + + private function writeToSocket($sendData) + { + + if (DEBUG == 1) + { + $this->log($sendData); + } + + try + { + if ($this->socketClass->sendSocket($this->sockInt, $sendData) === false) + { + throw new SendDataException("Could not write to socket"); + } + } + catch (SendDataException $e) + { + $this->disconnect(); + $this->exception = $e; + $this->status = STATUS_ERROR; + } + + } + + + private function pushAfter($data) + { + $this->textQueueLength++; + $this->textQueue[] = $data; + + if ($this->textQueueAdded == false) + { + $this->timerClass->addTimer("queue_timer", $this, "doQueue", "", $this->getQueueTimeout(), true); + $this->textQueueAdded = true; + } + + } + + + private function pushBefore($data) + { + $this->textQueueLength++; + $this->textQueue = array_merge(array($data), $this->textQueue); + + if ($this->textQueueAdded == false) + { + $this->timerClass->addTimer("queue_timer", $this, "doQueue", "", $this->getQueueTimeout(), true); + $this->textQueueAdded = true; + } + } + + public function sendRaw($text, $force = false) + { + if ($force == false) + { + $format = $this->clientFormat($text); + $this->pushBefore($format); + } + else + { + $this->writeToSocket($text . "\r\n"); + } + } + + + public function privMsg($who, $msg, $queue = 1) + { + $text = array( 'USER' => $who, + 'TEXT' => 'PRIVMSG ' . $who . ' :' . $msg); + + if ($queue) + { + $this->pushAfter($text); + } + else + { + $this->pushBefore($text); + } + } + + + public function action($who, $msg, $queue = 1) + { + $text = array( 'USER' => $who, + 'TEXT' => 'PRIVMSG ' . $who . ' :' . chr(1) . 'ACTION ' .$msg . chr(1)); + + if ($queue) + { + $this->pushAfter($text); + } + else + { + $this->pushBefore($text); + } + } + + + public function notice($who, $msg, $queue = 1) + { + + $text = array( 'USER' => $who, + 'TEXT' => 'NOTICE ' . $who . ' :' . $msg); + + if ($queue) + { + $this->pushAfter($text); + } + else + { + $this->pushBefore($text); + } + } + + public function getClientIP($long = 1) + { + if ($long == 1) + { + return $this->clientLongIP; + } + else + { + return $this->clientIP; + } + } + + public function setClientIP($ip = "") + { + if ($ip == "") + { + $ip = $this->socketClass->getHost($this->sockInt); + } + + $this->clientIP = $ip; + + $this->clientLongIP = ip2long($this->clientIP); + + if ($this->clientLongIP <= 0) + { + $this->clientLongIP += pow(2,32); + } + } + + + + public function purgeTextQueue() + { + $this->textQueueTime = 0; + unset($this->textQueue); + $this->textQueue = array(); + $this->textQueueLength = 0; + } + + public function getTextQueueLength() + { + return $this->textQueueLength; + } + + public function log($data) + { + $network = $this->getServerConf('Network') == "" ? $this->getClientConf('server') : $this->getServerConf('Network'); + + if (DEBUG == 1) + { + echo "[".date("h:i:s")."] " . "({$this->nick}@$network) > " . $data . "\n"; + } + else + { + if ($this->getClientConf('logfile') != "") + { + error_log("[".date("h:i:s")."] " . "({$this->nick}@$network) > " . $data . "\n", 3, $this->getClientConf('logfile')); + } + } + } + + public function getUsageList() + { + return $this->usageList; + } + + public function floodCheck($line) + { + $host = $line['fromHost']; + + if (!array_key_exists($host, $this->usageList)) + { + $this->usageList[$host] = new usageLink; + $this->usageList[$host]->isBanned = false; + $this->usageList[$host]->lastTimeUsed = time(); + $this->usageList[$host]->timesUsed = 1; + $user = $this->usageList[$host]; + } + else + { + $user = $this->usageList[$host]; + + $floodTime = intval($this->getClientConf('floodtime')); + if ($floodTime <= 0) + { + $floodTime = 60; + } + + if ($user->isBanned == true) + { + if ($user->timeBanned > time() - $floodTime) + { + return STATUS_ALREADY_BANNED; + } + $user->isBanned = false; + } + + + if ($user->lastTimeUsed < time() - 10) + { + $user->timesUsed = 0; + } + $user->lastTimeUsed = time(); + $user->timesUsed++; + } + + $numLines = intval($this->getClientConf('floodlines')); + if ($numLines <= 0) + { + $numLines = 5; + } + + if ($user->timesUsed > $numLines) + { + $user->isBanned = true; + $user->timeBanned = time(); + $user->timesUsed = 0; + $user->lastTimeUsed = 0; + $this->removeQueues($line['fromNick']); + return STATUS_JUST_BANNED; + } + + return STATUS_NOT_BANNED; + } + + public static function myStrToLower($text) + { + $textA = strtolower($text); + + $textA = str_replace("\\", "|", $textA); + $textA = str_replace("[", "{", $textA); + $textA = str_replace("]", "}", $textA); + $textA = str_replace("~", "^", $textA); + + return $textA; + } + + public static function myStrToUpper($text) + { + $textA = strtoupper($text); + + $textA = str_replace("|", "\\", $textA); + $textA = str_replace("{", "[", $textA); + $textA = str_replace("}", "]", $textA); + $textA = str_replace("^", "~", $textA); + + return $textA; + } + + public static function randomHash() + { + return md5(uniqid(rand(), true)); + } + + private function getQueueTimeout() + { + $timeout = $this->getClientConf("queuetimeout"); + $timeout = $timeout <= 0 ? 0 : $timeout; + return $timeout; + } + + public function addQuery($host, $port, $query, $line, $class, $function) + { + $remote = new remote($host, $port, $query, $line, $class, $function, 8); + $remote->setIrcClass($this); + $remote->setTimerClass($this->timerClass); + $remote->setSocketClass($this->socketClass); + return $remote->connect(); + } + +} + diff --git a/ircbot/module.php b/ircbot/module.php new file mode 100644 index 0000000..0cdee92 --- /dev/null +++ b/ircbot/module.php @@ -0,0 +1,126 @@ + module class +| > Module written by Manick +| > Module Version Number: 2.2.0 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +abstract class module { + + public $title = ""; + public $author = "<author>"; + public $version = "<version>"; + public $dontShow = false; + + public $ircClass; + public $dccClass; + public $timerClass; + public $parserClass; + public $socketClass; + public $db; + + public function __construct() + { + //Nothing here... + } + + public function __destruct() + { + $this->ircClass = null; + $this->dccClass = null; + $this->timerClass = null; + $this->parserClass = null; + $this->socketClass = null; + $this->db = null; + //Nothing here + } + + public final function __setClasses($ircClass, $dccClass, $timerClass, $parserClass, + $socketClass, $db) + { + $this->ircClass = $ircClass; + $this->dccClass = $dccClass; + $this->timerClass = $timerClass; + $this->parserClass = $parserClass; + $this->socketClass = $socketClass; + $this->db = $db; + } + + public final function getModule($modName) + { + $mods = $this->parserClass->getCmdList("file"); + + if ($mods === false) + { + return false; + } + + if (isset($mods[$modName])) + { + return $mods[$modName]['class']; + } + + return false; + } + + public function handle($chat, $args) + { + } + + public function connected($chat) + { + } + + public function main($line, $args) + { + $port = $this->dccClass->addChat($line['fromNick'], null, null, false, $this); + + if ($port === false) + { + $this->ircClass->notice($line['fromNick'], "Error starting chat, please try again.", 1); + } + } + + public function init() + { + //Global.. this needs to be overwritten + } + + public function destroy() + { + //Global.. this needs to be overwritten + } + +} + +?> diff --git a/ircbot/modules/default/ads.ini b/ircbot/modules/default/ads.ini new file mode 100644 index 0000000..e69de29 diff --git a/ircbot/modules/default/dcc_mod.conf b/ircbot/modules/default/dcc_mod.conf new file mode 100644 index 0000000..7caeedb --- /dev/null +++ b/ircbot/modules/default/dcc_mod.conf @@ -0,0 +1,81 @@ +;+--------------------------------------------------------------------------- +;| PHP-IRC Internal DCC Function Configuration File +;| ======================================================== +;| by Manick +;| (c) 2001-2004 by http://phpbots.sf.net +;| Contact: manick@manekian.com +;| irc: #manekian@irc.rizon.net +;| ======================================== +;+--------------------------------------------------------------------------- +;| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +;+--------------------------------------------------------------------------- + +file dcc_mod modules/default/dcc_mod.php + +privmsg dcc_mod monitor_check +mode dcc_mod monitor_check +join dcc_mod monitor_check +kick dcc_mod monitor_check +part dcc_mod monitor_check + +;new feature in 2.2.0, section definitions +section standard "Standard Functions" +section channel "Channel Functions" +section dcc "DCC Functions" +section info "Information Functions" +section admin "Administrative Functions" +section comm "IRC/Communication Functions" + +dcc monitor ~; command text (typed in dcc iface) + 0 ~; number of required arguments + "<channel>" ~; argument descriptions <arg1> <arg2> (or however you damn well please to do it hehe ;) + "Show messages from channel in dcc chat interface" ~; description of command + true ~; must be admin to use this command? + dcc_mod ~; class name (see file import section above) + dcc_monitor ~; function name + channel ; section name + +dcc exit 0 "" "Exits the DCC interface" false dcc_mod dcc_exit standard +dcc raw 1 "<raw query>" "Sends raw query to server" true dcc_mod dcc_raw comm +dcc who 0 "" "See who\'s online" false dcc_mod dcc_who standard +dcc ignore 0 "" "View ignore list" true dcc_mod dcc_ignore admin +dcc rignore 1 "<host>" "Remove ignore for a specified host" true dcc_mod dcc_rignore admin +dcc clearqueue 0 "<user>" "Removes all text queues, or queues for a specified user." true dcc_mod dcc_clearqueue admin +dcc server 1 "<server> <port>" "Change server to specified server" true dcc_mod dcc_server admin +dcc chat 1 "<text>" "Sends message to all admin users using the DCC interface" true dcc_mod dcc_chat comm +dcc restart 0 "" "Reconnect to the current server" true dcc_mod dcc_restart admin +dcc say 2 "<channel> <text>" "Sends text to some channel" true dcc_mod dcc_say comm +dcc action 2 "<channel> <text>" "Sends text to some channel in /me format" true dcc_mod dcc_action comm +dcc users 0 "" "Shows users in all channels" true dcc_mod dcc_users info +dcc maintain 0 "<channel> <key>" "Shows maintained channels, and adds or removes one" true dcc_mod dcc_maintain channel +dcc help 0 "" "Get information about command(s)" false dcc_mod dcc_help standard +dcc join 1 "<channel>" "Join a channel" true dcc_mod dcc_join channel +dcc part 1 "<channel>" "Part a channel" true dcc_mod dcc_part channel +dcc rejoin 1 "<channel>" "Rejoin a channel" true dcc_mod dcc_rejoin channel +dcc status 0 "" "Get status of bot, same as 5 minute status update" true dcc_mod dcc_status info +dcc function 0 "<activate/deactivate> <function>" "See which functions are activated, or activate/deactivate a function" true dcc_mod dcc_function admin +dcc reloadfunc 0 "" "Reloads function definition file" true dcc_mod dcc_reloadfunc admin +dcc shutdown 0 "" "Shuts the bot down" true dcc_mod dcc_shutdown admin +dcc connect 0 "" "Force connection to server when disconnected" true dcc_mod dcc_connect admin +dcc rehash 0 "" "Reload the configuration file" true dcc_mod dcc_rehash admin +dcc send 2 "<nick> <file>" "Send a user a file" true dcc_mod dcc_send dcc +dcc dcc 0 "" "Check current dcc status" true dcc_mod dcc_dccs dcc +dcc close 1 "<id>" "Close a download, upload, or chat window" true dcc_mod dcc_close dcc +dcc upload 1 "<yes/no>" "Allow files to be sent to this bot" true dcc_mod dcc_upload dcc +dcc botinfo 0 "" "View information about the bot" true dcc_mod dcc_botinfo info +dcc timers 0 "" "View active timer information" true dcc_mod dcc_timers info +dcc listul 0 "" "List upload directory contents" true dcc_mod dcc_listul dcc +dcc modules 0 "" "List installed modules" true dcc_mod dcc_modules info +dcc spawn 1 "<config file>" "Spawn a new bot in this process" true dcc_mod dcc_spawn admin \ No newline at end of file diff --git a/ircbot/modules/default/dcc_mod.php b/ircbot/modules/default/dcc_mod.php new file mode 100644 index 0000000..68002d7 --- /dev/null +++ b/ircbot/modules/default/dcc_mod.php @@ -0,0 +1,1057 @@ +<?php +/* ++--------------------------------------------------------------------------- +| PHP-IRC v2.2.0 +| ======================================================== +| by Manick +| (c) 2001-2005 by http://phpbots.sf.net/ +| Contact: manick@manekian.com +| irc: #manekian@irc.rizon.net +| ======================================== ++--------------------------------------------------------------------------- +| > dcc_mod module +| > Module written by Manick +| > Module Version Number: 2.2.0 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +class dcc_mod extends module { + + public $title = "DCC Chat Utils"; + public $author = "Manick"; + public $version = "2.1.1"; + public $dontShow = true; + + public function init() + { + $this->timerClass->addTimer("dccstatus", $this, "sendStatus", "", 5*60); + } + + public function destroy() + { + $this->timerClass->removeTimer("dccstatus"); + } + + /* DCC Functions */ + + public $monitorList = array(); + + public function monitor_check($line, $args) + { + switch($line['cmd']) + { + case "PRIVMSG": + if (isset($this->monitorList[irc::myStrToLower($line['to'])])) + { + if (preg_match("/\1ACTION (.+?)\1/", $line['text'], $match)) + { + $this->dccClass->dccInform("CHAN: " . $line['to'] . ": * " . $line['fromNick'] . " " . $match[1]); + } + else + { + $this->dccClass->dccInform("CHAN: " . $line['to'] . ": <" . $line['fromNick'] . "> " . $line['text']); + } + } + break; + + case "MODE": + if ($line['fromNick'] != $this->ircClass->getNick()) + { + if (isset($this->monitorList[irc::myStrToLower($line['to'])])) + { + $this->dccClass->dccInform("CHAN: " . $line['to'] . ": *** " . $line['fromNick'] . " sets mode: ".$line['params']); + } + } + break; + + case "JOIN": + if ($line['fromNick'] != $this->ircClass->getNick()) + { + if (isset($this->monitorList[irc::myStrToLower($line['text'])])) + { + $this->dccClass->dccInform("CHAN: " . $line['text'] . ": *** " . $line['fromNick'] . " joined channel."); + } + } + break; + + case "PART": + if ($line['fromNick'] != $this->ircClass->getNick()) + { + if (isset($this->monitorList[irc::myStrToLower($line['to'])])) + { + $this->dccClass->dccInform("CHAN: " . $line['to'] . ": *** " . $line['fromNick'] . " parted channel."); + } + } + break; + + case "KICK": + if ($line['params'] != $this->ircClass->getNick()) + { + if (isset($this->monitorList[irc::myStrToLower($line['to'])])) + { + $this->dccClass->dccInform("CHAN: " . $line['to'] . ": *** " . $line['params'] . " was kicked by ".$line['fromNick']." (".$line['text'].")."); + } + } + + break; + + default: + break; + + } + } + + + public function dcc_botinfo($chat, $args) + { + + $chat->dccSend("PHP-IRC v" . VERSION . " [".VERSION_DATE."] by Manick (visit http://phpbots.sf.net/ to download)"); + $chat->dccSend("total running time of " . $this->ircClass->timeFormat($this->ircClass->getRunTime(), "%d days, %h hours, %m minutes, and %s seconds.")); + + $fd = @fopen("/proc/" . $this->ircClass->pid() . "/stat", "r"); + if ($fd !== false) + { + $stat = fread($fd, 1024); + fclose($fd); + + $stat_array = explode(" ", $stat); + + $pid = $stat_array[0]; + $comm = $stat_array[1]; + $utime = $stat_array[13]; + $stime = $stat_array[14]; + $vsize = $stat_array[22]; + $meminfo = number_format($vsize, 0, '.',','); + $u_time = number_format($utime / 100, 2,'.',','); + $s_time = number_format($stime / 100, 2,'.',','); + + $fd = @fopen("/proc/stat", "r"); + if ($fd !== false) + { + $stat = fread($fd, 1024); + fclose($fd); + + $stat = str_replace(" ", " ", $stat); + $stat_array_2 = explode(" ", $stat); + $totalutime = $stat_array_2[1]; + $totalstime = $stat_array_2[3]; + $u_percent = number_format($utime / $totalutime, 6,'.',','); + $s_percent = number_format($stime / $totalstime, 6,'.',','); + + $chat->dccSend("cpu usage: " . $u_time . "s user (" . $u_percent . "%), " . $s_time . "s system (" . $s_percent . "%)"); + } + + $chat->dccSend("memory usage: " . $meminfo . " bytes"); + + } + + $fd = @fopen("/proc/loadavg", "r"); + if ($fd !== false) + { + $loadavg = fread($fd, 1024); + $loadavg_array = explode(" ", $loadavg); + $loadavgs = $loadavg_array[0] . " " . $loadavg_array[1] . " " .$loadavg_array[2]; + fclose($fd); + + $chat->dccSend("cpu load averages: " . $loadavgs); + } + + $realname = $this->ircClass->getClientConf('realname') == "" ? "n/a" : $this->ircClass->getClientConf('realname'); + $upload = $this->ircClass->getClientConf('upload') == "yes" ? "yes" : "no"; + + $chat->dccSend("configured nick: " . $this->ircClass->getClientConf('nick') . ", " . + "actual nick: " . $this->ircClass->getNick() . ", realname: " . $realname); + + $chat->dccSend("upload is currently set to " . $upload); + + if ($this->ircClass->getStatusRaw() == STATUS_CONNECTED_REGISTERED) + { + $network = $this->ircClass->getServerConf('Network') == "" ? $this->ircClass->getClientConf('server') : $this->ircClass->getServerConf('Network'); + $chat->dccSend("current server: " . $network . "(" . $this->ircClass->getClientConf('server') . ") port: " . $this->ircClass->getClientConf('port')); + } + else + { + $chat->dccSend("current server: none"); + } + + $maintain = $this->ircClass->getMaintainedChannels(); + + $maintained = "n/a"; + + if (isset($maintain[0])) + { + $maintained = ""; + foreach ($maintain AS $chan) + { + $maintained .= $chan['CHANNEL'] . " "; + } + $maintained = trim($maintained); + $chat->dccSend("configured channels: " . $maintained); + } + + $channels = $this->ircClass->getChannelData(); + + foreach($channels AS $chanPtr) + { + $chat->dccSend("in channel " . strtoupper($chanPtr->name) . ": users: " . $chanPtr->count); + } + + $chat->dccSend("status line: " . $this->getStatus()); + $chat->dccSend("config file: " . $this->ircClass->getConfigFilename()); + + return; + + } + + public function dcc_connect($chat, $args) + { + $status = $this->ircClass->getStatusRaw(); + + if ($status == STATUS_ERROR) + { + $this->ircClass->reconnect(); + } + } + + + public function dcc_monitor($chat, $args) + { + if ($args['nargs'] == 0) + { + $chat->dccSend("The following channels are being monitored:"); + foreach ($this->monitorList AS $channel => $random) + { + $chat->dccSend($channel); + } + } + else + { + + $chan = irc::myStrToLower($args['arg1']); + + if (!isset($this->monitorList[$chan])) + { + $this->monitorList[$chan] = 1; + $chat->dccSend("The channel '" . $chan . "' is now being monitored."); + } + else + { + $chat->dccSend("The channel '" . $chan . "' was removed from the monitored list."); + unset($this->monitorList[$chan]); + } + } + + } + + + + public function dcc_who($chat, $args) + { + $dccList = $this->dccClass->getDccList(); + + $chat->dccSend("--- Users Online ---"); + $chat->dccSend("[id] <nick> <admin>"); + foreach ($dccList AS $chatBox) + { + if ($chatBox->type == CHAT) + { + $chat->dccSend("[" . $chatBox->id . "] " . $chatBox->nick . ($chatBox->isAdmin ? " (admin)" : "")); + } + } + } + + public function dcc_timers($chat, $args) + { + $timers = $this->timerClass->getTimers(); + + $chat->dccSend("Active Timers:"); + + if (count($timers) > 0) + { + $time = timers::getMicroTime(); + foreach ($timers AS $timer) + { + $interval = "interval(" . $timer->interval . " sec)"; + + $timeTillNext = round(($timer->nextRunTime - $time < 0 ? 0 : $timer->nextRunTime - $time), 0); + + $ttnext = irc::timeFormat($timeTillNext, "%m min, %s sec"); + + if (is_object($timer->class)) + { + $cName = get_class($timer->class); + } + else + { + $cName = ""; + } + + $tName = preg_replace("/([a-z0-9]){32}/", "", $timer->name); + if ($tName == "") + { + $tName = "(random hash)"; + } + + $cName = preg_replace("/_([a-z0-9]){32}/", "", $cName); + + $chat->dccSend("Timer " . BOLD . $tName . BOLD . ": func(" . $cName . "::" . $timer->func . ") " . $interval . ", Time till next run: " . $ttnext); + } + } + else + { + $chat->dccSend("There are currently no timers."); + } + + } + + public function dcc_reloadfunc($chat, $args) + { + if ($this->ircClass->getClientConf('functionfile') != "") + { + $stat = $this->parserClass->loadFuncs($this->ircClass->getClientConf('functionfile')); + $chat->dccSend("Function reload complete"); + if ($stat == true) + { + $chat->dccSend("There were errors loading a function file! Cached version may still be in use!"); + } + } + else + { + $chat->dccSend("No function file defined in config file."); + } + } + + public function dcc_rehash($chat, $args) + { + + $chat->dccSend("Rehashing main config file, please wait..."); + + $currConfig = $this->ircClass->getClientConf(); + $newConfig = bot::parseConfig($this->ircClass->getConfigFilename()); + + if ($newConfig == false) + { + $chat->dccSend("Could not find config file or IO error."); + return; + } + + $this->ircClass->setConfig($newConfig, $this->ircClass->getConfigFilename()); + + if ($currConfig['nick'] != $newConfig['nick']) + { + $chat->dccSend("Changing nick..."); + $this->ircClass->changeNick($newConfig['nick']); + } + + if ($currConfig['server'] != $newConfig['server']) + { + $chat->dccSend("Connecting to new server..."); + $this->ircClass->disconnect(); + $this->ircClass->reconnect(); + } + else + { + if (isset($currConfig['channel'])) + { + if (!is_array($currConfig['channel'])) + { + $currConfig['channel'] = array($currConfig['channel']); + } + if (!is_array($newConfig['channel'])) + { + $newConfig['channel'] = array($newConfig['channel']); + } + + foreach($currConfig['channel'] AS $chan) + { + if (!in_array($chan, $newConfig['channel'])) + { + $chan = trim($chan) . " "; + $chan = trim(substr($chan, 0, strpos($chan, chr(32)) + 1)); + $this->ircClass->sendRaw("PART " . $chan); + } + + } + } + } + + $this->ircClass->purgeMaintainList(); + + $chat->dccSend("Rehashing channel list..."); + bot::createChannelArray($this->ircClass); + + $chat->dccSend("Rehashing IP address..."); + if (isset($newConfig['natip'])) + { + if (isset($currConfig['natip'])) + { + if ($currConfig['natip'] != $newConfig['natip']) + { + $this->ircClass->setClientIP($newConfig['natip']); + } + } + else + { + $this->ircClass->setClientIP($newConfig['natip']); + } + } + else + { + if ($this->ircClass->getStatusRaw() != STATUS_CONNECTED_REGISTERED) + { + $chat->dccSend("NOTICE: Cannot reset IP address unless connected to server. No change made."); + } + else + { + $this->ircClass->setClientIP(); + } + } + + if (isset($newConfig['dccrangestart']) && $newConfig['dccrangestart'] != $currConfig['dccrangestart']) + { + $chat->dccSend("Updating TCP Range..."); + $this->socketClass->setTcpRange($newConfig['dccrangestart']); + } + + if (isset($newConfig['logfile']) && $newConfig['logfile'] != $currConfig['logfile']) + { + $chat->dccSend("Changing log file..."); + $this->ircClass->closeLog(); + } + + if (isset($newConfig['functionfile'])) + { + if ($newConfig['functionfile'] != $currConfig['functionfile']) + { + $this->parserClass->loadFuncs($newConfig['functionfile']); + } + } + else + { + $chat->dccSend("Fatal Error, functionfile directive not set. The performance of this bot is no longer guaranteed (please restart and fix your error)"); + return; + } + + $chat->dccSend("Main config rehash complete."); + + } + + public function dcc_join($chat, $args) + { + $chat->dccSend("Joining: " . $args['query']); + $this->ircClass->sendRaw("JOIN " . $args['query']); + } + + public function dcc_part($chat, $args) + { + $this->ircClass->sendRaw("PART " . $args['query']); + } + + public function dcc_rejoin($chat, $args) + { + $chanPtr = $this->ircClass->getChannelData($args['arg1']); + + if ($chanPtr == NULL) + { + $chat->dccSend("You are not on channel '" . $args['arg1'] . "'"); + } + else + { + $chat->dccSend("Rejoining: " . $args['arg1']); + $this->ircClass->sendRaw("JOIN " . $args['arg1']); + $this->ircClass->sendRaw("PART " . $args['arg1']); + } + } + + + public function dcc_shutdown($chat, $args) + { + $chat->dccSend("Shutting down, sending kill command to irc class."); + + if ($this->ircClass->getStatusRaw() == STATUS_CONNECTED_REGISTERED) + { + $time = $this->ircClass->timeFormat($this->ircClass->getRunTime(), "%dd%hh%mm%ss"); + $msg = "php-irc v" . VERSION . " by Manick, running ".$time; + $this->ircClass->sendRaw("QUIT :" . $msg); + } + + $chat->dccSend("Waiting for server queue to flush..."); + + $this->timerClass->addTimer("shutdown", $this->ircClass, "shutdown", "", 1); + } + + public function dcc_ignore($chat, $args) + { + $usageList = $this->ircClass->getUsageList(); + + $chat->dccSend("--- Usage List (* denotes active ignore) ---"); + + foreach($usageList AS $host => $user) + { + if (trim($host) != "") + { + if (intval($user->timeBanned) > 5) + { + $chat->dccSend(($user->isBanned == true ? "*" : "") . $host . ": " . (intval($user->timeBanned) > 5 ? date("m-d-y h:i:s a", $user->timeBanned) : "never banned")); + } + } + } + + + } + + public function dcc_rignore($chat, $args) + { + $usageList = $this->ircClass->getUsageList(); + + if (array_key_exists($args['arg1'], $usageList)) + { + $usageList[$args['arg1']]->isBanned = false; + $chat->dccSend("Ignore for " . $args['arg1'] . " successfully removed."); + } + else + { + $chat->dccSend("No such ignore."); + } + + } + + public function dcc_clearqueue($chat, $args) + { + if ($args['nargs'] > 0) + { + $this->ircClass->removeQueues($args['arg1']); + $chat->dccSend("All text queues for " . $args['arg1'] . " removed."); + } + else + { + $this->ircClass->purgeTextQueue(); + $chat->dccSend("All text queues purged."); + } + } + + + public function dcc_status($chat, $args) + { + $chat->dccSend($this->getStatus()); + } + + public function dcc_server($chat, $args) + { + $server = $args['arg1']; + $port = 6667; + + if ($args['nargs'] > 1) + { + $port = intval($args['arg2']); + if ($port == 0) + { + $port = 6667; + } + } + + $chat->dccSend("Changing server to: " . $server . ":" . $port); + $this->ircClass->setClientConfigVar('server', $server); + $this->ircClass->setClientConfigVar('port', $port); + $this->ircClass->disconnect(); + $this->ircClass->reconnect(); + } + + public function dcc_exit($chat, $args) + { + $this->dccClass->dccInform("DCC: " . $chat->nick . " logged off", $chat); + $chat->disconnect("User quit"); + } + + public function dcc_action($chat, $args) + { + $this->ircClass->action($args['arg1'], substr($args['query'], strlen($args['arg1']) + 1)); + } + + public function dcc_restart($chat, $args) + { + $this->ircClass->disconnect(); + } + + public function dcc_chat($chat, $args) + { + $this->dccClass->dccInform("CHAT: (" . $chat->nick . ")> " . $args['query'], $chat); + } + + public function dcc_raw($chat, $args) + { + $this->ircClass->sendRaw($args['query']); + } + + public function dcc_say($chat, $args) + { + $this->ircClass->privMsg($args['arg1'], substr($args['query'], strlen($args['arg1']) + 1)); + } + + public function dcc_users($chat, $args) + { + $chat->dccSend($this->ircClass->displayUsers()); + } + + public function dcc_upload($chat, $args) + { + $args['arg1'] = irc::myStrToLower($args['arg1']); + + if ($args['arg1'] == "yes") + { + $this->ircClass->setClientConfigVar('upload', 'yes'); + $chat->dccSend("Upload is now set to allow."); + } + else if ($args['arg1'] == "no") + { + $this->ircClass->setClientConfigVar('upload', 'no'); + $chat->dccSend("Upload is now set to deny."); + } + else + { + $chat->dccSend("Upload is currently set to: " . $this->ircClass->getClientConf('upload')); + $chat->dccSend("Valid syntax is 'yes' or 'no'."); + } + } + + + public function dcc_maintain($chat, $args) + { + if ($args['nargs'] == 0) + { + + $chat->dccSend("-- Maintained Channels --"); + $chans = $this->ircClass->getMaintainedChannels(); + + $num = 0; + foreach($chans AS $chan) + { + $chat->dccSend($chan['CHANNEL'] . + ($chan['KEY'] != "" ? " with key " . $chan['KEY'] : "")); + $num++; + } + + $chat->dccSend($num . " total channels"); + + } + else + { + $chanArg = irc::myStrToLower($args['arg1']); + + $chans = $this->ircClass->getMaintainedChannels(); + + $found = false; + foreach($chans AS $chan) + { + if ($chan['CHANNEL'] == $chanArg) + { + $found = true; + break; + } + } + + if ($found == true) + { + $this->ircClass->removeMaintain($chanArg); + $chat->dccSend("Channel " . $chanArg . " successfully removed from maintain list."); + } + else + { + $this->ircClass->maintainChannel($chanArg, ($args['nargs'] >= 2 ? $args['arg2'] : "")); + $chat->dccSend("Channel " . $chanArg . " is now being maintained" + . ($args['nargs'] >= 2 ? " with key " . $args['arg2'] : ".")); + + if ($this->ircClass->isOnline($this->ircClass->getNick(), $chanArg) == false) + { + $this->ircClass->joinChannel($chanArg . ($args['nargs'] >= 2 ? " " . $args['arg2'] : "")); + } + + } + + } + + + } + + public function dcc_help($chat, $args) + { + $cmdList = $this->parserClass->getCmdList('dcc'); + $sectionList = $this->parserClass->getCmdList('section'); + + if ($args['nargs'] > 0) + { + $cmd = $args['arg1']; + + if (isset($cmdList[$cmd])) + { + $chat->dccSend("Usage: " . $cmd . " " . $cmdList[$cmd]['usage']); + $chat->dccSend("Section: " . $sectionList[$cmdList[$cmd]['section']]['longname']); + $chat->dccSend("Description: " . $cmdList[$cmd]['help']); + } + else + { + $chat->dccSend("Invalid Command: " . $line['arg1']); + } + + return; + } + + $chat->dccSend("Commands:"); + + $sections = array(); + + foreach ($cmdList AS $cmd => $cmdData) + { + if (!$chat->isAdmin && $cmdData['admin'] == true) + { + continue; + } + + $sections[$cmdData['section']][] = strtoupper($cmd) . " - " . $cmdData['help']; + } + + foreach ($sections AS $section => $data) + { + $chat->dccSend($sectionList[$section]['longname']); + + foreach ($data AS $cmd) + { + $chat->dccSend("-- " . $cmd); + } + } + + $chat->dccSend("Use HELP <command> for a list of arguments"); + + + } + + public function dcc_modules($chat, $args) + { + $cmdList = $this->parserClass->getCmdList(); + + if (isset($cmdList['file'])) + { + $chat->dccSend("Installed Modules:"); + + foreach($cmdList['file'] AS $module) + { + $class = $module['class']; + + $chat->dccSend("-- " . $class->title . " " . $class->version . " by " . $class->author); + } + } + else + { + $chat->dccSend("There are no installed modules."); + } + } + + + public function dcc_close($chat, $args) + { + $dccList = $this->dccClass->getDccList(); + + foreach ($dccList AS $sockInt => $dcc) + { + if ($args['arg1'] == $dcc->id) + { + if ($dcc->type == CHAT && $dcc->isAdmin == true) + { + $chat->dccSend("Cannot close admin session!"); + return; + } + + $dcc->disconnect("Owner Requested Close"); + break; + } + } + } + + public function dcc_listul($chat, $args) + { + + $uldir = $this->ircClass->getClientConf('uploaddir'); + + if ($uldir != "") + { + $chat->dccSend("Directory Contents, " . $uldir); + + $dir = @scandir($uldir); + + $dirs = 0; + $files = 0; + + if (count($dir)) + { + + foreach ($dir AS $file) + { + if (is_dir($uldir . "/" . $file)) + { + $dirs++; + $chat->dccSend(BOLD . "dir: " . BOLD . $file); + } + else if (is_file($uldir . "/" . $file)) + { + $files++; + $chat->dccSend($file); + } + } + } + $chat->dccSend($dirs . " directories, " . $files . " files"); + + } + else + { + $chat->dccSend("Error, no uploaddir configuration directive set in config file."); + } + } + + + public function dcc_dccs($chat, $args) + { + $dccList = $this->dccClass->getDccList(); + $currDcc = false; + + foreach ($dccList AS $sockInt => $dcc) + { + if ($dcc->type == FILE) + { + $currDcc = true; + + if ($dcc->speed_lastavg == 0) + { + $percent = "0%"; + $eta = "n/a"; + $speed = 0.0; + } + else + { + $percent = round(($dcc->bytesTransfered/$dcc->filesize)*100, 1) . "%"; + $eta = $this->ircClass->timeFormat(round(($dcc->filesize - $dcc->bytesTransfered)/$dcc->speed_lastavg, 0), "%hh,%mm,%ss"); + $speed = irc::intToSizeString($dcc->speed_lastavg); + } + + if ($dcc->transferType == UPLOAD) + { + $chat->dccSend("Upload[{$dcc->id}]: " . $dcc->nick . " " . $dcc->filenameNoDir . " ".$percent." " . $speed . "/s eta:" . $eta); + } + else + { + $chat->dccSend("Download[{$dcc->id}]: " . $dcc->nick . " " . $dcc->filenameNoDir . " ".$percent." " . $speed . "/s eta:" . $eta); + } + } + } + + if ($currDcc == false) + { + $chat->dccSend("No dcc transfers in progress"); + } + } + + + public function dcc_send($chat, $args) + { + $filename = substr($args['query'], strlen($args['arg1']) + 1); + + $this->dccClass->addFile($args['arg1'], null, null, UPLOAD, $filename, null); + } + + public function dcc_function($chat, $args) + { + $cmdList = $this->parserClass->getCmdList(); + + if ($args['nargs'] == 0) + { + $chat->dccSend("-- All user-defined function status --"); + + $num = 0; + foreach ($cmdList['priv'] AS $cmd => $data) + { + $chat->dccSend($cmd . " => " . ($data['active'] == true ? "active" : "inactive") . ", Usage: " . $data['usage']); + $num++; + } + $chat->dccSend($num . " total functions."); + + } + else if ($args['nargs'] == 2) + { + if ($args['arg1'] == "activate") + { + if ($args['arg2'] == "all") + { + foreach($cmdList['priv'] AS $cmd => $data) + { + //$cmdList['priv'][$cmd]['active'] = true; + $this->parserClass->setCmdListValue('priv', $cmd, 'active', true); + } + $chat->dccSend("All functions activated."); + } + else + { + $cmdLower = irc::myStrToLower($args['arg2']); + + if (isset($cmdList['priv'][$cmdLower])) + { + $this->parserClass->setCmdListValue('priv', $cmdLower, 'active', true); + //$cmdList['priv'][$cmdLower]['active'] = true; + $chat->dccSend("Function " . $cmdLower . " activated."); + } + else + { + $chat->dccSend("Invalid function specified."); + } + } + } + else if ($args['arg1'] == "deactivate") + { + if ($args['arg2'] == "all") + { + foreach($cmdList['priv'] AS $cmd => $data) + { + if ($data['canDeactivate'] != false) + { + $this->parserClass->setCmdListValue('priv', $cmd, 'active', false); + //$cmdList['priv'][$cmd]['active'] = false; + } + } + $chat->dccSend("All functions deactivated."); + } + else + { + $cmdLower = irc::myStrToLower($args['arg2']); + + if (isset($cmdList['priv'][$cmdLower])) + { + if ($cmdList['priv'][$cmdLower]['canDeactivate'] != false) + { + $this->parserClass->setCmdListValue('priv', $cmdLower, 'active', false); + //$cmdList['priv'][$cmdLower]['active'] = false; + $chat->dccSend("Function " . $cmdLower . " deactivated."); + } + else + { + $chat->dccSend("Cannot modify read-only function " . $cmdLower . "."); + } + } + else + { + $chat->dccSend("Invalid function specified."); + } + } + } + else + { + $chat->dccSend("Invalid Syntax, use 'all', or specify a function name."); + } + + } + else + { + $chat->dccSend("Invalid Syntax, use 'activate' or 'deactivate'."); + } + } + + public function dcc_spawn($chat, $args) + { + $chat->dccSend("Spawning " . $args['query'] . "..."); + $result= bot::addBot($args['query']); + if($result === true) + $chat->dccSend($args['query'] . " successfully spawned"); + else + $chat->dccSend($args['query'] . " was not spawned"); + } + + private function getStatus() + { + $sqlCount = 0; + $bwStats = $this->ircClass->getStats(); + + if (is_object($this->db)) + { + $sqlCount = $this->db->numQueries(); + } + + $bwUp = irc::intToSizeString($bwStats['BYTESUP']); + $bwDown = irc::intToSizeString($bwStats['BYTESDOWN']); + + $fileBwUp = irc::intToSizeString($this->dccClass->getBytesUp()); + $fileBwDown = irc::intToSizeString($this->dccClass->getBytesDown()); + + $txtQueue = $this->ircClass->getTextQueueLength() + 1; + + $ircStat = $this->ircClass->getStatusString($this->ircClass->getStatusRaw()); + + $status = "Status: [" . $ircStat . "] ". $sqlCount . + " SQL, " . $txtQueue . " SrQ, (irc BW: " . $bwUp . " up, " . $bwDown . " down, file BW: " . $fileBwUp . " up, " . $fileBwDown . " down)"; + return $status; + } + + public function sendStatus() + { + $this->dccClass->dccInform($this->getStatus()); + return true; + } + +/* + NOTE: If you're reading this, you're really bored. Anyway, this is just something I'm keeping in here + in case I ever need it again. It was a debug mechanism for a hash class I wrote to replace the associative arrays + that the bot currently uses. + + dcc debug 1 "<chan/mem> [<channel>]" "Debug channel/member hash tables" true dcc_mod dcc_debug info + public function dcc_debug($chat, $args) + { + if ($args['arg1'] == "chan") + { + $chanData = $this->ircClass->getChannelData(); + + $chat->dccSend($chanData->getCount() . " total buckets"); + + foreach ($chanData->debug() AS $debug) + { + $chat->dccSend($debug); + } + } + else if ($args['arg1'] == "mem") + { + if ($args['nargs'] > 1) + { + $chanData = $this->ircClass->getChannelData(); + + $chanPtr = $chanData->find($args['arg2']); + + if ($chanPtr !== false) + { + $chat->dccSend($chanPtr->memberList->getCount() . " total buckets"); + + foreach ($chanPtr->memberList->debug() AS $debug) + { + $chat->dccSend($debug); + } + } + } + } + } +*/ +} +?> diff --git a/ircbot/modules/default/priv_mod.conf b/ircbot/modules/default/priv_mod.conf new file mode 100644 index 0000000..a62cfd8 --- /dev/null +++ b/ircbot/modules/default/priv_mod.conf @@ -0,0 +1,35 @@ +;+--------------------------------------------------------------------------- +;| PHP-IRC Internal PRIVMSG Function Configuration File +;| ======================================================== +;| by Manick +;| (c) 2001-2004 by http://phpbots.sf.net +;| Contact: manick@manekian.com +;| irc: #manekian@irc.rizon.net +;| ======================================== +;+--------------------------------------------------------------------------- +;| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +;+--------------------------------------------------------------------------- + +file priv_mod modules/default/priv_mod.php + +priv admin ~; text typed in the channel/pm (admin) + true ~; active command on statup? + false ~; inform admins in dcc chat iface if this command is used + false ~; can be deactivated with 'function' dcc command + 0 ~; times used.. just leave this 0 (its for stats). + priv_mod ~; class name (see file import section above) + priv_admin ; function name + +priv !ad true true true 0 priv_mod priv_ad \ No newline at end of file diff --git a/ircbot/modules/default/priv_mod.php b/ircbot/modules/default/priv_mod.php new file mode 100644 index 0000000..8aa76f5 --- /dev/null +++ b/ircbot/modules/default/priv_mod.php @@ -0,0 +1,315 @@ +<?php +/* ++--------------------------------------------------------------------------- +| PHP-IRC v2.2.0 +| ======================================================== +| by Manick +| (c) 2001-2005 by http://phpbots.sf.net/ +| Contact: manick@manekian.com +| irc: #manekian@irc.rizon.net +| ======================================== ++--------------------------------------------------------------------------- +| > priv_mod module +| > Module written by Manick +| > Module Version Number: 2.2.0 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +class priv_mod extends module { + + public $title = "Privmsg Utils"; + public $author = "Manick"; + public $version = "2.1.1"; + public $dontShow = true; + + private $ads; + + public function init() + { + $this->loadAds(); + } + + public function destroy() + { + $this->destroyAds(); + } + + private function loadAds() + { + $ads = new ini("./modules/default/ads.ini"); + + if ($ads->getError()) + { + return; + } + + $sections = $ads->getSections(); + + foreach ($sections AS $ad) + { + + $int = $ads->getIniVal($ad, "int"); + $channel = $ads->getIniVal($ad, "chan"); + $msg = $ads->getIniVal($ad, "msg"); + + $argArray = array('msg' => $msg, 'channel' => $channel); + + $this->timerClass->addTimer($ad, $this, "misc_adTimer", $argArray, $int); + + } + + $this->ads = $ads; + + } + + private function destroyAds() + { + $sections = $this->ads->getSections(); + + foreach ($sections AS $ad) + { + $this->timerClass->removeTimer($ad); + } + } + + // Misc Timer + + public function misc_adTimer($msg) + { + + $ad = DARK . "[" . BRIGHT . "Request" . DARK . "] - [" . BRIGHT . + $msg['msg'] . DARK . + "] - PHP-IRC v" . VERSION; + + $raw = "PRIVMSG " . $msg['channel'] . " :" . $ad; + + $this->ircClass->sendRaw($raw); + + return true; + } + + /* public Message/Channel Functions */ + + // This function is an example, it will display an add with timer + public function priv_ad($line, $args) + { + $channel = irc::myStrToLower($line['to']); + if ($channel == $this->ircClass->getNick()) + { + return; + } + if (!$this->ircClass->isMode($line['fromNick'], $channel, "o")) + { + return; + } + + if ($args['nargs'] == 0) + { + $timerString = ""; + $timers = $this->timerClass->getTimers(); + foreach ($timers AS $timer) + { + if (substr($timer->name, 0, 2) == "ad") + { + if ($timer->args['channel'] == $channel) + { + $timerString .= $timer->name . " "; + } + } + } + if ($timerString == "") + { + $this->ircClass->notice($line['fromNick'], "No ads currently for " . $channel . "."); + } + else + { + $this->ircClass->notice($line['fromNick'], "Current ads for " . $channel .":"); + $this->ircClass->notice($line['fromNick'], $timerString); + } + + $this->ircClass->notice($line['fromNick'], "Type !ad <interval(seconds)> <msg> to add an ad, or !ad <ad[id]> to view an ad."); + } + else if ($args['nargs'] >= 1) + { + if (substr($args['arg1'], 0, 2) == "ad" && strlen($args['arg1']) > 2) + { + $id = $args['arg1']; + + $timers = $this->timerClass->getTimers(); + foreach ($timers AS $timer) + { + if ($timer->name == $id) + { + break; + } + } + + if ($timer == null || $channel != $timer->args['channel']) + { + $this->ircClass->notice($line['fromNick'], "There is no ad by that id."); + } + else + { + if ($args['nargs'] >= 2) + { + if (irc::myStrToLower($args['arg2']) == "delete") + { + $this->ads->deleteSection($timer->name); + $this->ads->writeIni(); + + $this->timerClass->removeTimer($timer->name); + + $this->ircClass->notice($line['fromNick'], "Ad successfully deleted."); + } + else + { + $this->ircClass->notice($line['fromNick'], "Invalid option. Valid options: delete"); + } + } + else + { + $this->ircClass->notice($line['fromNick'], "Ad: " . $timer->name); + $this->ircClass->notice($line['fromNick'], $timer->args['msg']); + $this->ircClass->notice($line['fromNick'], "Use '!ad " . $timer->name . " delete' to delete this ad."); + } + } + + } + else + { + if ($args['nargs'] == 1) + { + $this->ircClass->notice($line['fromNick'], "You must specify a message!"); + } + else + { + $int = intval($args['arg1']); + + if ($int <= 5) + { + $this->ircClass->notice($line['fromNick'], "Invalid Interval. Interval must be greater than 5 seconds."); + } + else + { + $ad = substr($args['query'], strlen($args['arg1']) + 1); + + $argArray = array('msg' => $ad, 'channel' => $channel); + + //Find next id + $highest = 0; + $timers = $this->timerClass->getTimers(); + foreach ($timers AS $timer) + { + if (substr($timer->name, 0, 2) == "ad") + { + $id = intval(substr($timer->name, 2)); + if ($id > $highest) + { + $highest = $id; + } + } + } + $highest++; + + $this->timerClass->addTimer('ad' . $highest, $this, "misc_adTimer", $argArray, $int, true); + $this->ircClass->notice($line['fromNick'], "The ad was successfully added."); + + $this->ads->setIniVal('ad' . $highest, "int", $int); + $this->ads->setIniVal('ad' . $highest, "chan", $channel); + $this->ads->setIniVal('ad' . $highest, "msg", $ad); + $this->ads->writeIni(); + } + } + + } + + + } + } + + public function priv_admin($line, $args) + { + if ($args['nargs'] < 2) + { + return; + } + + if ($this->ircClass->getClientConf('dccadminpass') == "") + { + return; + } + + if (md5($args['arg1']) != $this->ircClass->getClientConf('dccadminpass')) + { + return; + } + + $query = substr($args['query'], strlen($args['arg1']) + 1); + $myArgs = parser::createLine($query); + + switch ($args['arg2']) + { + case "chatme": + $port = $this->dccClass->addChat($line['fromNick'], null, null, true, null); + if ($port === false) + { + $this->ircClass->notice($line['fromNick'], "Error starting chat, please try again.", 1); + } + break; + default: + $chat = new chat_wrapper($line['fromNick'], $this->ircClass); + + $cmdList = $this->parserClass->getCmdList(); + + $cmdLower = $myArgs['cmd']; + + if (isset($cmdList['dcc'][$cmdLower])) + { + + if ($myArgs['nargs'] < $cmdList['dcc'][$cmdLower]['numArgs']) + { + $chat->dccSend("Usage: " . $cmdLower . " " . $cmdList['dcc'][$cmdLower]['usage']); + break; + } + + $module = $cmdList['dcc'][$cmdLower]['module']; + $class = $cmdList['file'][$module]['class']; + $func = $cmdList['dcc'][$cmdLower]['function']; + + $class->$func($chat, $myArgs); + + $chat->dccSend("ADMIN " . irc::myStrToUpper($cmdLower) . " Requested"); + } + else + { + $chat->dccSend("Invalid Command: " . $myArgs['cmd']); + } + + break; + } + + } + + +} +?> diff --git a/ircbot/modules/lw/lw_mod.conf b/ircbot/modules/lw/lw_mod.conf new file mode 100644 index 0000000..44670c6 --- /dev/null +++ b/ircbot/modules/lw/lw_mod.conf @@ -0,0 +1,21 @@ +file lw_mod modules/lw/lw_mod.php + +priv !r true false false 0 lw_mod getRank +priv !rank true false false 0 lw_mod getRank + +priv !civ true false false 0 lw_mod getCivRank +priv !mil true false false 0 lw_mod getMilRank +priv !fin true false false 0 lw_mod getFinRank +priv !idr true false false 0 lw_mod getIDR + +priv !a true false false 0 lw_mod getAllianceRank +priv !arank true false false 0 lw_mod getAllianceRank + +priv !t true false false 0 lw_mod nextTick +priv !tick true false false 0 lw_mod nextTick + +priv !g true false false 0 lw_mod listGames +priv !games true false false 0 lw_mod listGames + +priv !h true false false 0 lw_mod help +priv !help true false false 0 lw_mod help diff --git a/ircbot/modules/lw/lw_mod.php b/ircbot/modules/lw/lw_mod.php new file mode 100644 index 0000000..e8340c2 --- /dev/null +++ b/ircbot/modules/lw/lw_mod.php @@ -0,0 +1,438 @@ +<?php + + +class lw_mod extends module { + + public function init() { + } + + public function destroy() { + } + + public function nextTick($line, $args) { + $gameID = $args['nargs'] == 1 ? $args['query'] : null; + + // Get the game + $game = is_null($gameID) ? config::getDefaultGame() : config::getGame($gameID); + if (is_null($game)) { + $msg = "Game ID '" . BOLD . $gameID . BOLD . "' not found"; + } else { + // Try finding public ticks + $minTick = null; + foreach ($game->ticks as $tick) { + if (! $tick->definition->public) { + continue; + } + $tick->computeNext(); + if (is_null($tick->next)) { + continue; + } + if (is_null($minTick) || $minTick->next > $tick->next) { + $minTick = $tick; + } + } + + if (is_null($minTick)) { + $msg = "[" . BOLD . $game->text . BOLD . "] No more ticks on this game"; + } else { + $msg = "[" . BOLD . $game->text . BOLD . "] Next tick: " . BOLD + . $minTick->definition->getName('en') . BOLD . " at " . BOLD + . gmstrftime("%H:%M:%S", $minTick->next) . BOLD; + if (gmstrftime("%Y-%m-%d", $minTick->next) != gmstrftime("%Y-%m-%d", time())) { + $msg .= " on " . BOLD . gmstrftime("%d/%m/%Y", $minTick->next) . BOLD; + } + } + } + + if ($line['to'] == $this->ircClass->getClientConf('nick')) { + $to = $line['fromNick']; + } else { + $to = $line['to']; + } + + $this->ircClass->sendRaw("PRIVMSG $to :$msg"); + } + + public function listGames($line, $args) { + static $statusText = array( + "READY" => "open for registration", + "RUNNING" => "running", + "VICTORY" => "victory conditions reached", + "ENDING" => "being terminated", + "FINISHED" => "terminated" + ); + + $to = $line['fromNick']; + + dbConnect(); + foreach (config::getGames() as $game) { + if ($game->name == 'main' || $game->status() == 'PRE') { + continue; + } + + $msg = "(" . BOLD . $game->name . BOLD . ") " . BOLD . $game->text . BOLD . " - Status: " + . BOLD . $statusText[$game->status()] . BOLD; + if ($game->status() == "READY") { + $msg .= " - Starting at " . BOLD . gmstrftime("%H:%M:%S", $game->firstTick()) + . BOLD . " on " . BOLD . gmstrftime("%d/%m/%Y", $game->firstTick()) . BOLD; + } elseif ($game->status() == "ENDING") { + $msg .= " - Ending at " . BOLD . gmstrftime("%H:%M:%S", $game->lastTick()) + . BOLD . " on " . BOLD . gmstrftime("%d/%m/%Y", $game->lastTick()) . BOLD; + } + $this->ircClass->sendRaw("PRIVMSG $to :$msg"); + } + } + + public function getRank($line, $args) { + list($player, $game) = $this->getParams($line, $args); + + dbConnect(); + $rv = $this->fetchGenRank($player, $game); + dbClose(); + + if ($line['to'] == $this->ircClass->getClientConf('nick')) { + $to = $line['fromNick']; + } else { + $to = $line['to']; + } + + if (is_array($rv)) { + $msg = "[" . BOLD . $rv[0] . BOLD . "] Player " . BOLD . $player . BOLD + . ": " . BOLD . "#{$rv[1]['ranking']}" . BOLD . " (" . BOLD + . number_format($rv[1]['points']) . BOLD . " points)"; + if (! is_null($rv[2])) { + $msg .= " - Overall round ranking: " . BOLD . "#{$rv[2]['ranking']}" + . BOLD . " (" . BOLD . number_format($rv[2]['points']) + . BOLD . " points)"; + } + } elseif ($rv == 1) { + $msg = "Game ID '" . BOLD . $game . BOLD . "' not found"; + } elseif ($rv == 2) { + $msg = "Player " . BOLD . $player . BOLD . " not found"; + } + + $this->ircClass->sendRaw("PRIVMSG $to :$msg"); + } + + public function getCivRank($line, $args) { + list($player, $game) = $this->getParams($line, $args); + + dbConnect(); + $rv = $this->fetchDetRank($player, $game, 'p_civ'); + dbClose(); + + if ($line['to'] == $this->ircClass->getClientConf('nick')) { + $to = $line['fromNick']; + } else { + $to = $line['to']; + } + + if (is_array($rv)) { + $msg = "[" . BOLD . $rv[0] . BOLD . "] Player " . BOLD . $player . BOLD + . " (civilisation): " . BOLD . "#{$rv[1]['ranking']}" . BOLD . " (" . BOLD + . number_format($rv[1]['points']) . BOLD . " points)"; + } elseif ($rv == 1) { + $msg = "Game ID '" . BOLD . $game . BOLD . "' not found"; + } elseif ($rv == 2) { + $msg = "Player " . BOLD . $player . BOLD . " not found"; + } + + $this->ircClass->sendRaw("PRIVMSG $to :$msg"); + } + + public function getFinRank($line, $args) { + list($player, $game) = $this->getParams($line, $args); + + dbConnect(); + $rv = $this->fetchDetRank($player, $game, 'p_financial'); + dbClose(); + + if ($line['to'] == $this->ircClass->getClientConf('nick')) { + $to = $line['fromNick']; + } else { + $to = $line['to']; + } + + if (is_array($rv)) { + $msg = "[" . BOLD . $rv[0] . BOLD . "] Player " . BOLD . $player . BOLD + . " (financial): " . BOLD . "#{$rv[1]['ranking']}" . BOLD . " (" . BOLD + . number_format($rv[1]['points']) . BOLD . " points)"; + } elseif ($rv == 1) { + $msg = "Game ID '" . BOLD . $game . BOLD . "' not found"; + } elseif ($rv == 2) { + $msg = "Player " . BOLD . $player . BOLD . " not found"; + } + + $this->ircClass->sendRaw("PRIVMSG $to :$msg"); + } + + public function getMilRank($line, $args) { + list($player, $game) = $this->getParams($line, $args); + + dbConnect(); + $rv = $this->fetchDetRank($player, $game, 'p_military'); + dbClose(); + + if ($line['to'] == $this->ircClass->getClientConf('nick')) { + $to = $line['fromNick']; + } else { + $to = $line['to']; + } + + if (is_array($rv)) { + $msg = "[" . BOLD . $rv[0] . BOLD . "] Player " . BOLD . $player . BOLD + . " (military): " . BOLD . "#{$rv[1]['ranking']}" . BOLD . " (" . BOLD + . number_format($rv[1]['points']) . BOLD . " points)"; + } elseif ($rv == 1) { + $msg = "Game ID '" . BOLD . $game . BOLD . "' not found"; + } elseif ($rv == 2) { + $msg = "Player " . BOLD . $player . BOLD . " not found"; + } + + $this->ircClass->sendRaw("PRIVMSG $to :$msg"); + } + + public function getIDR($line, $args) { + list($player, $game) = $this->getParams($line, $args); + + dbConnect(); + $rv = $this->fetchDetRank($player, $game, 'p_idr'); + dbClose(); + + if ($line['to'] == $this->ircClass->getClientConf('nick')) { + $to = $line['fromNick']; + } else { + $to = $line['to']; + } + + if (is_array($rv)) { + $msg = "[" . BOLD . $rv[0] . BOLD . "] Player " . BOLD . $player . BOLD + . " (inflicted damage): " . BOLD . "#{$rv[1]['ranking']}" . BOLD . " (" . BOLD + . number_format($rv[1]['points']) . BOLD . " points)"; + } elseif ($rv == 1) { + $msg = "Game ID '" . BOLD . $game . BOLD . "' not found"; + } elseif ($rv == 2) { + $msg = "Player " . BOLD . $player . BOLD . " not found"; + } + + $this->ircClass->sendRaw("PRIVMSG $to :$msg"); + } + + + private function getParams($line, $args) { + if ($args['nargs'] == 0) { + $player = $line['fromNick']; + $game = null; + } else { + $query = explode(' ', trim(preg_replace('/\s+/', ' ', $args['query']))); + if (preg_match('/^{[a-z0-9]+}$/', $query[0], $matches)) { + $game = preg_replace('/[{}]/', '', array_shift($query)); + } else { + $game = null; + } + $player = join(' ', $query); + if ($player == '') { + $player = $line['fromNick']; + } + } + return array($player, $game); + } + + + private function fetchGenRank($player, $gameID) { + // Get the game + $game = is_null($gameID) ? config::getDefaultGame() : config::getGame($gameID); + if (is_null($game)) { + return 1; + } + + // Access the rankings library + $rLib = $game->getLib('main/rankings'); + + // Get player ranking + $rType = $rLib->call('getType', 'p_general'); + $genRank = $rLib->call('get', $rType, $player); + if (is_null($genRank['points'])) { + return 2; + } + + // Try getting the overall round rankings + $rType = $rLib->call('getType', 'p_round'); + $orRank = $rLib->call('get', $rType, $player); + if (is_null($orRank['points'])) { + $orRank = null; + } + return array($game->text, $genRank, $orRank); + } + + + private function fetchDetRank($player, $gameID, $type) { + // Get the game + $game = is_null($gameID) ? config::getDefaultGame() : config::getGame($gameID); + if (is_null($game)) { + return 1; + } + + // Access the rankings library + $rLib = $game->getLib('main/rankings'); + + // Get player ranking + $rType = $rLib->call('getType', $type); + $rank = $rLib->call('get', $rType, $player); + if (is_null($rank['points'])) { + return 2; + } + return array($game->text, $rank); + } + + + public function getAllianceRank($line, $args) { + list($alliance, $game) = $this->getParams($line, $args); + + dbConnect(); + $rv = $this->fetchDetRank($alliance, $game, 'a_general'); + dbClose(); + + if ($line['to'] == $this->ircClass->getClientConf('nick')) { + $to = $line['fromNick']; + } else { + $to = $line['to']; + } + + if (is_array($rv)) { + $msg = "[" . BOLD . $rv[0] . BOLD . "] Alliance " . BOLD . $alliance . BOLD + . ": " . BOLD . "#{$rv[1]['ranking']}" . BOLD . " (" . BOLD + . number_format($rv[1]['points']) . BOLD . " points)"; + } elseif ($rv == 1) { + $msg = "Game ID '" . BOLD . $game . BOLD . "' not found"; + } elseif ($rv == 2) { + $msg = "Alliance " . BOLD . $alliance . BOLD . " not found"; + } + + $this->ircClass->sendRaw("PRIVMSG $to :$msg"); + } + + public function help($line, $args) { + $help = array( + "" => array( + "This bot allows you to get some information from the", + "Legacy Worlds game directly here, on IRC!", + " ", + "You can use any of the following commands:", + " ", + " " . BOLD . "Displaying rankings" . BOLD, + " " . BOLD . "rank" . BOLD . " - displays players' general and round rankings", + " " . BOLD . "civ" . BOLD . " - displays players' civilian rankings", + " " . BOLD . "mil" . BOLD . " - displays players' military rankings", + " " . BOLD . "fin" . BOLD . " - displays players' financial rankings", + " " . BOLD . "idr" . BOLD . " - displays players' inflicted damage rankings", + " " . BOLD . "arank" . BOLD . " - displays alliances' rankings", + " ", + " " . BOLD . "General information" . BOLD, + " " . BOLD . "games" . BOLD . " - lists available games", + " " . BOLD . "tick" . BOLD . " - displays the time and date of the next tick", + " " . BOLD . "help" . BOLD . " - help access", + " ", + "All commands must start with the '!' character. To get more", + "information on a specific command, type '!help <command>'" + ), + "arank" => array( + "Syntax: " . BOLD . "!arank [{game}] tag", + " " . BOLD . "!a [{game}] tag", + " ", + "This command gives information about an alliance's ranking.", + " ", + "It is possible to select the game by adding the game's ID between", + "brackets just before the player's name.", + ), + "rank" => array( + "Syntax: " . BOLD . "!rank [{game}] [player]", + " " . BOLD . "!r [{game}] [player]", + " ", + "This command gives information about a player's general ranking", + "as well as his round ranking if he has one.", + " ", + "Using the command without parameters will cause the bot to look", + "for your current nick, if it's the same as your in-game name.", + " ", + "It is possible to select the game by adding the game's ID between", + "brackets just before the player's name.", + " ", + "Examples: !r TSeeker", + " -> Displays TSeeker's rankings in the default game", + " !r {b5m2}", + " -> Displays your rankings in Match 2", + ), + "civ" => array( + "Syntax: " . BOLD . "!civ [{game}] [player]", + " ", + "This command gives information about a player's civilisation", + "ranking.", + " ", + "Using the command without parameters will cause the bot to look", + "for your current nick, if it's the same as your in-game name.", + " ", + "It is possible to select the game by adding the game's ID between", + "brackets just before the player's name.", + " ", + "See also: " . BOLD . "!help rank" . BOLD + ), + "mil" => array( + "Syntax: " . BOLD . "!mil [{game}] [player]", + " ", + "This command gives information about a player's military", + "ranking.", + " ", + "Using the command without parameters will cause the bot to look", + "for your current nick, if it's the same as your in-game name.", + " ", + "It is possible to select the game by adding the game's ID between", + "brackets just before the player's name.", + " ", + "See also: " . BOLD . "!help rank" . BOLD + ), + "fin" => array( + "Syntax: " . BOLD . "!fin [{game}] [player]", + " ", + "This command gives information about a player's financial", + "ranking.", + " ", + "Using the command without parameters will cause the bot to look", + "for your current nick, if it's the same as your in-game name.", + " ", + "It is possible to select the game by adding the game's ID between", + "brackets just before the player's name.", + " ", + "See also: " . BOLD . "!help rank" . BOLD + ), + "tick" => array( + "Syntax: " . BOLD . "!tick [game]", + " " . BOLD . "!t [game]", + " ", + "This command displays the next tick.", + " ", + "It is possible to select the game by adding the game's ID", + "after the command.", + ), + "games" => array( + "Syntax: " . BOLD . "!games", + " " . BOLD . "!g", + " ", + "This command displays the list of available games.", + ), + ); + + $topic = $args['query']; + if (! array_key_exists($topic, $help)) { + $topic = ""; + } + + $to = $line['fromNick']; + for ($i = 0; $i < count($help[$topic]); $i ++) { + $this->ircClass->privMsg($to, $help[$topic][$i]); + } + } +} + +?> + diff --git a/ircbot/modules/more_mods.txt b/ircbot/modules/more_mods.txt new file mode 100644 index 0000000..c176cd7 --- /dev/null +++ b/ircbot/modules/more_mods.txt @@ -0,0 +1 @@ +For More Modules, see the website: http://www.phpbots.org \ No newline at end of file diff --git a/ircbot/modules/seen/seen_mod.conf b/ircbot/modules/seen/seen_mod.conf new file mode 100644 index 0000000..40becc1 --- /dev/null +++ b/ircbot/modules/seen/seen_mod.conf @@ -0,0 +1,11 @@ + +file seen_mod modules/seen/seen_mod.php + +privmsg seen_mod seen +;notice seen_mod seen +join seen_mod seen +kick seen_mod seen +part seen_mod seen +quit seen_mod seen + +priv !seen true true true 0 seen_mod priv_seen \ No newline at end of file diff --git a/ircbot/modules/seen/seen_mod.php b/ircbot/modules/seen/seen_mod.php new file mode 100644 index 0000000..fb0c31b --- /dev/null +++ b/ircbot/modules/seen/seen_mod.php @@ -0,0 +1,234 @@ +<?php +/* ++--------------------------------------------------------------------------- +| PHP-IRC v2.2.0 +| ======================================================== +| by Manick +| (c) 2001-2005 by http://phpbots.sf.net/ +| Contact: manick@manekian.com +| irc: #manekian@irc.rizon.net +| ======================================== ++--------------------------------------------------------------------------- +| > Seen Mod +| > Module written by Manick +| > Module Version Number: 0.1 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +class seen_mod extends module { + + public $title = "Seen Mod"; + public $author = "Manick"; + public $version = "0.1"; + + private $seen; + + public function init() + { + $this->timerClass->addTimer("seen_mod_updateini", $this, "seen_update", "", 60*15, false); + $this->seen = new ini("modules/seen/seen.ini"); + } + + public function destroy() + { + $this->timerClass->removeTimer("seen_mod_updateini"); + } + + // Write to file + public function seen_update($args) + { + if ($this->seen->getError()) + { + return; + } + + $this->seen->writeIni(); + $this->dccClass->dccSend("Updated Seen Mod ini database file"); + + return true; + } + + // Update actions + public function seen($line, $args) + { + if ($this->seen->getError()) + { + if (DEBUG == 1) + { + echo "Seen error!\n"; + } + return; + } + + if (strtolower($line['cmd']) == "join") + { + $line['text'] = ""; + } + + if (strtolower($line['cmd']) == "kick") + { + $offsetA = strpos($line['params'], chr(32)); + + $act = "kick"; + $user = substr($line['params'], $offsetA + 1); + + $this->addLast($user, $act, $line['text']); + + } + else + { + $this->addLast($line['fromNick'], strtolower($line['cmd']), $line['text']); + } + + $this->getLast($line['fromNick']); + } + + private function getLast($user) + { + $user = irc::myStrToLower($user); + + if (!$this->seen->sectionExists("seen")) + { + return; + } + + $var = $this->seen->getIniVal("seen", $user); + + if ($var == false) + { + return false; + } + + $offsetA = strpos($var, "="); + $offsetB = strpos($var, "=", $offsetA + 1); + $offsetC = strpos($var, "=", $offsetB + 1); + + $info = array(); + + $info['user'] = substr($var, 0, $offsetA); + $info['time'] = substr($var, $offsetA + 1, $offsetB - $offsetA - 1); + $info['act'] = substr($var, $offsetB + 1, $offsetC - $offsetB - 1); + $info['txt'] = substr($var, $offsetC + 1); + + return $info; + } + + private function addLast($user, $act, $txt) + { + $Suser = irc::myStrToLower($user); + + $tAction = $user . "=" . time() . "=" . irc::myStrToLower($act) . "=" . $txt; + $this->seen->setIniVal("seen", $Suser, $tAction); + } + + // User interface + public function priv_seen($line, $args) + { + if ($this->seen->getError()) + { + $this->ircClass->notice($line['fromNick'], "There was an error while attempting to access the seen database."); + return; + } + + if ($line['to'] == $this->ircClass->getNick()) + { + return; + } + + if ($args['nargs'] <= 0) + { + $this->ircClass->notice($line['fromNick'], "Usage: !seen <nick>"); + return; + } + + $user = irc::myStrToLower($args['arg1']); + + if ($user == irc::myStrToLower($line['fromNick'])) + { + $this->ircClass->privMsg($line['to'], $line['fromNick'] . ", umm... O..kay..."); + $this->ircClass->action($line['to'], "points at " . $line['fromNick'] . "..."); + return; + } + + $data = $this->getLast($user); + + if ($data === false) + { + $this->ircClass->privMsg($line['to'], $line['fromNick'] . ", I have never seen " . $args['arg1'] . " before."); + return; + } + + $time = time() - $data['time']; + + if ($time > 3600*24) + { + $timeString = irc::timeFormat($time, "%d days %h hours %m min %s sec"); + } + else if ($time > 3600) + { + $timeString = irc::timeFormat($time, "%h hours %m min %s sec"); + } + else if ($time > 60) + { + $timeString = irc::timeFormat($time, "%m min %s sec"); + } + else + { + $timeString = irc::timeFormat($time, "%s sec"); + } + + $action = ""; + + switch ($data['act']) + { + case "privmsg": + $action = "saying in a channel"; + break; + case "notice": + $action = "noticing a channel"; + break; + case "join": + $action = "joining a channel"; + break; + case "kick": + $action = "being kicked from a channel"; + break; + case "part": + $action = "parting a channel"; + break; + case "quit": + $action = "quitting"; + break; + } + + if ($data['txt'] != "") + { + $action .= ": " . $data['txt']; + } + + $this->ircClass->privMsg($line['to'], $line['fromNick'] . ", I last saw " . $data['user'] . " " . $timeString . " ago " . $action . "."); + + } +} + +?> diff --git a/ircbot/modules/template.txt b/ircbot/modules/template.txt new file mode 100644 index 0000000..4afaeb7 --- /dev/null +++ b/ircbot/modules/template.txt @@ -0,0 +1,39 @@ +<?php +/* + +Change: + +'class_name' to the name of your module (specified in function.conf) + +Add: + +Your functions after "//Methods here:" + +Add: + +Timer declarations and other general startup stuff to be run when the bot starts up in the init() function + +*/ + +class class_name extends module { + + public $title = "<title>"; + public $author = "<author>"; + public $version = "<version>"; + + public function init() + { + // Add your timer declarations and whatever + // else here... + } + + public function destroy() + { + // Put code here to destroy the timers that you created in init() + // and whatever else cleanup code you want. + } + + //Methods here: +} + +?> \ No newline at end of file diff --git a/ircbot/parser.php b/ircbot/parser.php new file mode 100644 index 0000000..f8311c2 --- /dev/null +++ b/ircbot/parser.php @@ -0,0 +1,1143 @@ +<?php +/* ++--------------------------------------------------------------------------- +| PHP-IRC v2.2.1 Service Release +| ======================================================== +| by Manick +| (c) 2001-2005 by http://www.phpbots.org/ +| Contact: manick@manekian.com +| irc: #manekian@irc.rizon.net +| ======================================== ++--------------------------------------------------------------------------- +| > parser module +| > Module written by Manick +| > Module Version Number: 2.2.0 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +class parser { + + private $cmd; + private $args; + private $timers = array(); + + private $cmdList = array(); + private $cmdTypes = array(); + + private $fileModified = array(); + private $loadDefError = false; + + //Classes + private $ircClass; + private $dccClass; + private $timerClass; + private $socketClass; + private $db; + +/* Part of easy alias idea I've been working on..., look below for large bit of commented out code + for info... + + private $aliasArray = array( "notice" => "ircClass", + "privMsg" => "ircClass", + "action" => "ircClass", + "sendRaw" => "ircClass", + "getNick" => "ircClass", + "isOnline" => "ircClass", + "isMode" => "ircClass", + "isChanMode" => "ircClass", + "sendFile" => "dccClass", + "dccInform" => "dccClass", + "addTimer" => "timerClass", + "addListener" => "socketClass", + "removeTimer" => "timerClass", + ); +*/ + + public function __construct() { + $this->fileModified = array(); + } + + public function init() + { + + if ($this->ircClass->getClientConf('functionfile') != "") + { + $this->loadFuncs($this->ircClass->getClientConf('functionfile')); + } + + } + + public function setDccClass($class) + { + $this->dccClass = $class; + } + + public function setIrcClass($class) + { + $this->ircClass = $class; + } + + public function setTimerClass($class) + { + $this->timerClass = $class; + } + + public function setSocketClass($class) + { + $this->socketClass = $class; + } + + public function setDatabase($class) + { + $this->db = $class; + } + + public function getCmdList($type = "") + { + if ($type == "") + { + return $this->cmdList; + } + else + { + if (isset($this->cmdList[$type])) + { + return $this->cmdList[$type]; + } + else + { + return false; + } + } + } + + public function destroyModules() + { + if (is_array($this->cmdList) && count($this->cmdList) > 0) + { + if (isset($this->cmdList['file']) && is_array($this->cmdList['file'])) + { + foreach ($this->cmdList['file'] AS $index => $data) + { + if (is_object($data['class'])) + { + $data['class']->destroy(); + } + } + } + } + } + + private function readFile($file) + { + + $configRaw = file_get_contents($file); + + if ($configRaw === false) + { + if (DEBUG == 1) + { + echo "Could not find function file '$file' or error.\n"; + } + $this->dccClass->dccInform("Could not find function file '$file' or error."); + return false; + } + + return $configRaw; + + } + + public function include_recurse($file) + { + $configRaw = $this->readFile($file); + + if ($configRaw === false) + { + return false; + } + + $configRaw = preg_replace("/;(.*\n?)?/", "\n", $configRaw); + $configRaw = preg_replace("/~.*\n/", "", $configRaw); + + $configRaw = trim($configRaw); + + $lines = explode("\n", $configRaw); + + $num = 0; + $lineNo = 1; + $extra = 0; + $fullLine = ""; + foreach ($lines as $line) + { + $line = trim($line); + + if ($line == "") + { + $lineNo++; + continue; + } + + $line = trim($fullLine . " " . $line); + + $newLine = $this->parseFunc($file, $lineNo, $line); + + $lineNo += $extra + 1; + $extra = 0; + $fullLine = ""; + + if ($newLine === false) + { + continue; + } + + if ($newLine['type'] == "type") + { + $this->cmdTypes[$newLine['typeArray']['name']]['numArgs'] = count($newLine['typeArray']); + $this->cmdTypes[$newLine['typeArray']['name']]['args'] = $newLine['typeArray']; + } + else if ($newLine['type'] == "include") + { + if (isset($newLine['typeArray'][0])) + { + $num += $this->include_recurse($newLine['typeArray'][0]); + } + else + { + if (DEBUG == 1) + { + echo "Malformed include line on line " . $lineNo . " of function file: " . $file . "\n"; + } + $this->dccClass->dccInform("Malformed include line on line " . $lineNo . " of function file: " . $file); + } + } + else + { + if (!isset($newLine['typeArray']['name'])) + { + $name = irc::randomHash() . "_" . rand(1,1024); + } + else + { + $name = $newLine['typeArray']['name']; + } + + unset($newLine['typeArray']['name']); + + $this->cmdList[$newLine['type']][$name] = $newLine['typeArray']; + + if (isset($this->cmdList[$newLine['type']][$name]['usage'])) + { + if (isset($oldCmdList[$newLine['type']][$name]['usage'])) + { + $this->cmdList[$newLine['type']][$name]['usage'] = $oldCmdList[$newLine['type']][$name]['usage']; + } + } + + } + + $num++; + } + + return $num; + } + + + + + public function loadFuncs($file) + { + $error = false; + + clearstatcache(); + + $this->destroyModules(); + + $oldCmdList = $this->cmdList; + if (!is_array($oldCmdList)) + { + $oldCmdList = array(); + } + + unset($this->cmdList); + unset($this->cmdTypes); + $this->cmdList = array(); + $this->cmdTypes = array(); + + $this->dccClass->dccInform("Rehashing Function File, please wait..."); + if (DEBUG == 1) + { + echo "Rehashing Function File, please wait...\n"; + } + + //Read in main function file + $num = $this->include_recurse($file); + + //Book keeping + foreach ($this->cmdList AS $cmd => $data) + { + ksort($this->cmdList[$cmd]); + } + + if (isset($this->cmdList['file'])) + { + foreach ($this->cmdList['file'] AS $file => $data) + { + + $classDef = $this->loadClassDef($data['filename'], $file); + + if ($this->loadDefError == true) + { + $error = true; + } + $this->loadDefError = false; + + if ($classDef === false) + { + continue; + } + + //require_once($data['filename']); + + $this->cmdList['file'][$file]['class'] = new $classDef; + + $this->cmdList['file'][$file]['class']->__setClasses( $this->ircClass, + $this->dccClass, + $this->timerClass, + $this, + $this->socketClass, + $this->db + ); + $this->cmdList['file'][$file]['class']->init(); + } + } + + $this->dccClass->dccInform("Successfully loaded " . $num . " functions into memory."); + if (DEBUG == 1) + { + echo "Successfully loaded " . $num . " functions into memory.\n"; + } + + return $error; + + } + + private function loadClassError($filename, $msg) + { + $this->ircClass->log("Error loading $filename: $msg"); + $this->dccClass->dccInform("Error loading $filename: $msg"); + } + + /* Okay.. + * This method is really freaking cool. It loads a module file with fopen, then + * it finds the classname, and gives it a random name (changes it) so that you can + * include functions you've changed multiple times without having to restart the bot! + * Also, easier names than having to use $this->ircClass->blah() all the time, you can + * just use blah()... These are defined in an array somewhere... I'm not sure where yet... + * because I haven't written that yet!!!!!!!!!!!!111oneone. + */ + + private function loadClassDef($filename, $classname) + { + + $stat = stat($filename); + + if ($stat === false) + { + $this->loadClassError($filename, "Could not find function file"); + return false; + } + + $modified = $stat['mtime']; + + if (isset($this->fileModified[$filename])) + { + if ($modified == $this->fileModified[$filename]['modified']) + { + return $this->fileModified[$filename]['classdef']; + } + } + + $fileData = file_get_contents($filename); + + if ($fileData === false) + { + $this->loadClassError($filename, "Could not find function file"); + return false; + } + + //Okay, we have the module now.. now we need to find some stuff. + + if (!preg_match("/class[\s]+?".$classname."[\s]+?extends[\s]+?module[\s]+?{/", $fileData)) + { + $this->loadClassError($filename, "Could not find valid classdef in function file"); + return false; + } + + //Okay, our module is in the file... replace it with random hash. + + $newHash = irc::randomHash(); + + $newClassDef = $classname . "_" . $newHash; + + $fileData = preg_replace("/(class[\s]+?)".$classname."([\s]+?extends[\s]+?module[\s]+?{)/", "\\1" . $newClassDef . "\\2", $fileData); + + /* Interesting idea, but lets leave it out for now + foreach($this->aliasArray AS $func => $class) + { + $fileData = preg_replace("/([=\n\(\t\s]+?)".$func."[\s\t\n\r]*?\(/s", "\\1\$this->" . $class . "->" . $func . "(", $fileData); + } + */ + + $success = eval("?>" . $fileData . "<?php "); + + if ($success === false) + { + $this->loadClassError($filename, "Error in function file"); + + /* Attempt to fallback on a previous revision that worked! */ + if (isset($this->fileModified[$filename])) + { + $this->loadClassError($filename, "Using a cached version of the class definition"); + $this->loadDefError = true; + return $this->fileModified[$filename]['classdef']; + } + + return false; + } + + $this->fileModified[$filename]['modified'] = $modified; + $this->fileModified[$filename]['classdef'] = $newClassDef; + + return $newClassDef; + + } + + //Used to show array, as there seems to be some crazy bug in var_dump/print_r that + //shows EVERY variable in my program when I do var_dump($this->cmdList) or use print_r the same way + //This isn't used anywhere in the production copy of this script. (DEBUG ONLY!) + private function show_all($title, $array, $level) + { + echo $title . " = array(" . "\r\n"; + + foreach($array AS $index => $val) + { + for ($i = 0; $i < $level; $i++) + { + echo " "; + } + + if (is_array($val)) + { + $this->show_all($index, $val, $level + 1); + } + else if (is_object($val)) + { + echo "[$index] => [object]\r\n"; + } + else + { + echo "[$index] => [$val]\r\n"; + } + } + + for ($i = 0; $i < $level; $i++) + { + echo " "; + } + + echo ")\r\n"; + + } + + public function setCmdListValue($type, $cmd, $var, $value) + { + if (isset($this->cmdList[$type][$cmd][$var])) + { + $this->cmdList[$type][$cmd][$var] = $value; + return true; + } + return false; + } + + private function parseFunc($file, $lineNo, $line) + { + $strings = array(); + $line = str_replace("\t", " ", $line); + + $quotes = array("'", "\""); + + foreach($quotes AS $quote) + { + $currPos = 0; + $extraPos = 0; + while (($firstPos = strpos($line, $quote, $currPos)) !== false && substr($line, strpos($line, $quote, $currPos)-1, 1) != "\\") + { + + while (($secondPos = strpos($line, $quote, $firstPos + 1 + $extraPos)) !== false && substr($line, strpos($line, $quote, $firstPos + 1 + $extraPos)-1, 1) == "\\") + { + $extraPos = $secondPos; + } + + if ($secondPos === false) + { + if (DEBUG == 1) + { + echo "Syntax Error on line " . $lineNo . " of function file: " . $file . ". Expected '".$quote."', got end of line.\n"; + } + $this->dccClass->dccInform("Syntax Error on line " . $lineNo . " of function file: " . $file . ". Expected '".$quote."', got end of line."); + return false; + } + + $strings[$quote][] = substr($line, $firstPos + 1, $secondPos - $firstPos - 1); + $currPos = $secondPos + 1; + } + } + + foreach ($strings AS $string) + { + $line = str_replace($string, "", $line); + } + + $lineElements = explode(chr(32), $line); + + $type = ""; + $currElement = 0; + $typeArray = array(); + + foreach($lineElements AS $element) + { + if (trim($element) == "") + { + continue; + } + + $currElement++; + + if ($currElement == 1) + { + $element = irc::myStrToLower($element); + + if ($element == "type") + { + $type = "type"; + } + else if ($element == "include") + { + $type = "include"; + } + else + { + if (isset($this->cmdTypes[$element])) + { + $type = $element; + } + else + { + if (DEBUG == 1) + { + echo "Error: Undefined type, '" . $element . "' on line " . $lineNo . " of function file: " . $file . "\n"; + } + $this->dccClass->dccInform("Error: Undefined type, '" . $element . "' on line " . $lineNo . " of function file: " . $file . ""); + return false; + } + } + continue; + } + + if ($element == "\"\"") + { + $element = array_shift($strings["\""]); + } + else if ($element == "''") + { + $element = array_shift($strings["'"]); + } + + $element = str_replace("\\" . "'", "'", $element); + $element = str_replace("\\" . '"', '"', $element); + + if ($type == "type") + { + if ($currElement == 2) + { + $typeArray['name'] = $element; + } + else + { + $typeArray[] = $element; + } + } + else if ($type == "include") + { + $typeArray[] = $element; + } + else + { + if ($currElement > $this->cmdTypes[$type]['numArgs']) + { + if (DEBUG == 1) + { + echo "Error on line " . $lineNo . " of function file: " . $file . ", too many arguments\n"; + } + $this->dccClass->dccInform("Error on line " . $lineNo . " of function file: " . $file . ", too many arguments"); + return false; + } + + $element = (irc::myStrToLower($element) == "true" ? true : $element); + $element = (irc::myStrToLower($element) == "false" ? false : $element); + + $typeArray[$this->cmdTypes[$type]['args'][ $currElement - 2 ]] = $element; + } + + + } + + if ($type != "type" && $type != "include") + { + if ($currElement < $this->cmdTypes[$type]['numArgs']) + { + if (DEBUG == 1) + { + echo "Error on line " . $lineNo . " of function file: " . $file . ", not enough arguments\n"; + } + $this->dccClass->dccInform("Error on line " . $lineNo . " of function file: " . $file . ", not enough arguments"); + return false; + } + } + + return array('type' => $type, 'typeArray' => $typeArray); + + } + + + + public function parseDcc($chat, $handler) + { + + $chat->readQueue = str_replace("\r", "", $chat->readQueue); + +// if (!($offSet = strpos($chat->readQueue, "\n"))) +// { +// return false; +// } +// $rawLine = trim(substr($chat->readQueue, 0, $offSet)); +// $chat->readQueue = substr($chat->readQueue, $offSet + 1); + + $rawLine = $chat->readQueue; + $chat->readQueue = ""; + + $this->ircClass->log("DCC Chat(" . $chat->nick . "): " . $rawLine); + + $line = $this->createLine($rawLine); + + if ($line == false) + { + return; + } + + if ($handler != false) + { + if (is_object($handler)) + { + $handler->handle($chat, $line); + return; + } + } + + if ($chat->isAdmin == true && $chat->verified == false) + { + if (md5($line['cmd']) == $this->ircClass->getClientConf('dccadminpass')) + { + $this->dccClass->dccInform("DCC: " . $chat->nick . " has successfully logged in."); + $chat->verified = true; + $chat->dccSend("You have successfully logged in."); + + } + else + { + $chat->dccSend("Invalid password, bye bye."); + $this->dccClass->disconnect($chat); + } + return; + } + + $cmdLower = irc::myStrToLower($line['cmd']); + + if (isset($this->cmdList['dcc'][$cmdLower])) + { + if ($this->cmdList['dcc'][$cmdLower]['admin'] == 1 && !$chat->isAdmin) + { + $chat->dccSend("Request Denied. You must have admin access to use this function."); + return; + } + + if ($line['nargs'] < $this->cmdList['dcc'][$cmdLower]['numArgs']) + { + $chat->dccSend("Usage: " . $cmdLower . " " . $this->cmdList['dcc'][$cmdLower]['usage']); + return; + } + + $module = $this->cmdList['dcc'][$cmdLower]['module']; + $class = $this->cmdList['file'][$module]['class']; + $func = $this->cmdList['dcc'][$cmdLower]['function']; + + $class->$func($chat, $line); + + if ($chat->isAdmin) + { + $chat->dccSend("ADMIN " . irc::myStrToUpper($cmdLower) . " Requested"); + } + else + { + $chat->dccSend("CLIENT " . irc::myStrToUpper($cmdLower) . " Requested"); + } + } + else + { + $chat->dccSend("Invalid Command: " . $line['cmd']); + } + + } + + + public static function createLine($rawLine) + { + + $line = array(); + $rawLineArray = explode(chr(32), $rawLine); + $lineCount = count($rawLineArray); + + if ($lineCount < 1) + { + return false; + } + else if ($lineCount == 1) + { + $line['cmd'] = irc::myStrToLower($rawLine); + $line['nargs'] = 0; + $line['query'] = ""; + } + else + { + $line['nargs'] = 0; + $line['cmd'] = irc::myStrToLower(array_shift($rawLineArray)); + while (($arg = array_shift($rawLineArray)) !== NULL) // NULL fixed contributed by cortex, 05/01/05 + { + if (trim($arg) == "") + { + continue; + } + + $line['arg' . ++$line['nargs']] = $arg; + if ($line['nargs'] > MAX_ARGS-1) + { + break; + } + } + $line['query'] = trim(substr($rawLine, strlen($line['cmd']) + 1)); + } + + return $line; + + } + + public function parseLine($line) + { + if (DEBUG==1) + { + //print_r($line); + } + + if ($this->ircClass->checkIgnore($line['from'])) + { + return; + } + + switch($line['cmd']) + { + case "PRIVMSG": + $args = $this->createLine($line['text']); + $cmdLower = irc::myStrToLower($args['cmd']); + if (isset($this->cmdList['priv'][$cmdLower])) + { + if ($this->cmdList['priv'][$cmdLower]['active'] == true) + { + $theCase = $this->ircClass->floodCheck($line); + + switch ($theCase) + { + case STATUS_NOT_BANNED: + if ($this->cmdList['priv'][$cmdLower]['inform'] == true) + { + $this->dccClass->dccInform("Sending " . irc::myStrToUpper($line['text']) . " to " . $line['fromNick']); + } + $this->cmdList['priv'][$cmdLower]['usage']++; + $func = $this->cmdList['priv'][$cmdLower]['function']; + $module = $this->cmdList['priv'][$cmdLower]['module']; + $class = $this->cmdList['file'][$module]['class']; + + if ($this->ircClass->getTextQueueLength() > 5) + { + $this->ircClass->notice($line['fromNick'], "Request Queued. Please wait " . $this->ircClass->getTextQueueLength() . " seconds for your data.", 0); + } + + $class->$func($line, $args); + break; + case STATUS_JUST_BANNED: + $this->ircClass->notice($line['fromNick'], "Flood Detected. All of your queues have been discarded and you have been banned from using this bot for ". $this->ircClass->getClientConf('floodtime') . " seconds."); + $this->dccClass->dccInform("BAN: (*!". irc::myStrToUpper($line['fromHost']) . "): " . $line['fromNick'] . " is on ignore for " . $this->ircClass->getClientConf('floodtime') . " seconds."); + break; + case STATUS_ALREADY_BANNED: + break; + } + } + else + { + $this->dccClass->dccInform("FUNCTION: " . $line['fromNick'] . " attempted to use deactivated command '" . $cmdLower . "'"); + } + } + else + { + if ($line['to'] == $this->ircClass->getNick()) + { + if (strpos($line['text'], chr(1)) !== false) + { + $this->ircClass->floodCheck($line); + $this->parseCtcp($line); + } + else + { + $this->dccClass->dccInform("PRIVMSG: <" . $line['fromNick'] . "> " . $line['text']); + } + } + else + { + if (strpos($line['text'], chr(1)) !== false) + { + $this->parseCtcp($line, "CHAN: " . $line['to']); + } + else + { + $chanData = $this->ircClass->getChannelData($line['to']); + + if ($chanData == NULL) + { + $this->dccClass->dccInform("CHAN PRIVMSG [".$line['to']."]: <" . $line['fromNick'] . "> " . $line['text']); + } + } + } + } + break; + + case "MODE": + break; + + case "NOTICE": + $chan = $line['to'] != $this->ircClass->getNick() ? ":" . $line['to'] : ""; + $this->dccClass->dccInform("NOTICE: <" . ($line['fromNick'] == "" ? $line['from'] : $line['fromNick']) . $chan . "> " . $line['text']); + break; + + case "JOIN": + if ($line['fromNick'] == $this->ircClass->getNick()) + { + $this->dccClass->dccInform("Joined: " . irc::myStrToUpper($line['text'])); + } + break; + + case "PART": + if ($line['fromNick'] == $this->ircClass->getNick()) + { + $this->dccClass->dccInform("Parted: " . irc::myStrToUpper($line['to'])); + } + break; + + case "KICK": + if ($line['params'] == $this->ircClass->getNick()) + { + $this->dccClass->dccInform("Kicked: " . $line['fromNick'] . " kicked you from " . $line['to']); + } + break; + + case "ERROR": + $this->dccClass->dccInform("Server Error: " . $line['text']); + break; + + case "366": + $params = explode(chr(32), $line['params']); + $channel = $params[0]; + $this->dccClass->dccInform("Finished receiving NAMES list for " . $channel); + break; + + case "005": + if ($this->ircClass->getServerConf("NETWORK") != "") + { + //Only show this once... + if (strpos($line['params'],"NETWORK") !== false) + { + $this->dccClass->dccInform("Parsing IRC-Server specific configuration..."); + $this->dccClass->dccInform("Network has been identified as " . $this->ircClass->getServerConf("NETWORK") . + "(" . $line['from'] . ")"); + } + } + break; + + case "315": + $params = explode(chr(32), $line['params']); + $this->dccClass->dccInform("Finished receiving WHO list for: " . $params[0]); + break; + + case "368": + $params = explode(chr(32), $line['params']); + $this->dccClass->dccInform("Finished receiving ban list for: " . $params[0]); + break; + + case "433": + $this->dccClass->dccInform("Nick collision! Unable to change your nick. Nickname already in use!"); + break; + + default: + break; + + } + + // Lets alias 004 to CONNECT, for the n00bs + + if ($line['cmd'] == "004") + { + $line['cmd'] = "connect"; + } + if ($line['cmd'] == "error") + { + $line['cmd'] = "disconnect"; + } + + // Action type handler + if (isset($this->cmdList['action']) && strtolower($line['cmd']) == "privmsg") + { + if (substr($line['text'], 0, 8) == chr(1) . "ACTION ") + { + $newLine = $line; + $newLine['text'] = substr($line['text'], 8, strlen($line['text']) - 9); + + $sArgs = $this->createLine($newLine['text']); + + foreach($this->cmdList['action'] AS $item) + { + $func = $item['function']; + $class = $this->cmdList['file'][$item['module']]['class']; + $class->$func($newLine, $sArgs); + } + } + } + + // Raw type handler + if (isset($this->cmdList['raw'])) + { + if (!isset($args)) + { + $args = $this->createLine($line['text']); + } + + foreach($this->cmdList['raw'] AS $item) + { + $func = $item['function']; + $class = $this->cmdList['file'][$item['module']]['class']; + $class->$func($line, $args); + } + } + + + // Here we will call any type + + if (isset($this->cmdList[irc::myStrToLower($line['cmd'])])) + { + if (!isset($args)) + { + $args = $this->createLine($line['text']); + } + + foreach($this->cmdList[irc::myStrToLower($line['cmd'])] AS $item) + { + $func = $item['function']; + $class = $this->cmdList['file'][$item['module']]['class']; + $class->$func($line, $args); + } + } + + if (isset($args)) + { + unset($args); + } + + } + + + /* Misc Functions */ + + private function parseCtcp($line, $msgs = "PRIVMSG") + { + $cmd = str_replace(chr(1), "", $line['text']) . " "; + $query = trim(substr($cmd, strpos($cmd, chr(32)) + 1)); + $cmd = substr(irc::myStrToLower($cmd), 0, strpos($cmd, chr(32))); + + $msg = ""; + + switch($cmd) + { + case "version": + $this->dccClass->dccInform("CTCP VERSION: " . $line['fromNick'] . " versioned us."); + break; + + case "time": + $this->dccClass->dccInform("CTCP TIME: " . $line['fromNick'] . " requested the time."); + break; + + case "uptime": + $this->dccClass->dccInform("CTCP UPTIME: " . $line['fromNick'] . " requested our uptime."); + break; + + case "ping": + $this->dccClass->dccInform("CTCP PING: " . $line['fromNick'] . " pinged us."); + break; + + case "dcc": + $vars = explode(chr(32), $query); + $this->dccParse($line, $vars, $query); + break; + + } + + if ($msg != "") + { + $this->notice($this->lVars['fromNick'], chr(1) . $msg . chr(1)); + } + + if (isset($this->cmdList['ctcp'][$cmd])) + { + $func = $this->cmdList['ctcp'][$cmd]['function']; + $class = $this->cmdList['file'][$this->cmdList['ctcp'][$cmd]['module']]['class']; + $args = $this->createLine($cmd . " " . $query); + $class->$func($line, $args); + } + + } + + function dccParse($line, $vars, $txt) + { + $cVars = count($vars); + + if ($cVars < 1) + { + return; + } + + $cmd = irc::myStrToUpper($vars[0]); + + switch ($cmd) + { + case "CHAT": + if ($cVars == 4) + { + $iplong = long2ip( (double) $vars[2]); + $port = $vars[3]; + $this->dccClass->addChat($line['fromNick'], $iplong, (int) $port, false, null); + } + break; + case "SEND": + if ($this->ircClass->getClientConf('upload') != 'yes') + { + $this->ircClass->notice($line['fromNick'], "DCC: I do not accept dcc transfers at this time.", 0); + break; + } + if ($cVars >= 5) + { + //Some bastard sent a file with spaces. Shit. Ass. + if (strpos($query, chr(34)) !== false) + { + $first = strpos($query, chr(34)); + $second = strpos($query, chr(34), $first + 1); + $filename = substr($query, $first + 1, $second - $first - 1); + $query = str_replace("\"".$filename."\"", "file.ext", $query); + $vars = explode(chr(32), $query); + } + else + { + $filename = $vars[1]; + } + + $iplong = long2ip( (double) $vars[2]); + $port = $vars[3]; + $filesize = $vars[4]; + + $this->dccClass->addFile($line['fromNick'], $iplong, (int) $port, DOWNLOAD, $filename, $filesize); + } + break; + case "ACCEPT": + if ($cVars == 4) + { + $port = $vars[2]; + $bytes = $vars[3]; + $this->dccClass->dccAccept($port, $bytes); + } + break; + case "RESUME": + if ($cVars == 4) + { + $port = $vars[2]; + $bytes = $vars[3]; + $this->dccClass->dccResume($port, $bytes); + } + break; + } + + } + +} + +/* Used to access dcc admin commands via private message */ +class chat_wrapper { + + public $nick; + private $ircClass; + public $isAdmin; + + public function __construct($nick, $ircClass) + { + $this->nick = $nick; + $this->ircClass = $ircClass; + $this->isAdmin = 1; + } + + public function dccSend($data, $to = null) + { + $this->ircClass->privMsg($this->nick, "--> " . $data); + } + + public function disconnect($msg = "") + { + $this->ircClass->privMsg($this->nick, "Right........"); + } + +} + +?> diff --git a/ircbot/queue.php b/ircbot/queue.php new file mode 100644 index 0000000..e41101c --- /dev/null +++ b/ircbot/queue.php @@ -0,0 +1,339 @@ +<?php +/* ++--------------------------------------------------------------------------- +| PHP-IRC v2.2.1 Service Release +| ======================================================== +| by Manick +| (c) 2001-2005 by http://www.phpbots.org/ +| Contact: manick@manekian.com +| irc: #manekian@irc.rizon.net +| ======================================== ++--------------------------------------------------------------------------- +| > queue module +| > Module written by Manick +| > Module Version Number: 2.2.0 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +/* This module is my response to a big problem. PHP-IRC, on idling, would use + * 2.0% or more of the CPU on a 500mhz machine. This annoyed me, and I decided + * to do something about it. So, I changed the way that PHP-IRC works, from a + * "round-robin" approach to an "interrupt" type approach. Whenever something + * happens, say, new data is read from a socket, a process Queue is added with + * a pointer to the function that reads input for that socket; or for + * another example, if a file transfer is in effect, and new data is read from + * that socket, but we also have a dcc chat going, we will only handle data for + * the file transfer, instead of wasting CPU cycles on the dcc chat, like is + * currently done in <=2.1.1. I've learned a bit more about timers and socket + * timeouts since then. Also, we have to handle timers in a different way now. + * There will still be a "timers" class, for re-occuring processes, but the + * next timer is inserted into this process queue, and the callback for the timer + * will be a pointer to the timer class which handles the timer. Then, the timer + * class will do the appropriate stuff for the timer, and then add the next timer + * into the process Queue. --Manick + * + * P.S.; I never knew this would change so much code... I figured adding in select() + * timeouts would be a piece of cake... until I realized that my whole framework + * was incompatible with the idea. What a pain in the ass -_-. + */ + + /* Module Written 11/30/04 by Manick */ + +class processQueue { + + private $numQueued; + private $queuedItems; + private $currProc; + + function __construct() + { + $this->queuedItemsArray = array(); + $this->queuedItems = NULL; + $this->currProc = NULL; + $this->numQueued = 0; + } + + public function getNumQueued() + { + return $this->numQueued; + } + + public static function getMicroTime() + { + return microtime(true); + } + + /* Only allow removal of entire irc class (as in we shut down the bot.. otherwise, we don't + want to deal with shutting down specific queues during the queueing process. Have the callbacks + handle that themselves. We only have to worry when the callbacks won't exist anymore, as when an + irc bot is shut down, and the ircclass is discarded + */ + public function removeOwner($class) + { + $next = NULL; + + for ($queue = $this->queuedItems; $queue != NULL; ) + { + $next = $queue->next; + + if ($queue->owner === $class) + { + $this->removeQueue($queue); + } + + $queue = $next; + + } + } + + /* Remove reference to queued item, let PHP5 do the rest */ + private function removeQueue($item) + { + if ($item->prev == NULL) + { + $this->queuedItems = $item->next; + + if ($item->next != NULL) + { + $item->next->prev = NULL; + } + } + else + { + $item->prev->next = $item->next; + + if ($item->next != NULL) + { + $item->next->prev = $item->prev; + } + } + + $item->removed = true; + + unset($item->args); + unset($item->owner); + unset($item->callBack_class); + unset($item->next); + unset($item->prev); + + unset($item); + + $this->numQueued--; + } + + /* Add an item to the process queue */ + public function addQueue($owner, $class, $function, $args, $nextRunTime) + { +// echo "Queue Added: $function with $nextRunTime\n"; + + if ($function == "" || $function == NULL) + { + return false; + } + + if (!is_object($class)) + { + $class = null; + } + + $nextRunTime = floatval($nextRunTime); + + $queue = new queueItem; + + $queue->args = $args; + $queue->owner = $owner; + $queue->removed = false; + $queue->callBack_class = $class; + $queue->callBack_function = $function; + $queue->nextRunTime = self::getMicroTime() + $nextRunTime; + + //Now insert as sorted into queue + + $prev = NULL; + + for ($item = $this->queuedItems; $item != NULL; $item = $item->next) + { + if ($queue->nextRunTime < $item->nextRunTime) + { + break; + } + + $prev = $item; + } + + if ($item == NULL) + { + if ($prev == NULL) + { + $queue->next = NULL; + $queue->prev = NULL; + $this->queuedItems = $queue; + } + else + { + $queue->next = NULL; + $queue->prev = $prev; + $prev->next = $queue; + } + } + else + { + if ($item->prev == NULL) + { + $queue->next = $this->queuedItems; + $queue->prev = NULL; + + $item->prev = $queue; + $this->queuedItems = $queue; + } + else + { + $queue->next = $item; + $queue->prev = $item->prev; + + $item->prev = $queue; + $queue->prev->next = $queue; + } + } + + //Okay, we're inserted, return true; + + $this->numQueued++; + + return true; + } + + public function displayQueue() + { + //Used for debug + echo "Current Time: " . self::getMicroTime() . "\n"; + + echo "\n\n"; + for ($i = $this->queuedItems; $i != NULL; $i = $i->next) + { + echo $i->callBack_function . "-" . $i->nextRunTime . "\n"; + echo "---" . "Prev: " . $i->prev . " Next: " . $i->next . " Me: " . $i . "\n"; + } + echo "\n\n"; + } + + /* Handle the process queue, return the time until the next item */ + public function handle() + { + // Handle all items with $queue->nextRunTime < getMicroTime(), then return with time until next item must + // be run + + // Populate a runQueue with all current items that need to be run. We need to do this because some of these + // callback functions might add another process to the queue, and if the runtime is < 0, we would run that item + // instead of timing out before we do. If we have something like a file transfer, this could be a bad thing. + $runQueue = array(); + + $time = self::getMicroTime(); + + for ($item = $this->queuedItems; $item != NULL; $item = $item->next) + { + if ($item->nextRunTime <= $time) + { + $runQueue[] = $item; + } + else + { + break; + } + } + + //Okay, now run each item. + + foreach ($runQueue AS $index => $item) + { + if (!is_object($item) || $item->removed === true) + { + if (is_object($item)) + { + unset($item); + } + continue; + } + + self::handleQueueItem($item); + } + + unset($runQueue); + + //Return time until next item needs to be run, or true if there are no queued items + //Hmm, true returned here, means we'll just sleep for like an hour or something until data + //is recieved from the sockets, because we have no active timers + if ($this->queuedItems == null) + { + return true; + } + + //Get new time + $time = self::getMicroTime(); + + $timeTillNext = $this->queuedItems->nextRunTime - $time; + + if ($timeTillNext < 0) + { + $timeTillNext = 0; + } + + //When zero is returned, we'll always sleep at least 50000 usec in the socket class anyway + return $timeTillNext; + + } + + /* Specific function to deal with queued items */ + private function handleQueueItem($item) + { + $this->currTimer = $item; + + $class = $item->callBack_class; + $func = $item->callBack_function; + + //Call the callback function! Now the callback function will check all possible triggers, + //such as socket input, etc, and add new queued items if it needs more processing/other processing + + if ($class == null) + { + $status = $func($item->args); + } + else + { + $status = $class->$func($item->args); + } + + //If true is returned from the function, then keep the bitch in the queue. This is useful when a + //function has not completed processing (i.e., irc->connection waiting on socket class to return + //the fact that its connected. + + if ($item->removed !== true && $status !== true) + { + self::removeQueue($item); + } + + } + +} + +?> diff --git a/ircbot/readme.txt b/ircbot/readme.txt new file mode 100644 index 0000000..69de0ae --- /dev/null +++ b/ircbot/readme.txt @@ -0,0 +1,1364 @@ ++--------------------------------------------------------------------------- +| PHP-IRC v2.2.1 Service Release +| ======================================================== +| by Manick +| (c) 2001-2006 by http://www.phpbots.org/ +| Contact: manick@manekian.com +| irc: #manekian@irc.rizon.net +| ======================================== +| Special Contributions were made by: +| cortex ++--------------------------------------------------------------------------- +| > Documentation ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- + +NOTE! Please enable word-wrap to view this file. +NOTE! PLEASE READ SECTION '3. Installation - A few things to consider' BEFORE USING. + +Please visit our new mods/forums website here: http://www.phpbots.org/ + +Table of Contents +----------------- + +1. Introduction and Release Notes +1-a. What's new in 2.2.1! +1-b. Features +2. Included +3. Installation - A few things to consider +3-a. Quick Install +3-b. Installing PHP 5 +3-c. Using alternate PHP.INI (PHP Configuration) file +3-d. Installing PHP-IRC +4. Configuration +5. Running +6. Remote Administration (via DCC and Private Message) +7. File Transfer +7-a. File Transfer behind a firewall or NAT +8. Modules and User-defined functions +8-a. Submitting modules for others to download +9. Querying outside servers (alternative to fopen, fsockopen, etc) READ THIS if you need this functionality! +9-a. Basic Queries, using the addQuery() function +9-b. Intro to Connection Class, Advanced Queries +10. Custom DCC Chat Sessions +10-a. Custom DCC Chat Example: Simple File Server (SFS) +11. Database support +11-a. Mysql Database +11-b. Postgre Database +11-c. Serverless, mIRC compatible ini-file based Database +12. Timers +13. Multiple servers under one process +14. Provided Sample Modules +14-a. IMDB Parser +14-b. Quotes mod with mysql/ini file system +14-c. Simple http server +14-d. Bash.org Parser +14-e. News System/Rules Script +14-f. Seen System (beta!) +14-g. Request Ads System +15. Function Reference +16. Special Thanks + +================================= +1. Introduction and Release Notes +================================= +Please visit our new website: http://www.phpbots.org/ for support and user-submitted modules. + +PHP-IRC is a totally php based irc bot meant to automate some applications related to irc. I've personally programmed several modules which I currently use on irc.rizon.net. This version is my attempt at bringing such a device to the public. I hope you find it as useful as I do. I have begun work on a module submission site which will be hosted at http://www.phpbots.org once finished. I have already recieved modules from serveral enthusiastic people wishing to contribute in some way to the project. Thank you for your time and effort. + +It has always been my opinion that IRC scripting has long needed a simple programming interface that people with already existent skills could utilize. There are two main reasons that PHP was selected as the primary language. First off, I wanted to provide novice programmers a way to code their own functions and algorithms into a powerful platform without having to worry about such things as memory management and compilers. Secondly, I wanted to do something that no one has done before--create a fully featured bot in php. I don't know if I will ever complete the second objective, but I will keep on developing, as it will always be the main goal of this project. + +If I have left out information in this file, or there is something I said that confused you, please email me your concerns at manick@manekian.com, and I will try to clarify where you were confused. This will also help me determine what I need to fix in this file for the next version. + + +========================= +1-a. What's new in 2.2.1! +========================= +As this is a service update and not a full release, the readme file included below has not been altered from the 2.2.0 version. Version 2.2.1 brings several bug fixes (which are viewable in changes.txt). + +Development on PHP-IRC is not halted, simply stalled, as I am approaching the culmination of my college career. Thank you for your continued interest in PHP-IRC. + +We have a new website! Please view it here: http://www.phpbots.org/ + +============= +1-b. Features +============= +For a full list of current major features, please visit: http://www.phpbots.org/ + +=========== +2. Included +=========== + +Core Files: + ============== + Config Files + -------------- + bot.conf + function.conf + typedefs.conf (added 2.2.0) + modules/default/priv_mod.conf (added 2.2.0) + modules/default/dcc_mod.conf (added 2.2.0) + ============== + Readme Files + -------------- + readme.txt + modules/template.txt (added 2.1) + gpl.txt (added 2.1) + command_reference.txt (added 2.1.1) + changes.txt (added 2.1.1) + upgrade-2.1.1-2.2.0.txt (added 2.2.0) + install.txt (added 2.2.0) + ============== + Source code + -------------- + bot.php + chat.php (added 2.1) + connection.php (added 2.2.0) + dcc.php + defines.php (added 2.0.1/2) + error.php + file.php (added 2.1) + irc.php + module.php (added 2.2.0) + parser.php + queue.php (added 2.2.0) + remote.php (added 2.2.0) + socket.php + timers.php + modules/default/dcc_mod.php (added 2.1) + modules/default/priv_mod.php (added 2.1) + +========================================== +3. Installation - A few things to consider +========================================== + +I've been getting a lot of emails and private messages on rizon about how to run this bot via a web browser. However, most webhosts do not compile php with socket support enabled, and for that matter, most web hosts don't have php5 as of yet. Even so, in order to start the bot, you have to specify a config file on the command line. Thus, running this bot from a web browser without modification is impossible. I will not make this possible, so in other words, please stop asking. + +Another thing that might deter people from using this script is the enourmous memory requirement by php to keep +a script loaded in memory. PHP5 loads all of the program and everything it could possibly use into memory. This can take up quite a bit of space! I've had a bot use 8 megs of ram on the low end, and up to 15 megs on the high end. If you don't mind dropping 15 megs of ram for this bot, then continue on. Otherwise, don't whine to me that its a resource hog. I believe the advantages far outweigh the disadvantages--how else could I have gotten this far? + +In order to run this bot, you must have shell access to the box you will be running it on. For instructions on how to install php5 for use with this bot, please see the next section, "Installing PHP 5". + +A note about errno constants: +----------------------------- + +In unix/windows/etc, when trying to write or read from a socket sometimes (a socket is the connection between your computer and another computer), errors occur. In order to be able to determine what these errors are, all systems have something called 'errno' constants. Unfortunatly, PHP5 does not know these constants, so we have to set them manually in order for this program to run. In the defines.php file, I have set common values for windows, freebsd, linux, and unix. If you are using one of these systems, an autodetection script should take care of detecting which system you are running the bot on, and set the appropriate values. If this does not work, and you cannot connect to a server, you may need to set your OS type to either "windows", "freebsd", "linux", or "unix". If you do not experience problems with this, then skip down to "Installing PHP 5". Otherwise, keep reading. + +I have run this successfully on Windows XP, Mandrake 9.0 Linux, and Debian. If this program does not work with any of the os settings for OS in defines.php, you need to find the correct values for EAGAIN, EINPROGRESS, EALREADY, and EISCONN(these are those error constants that I was talking about earlier) for your system. + +They are usually in /usr/include. You can find them by using a command sequence such as this: + +------------------------------------------------------------------------------- +grep -r 'EAGAIN' /usr/include/* +grep -r 'EINPROGRESS' /usr/include/* +grep -r 'EALREADY' /usr/include/* +grep -r 'EISCONN' /usr/include/* +------------------------------------------------------------------------------- + +After doing this, you'll get a line that looks like this: + +------------------------------------------------------------------------------- +/usr/include/asm/errno.h:#define EISCONN 106 /* Transport endpoint is already connected */ +------------------------------------------------------------------------------- + +The constant we are looking for is 106. Now, after finding all those numbers, go into defines.php. Set your OS type to "unknown"; then, scroll down a bit till you find this code: + +------------------------------------------------------------------------------- +if ($OS == 'unknown') +{ + define('EAGAIN', 0); /* Try again */ + define('EISCONN', 0); /* Transport endpoint is already connected */ + define('EALREADY', 0); /* Operation already in progress */ + define('EINPROGRESS', 0); /* Operation now in progress */ +} +------------------------------------------------------------------------------- + +Now, change the numbers, etc, with the corresponding values you got when you found the constants above. Do this with all four constants, and then move on to the next section. + +================== +3-a. Quick Install +================== + +Running php-irc consists of 6 general steps (EXPERTS ONLY!): + +1) Download and unpack, compile the php 5 5.0.2+ package with sockets and pcntl. +2) Edit the php.ini file following the steps in section 3-b (bottom half of section under "First, locate this line in the file:") +3) Unzip php irc +4) If the bot does not start up and connect to the server, you may have to set the OS parameter in the defines.php file. Read the previous section. +5) Change server/port information in bot.conf. You can run php-irc with the -c switch to get an md5 password hash to set dccadminpass with. +6) run php-irc with: /path/to/php/php.exe bot.php bot.conf from the php-irc directory, or edit the shebang line and change it to reflect your php location, (also use -c switch to specify php.ini location), and then chmod the bot.php file 755, and run with ./bot.php bot.conf + +Of course, I recommend that you read the rest of this file, as it provides some very important information. + +===================== +3-b. Installing PHP 5 +===================== + +Please read the following notices before continuing to install: + +Notice 1: +--------- +Do not use this bot with alpha/beta versions of php5. I have successfully run it on rc2/rc3, however. + +Notice 2: +--------- +Do not use this bot with php 5.0.0 or 5.0.1. These versions have a bug which makes it work incorrectly. If you would like to find out more about this bug, view this page: http://bugs.php.net/bug.php?id=28892 + +Installation: (see below for installations on linux) + +--------------------- +Installing on Windows +--------------------- +Download PHP5 from this location: 'http://www.php.net/downloads.php'. Select the latest PHP 5 zip package under "Windows Binaries". Unzip this to a folder, c:\php, for instance. + +Then, copy the included 'php.ini-dist' file in the c:\php directory to 'c:\windows\php.ini'. Open this file with notepad or your favorite text editor. (Usually, you can right click the file and hit 'open with'). + +First, locate this line in the file: +--------------------------------------------------------------------------- +; This directive tells PHP whether to declare the argv&argc variables (that +--------------------------------------------------------------------------- + +A few lines below this, you'll see this directive: +-------------------------------- +register_argc_argv +-------------------------------- +Make sure that this line says: +-------------------------------- +register_argc_argv = On +-------------------------------- + +Now, locate this line: +-------------------------------- +extension_dir = "./" +-------------------------------- +Change this to: +-------------------------------- +extension_dir = "./ext" +-------------------------------- + +If you are going to use a mysql database, find: +------------------------ +;extension=php_mysql.dll +------------------------ +And change to: +------------------------ +extension=php_mysql.dll +------------------------ + +Then find: +-------------------------------- +;extension=php_sockets.dll +-------------------------------- +And change to: +-------------------------------- +extension=php_sockets.dll +-------------------------------- + +You are now set to run PHP-IRC. See section "Configuration" for information related to configuring the bot. + +------------------- +Installing on Linux +------------------- +Download php from php website, here: 'http://www.php.net/downloads.php'. Select the latest php5 tar.gz package under "Complete Source Code". + +Extract this file. To do this, copy the file to your home directory. Then, extract it using this command: + +tar -zxf php-<your version>.tar.gz + +Replace <your version> with your version of PHP. Make sure it is 5.0.2 or greater! +Now, run this command: + +cd php-<your version>/ + +Also make sure to replace <your version> with your php5 version. +Now, we have a few things to consider here. Do you want to run a mysql database or not? If you do, then use this command: + +./configure --enable-sockets --enable-pcntl --with-mysql + +However, if you do not have mysql installed on your system, do this: + +./configure --enable-sockets --enable-pcntl + +If for any reason this fails, please read below. If it does not, skip these next few paragraphs. + +Usually the configure script fails when it cannot find a module or package it needs to compile. Sometimes I have had it fail with the 'XML LIB' package, saying it could not be found. In order to rectify this, use this command: + +./configure --help + +And then search through the output, looking for the package that failed. See if there is a --disable-<package> option that you can add to the ./configure command above, which will make it skip that package. Then, run the configure command again, until you remove all packages that do not work with your system. + +Continuing on +------------- + +Now, after you see the "Thanks for using PHP" message, run the following command: + +make + +After this is completed, you are all done installing php! You will need to copy the binary to a usable and NON WEB ACCESSABLE directory, by using this command: + +cp sapi/cli/php /<my directory>/ + +Where <my directory> is the directory you wish to copy it to. When I say NON WEB ACCESSABLE, I mean that you could not access this file directly from the web. + +You are now set to run PHP-IRC. See section "Configuration" for information related to configuring the bot. + +===================================================== +3-c. Using alternate PHP.INI (PHP Configuration) file +===================================================== + +You may be running another version of php on the server that you run this on specifically for apache, or some other purpose. In this case, you already have a php.ini file in /etc. You can use an alternate php.ini file by using this syntax when running php: + +/path/to/php.exe -c /dir/of/php.ini/ bot.php bot.conf + +If on linux, you can also chmod bot.php 755, and then edit the #! line at the top of the file to look like this: + +#!/path/to/php -c /dir/of/php.ini/ + +And then you can run php-irc like this: + +./bot.php bot.conf + +======================= +3-d. Installing PHP-IRC +======================= +If you are reading this file, you have already downloaded and unpacked the source package from sourceforge.net. Good, the hard part is out of the way. Continue on to the next section. + + +================ +4. Configuration +================ + +PHP5 +---- +Please see above, our guide to "installing php 5" for configuration options to php.ini. + +defines.php +----------- +The main things you need to look at in here is DEBUG mode and OS. DEBUG 1 will make it so everything that happens to the bot is printed in the main window. DEBUG 0 will instead print it to a logfile specified in bot.conf. Make sure that you set your OS to either linux, unix, windows or bsd, otherwise your bot won't run (if autodetection fails). + +The lines you need to change are: + +define('DEBUG', 1); + +and: + +define('OS', 'windows'); + +You might also want to change the name of the PID file that is written when you run the bot in background mode. You can edit that filename with this line: + +define('PID', "bot.pid"); + +bot.conf +-------- +Edit this file and change all the options to your liking. Of special interest should be natip, which allows you to work from behind a nat. Also, as of 2.1, pay special attention to the 'upload' and 'uploaddir' parameters. You can accept file transfers with those. Please also look at dccadminpass. You will need to set this in order to use dcc chat or private message administration of your bot. You will need to run 'bot.php -c password' to generate an md5 password hash of 'password'. + +function.conf +------------- +This file is a little more complicated. php-irc will respond to various text typed in a channel (triggers). You can configure those triggers in this file. Please see the section titled "Modules and User-defined functions" for help with this file. + +At this point, you should be able to start up your bot--although it won't do much. See the section "Modules and User-defined functions" for information about adding functionality. + + +========== +5. Running +========== +Running this bot is rather simple. + +Windows: +-------- + +Run the bot with the following command: + +c:\php\php.exe bot.php bot.conf + +Make sure that this is the correct path that php is in. You may need to change 'c:\php' to the directory where your php5's php.exe resides. Make sure, also, that you are located in your php-irc main directory when you start the bot. i.e., + +cd <my php-irc directory> + +I have a little trick. I make a windows shortcut (.lnk file) to php.exe, and put it in my php-irc directory. Then I edit the shortcut and make sure that my working directory is my php-irc directory. Then, I can run the bot as such: + +php.lnk bot.php bot.conf + +Linux: +------ +The basic syntax is: + +/path/to/php5/php bot.php bot.conf + +If you followed my php5 installation instructions, /path/to/php5/ would be the <my directory> that we talked about earlier. + +Linux Alternative: + +You can edit the #! line in bot.php to reflect your php cgi binary location, and 'chmod 755 bot.php'. This will make 'bot.php' executable. You can then run it as such: + +./bot.php bot.conf + +You can also run several different configurations under the same process. Simply make another bot.conf file and then run it like this: + +./bot.php bot.conf bot2.conf + +If you are on linux/unix, you can also run in the background by using the -b switch: +./bot.php -b bot.conf +However, in order for this to work, you need to set DEBUG mode to 0 in defines.php. (otherwise your bot won't spawn) Also keep in mind that errors will not be displayed when in background mode. Although text is logged to log.txt or whatever file you choose, the errors are not. I will work on this for future versions. However, if you're going to be doing a lot of debugging and are going to want to see all errors, you may want to keep DEBUG=1 and then use a program such as nohup to start the bot: + +nohup ./bot.php bot.conf + +If this doesn't work as expected, delete the 'fclose(STDOUT)' line in bot.php and try running it like this: + +nohup ./bot.php bot.conf -b + + +====================================================== +6. Remote Administration (via DCC and Private Message) +====================================================== + +You can administer your bot via private messages (i.e., /msg php-irc <command>), or via dcc chat interface. However, in order to use these features, make sure that the 'dccadminpass' setting in bot.conf is uncommented, (remove the ';'), and make sure that you change the password to something people can't guess. You need to specify a password hash here, and you can do that by running: + +Windows +------- +drive:\path\to\php.exe bot.php -c <password> + +*nix +---- +/path/to/php bot.php -c <password> + +Where <password> is your password you wish to use. This will generate an md5 hash of '<password>', and you can replace the dccadminpass setting in bot.conf with this value. + +Via private message, you can access admin commands like so (if you are using mIRC): + +/msg <mybot> admin <mypass> <command> + +Where <mybot> is the nick of your bot, <mypass> is the password you selected for dccadminpass, and <command> is the command you will use. You can get a list of commands by using the command 'help'. + +To use the dcc chat administration interface, use: + +/msg <mybot> admin <mypass> chatme + +The bot should then send you a dcc chat request. You will have to type in your password to validate your session, which is just the password you set with dccadminpass again. Then, you can type 'help' for a list of commands. + + +================ +7. File Transfer +================ + +This bot supports file transfers now. I will implement a speed capping system in a later version. For right now, you can send/recieve files at your max bw potential. Really fast transfers (in the line of 100 mbit) take up nearly 99% of your CPU, however. The system supports resume, as well as the mIRC File Server protocol (see below for configuration) + +To send someone a file, use: + +SEND <nick> /path/to/file + +You can use 'DCC' for speed/eta, although it doesn't work very well. Its based off of 3 second averages. Oh, and you can use 'CLOSE id' to close a specific transfer. These id's are the numbers between the brackets ([ and ]) when you run the 'DCC' command. + +NOTE! This may be a security risk, as people with admin access could send themselves any file on your computer with it. You may want to disable this function in the function.conf file if you feel this may be a problem. (just comment it out by putting a ';' in front of the line, or removing the line completely) + +=========================================== +7-a. File Transfer behind a firewall or NAT +=========================================== + +To setup the bot to work behind a NAT (i.e., you are a computer behind a router, and you do not have a net-accessable IP, like 192.168.1.100), you can use the 'natip' setting in bot.conf. Then, you can set the 'dccrangestart' item to choose what port file transfers will use for outsiders to connect. Normally, the bot will use port 1024+, but if you are using forwarded ports, you can set this setting to use those ports. + +If you are behind a firewall, and cannot use the natip feature: +This bot also supports the mIRC reverse dcc protocol. The mIRC File Server protocol can be turned on by uncommeting the 'mircdccreverse' setting in bot.conf. The 59 there is just the port that you will connect to. Common numbers are 59 and 212. + +===================================== +8. Modules and User-defined functions +===================================== + +Modules are what add the functionality to php-irc. You can run php-irc by itself, and it may come with some pre-programmed features, but other than that it doesn't do much. You need to extend it by adding modules that other people have written, or by writing modules yourself. In this section, I will attempt to guide you through the process of doing just that. + +Modules reside in the 'modules/' directory. I have several modules in this directory already, and you can read about them in the section "Provided Sample Modules". + +A php-irc module is a user defined class inside of a file in the modules/ directory. See "template.txt" in the modules/ directory for instructions on how to setup a class. The basic declaration of a module class is as follows: + +class class_name extends module { + + //Other stuff, can be found in template.txt + +} + +This declaration syntax is INCREDIBLY important. You MUST have the format: + +class[space]class_name[space]extends[space]{ + +In order for the dynamic module support in PHP-IRC 2.2.0 to work, class definitions for modules must be defined in this way. If you have outside classes to include, you must include them with "require_once()", instead of "require()". + +Now, say that we created a module; we copied template.txt to my_mod.php, and changed "class_name" to "my_mod" inside my_mod.php. Now, we will have to declare this module inside function.conf. + +function.conf +------------- +There are three main types of directives that you can set in this file. Types, Commands, and Includes. A "Type" statement is a statement which declares a format for a "Command" to use. + +For instance, if I had a type, "notice", and I wanted it to have the arguments "module", and "function", I would do this as follows: + +type notice module function + +Now, I can create commands that use type "notice". Here is an example of a command that uses type "notice": + +notice my_mod my_function + +Thus, whenever a "notice" event took place on IRC, the function "my_function" in "my_mod" would be run with various useful parsed parameters passed. (Discussed Later) + +Note the "module" and "function" arguments. These are required arguments. This means for every single type declared, there must be a "module" and "function" argument specified, because if these didn't exist, what would be the point of catching the event? + +"Include" statements specify external function files to include into the main function.conf file. They are useful when packaging a module with several function.conf statements. Instead of copying/pasting all the statements on installation of the module, one need only to instruct a user to include a statement such as this: + +include modules/my_mod.conf + +Now to get a little more complicated: +------------------------------------- +In addition to parsing standard irc messages and events and defining functions to parse them, the bot comes with 4 standard types. These types are "priv", "dcc", "ctcp", and "file". The "file" type is used to include module files and their associated functions into the bots runtime code. The "ctcp" type makes parsing mIRC's "/ctcp" commands simpler. This could also be done by using a "privmsg" type and then parsing the line manually (to remove the ctcp characters), but its simpler to just do it this way. The "priv" type handles text typed in a channel. Thus, we can capture "!ad" or other triggers with this. The "dcc" type handles text typed in the standard user and admin dcc chat interface. + +file +---- +You use this to declare and include modules into php-irc. The syntax is as follows: + +file - the type +name - the module (or name of the class in the file) +filename - the filename to include (the file that the class resides in) + +Example: + +file priv_mod modules/priv_mod.php + +Filenames with spaces can be enclosed with quotes: + +file priv_mod "modules/My totally kick ass mod.php" + +priv +---- +The "priv" type comes with several parameters, each explained below: + +priv - the type +name - the trigger typed into the channel, i.e., "!ad" +active - whether this particular function is active (can be triggered) + when the bot starts up +inform - Inform the administrator (through dcc chat interface, when the admin + is logged in) that someone used this function. +canDeactivate - admin can deactivate this function using FUNCTION command in dcc interface +usage - how many times this function was used +module - the module that the function is located in +function - the function in the module specified above that is run + +An example of a command that uses the "priv" type is defined below: + +priv !ad true true true 0 priv_mod priv_ad + +The "!ad" command uses the function "priv_ad" in the "priv_mod" module. So whenever someone types in a channel: + +!ad <some arguments> + +The priv_ad function in priv_mod will be called to handle the arguments and respond to the user. + +dcc +--- +The "dcc" type comes with several parameters, each explained below: + +dcc - the type +name - the command typed, i.e., "HELP" +numArgs - the expected number of arguments for this command +usage - a string containing an overview of arguments, i.e., "<id> <nick>" +help - a string containing information about what the command does +admin - whether this command is only available in the admin interface, or can be used in the standard user dcc chat interface +module - the module that the function is located in +function - the function in the module specified above that is run +section - the section the command is displayed in in dcc chat interface (explained below) + +An example of a command that uses the "dcc" type is defined below: + +dcc raw 1 "<raw query>" "Sends raw query to server" true dcc_mod dcc_raw standard + +You might also notice that in this case we used quotes. Quotes can be used for any string longer than 1 word. Single and Double quotes are allowed. Escape double/single quotes inside the quotes with a backslash: i.e., "Who\'s there?". This does not follow the standard convention for escaping quotes. So, even if you use double quotes to specify a multi-word string, you must still escape single quotes. + +section +------- +In the dcc command above, the "section" argument was used, and specified as "standard". PHP-IRC 2.2.0 comes with a new ability to package dcc commands with sections. Thus, when someone types "Help" in dcc chat, all of the functions are organized by category. + +A section can be declared with the following arguments: + +name - a small idname to be used in 'dcc' statements +longname - a usually quoted string which speicfies this sections title + +PHP-IRC comes with a few pre-defined sections which include 'standard', 'channel', 'dcc', 'info', 'admin', and 'comm'. The declaration for the standard section type is shown below: + +section standard "Standard Functions" + +You can of course specify your own sections in the same manner; say, for a custom module perhaps. + +ctcp +---- +This is a shortcut to parsing commands sent to this bot with mIRC's /ctcp command. The format is as follows: + +ctcp - the type +name - the trigger, i.e., "files" (see fileserver example in function.conf) +module - the module that the function is located in +function - the function in the module specified above that is run + +See the fileserver ctcp command in modules/fileserver/fileserver.conf for an example. + +Now that you know what function.conf is and how it works: +--------------------------------------------------------- + +Suppose we have a random function in a module we've written, such as the one defined in the next section titled "Querying Outside Servers" named "query". Having the functon is all fine and dandy, but how do we access it? That is where function.conf comes in. We will specify a trigger which can be used to access the query function during runtime. First, we have to include our module: + +file my_mod modules/my_mod.php + +We'll pretend that this is where the 'query' function resides for the moment. Now, lets say we want to run that function every time someone types '!info' in the channel. To do this, we would add (below all the type declarations, and below the 'file' declaration we just made above): + +priv !info true true true 0 my_mod query + +Thus, every time someone typed !info in the channel, the function 'query' would run. This is the main basis for php-irc. It is the heart of its purpose--responding to triggers. + +Creating Functions to Respond to Triggers +----------------------------------------- +Now that you know how to create modules, and specify them in function.conf, we will now cover writing your own functions to handle these triggers. There are four main types of functions that you will write. "timer" functions, "standard" functions, "query" functions, and "dcc" functions. + +standard +-------- +Standard functions are the functions which handle all the non-dcc type command statements declared in function.conf. Remember our example earlier, when a user could type '!info' in the channel. This triggered the 'query' function in the module 'my_mod'. A sample declaration of this function could be: + + public function query($line, $args) + { + //your code + } + +Notice the "$line, $args". The declarations for 'query', 'dcc', and 'timer' functions all use different values here. They are shown below: + + public function standard($line, $args) + public function query($line, $args, $result, $response) + public function timer($arguments) + public function dcc($chat, $args) + +Definitions of variables +------------------------ + +$line +----- +Line is an array containing a parsed version of the raw line sent from the server. A raw line sent from the server could look like this: + +:Manick!~bugs@Rizon-2EFC6E17.resnet.purdue.edu PRIVMSG #manekian :!ad + +We need to parse this into meaningful parts in order to do anything with the bot. The $line array does that: +from => full nick/ident/host, in this case: "Manick!~bugs@Rizon-2EFC6E17.resnet.purdue.edu" +fromNick => only nick, in this case, "Manick" +fromHost => only host, in this case, "Rizon-2EFC6E17.resnet.purdue.edu" +fromIdent => The Ident of the user, in this case, "~bugs" +cmd => irc command used, (i.e., PRIVMSG, NOTICE, 366, 353, etc), in this case "PRIVMSG" +to => who this command was directed at (channel or your nick), in this case, "#manekain" +text => everything after : in the line; basically the text of what someone says, in this case, "!ad" +params => useful when parsing 'mode' commands etc. in this case, ":!ad" +raw => the full untouched line. + +To access an element in $line, reference it as such: $line['element'], such as: + +$line['fromNick'] + +This would return "Manick", in this case. Sometimes these variables are not populated perfectly. In a particular circumstance, you can use: + +print_r($line); + +by itself to to print the contents of the $line variable to the screen while in debug mode. + +$args +----- +This is a simple array created from the $line['text'] variable. It contains the following data: + +nargs => the number of arguments +cmd => the command used +arg1 => present if there are 1 arguments or more +arg2 => present if there are 2 arguments or more +arg3 => present if there are 3 arguments or more +arg4 => present if there are 4 arguments or more +query => The full text (all of args put together) +(there are no more after arg4..) + +So, if I typed "!ad 60 Here is an ad that I want to talk about...": + +nargs would be "4" (yes, 4) +cmd would be "!ad" +arg1 would be "60" +arg2 would be "Here" +arg3 would be "is" +arg4 would be "an" +query would be "60 Here is an ad that I want to talk about..." + +The maximum arguments can be set by the MAX_ARGS define in the defines.php. It was chosen to be four for performance reasons. + +$result +------- +This is discussed in the next section "Querying Remote Servers" + +$response +--------- +This is discussed in the next section "Querying Remote Servers" + +$arguments +---------- +When creating a timer, you can specify one argument, whether it be a string, array, or object, to send to a timer function every time that timer runs. Timers are discussed in a later section. + +$chat +----- +This is the object containing the current session of the user who typed the command in the dcc chat window. $args for dcc functions is the same as $args above. + +Sending messages and responding to users +---------------------------------------- + +After you've received a query and processed it, you might want to send back information. There are several functions that can be used to do just that: + +$this->ircClass->sendRaw($text); //send raw data to server +$this->ircClass->notice($line['fromNick'], $text, $queue = 1); // (where $queue defaults to 1.. meaning its not + // sent right away. +$this->ircClass->privMsg($line['fromNick'], $text, $queue = 1); // $line['fromNick'] can also be the channel + // which is usually $line['to']. +$this->ircClass->action($line['fromNick'], $text, $queue = 1); // does the '/me' thing that mIRC does + +To send data to dcc users, use: + +$chat->dccSend("text here"); + +To send a CTCP messege, do this: + +$cmd = "VERSION"; +$msg = ""; +$this->ircClass->privMsg($line['fromNick'], chr(1) . trim(strtoupper($cmd) . " " . $msg) . chr(1)); + +Where $cmd is the command, like "VERSION", or "ACTION", and $msg is the parameters to the command. + +$queue +------ +php-irc maintains a text queue, so that it does not flood the server with text. This is not a perfect system, and still fails ocassionaly, but works for the most part. If $queue is set to 1, or not specified at all, then $text will be appened to the end of the queue waiting to be sent to the server. Otherwise, if you set it to 0, it will be prepended to the beginning of the text queue. + + +Now you should know all you need to know to write your own modules for php-irc. + + + + +============================================== +8-a. Submitting modules for others to download +============================================== + +I am currently working on a site to submit modules. This site will be http://www.phpbots.org when finished. + +=========================== +9. Querying outside servers +=========================== + +You may need to read the section titled: "Modules and User-defined functions" before reading this. + +Sometimes when programming a module, you might find it necessary to parse some webpage to return some data to a user requesting it. Your first impression might be to use "fopen" or "fsockopen" to make a connection to the webserver and get the data. + +Consider something for a moment. This bot is running on one process, one thread. Say that you were sending a file to someone, and you also went to query a webpage. What if the webpage was extremely bogged down with traffic? The query would block, and the file transfer would stall. To rectify this, I implemented a few procedures for querying outside servers, including webservers. This eliminates the blocking problem, as the connections are handled by php-irc. + +There are two basic ways to communicate with the outside world easily in PHP-IRC. + +1) Use $this->ircClass->addQuery() +2) Utilize the connection class manually + +The addQuery() method is a specialized way of using the 'connection' class to get the job done. In other words, you could write your own connection.php class method that does exactly what addQuery does, or even more if you wanted. The irc bot itself uses the connection class to connect to irc servers, run dcc chats, and even run file transfers. You can even use the connection class to start a listening socket, which can be used to write servers such as http servers, ftp servers, or pretty much anything that uses a tcp socket. + +================================================= +9-a. Basic Queries, using the addQuery() function +================================================= + +This method is used mostly for retrieving data from http servers. It's main advantage is that it is incredibly easy to use, but the main disadvantage is that you can only send one query per connection, and then data related to that query is returned before the connection is terminated. + +The basic procedure is this: You will run a function with a query, and specify what function to run when data has been recieved from that server. Then, the function, using a specifically defined prototype, will have access to all the data + results from the server as soon as the query is complete. + +Functions used in this process are: + +irc::addQuery($host, $port, $query, $line, $class, $function) +socket::generateGetQuery($query, $host, $path, $httpVersion = "1.0") <-- $httpVersion can be omitted +socket::generatePostQuery($query, $host, $path, $httpVersion = "1.0") <-- $httpVersion can be omitted + +These functions, (addQuery, generateGetQuery, and generatePostQuery) are outlined in the command_reference.txt file. But here is how it works: + +For instance, if you wanted to parse data at animenfo.com, you would do this: + +Define a function in your module file. We'll call it "query" for now. Also, see the previous section for information about how to make this method accessable (by editing function.conf) + + public function query($line, $args) + { + $query = "search=naruto&queryin=anime_titles&action=Go&option=smart"; + $getQuery = socket::generateGetQuery($query, "www.animenfo.com", "/search.php"); + $this->ircClass->addQuery("www.animenfo.com", 80, $getQuery, $line, $this, "queryTwo"); + } + +Notice the "queryTwo". Now define another function in your module file. We'll call it "queryTwo". + + public function queryTwo($line, $args, $result, $response) + { + + } + +The function, "queryTwo" will be run as soon as a response is recieved from the server, and the response will be stored in the '$response' variable. The result of the query will be stored in the '$result' variable. For instance, if there was an error on query, $result will equal QUERY_ERROR, and the error message will be stored in $response. If there is no error, $result will equal QUERY_SUCCESS. + +Notice +------ +As of version 2.1.1, the socket::httpGetQuery, and socket::httpPostQuery functions were removed. They were replaced with the addQuery function. + +================================================ +9-b. Intro to Connection Class, Advanced Queries +================================================ + +As the development of PHP-IRC 2.2.0 continued, a need for a connection layer to the socket class appeared. Code was being repeated often in the irc bot class, the dcc class, and other places; in addition, it was becoming quite cluttered and hard to understand. Also, the old way just simply didn't work anymore with the process queue method implmented in 2.2.0. Therefore, the connection class was implemented to solve this problem. + +The connection class serves as a layer between the socket class and the user. A user can create a connection, specify a host and port, and the connection class will do everything else automatically; connect, send read/write events, accept connections, close connections, and much more. This works by specifying a callback class, with pre-named, user-defined functions to accept events from the socket class. Say, for example, whenever data is read for a specifc socket, that sockets user-assigned connection class will notify the callback class of an event, say, onRead(), and then the onRead() function in the callback class can read the data. + +Here's a simple schematic: + ++--------------+ +------------------+ +----------------+ +| Socket Class |->Triggers->| Connection Class |->Triggers->| Callback Class | ++--------------+ +------------------+ +----------------+ + +The callback class can then use socket class functions, such as getQueueLine(), or what have you, to retrieve data from the socket read queue, or send data using the appropriate function. + +General Overview +---------------- +There are two specific ways to run the connection class: + +1) Open connection to other server specifying host and port +2) Listen for connections on a specific or random port, accepting new connections + +The ins and outs of the callback class will then be explained. + +Method One - Connecting to another server +----------------------------------------- +For this method, one needs 3 things, with the fourth being optional: + +1) Host +2) Port +3) Connection timeout +4) Transfer timeout + +The connection timeout is how long before the connection class will think that it cannot connect to the server before timing out, and the transfer timeout is how long (after a connection has connected) before the connection class believes the connection to have failed after no activity. + +Say you wanted to connect to "www.animenfo.com" on port "80". First, you need to declare a new connection: + + $conn = new connection("www.animenfo.com", "80", CONNECT_TIMEOUT); + +CONNECT_TIMEOUT is just the default connect timeout used by the irc class. You can use whatever integer/float number you want. + + $this->conn = $conn; + +You need some way of remembering this connection; in this case, the call back class will be the one that the connection is created in. In most cases, you will need to make a whole new class because of the need for unique functions to handle the events of any general connection. + + $conn->setSocketClass($this->socketClass); + $conn->setIrcClass($this->ircClass); + $conn->setTimerClass($this->timerClass); + +The connection class needs access to these three classes, socket class, irc class, and timer class. + + $conn->setCallbackClass($this); + +This is probably the most important call of all. Whenever an event happens for this connection, specific functions in this class will be called to handle the event. This is explained more thouroughly below in the "Ins and Outs" of the callback class sub-section. + + $conn->init(); + +This will try to create the socket with the socket class. + +Also, optionally here you can specify transfer timeout. That would look like this: + + $conn->setTransTimeout($timeout); + +Where $timeout is in seconds or fractions of seconds. + + if ($conn->getError()) + { + $this->ircClass->notice($line['fromNick'], "Error connecting: " . $conn->getErrorMsg() ); + } + +If there was a problem init()'ing the connection, then $conn->getError() will be true, and $conn->getErrorMsg() will be why. + + $this->sockInt = $conn->getSockInt(); + +The sockInt is necessary to use socket class functions for this socket. + + $conn->connect(); + +The final step, this will start the connection process to the server. + +Method Two - Listening for Connections +-------------------------------------- +This method follows reletively the same method as for connecting to a server. There are two slight differences: + +1) Specify the host as null when creating a connection, and specify the connect timeout as zero. + + $conn = new connection(null, "80", 0); + +You can change the "80" to null to listen on a random port. + +2) Do not run $conn->connect(); + +This function is used ONLY when connecting to a server. It has no purpose otherwise. + +The Ins and Outs of the Callback Class +-------------------------------------- +Okay, so now we know how to create a listening connection or a connect connection. Now what do we do with it? We have to have some way of responding to the events. A callback class must have seven pre-named functions: + +class my_class { + + public function onTransferTimeout($connection) + { + } + + public function onConnectTimeout($connection) + { + } + + public function onConnect($connection) + { + } + + public function onRead($connection) + { + return true; (or return false;) + } + + public function onWrite($connection) + { + } + + public function onAccept($listener, $newConnection) + { + } + + public function onDead($connection) + { + } + +} + +Each function is pretty self explanitory. The only one that might cause some confusion is onRead() or onAccept(). + +onRead() +-------- +This function returns true if not all data has been read from the socket (i.e., you used socketClass->getQueueLine() but want to give other processes a chance to run before you continue processing). This will make the function run immediately again. Otherwise, returning false will signal to the connection class that you don't need to run this function again. + +onAccept() +---------- +Listener is the connection that was listening for new connections, and $newConnection is the new connection. $newConnection is another object of type connection class, and all one needs to do is call getSockInt() on it to get the socket identifier. The callback class is automatically its parents callback class ($listener's callback class). This can be changed by runing the setCallbackClass() function again with a different class. + +Closing a connection +-------------------- +This is very important. The connection class will, under no circumstances, close a connection. Even if the onDead() function is called, for instance, the callback class MUST close the connection. If you have a connection, $conn, and you want to close it, you would run: + +$conn->disconnect(); + +This kills the socket, and removes it from the socket class. From this point on, it is completely inactive. + +What if I want to accept a connection, but close the parent connection in the process? +-------------------------------------------------------------------------------------- +The dcc class is a sample class that does just this. + + public function onAccept($oldConn, $newConn) + { + $this->conn = $newConn; + //We just want to use $newConn from now on + + $this->sockInt = $newConn->getSockInt(); + //Get $newConn's sockint + + $oldConn->disconnect(); + //Close our old connection + + $this->onConnect($newConn); + //Trigger the onConnect event + } + +============================ +10. Custom DCC Chat sessions +============================ + +A custom dcc chat handler is where you send out a chat request to a user, and instead of php-irc's default dcc interface handling and parsing the input, you will handle and parse the input yourself. Just follow the template.txt in the modules directory to create your handler. However, in addition, you need to do a few extra things. + +You will need to include these functions: + + public function main($line, $args) + { + $port = $this->dccClass->addChat($line['fromNick'], null, null, false, $this); + + if ($port === false) + { + $this->ircClass->notice($line['fromNick'], "Error starting chat, please try again.", 1); + } + } + + public function handle($chat, $args) + { + } + + public function connected($chat) + { + } + + //Following function added for 2.2.1 + public function disconnect($chat) + { + } + +'main' function +--------------- +This is the function that your trigger in function.conf will call to initiate the chat + +'handle' function +----------------- +Whenever something is typed in the dcc chat window, it will be passed to this function in the '$args' array. See the previous section for the general definition of this array. + +'connected' function +-------------------- +Use this function to do any "upon connection" tasks, as in setup the session or whatever you want. + +fileserver.php is a module that uses a custom dcc chat handler, and is detailed below. + + +'disconnected' function +----------------------- +This is called when a dcc chat session is closed. + +============================== +10-a. Simple File Server (SFS) +============================== + +I've include a small, not-to-complex fileserver which you can use as an example to create your own modules. This particular one is a custom dcc chat handler (refer to previous section). To activate, uncomment the 'ctcp files' section and 'file fileserver' section in the function.conf (you can change 'files' to whatever you want, its your trigger). Then edit these variables in fileserver.php: + +private $vDir = "F:/BT/"; (use forward slashes, with a trailing slash as shown) +private $maxSends = 3; +private $maxQueues = 15; +private $queuesPerPerson = 1; +private $sendsPerPerson = 1; + +vDir is the root directory of the fileserver. I haven't beta tested this too much. If it breaks, tell me I guess. It was meant as a demonstration rather than something to actually be used. + +==================== +11. Database support +==================== + +PHP-IRC offers ever increasing database support. It currently offers a tried and tested mysql database abstraction layer(dba), and a beta postgre dba. There is also a new "ini" database type which operates out of ini files. This is similar to mIRC's /writeini and $readini functions. + +===================== +11-a. MySQL Database +===================== + +To use a mysql database, you will need to uncomment and fill out the database settings in bot.conf. You will also want to make sure that you have php set to use mysql. View the "Installing PHP 5" section for more information about this. + +You will need to set 'usedatabase' in bot.conf to this: + +usedatabase mysql + +INPORTANT DATABASE INFO: +------------------------ +Because PHP does not escape input from sockets as it does with get/post/cookies (from http), you need to escape your database information before you put it into the database. Therefore, if you have a table with 3 fields like ... name, password, email, then the safest way to put this information into the database using the db class provided in the bot would be: + +$fieldArray = array("Somebody's Name", "somePass", "something@something.com"); + +$this->db->query("INSERT INTO sometable (name,password,email) values ('[1]','[2]','[3]')", $fieldArray); + +This ensures that the data is safe to put into the database. The [1] etc will be replaced with their place in the array above in real time. Of course, you are free to do it however you wish. + +====================== +11-b. Postgre Database +====================== + +You will need to set 'usedatabase' in bot.conf to this: + +usedatabase postgre + +Also, read "IMPORTANT DATABASE INFO" above for more information. Support for this dba is SKETCHY at best. + +========================================================= +11-c. Serverless, mIRC compatible ini-file based Database +========================================================= + +INI-file support compliments the database system provided by PHP-IRC. Say you have a simple module which only needs to store a few data to file. Instead of having to have a mysql database, you can achieve this feat with ini files. They are basically identical to mIRC ini files--so, if you have any experience with $readini or /writeini, you will be pretty well off. + +To use ini files, there are several functions you can perform: + +1) Declaring, Creating, or Opening an INI file +2) Setting ini values +3) Retrieving ini values +4) Writing changes to ini file +5) Miscellaneous ini-file functions. + +1 - Declaring, Creating, or Opening an INI file +----------------------------------------------- +Creating or opening an ini file is incredibly simple. In fact, they consist of the same step: + +$myIni = new ini("somefile.ini"); + +Thats it! Then: + +if ($myIni->getError()) +{ + //some code to handle ini file error +} + +If this is false, then the ini file cannot be created. In this case, you need to check the permissions on the directory you're attempting to create this file. (It may be that you don't have read access either.) + +Now you are ready to import/export data. One idea you may use is saving the $myIni variable in a global variable in your module, and then using it just as if it was a database, running queries and updating data occasionally. + +2 - Setting ini values +---------------------- +INI files consist of three types of data. Sections, Variables, and Values. They look somewhat like this: + +[section] +variable=value +another_variable=value +[section2] + +etc.... + +To set an ini value on ini-object "$myIni", use the following function: + +$myIni->setIniVal($section, $variable, $value); + +None of these arguments may contain a newline character. + +3 - Retrieving ini values +------------------------- +There are many many ways of retrieving data from an ini object. To assist, there are around ten helper functions: + +1) getSections() //get all section names +2) sectionExists() //check whether a section exists +3) getSection() //get all vars in a section, associative array form +4) randomSection() //get a random section +5) randomVar() //get a random variable in a section +6) searchSections() //see command_reference.txt +7) searchVars() //see command_reference.txt ... etc +8) searchSectionsByVar() +9) searchVals() +10) numSections() +11) numVars() +12) getVars() + +Perhaps the most important function is getIniVal($section, $var). See command_reference.txt for more detailed function information. + +4 - Writing changes to ini file +------------------------------- +When you want to finalize changes to an ini database, and write the data to a file, call this command: + +$myIni->writeIni(); + +This will write the ini file. You can continue editing, and then use this function again as many times as you wish. + + +5 - Miscellaneous ini-file functions +------------------------------------ +There are some other useful functions. See command_reference.txt for more info. + +1) deleteVar($section, $var) +2) deleteSection($section) +3) getSection($section) + +========== +12. Timers +========== + +Timers allow you to run some procedure or function later, but not have to actually "run" it later. You specify a delay, and the script will run it in the interval selected. Timers are used all over php-irc. The point here is that you can also use them. + +Timer functions look like this: + + public function myfunc($arguments) + { + return true/false; + } + +This form is declared more readily in the section titled "Modules and User-defined functions". By default, a timer will expire upon running, and will not be run again. To change this so that the timer runs again in another interval, the timer function MUST return 'true': + +return true; + +This will signal the timer class to run the timer again after 'interval'. Otherwise, the timer is removed. + +If I wanted to create a timer to run a function in sixty seconds, I would do this: + +$this->timerClass->addTimer("my_timer", $this, "myfunc", "", 60, false); + +The arguments are as follows: +$name - The timer's name. Used with removeTimer to remove the timer +$class - pointer to class with function, usually '$this' +$function - the function that you will want to run after the interval expires +$args - the arguments you want to send to the timer +$interval - duh, in seconds and fractions of seconds (i.e., 5.2) +$runRightAway - whether this timer should run as soon as it is added, or wait until after the interval. 'true' or 'false'. + +A few notes: +------------ + +$name +----- +If you want to create a random name to send as timer title, with least chance for name collision, use: + +irc::randomHash(); + +This function will generate a string which should be unique. + +$args +----- +This can be anything you want, from a class, to an array, to an object. I have the 'argClass' defined in defines.php which I use to send multiple variables to my timers: + +$args = new argClass; + +$args->arg1 = "blah"; +etc... + +Otherwise, you could just send an associative array. + +Removing Timers +--------------- +In order to remove a timer, you will need to use this function: + +$this->timerClass->removeTimer($name); + +The "$name" variable is the same as the name variable declared with addTimer above. Make sure that you specify unique timer names. Also, make sure that you keep track of the timer name if you are going to want to delete it. You can usually do this by sending it via the $args variable. + +Instead of relying on removeTimer, I many times simply return false instead of true when the timer is next run. That is also possible. + +=================================== +13. Multiple bots under one process +=================================== + +This script supports running multiple bots under one process. In order to do this, create another bot.conf file, say, bot2.conf, and just specify it when starting the bot: + +./bot.php bot.conf bot2.conf bot3.conf + +etc. See the "Running" section for more information. Performance of multiple bots has been significantly increased in 2.2.0. + + +=========================== +14. Provided Sample Modules +=========================== +To foster continued support of PHP-IRC; I have included some basic module examples in this release. Please note that a parser can and will fail when the html of a site is changed, so some of these releases may NOT work at future dates. They all worked previous to 01/14/05, however. + +================= +14-a. IMDB Parser +================= +This module parses the internet movie database, http://www.imdb.com + +To use, uncomment the following line from function.conf + +;include modules/imdb/imdb_mod.conf + +The command is "!imdb". You can do a search by using "!imdb <query>". If multiple dates were found for your query, you can do: "!imdb <title> (date)" to see it's results. Note that the parenthesis on the date must be there. + +Note, as this is a parser, it may not work out of the box, as imdb might have changed their site. + +=========================================== +14-b. Quotes mod with mysql/ini file system +=========================================== +Possibily the most usable feature of any bot, this bot comes with two different forms of quote systems. You can either utilize a mysql database, or use the onboard ini file database. + +Mysql +----- +To use, uncomment in function.conf: + +;include modules/quotes_sql/quote_mod.conf + +You must also set the database information in bot.conf, and run this query in the database: + +create table quotes (id int default null, deleted tinyint(1) default 0, author varchar(50) default "", quote text, channel varchar(100) default "", time int, key(id)); + +See modules/quotes_sql/quotes_mod.conf for usable commands. + +Ini +--- +To use, uncomment in function.conf: + +;include modules/quotes_ini/quote_mod.conf + +Quotes are stored in the included "quotes_database" directory. + +See modules/quotes_ini/quotes_mod.conf for usable commands. + +======================== +14-c. Simple http server +======================== +Alright, this is more of a proof of concept than something actually usable. This shows off the use of PHP-IRC's new "connection" class to handle incoming http connections in an event based way. + +I really know just about nothing about the http protocol, thus I give no support or instructions for this module. I have had it display pages and images with much success. + +To use, uncomment in function.conf: + +;include modules/httpd/httpd_mod.conf + +Then, the bot will listen on the port specified in modules/httpd/http.ini which defaults to 5050. + +===================== +14-d. Bash.org Parser +===================== +This will allow users to display random, searched, or specific quotes from http://bash.org + +The commands are: + +!bash - random quote +!bash + - random positive quote +!bash search - search for a quote, will display the first result containing 'search' +!bash id - will return the specific quote with id 'id'. + +To use, uncomment in function.conf: + +;include modules/bash/bash_mod.conf + +============================== +14-e. News System/Rules Script +============================== +This is a small standard script where users can type a trigger and information will come back to them. It can be used to generate !rules scripts, !news scripts, or pretty much anything you can think of. To use it, uncomment the appropriate line (referring to news_mod.conf) in function.conf. Then, you can use the default trigger, !news, to add stuff. You must be opped/admined to add/delete items. + +Here are the commands: + +!news <command> <arguments> + +command: +add - add a new item to end of list. You can also add to a specific line number, by doing add <num> <stuff> +del - delete a line number: del <num> +show - show line numbers with their respective lines, so you can use del. +clear - clear out all lines for a specific channel. + +Running the command with no arguments is available to everyone, and will show the lines in order of line number. + +The usefulness of this mod is very apparent. However, to make it even more useful, I've added another feature. Say that you have these two items in news_mod.conf: + +priv !news true true true 0 news_mod priv_news +priv !requests true true true 0 news_mod priv_news + +Both of these are pointing to the same function/module, but they will both access separate database files. Thats right, separate. That is because any call to the function will make the function open the database that is of the same name as the trigger. For instance, doing: + +!news add Hello! This is some news! + +This will add the line to the news database. And doing: + +!requests add Some Request + +This will add the line to the requests database, but they use the same module/function! Cool huh :) + +=========================== +14-f. Seen System (beta!) +=========================== +This is a very beta mod which I wrote like 15 minutes prior to releasing this version of php-irc. Basically, it works as a normal seen mod would, you type !seen <user> and it tells you the last time it saw that person. It uses the ini file system to store information. This mod is VERY BETA!!! I haven't even tested 85% of it. I ran it once and thought, "heh, interesting", and never looked at it again. I will not support this mod until either someone updates it and sends me a better revision, or I get the time to make it better. + +======================== +14-g. Request Ads System +======================== +This has been around since the early days of php-irc. It is the classic example. Except in 2.2.0, it now utilizes the ini file system, so ads that are added are saved and restarted when the bot process is killed and restarted. The "!ad" trigger is on by default. + +====================== +15. Function Reference +====================== +Please view the "command_reference.txt" file for a complete indepth look at all the functions that you can use while writing your modules. diff --git a/ircbot/remote.php b/ircbot/remote.php new file mode 100644 index 0000000..a6868c0 --- /dev/null +++ b/ircbot/remote.php @@ -0,0 +1,213 @@ +<?php +/* ++--------------------------------------------------------------------------- +| PHP-IRC v2.2.1 Service Release +| ======================================================== +| by Manick +| (c) 2001-2005 by http://www.phpbots.org/ +| Contact: manick@manekian.com +| irc: #manekian@irc.rizon.net +| ======================================== ++--------------------------------------------------------------------------- +| > remote class module +| > Module written by Manick +| > Module Version Number: 2.2.0 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +/* Remote, a class to handle addQuery connection from ircClass */ + +class remote { + + //External Classes + private $socketClass; + private $ircClass; + private $timerClass; + + //Internal variables + private $host; + private $port; + private $query; + private $line; + private $class; + private $function; + private $connTimeout; + private $transTimeout; + private $sockInt; + private $connection; + private $connected; + + //Output internal variables + private $response; + private $type; + + function __construct($host, $port, $query, $line, $class, $function, $transTimeout) + { + $this->host = $host; + $this->port = $port; + $this->query = $query; + $this->line = $line; + $this->class = $class; + $this->function = $function; + $this->transTimeout = $transTimeout; + $this->response = ""; + $this->connected = false; + $this->type = QUERY_SUCCESS; + } + + public function setSocketClass($class) + { + $this->socketClass = $class; + } + + public function setIrcClass($class) + { + $this->ircClass = $class; + } + + public function setTimerClass($class) + { + $this->timerClass = $class; + } + + public function connect() + { + if ($this->host == null || $this->port == null) + { + return false; + } + + if (!is_object($this->socketClass)) + { + return false; + } + + if (!is_object($this->ircClass)) + { + return false; + } + + $conn = new connection($this->host, $this->port, CONNECT_TIMEOUT); + + $conn->setSocketClass($this->socketClass); + $conn->setIrcClass($this->ircClass); + $conn->setCallbackClass($this); + $conn->setTimerClass($this->timerClass); + + /* Set Timeouts */ + $conn->setTransTimeout($this->transTimeout); + + $conn->init(); + + if ($conn->getError()) + { + $this->setError("Could not allocate socket"); + return false; + } + + $this->sockInt = $conn->getSockInt(); + $conn->connect(); + + $this->connection = $conn; + + return true; + } + + public function disconnect() + { + $this->connection->disconnect(); + $this->setError("Manual disconnect"); + } + + /* Specific handling functions */ + + public function onTransferTimeout($conn) + { + $this->connection->disconnect(); + $this->setError("The connection timed out"); + } + + public function onConnectTimeout($conn) + { + $this->connection->disconnect(); + $this->setError("Connection attempt timed out"); + } + + public function onConnect($conn) + { + $this->connected = true; + $this->socketClass->sendSocket($this->sockInt, $this->query); + } + + public function onRead($conn) + { + $this->response .= $this->socketClass->getQueue($this->sockInt); + } + + public function onWrite($conn) + { + // do nothing, we really don't care about this + } + + public function onDead($conn) + { + $this->connection->disconnect(); + + if ($this->connected === true) + { + $this->doCallback(); + } + else + { + $this->setError($this->connection->getErrorMsg()); + } + + } + + /* Error handling */ + + private function setError($msg) + { + $this->response = $msg; + $this->type = QUERY_ERROR; + $this->doCallback(); + } + + private function doCallback() + { + if ($this->line != null && is_array($this->line) && isset($this->line['text'])) + { + $lineArgs = parser::createLine($this->line['text']); + } + else + { + $lineArgs = array(); + } + + $func = $this->function; + $this->class->$func($this->line, $lineArgs, $this->type, $this->response); + } + +} + +?> diff --git a/ircbot/socket.php b/ircbot/socket.php new file mode 100644 index 0000000..b92fafd --- /dev/null +++ b/ircbot/socket.php @@ -0,0 +1,1010 @@ +<?php +/* ++--------------------------------------------------------------------------- +| PHP-IRC v2.2.1 Service Release +| ======================================================== +| by Manick +| (c) 2001-2005 by http://www.phpbots.org/ +| Contact: manick@manekian.com +| irc: #manekian@irc.rizon.net +| ======================================== ++--------------------------------------------------------------------------- +| > socket module +| > Module written by Manick +| > Module Version Number: 2.2.0 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +class socket { + + private $rawSockets; //array of raw sockets to be used by select + private $socketInfo; //index by intval($rawSockets[socket]) + private $numSockets; //number of sockets currently in use + private $writeSocks; //sockets that have write buffers queued + private $numWriteSocks; + + private $readQueueSize = 0; + + private $tcpRangeStart = 1025; + private $timeoutSeconds = 0; + private $timeoutMicroSeconds = 0; + + private $myTimeout = 0; + + private $procQueue; + + public function __construct() + { + $this->connectSockets = array(); + $this->rawSockets = array(); + $this->socketInfo = array(); + $this->writeSocks = array(); + $this->readQueueSize = 0; + $this->numSockets = 0; + $this->numWriteSocks = 0; + } + + public function setProcQueue($class) + { + $this->procQueue = $class; + } + + public function getNumSockets() + { + return $this->numSockets; + } + + public function setTcpRange($range) + { + if (intval($range) != 0) + { + $this->tcpRangeStart = $range; + } + } + + public function getHost($sockInt) + { + $status = socket_getsockname($this->socketInfo[$sockInt]->socket, $addr); + + if ($status == false) + { + return false; + } + + return $addr; + + } + + public function getRemoteHost($sockInt) + { + $status = socket_getpeername($this->socketInfo[$sockInt]->socket, $addr); + + if ($status == false) + { + return false; + } + + return $addr; + + } + + public function setTimeout($time) + { + $sec = intval($time); + $msec = intval(($time - $sec)*1e6); + + if ($sec == 0) + { + $msec = $msec < $this->myTimeout ? $this->myTimeout : $msec; + } + + if ($sec < $this->timeoutSeconds) + { + $this->timeoutSeconds = $sec; + $this->timeoutMicroSeconds = $msec; + } + else if ($sec == $this->timeoutSeconds) + { + if ($msec < $this->timeoutMicroSeconds) + { + $this->timeoutMicroSeconds = $msec; + } + } + } + + public function setHandler($sockInt, $owner, $class, $function) + { + if (!isset($this->socketInfo[$sockInt])) + { + return false; + } + + $sock = $this->socketInfo[$sockInt]; + + $sock->owner = $owner; + $sock->class = $class; + $sock->func = $function; + + return true; + } + + /* For debug... */ + public function showSocks($read, $write) + { + echo "\n\nRead:\n"; + if (is_array($read)) + { + foreach($read AS $sock) + { + echo $sock . "\n"; + } + } + echo "\nWrite:\n"; + if (is_array($write)) + { + foreach($write AS $sock) + { + echo $sock . "\n"; + } + } + echo "\n"; + } + + public function handle() + { + //For debug + //echo "Read: " . $this->readQueueSize . " Write: " . $this->writeQueueSize . "\n"; + //echo "timeout: " . $this->timeoutSeconds . "-" . $this->timeoutMicroSeconds . "\n"; + + if ($this->numSockets < 1) + { + if ($this->timeoutSeconds > 0) + { + sleep($this->timeoutSeconds); + } + + if ($this->timeoutMicroSeconds > 0) + { + usleep($this->timeoutMicroSeconds); + } + + $this->timeoutSeconds = 1000; + return; + } + + if ($this->numSockets < 1) + { + $sockArray = NULL; + $except = NULL; + } + else + { + $sockArray = $this->rawSockets; + $except = $this->rawSockets; + } + + if ($this->numWriteSocks < 1) + { + $writeArray = NULL; + } + else + { + $writeArray = $this->writeSocks; + } + + //For debug + //$this->showSocks($sockArray, $writeArray); + + $newData = socket_select($sockArray, $writeArray, $except, $this->timeoutSeconds, $this->timeoutMicroSeconds); + + $this->timeoutSeconds = 1000; + + if ($newData === false) + { + die("socket_select error"); // need to change this to handle errors + return; + } + + if (!$newData) + { + return; + } + + if (count($sockArray) != 0) + { + foreach($sockArray AS $socket) + { + $sockIntval = intval($socket); + + switch($this->socketInfo[$sockIntval]->status) + { + case SOCK_CONNECTED: + $this->readSocket($sockIntval); + break; + case SOCK_LISTENING: + $this->acceptSocket($sockIntval); + break; + case SOCK_CONNECTING: + $this->connectSocket($sockIntval); + break; + default: + break; + } + } + + } + + if (count($writeArray) != 0) + { + foreach ($writeArray AS $socket) + { + $sockIntval = intval($socket); + $this->sendSocketQueue($sockIntval, 1); + } + } + + if (count($except) != 0) + { + foreach($except AS $socket) + { + $sockIntval = intval($socket); + $this->markDead($sockIntval); + } + } + + } + + + private function callBack($sockIntval, $msg) + { + //Schedule the callback to run + if ($this->socketInfo[$sockIntval]->func != "" && + $this->socketInfo[$sockIntval]->func != null) + { + $this->procQueue->addQueue( $this->socketInfo[$sockIntval]->owner, + $this->socketInfo[$sockIntval]->class, + $this->socketInfo[$sockIntval]->func, + $msg, + .01); + } + } + + private function readSocket($sockIntval) + { + + if ($this->isDead($sockIntval)) + { + return; + } + + if ($this->socketInfo[$sockIntval]->status != SOCK_CONNECTED) + { + return; + } + + $dataRead = false; + + $socket = $this->socketInfo[$sockIntval]->socket; + //Read in 4096*30 bytes + + for ($i = 0; $i < 30; $i++) + { + $response = @socket_read($socket, 8192, PHP_BINARY_READ); + + $respLength = strlen($response); + + if ($response === false) + { + $err = socket_last_error($this->socketInfo[$sockIntval]->socket); + + if ($err != EALREADY && $err != EAGAIN && $err != EINPROGRESS) + { + $this->markDead($sockIntval); + } + break; + } + else if ($respLength === 0) + { + if ($i == 0) + { + $this->markDead($sockIntval); + } + break; + } + + $dataRead = true; + + $this->readQueueSize += $respLength; + $this->socketInfo[$sockIntval]->readLength += $respLength; + $this->socketInfo[$sockIntval]->readQueue .= $response; + + } + + if ($dataRead == true) + { + if ($this->socketInfo[$sockIntval]->readScheduled == false) + { + $this->callBack($sockIntval, CONN_READ); + $this->socketInfo[$sockIntval]->readScheduled = true; + } + } + } + + + private function sendSocketQueue($sockIntval, $queued = 0) + { + $socket = $this->socketInfo[$sockIntval]->socket; + + if ($this->isDead($sockIntval)) + { + return; + } + + if (($bytesWritten = @socket_write($socket, $this->socketInfo[$sockIntval]->writeQueue)) === false) + { + + $socketError = socket_last_error($socket); + + switch ($socketError) + { + case EAGAIN: + case EALREADY: + case EINPROGRESS: + break; + default: + $this->markDead($sockIntval); + break; + } + + } + else + { + $this->socketInfo[$sockIntval]->writeQueue = substr($this->socketInfo[$sockIntval]->writeQueue, $bytesWritten); + $this->socketInfo[$sockIntval]->writeLength -= $bytesWritten; + + //Queue Empty, Remove socket from write + if ($this->socketInfo[$sockIntval]->writeLength == 0 && $queued == 1) + { + $this->removeWriteSocketFromArray($sockIntval); + } + + if ($this->socketInfo[$sockIntval]->writeLength == 0) + { + unset($this->socketInfo[$sockIntval]->writeQueue); + $this->socketInfo[$sockIntval]->writeQueue = ""; + } + + //Callback after we wrote to socket + + if ($this->socketInfo[$sockIntval]->writeScheduled == false) + { + $this->callBack($sockIntval, CONN_WRITE); + $this->socketInfo[$sockIntval]->writeScheduled = true; + } + + } + + } + + private function createSocket() + { + $socket = socket_create(AF_INET, SOCK_STREAM, 0); + + socket_set_option($socket,SOL_SOCKET,SO_REUSEADDR,1); + + if ($socket == false) + { + return false; + } + + socket_clear_error($socket); + + if (socket_set_nonblock($socket) == false) + { + @socket_close($socket); + return false; + } + + return $socket; + } + + public function bindIP($sockInt, $ip) + { + if (!isset($this->socketInfo[$sockInt])) + { + return; + } + + $sockData = $this->socketInfo[$sockInt]; + + if ($sockData->status != SOCK_CONNECTING) + { + return; + } + + socket_bind($sockData->socket, $ip); + } + + public function addSocket($host, $port) + { + $listening = false; + + if ($host == null) + { + $host = false; + $listening = true; + } + + $socket = $this->createSocket(); + + if ($socket == false) + { + return false; + } + + if ($listening == true) + { + $boundError = false; + $currentPort = $this->tcpRangeStart; + + if ($port !== null) + { + $boundError = @socket_bind($socket, 0, $port); + if ($boundError === false) + { + return false; + } + } + else + { + while ($boundError === false) + { + $boundError = @socket_bind($socket, 0, $currentPort); + $currentPort++; + + if ($currentPort > $this->tcpRangeStart + HIGHEST_PORT) + { + return false; + } + } + + $port = $currentPort - 1; + } + + + if (socket_listen($socket) === false) + { + return false; + } + + + if (DEBUG == 1) + { + echo "Socket Listening: " . intval($socket) . "\n"; + } + + + } + else + { + if (DEBUG == 1) + { + echo "Socket Opened: " . intval($socket) . "\n"; + } + } + + $newSock = new socketInfo; + + $newSock->socket = $socket; + $newSock->owner = null; + $newSock->class = null; + $newSock->func = null; + $newSock->readQueue = ""; + $newSock->writeQueue = ""; + $newSock->readLength = 0; + $newSock->writeLength = 0; + $newSock->host = $host; + $newSock->port = $port; + $newSock->newSockInt = array(); + $newSock->readScheduled = false; + $newSock->writeScheduled = false; + + $this->socketInfo[intval($socket)] = $newSock; + + if ($listening == true) + { + $newSock->status = SOCK_LISTENING; + } + else + { + $newSock->status = SOCK_CONNECTING; + } + + $this->numSockets++; + $this->rawSockets[] = $socket; + + return intval($socket); + } + + public function clearReadSchedule($sockInt) + { + if (!isset($this->socketInfo[$sockInt])) + { + return false; + } + + $this->socketInfo[$sockInt]->readScheduled = false; + + return true; + } + + public function clearWriteSchedule($sockInt) + { + if (!isset($this->socketInfo[$sockInt])) + { + return false; + } + + $this->socketInfo[$sockInt]->writeScheduled = false; + + return true; + } + +/* + public function beginConnect($sockInt) + { + $this->procQueue->addQueue(null, $this, "connectSocketProcess", $sockInt, 0); + } +*/ + + //process to connect the socket $sockInt + public function connectSocketTimer($sockInt) + { + if (!isset($this->socketInfo[$sockInt])) + { + return false; + } + + if ($this->socketInfo[$sockInt]->status != SOCK_CONNECTING) + { + return false; + } + + $this->connectSocket($sockInt); + + return true; + } + + + //Remove all sockets from a specific irc bot + public function removeOwner($class) + { + foreach($this->socketInfo AS $sockInt => $data) + { + if ($class === $data->owner) + { + $this->killSocket($sockInt); + $this->removeSocket($sockInt); + } + } + } + + public function killSocket($sockInt) + { + if (DEBUG == 1) + { + echo "Killing socket: " . $sockInt . "\n"; + } + + if ($this->socketInfo[$sockInt]->status == SOCK_ACCEPTED) + { + $this->acceptedSockets--; + } + + if ($this->socketInfo[$sockInt]->status != SOCK_DEAD) + { + $this->removeReadSocketFromArray($sockInt); + $this->removeWriteSocketFromArray($sockInt); + $this->socketInfo[$sockInt]->status = SOCK_DEAD; + } + + if (is_resource($this->socketInfo[$sockInt]->socket)) + { + if (DEBUG == 1) + { + echo "Closed socket: " . $sockInt . "\n"; + } + + socket_clear_error($this->socketInfo[$sockInt]->socket); + socket_close($this->socketInfo[$sockInt]->socket); + } + else + { + if (DEBUG == 1) + { + echo "Socket already closed: " . $sockInt . "\n"; + } + } + } + + public function removeSocket($socketIntval) + { + + $this->readQueueSize -= $this->socketInfo[$socketIntval]->readLength; + + unset($this->socketInfo[$socketIntval]->class); + unset($this->socketInfo[$socketIntval]->owner); + unset($this->socketInfo[$socketIntval]); + } + + private function removeReadSocketFromArray($socketIntval) + { + foreach ($this->rawSockets AS $index => $socket) + { + if ($socket === $this->socketInfo[$socketIntval]->socket) + { + unset($this->rawSockets[$index]); + $this->numSockets--; + break; + } + } + } + + private function removeWriteSocketFromArray($socketIntval) + { + + foreach ($this->writeSocks AS $index => $rawSocket) + { + if ($rawSocket === $this->socketInfo[$socketIntval]->socket) + { + unset($this->writeSocks[$index]); + $this->numWriteSocks--; + break; + } + } + } + + public function sendSocket($sockInt, $data) + { + if ($this->isDead($sockInt)) + { + return; + } + + if ($this->socketInfo[$sockInt]->status != SOCK_CONNECTED) + { + return; + } + + $inQueue = $this->socketInfo[$sockInt]->writeLength > 0 ? true : false; + + $len = strlen($data); + $this->socketInfo[$sockInt]->writeQueue .= $data; + $this->socketInfo[$sockInt]->writeLength += $len; + + if (!$inQueue) + { + $this->sendSocketQueue($sockInt, 0); + + if ($this->socketInfo[$sockInt]->status == SOCK_CONNECTED) + { + if ($this->socketInfo[$sockInt]->writeLength > 0) + { + $this->writeSocks[] = $this->socketInfo[$sockInt]->socket; + $this->numWriteSocks++; + } + } + } + + return $len; + } + + private function acceptSocket($sockInt) + { + $sockData = $this->socketInfo[$sockInt]; + + $newSock = @socket_accept($sockData->socket); + socket_set_nonblock($newSock); + + if ($newSock === false) + { + return false; + } + + $newSockInt = intval($newSock); + + if (DEBUG == 1) + { + echo "Accepted new connection on: " . $newSockInt . "\n"; + } + + + $this->socketInfo[$newSockInt] = clone $sockData; + $this->socketInfo[$newSockInt]->socket = $newSock; + $this->socketInfo[$newSockInt]->status = SOCK_ACCEPTING; /* fix a onRead done before onAccept */ + + $this->numSockets++; + $this->rawSockets[] = $newSock; + + $this->socketInfo[$sockInt]->newSockInt[] = $newSockInt; + + //Schedule the callback to run + $this->callBack($sockInt, CONN_ACCEPT); + + return true; + } + + private function connectSocket($sockInt) + { + if (!isset($this->socketInfo[$sockInt])) + { + return; + } + + if ($this->socketInfo[$sockInt]->status == SOCK_CONNECTED) + { + return; + } + + if (@socket_connect($this->socketInfo[$sockInt]->socket, + $this->socketInfo[$sockInt]->host, + $this->socketInfo[$sockInt]->port) === true) + { + $this->socketInfo[$sockInt]->status = SOCK_CONNECTED; + $this->callBack($sockInt, CONN_CONNECT); + } + else + { + $socketError = socket_last_error($this->socketInfo[$sockInt]->socket); + + switch ($socketError) + { + case 10022: + if (OS != 'windows') + { + $this->markDead($sockInt); + } + break; + case EISCONN: + $this->socketInfo[$sockInt]->status = SOCK_CONNECTED; + $this->callBack($sockInt, CONN_CONNECT); + break; + case EAGAIN: + case EALREADY: + case EINPROGRESS: + break; + default: + $this->markDead($sockInt); + break; + } + } + + return; + } + + public function getSockStatus($sockInt) + { + return (isset($this->socketInfo[$sockInt]) ? $this->socketInfo[$sockInt]->status : false); + } + + public function getSockData($sockInt) + { + return (isset($this->socketInfo[$sockInt]) ? $this->socketInfo[$sockInt] : false); + } + + public function alterSocket($sockInt, $level, $opt, $val) + { + return socket_set_option($this->socketInfo[$sockInt]->socket, $level, $opt, $val); + } + + public function getSockError($sockInt) + { + return socket_last_error($this->socketInfo[$sockInt]->socket); + } + + public function getSockStringError($sockInt) + { + $strErr = "[" . self::getSockError($sockInt) . "]:" . socket_strerror(socket_last_error($this->socketInfo[$sockInt]->socket)); + $strErr = str_replace("\n", "", $strErr); + return $strErr; + } + + public function hasAccepted($sockInt) + { + if (!isset($this->socketInfo[$sockInt])) + { + return false; + } + + $newSockInt = array_shift($this->socketInfo[$sockInt]->newSockInt); + + $this->socketInfo[$newSockInt]->status = SOCK_CONNECTED; + + return $newSockInt; + } + + private function markDead($sockInt) + { + if (DEBUG == 1) + { + echo "Marking socket dead: " . $sockInt . "\n"; + } + + $this->removeReadSocketFromArray($sockInt); + $this->removeWriteSocketFromArray($sockInt); + $this->socketInfo[$sockInt]->status = SOCK_DEAD; + $this->callBack($sockInt, CONN_DEAD); + } + + public function isDead($sockInt) + { + $socket = $this->socketInfo[$sockInt]->socket; + + if (!is_resource($socket)) + { + $this->markDead($sockIntval); + return true; + } + + if (!isset($this->socketInfo[$sockInt])) + { + return true; + } + + switch ($this->socketInfo[$sockInt]->status) + { + case SOCK_DEAD: + return true; + break; + default: + return false; + break; + } + } + + public function hasWriteQueue($sockInt) + { + if ($this->socketInfo[$sockInt]->writeLength > 0) + { + return $this->socketInfo[$sockInt]->writeLength; + } + else + { + return false; + } + } + + public function getQueue($sockInt) + { + $this->readQueueSize -= $this->socketInfo[$sockInt]->readLength; + $queue = $this->socketInfo[$sockInt]->readQueue; + unset($this->socketInfo[$sockInt]->readQueue); + $this->socketInfo[$sockInt]->readQueue = ""; + $this->socketInfo[$sockInt]->readLength = 0; + return $queue; + } + + public function hasQueue($sockInt) + { + if ($this->socketInfo[$sockInt]->readLength > 0) + { + return true; + } + + return false; + } + + + public function hasLine($sockInt) + { + if (strpos($this->socketInfo[$sockInt]->readQueue, "\n") !== false) + { + return true; + } + return false; + } + + public function getQueueLine($sockInt) + { + $readQueue =& $this->socketInfo[$sockInt]->readQueue; + + if (!$this->hasLine($sockInt)) + { + return false; + } + + $crlf = "\r\n"; + $crlfLen = 2; + + $lineEnds = strpos($readQueue, $crlf); + + if ($lineEnds === false) + { + $crlf = "\n"; + $crlfLen = 1; + $lineEnds = strpos($readQueue, $crlf); + } + + $line = substr($readQueue, 0, $lineEnds); + $readQueue = substr($readQueue, $lineEnds + $crlfLen); + + $this->readQueueSize -= ($lineEnds + $crlfLen); + $this->socketInfo[$sockInt]->readLength -= ($lineEnds + $crlfLen); + + if ($readQueue == "") + { + unset($this->socketInfo[$sockInt]->readQueue); + $this->socketInfo[$sockInt]->readQueue = ""; + } + + return $line; + } + + + + /* Misc HTTP Functions */ + + public static function generatePostQuery($query, $host, $path, $httpVersion = "1.0") + { + if ($query != "" && substr($query, 0, 1) != "?") + { + $query = "?" . $query; + } + + if ($path == "") + { + $path = "/"; + } + + $postQuery = "POST " . $path . " HTTP/".$httpVersion."\r\n"; + $postQuery .= "Host: " . $host . "\r\n"; + $postQuery .= "Content-type: application/x-www-form-urlencoded\r\n"; + $postQuery .= "Content-length: " . strlen($query) . "\r\n\r\n"; + $postQuery .= $query; + + return $postQuery; + } + + public static function generateGetQuery($query, $host, $path, $httpVersion = "1.0") + { + if ($path == "") + { + $path = "/"; + } + + if ($query != "" && substr($query, 0, 1) != "?") + { + $query = "?" . $query; + } + + $getQuery = "GET " . $path . $query . " HTTP/".$httpVersion."\r\n"; + $getQuery .= "Host: " . $host . "\r\n"; + $getQuery .= "Connection: close\r\n"; + $getQuery .= "\r\n"; + + return $getQuery; + } + +} + +?> diff --git a/ircbot/timers.php b/ircbot/timers.php new file mode 100644 index 0000000..ac85b99 --- /dev/null +++ b/ircbot/timers.php @@ -0,0 +1,235 @@ +<?php +/* ++--------------------------------------------------------------------------- +| PHP-IRC v2.2.1 Service Release +| ======================================================== +| by Manick +| (c) 2001-2005 by http://www.phpbots.org/ +| Contact: manick@manekian.com +| irc: #manekian@irc.rizon.net +| ======================================== ++--------------------------------------------------------------------------- +| > timers module +| > Module written by Manick +| > Module Version Number: 2.2.0 ++--------------------------------------------------------------------------- +| > 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ++--------------------------------------------------------------------------- +| Changes +| =======------- +| > If you wish to suggest or submit an update/change to the source +| > code, email me at manick@manekian.com with the change, and I +| > will look to adding it in as soon as I can. ++--------------------------------------------------------------------------- +*/ + +/* + * Redesigned 12/22/04... Yes... this file sucks. I basically am going to + * do a few things here. I have been stuck on this file for the past 2 weeks, + * trying to come up with some ideas to handle the hopeless situation which + * faced me. With the new queue system, this timer class became very interesting + * to handle. I had to decide how I would add proc queues to the queue class + * to handle specific timers in this file. Say, for instance, that a timer + * is added, and then another is added that has a shorter time than that one. + * The first one will have a proc queue added into the queue class, but then + * the second one will have to add another proc queue. But hold on a second, + * the way this works right now, after a timer is done running, the next one + * is added to the process queue. I handled this by keeping track of how + * many processes were in the queue, and didn't add one if there were more than + * one, and the call to setCurrentTimer was from handle(). This worked, unless + * you have a timer that repeats. Then the problem comes in, as it will not be + * added to the proc queue until the next timer is complete. To just handle + * this problem, I'm just going to add a proc to the queue for every timer + * that is added, and then every timer will have a queue in the procqueue. + * that way, we don't have to worry about anything. + * + * Also, I added a "timerStack" so that I could have reserved names and what + * not. Each timer gets a unique name or ID, and that is added to the stack, + * as well as sorted into the timerList. + * + * Okay, way that timers are handled... has changed, if you want a timer to + * repeat, you must return true from the timer, runOnce was removed. + * + * Ooohh oohh ohh! Idea. screw linked lists and shit. I'll just add each + * timer to the proc queue, and then have them call handle() with the timer + * referenced! This solves all problems, and is incredibly more efficient! + * This officially takes the last linked list out of my bot. I have NO IDEA + * why I even used them in the first place, as php already has associative arrays + * which are a lot better! GEEZ! + */ + +class timers { + + //Local variables + private $timerStack = array(); //list of all timers indexed by name + + //External Classes + private $procQueue; + private $socketClass; + private $ircClass; + + //Private list of reserved php-irc timer names (please do not + //use these names) + private $reserved = array( "listening_timer_[0-9]*", + "check_nick_timer", + "check_channels_timer", + "check_ping_timeout_timer", + ); + + public function __construct() + { + $this->time = time(); + $this->timerStack = array(); + } + + public function setSocketClass($class) + { + $this->socketClass = $class; + } + + public function setIrcClass($class) + { + $this->ircClass = $class; + } + + public function setProcQueue($class) + { + $this->procQueue = $class; + } + + public static function getMicroTime() + { + return microtime(true); + } + + public function getTimers() + { + return $this->timerStack; + } + + public function handle($timer) + { + $microTime = self::getMicroTime(); + + if (!isset($this->timerStack[$timer->name])) + { + return false; + } + + if ($this->timerStack[$timer->name] !== $timer) + { + return false; + } + + $timer->lastTimeRun = $microTime; + $timer->nextRunTime = $microTime + $timer->interval; + + if ($timer->class != null) + { + $theFunc = $timer->func; + $status = $timer->class->$theFunc($timer->args); + } + else + { + $theFunc = $timer->func; + $status = $theFunc($timer->args); + } + + if ($status != true) + { + $this->removeTimer($timer->name); + } + else + { + $this->procQueue->addQueue($this->ircClass, $this, "handle", $timer, $timer->interval); + } + + return false; + } + + public function removeAllTimers() + { + foreach ($this->timerStack AS $timer) + { + $this->removeTimer($timer->name); + } + } + + + public function addTimer($name, $class, $function, $args, $interval, $runRightAway = false) + { + if (trim($name) == "") + { + return false; + } + + if (isset($this->timerStack[$name])) + { + return false; + } + + $newTimer = new timer; + + $newTimer->name = $name; + $newTimer->class = $class; + $newTimer->func = $function; + $newTimer->args = $args; + $newTimer->interval = $interval; + $newTimer->removed = false; + + if ($runRightAway == false) + { + $newTimer->lastTimeRun = $this->getMicroTime(); + $newTimer->nextRunTime = $this->getMicroTime() + $interval; + $tInterval = $interval; + } + else + { + $newTimer->lastTimeRun = 0; + $newTimer->nextRunTime = $this->getMicroTime(); + $tInterval = 0; + } + + $this->procQueue->addQueue($this->ircClass, $this, "handle", $newTimer, $tInterval); + + $this->timerStack[$newTimer->name] = $newTimer; + + return $name; + } + + /* Remove the current timer from both the list and stack, changed in 2.1.2, can only call by + * timer name now. + */ + public function removeTimer($name) + { + if (!isset($this->timerStack[$name])) + { + return false; + } + + //Set removed flag, + $this->timerStack[$name]->removed = true; + + //Remove from stack + unset($this->timerStack[$name]->args); + unset($this->timerStack[$name]->class); + unset($this->timerStack[$name]); + + return true; + } + +} + +?> diff --git a/ircbot/typedefs.conf b/ircbot/typedefs.conf new file mode 100644 index 0000000..07d09cc --- /dev/null +++ b/ircbot/typedefs.conf @@ -0,0 +1,75 @@ +type file ~ ;----Used to import files + name ~ ;----the module name + filename + +type section ~ ;----Used to section functions in dcc chat (used with 'help') + name ~ ;-id name to specify with dcc function + longname ~ ;-section name displayed when 'help' is pressed + +type ctcp ~ ;----handle all ctcp commands + name ~ ;----the command typed (or trigger, like !list) + module ~ + function + +type priv ~ ;----Used to process input of users in channels + name ~ + active ~ + inform ~ + canDeactivate ~ + usage ~ + module ~ + function + +type dcc ~ ;----Used to process input of users in dcc interface + name ~ + numArgs ~ + usage ~ + help ~ + admin ~ + module ~ + function ~ + section ;--added 2.2.0 + +type privmsg ~ ;----Used to process privmsg irc command + module ~ + function + +type notice ~ ;----Used to process notice irc command + module ~ + function + +type mode ~ ;----Used to process mode irc command + module ~ + function + +type join ~ ;----Used to process join irc command + module ~ + function + +type kick ~ ;----Used to process kick irc command + module ~ + function + +type part ~ ;----Used to process part irc command + module ~ + function + +type quit ~ ;----Used to process quit irc command + module ~ + function + +type connect ~ ;----Perform on connect + module ~ ;----Note this is really 004 from irc + function + +type disconnect ~ ;----Perform on disconnect $line['text'] holds reason + module ~ ;----Note, this is really ERROR from irc + function + +type raw ~ + module ~ + function + +type action ~ + module ~ + function diff --git a/manual/beta5/en/account_games.lwdoc b/manual/beta5/en/account_games.lwdoc new file mode 100644 index 0000000..41baad4 --- /dev/null +++ b/manual/beta5/en/account_games.lwdoc @@ -0,0 +1,109 @@ +<?xml version="1.0"?> +<lwdoc> + <version>beta5</version> + <language>en</language> + <title>Accounts and Games +
+ Each player in LegacyWorlds has a single account which provides him access to all possible games he wishes to play. This section of the manual explains in details how to create and activate an account along with account deletion and how to manage games. +
+
+
+ From the game home page, clicking on the Create Account link directs you to account creation form. You have to fill in:
    +
  • Username: your in game name
  • +
  • Language: language in which you want everything to be displayed
  • +
  • E-mail: a valid e-mail address to be associated with the account. This has to be typed in twice to avoid mistakes
  • +
  • Password: the password to be used along with your username to access your account. This has to be typed in twice to avoid mistakes
  • +
+ Once your are satisfied with the data you've entered you can click the Create Account Button. +
+ If the account creation process has been successful an e-mail is sent to the provided e-mail address containing:
    +
  • a reminder about the typed in account information
  • +
  • information about the account activation procedure
  • +
+
+
+ The e-mail you have received when creating an account contains information to activate your account. In order to activate your account you have to look at the top part of the left panel of the home page. A logging form is displayed there. In the form, you have to provide:
    +
  • Username: the in game username you've chosen
  • +
  • Password: the password you've chosen
  • +
+ You can then click the ok button to log in. +
+ A new form is then displayed asking for a confirmation code. This code has been sent to you in the e-mail mentioned above. Type in the code in the provided text field and click on the Confirm account creation button. If you provide the right code you get directed towards the main game selection page. +
+
+
+ Closing your account allows you to delete all game data related to your account. But be careful: it doesn't deleted your account and you can reactivate it any time you wish. The next paragraphs will cover those topics more closely. +
+
+
+ The form to close your account is accessible from the home page of the game when you are logged it. In order to access the page you have two options:
    +
  • if you're not logged in, follow the logging in procedure. The bottom section of the page displayed in the body of the home page should be about closing your account. Below an introduction about the topic, Is displayed a Close my account button. Clicking the button opens the forms that lead to the path to closing your account
  • +
  • if you are logged in and playing a LegacyWorlds game, clicking on the My Account menu item gets you to the logged in home page of the game. As in the previous case below the introduction about the topic, is displayed the Closing my account button. Clicking the button opens the forms that lead to closing your account
  • +
+
+
+ Clicking the button opens a new form that you have to fill in in order to close the account. You have to provide the following information:
    +
  • Your password in the corresponding textfield to make sure you're really you're who you're saying you are
  • +
  • Optionally a text area is provided where you can if you wish explain why you're closing your account
  • +
+ Below these two fields are displayed two buttons:
    +
  • I really want to close my account: clicking this button starts a countdown to real account closing. You have a 24 to 48h delay to cancel the action
  • +
  • No, I was just kidding: clicking this button cancels the account closing procedure
  • +
+
+
+
+
+ During the delay to account closing you can still cancel the operation. The form to cancel account closing is accessible from the home page of the game when you are logged it. In order to access the page you have two options:
    +
  • if you're not logged in, follow the logging in procedure. The bottom section of the page displayed in the body of the home page should be about closing your account. Below an introduction about the topic and information about the time at which the account will be closed, is displayed a Don't close my account button. Clicking the button opens the forms that allow to cancel the closing operation
  • +
  • if you are logged in and playing a LegacyWorlds game, clicking on the My Account menu item gets you to the logged in home page of the game. As in the previous case below the introduction about the topic, is displayed the Don't close my account button. Clicking the button opens the forms that allow to cancel the operation
  • +
+
+
+ Clicking the button opens a new page with two buttons:
    +
  • Yes, cancel the countdown: clicking this button cancels account closing
  • +
  • No, get on with it: clicking this button goes on with the account closing procedure
  • +
+
+
+
+ You have to be aware of the fact that closing your account doesn't delete your account information from the LegacyWorlds database but only your game data. This prevents anyone from using your player name at your place and impersonate you in game. It also allows you to reactivate your account. +
+ This also means that all information regarding your account and in particular your personal data are permanently stored in the LegacyWorlds database. If you want those information to be erased from the database, please contact the staff. +
+
+ If you want to reactivate your account you only have to log in as usual. Joining a LegacyWorlds game is then all the same as for new accounts. +
+
+
+
+ In the login form of the left panel of the home page you have to fill in:
    +
  • Username: the in game username you've chosen
  • +
  • Password: the password you've chosen
  • +
+ You can then click the Log in -> button to log in. +
+
+
+ Several LegacyWorlds games may be running at once. You can choose which games you want to play from the main game selection page you reach when logging in. +
+
+ The game selection list provides informations about current available games, including:
    +
  • Game name: name of the game. This name is a link to join that particular game
  • +
  • Players: number of players in this game
  • +
  • Running: if you are not currently playing this game this field presents if the game is running or not thus informing you if you can join in or not
  • +
+ In order to join a particular game click on its name in the games list. +
+ You'll get then directed to a planet creation form. This form will allow you to choose the name of your first planet in the game. Type in the planet name in the provided text field and click the ok button to validate your input. You can now start taking care of your newborn empire in this game. +
+
+ A list of current games you are playing is also provided. This list presents:
    +
  • Game name: name of the game. This name is a link to play that particular game
  • +
  • Planets: number of planets in your empire in this game
  • +
  • Cash: amount of money you have in this particular game
  • +
+ In order to play a particular game click on its name in the list. You'll get directed to the overview page for this particular game. +
+
+ diff --git a/manual/beta5/en/accounts.lwdoc b/manual/beta5/en/accounts.lwdoc new file mode 100644 index 0000000..9a435ce --- /dev/null +++ b/manual/beta5/en/accounts.lwdoc @@ -0,0 +1,35 @@ + + + beta5 + en + Accounts +
+ Along with the good practices, some rules apply to your account in Legacy Worlds. Those are summed up in this section of the manual. +
+
+ As for every game of the kind the general rule is: one account per player and one account only. +
+ Having multiple accounts for one player provides too big an advantage to the player controlling them and it wouldn't be a fair game to allow such a behaviour. It's simply cheating. +
+ There might not be an automated Anti-Multi System in Legacy Worlds for now but the small number of current players allows for the game administration team to check in the game database and spot by hand suspect behaviours. An automated system will be set up in a future Beta so it doesn't cost much to get good habits from the beginning. +
+
+ Occasional password sharing among a little group of players is tolerated as long as several accounts are in the control of the same player for only a short period of time. Holding a database or file of allied account information isn't forbidden either as long as the actual use of those data complies with the previous statement. +
+ The "trusted allies" list should provide you with most of the gains you might expect from password sharing. Use this feature... +
+
+ Depending on their ages, newly created accounts face a certain amount of limitations that are lifted over time:
    +
  • Your account has to be 10 days old to allow cash transfer
  • +
  • Your account has to be 10 days old to allow for diplomatic exchange of technologies
  • +
+
+
+ Inactive accounts are accounts on which no activity hasn't occured in a certain amount of time. Those accounts will be neutralised. This means they will still exist in the database but won't be involved in any current game anymore. You may be able to reactivate them but you'll have to rejoin all the games you were playing. +
+
+ In order to check on suspicious behaviour from some players the game administrators might have to look into the database for insult messages and such. As a consequence you have to be aware that they might read your messages. So be careful about what you may say in those. +
+ You also have to be aware that no messages are actually deleted even if you delete them in-game. They are kept in the database with a "deleted" flag. +
+
diff --git a/manual/beta5/en/alliance.lwdoc b/manual/beta5/en/alliance.lwdoc new file mode 100644 index 0000000..73e0c57 --- /dev/null +++ b/manual/beta5/en/alliance.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Alliance +
+ An alliance is a group of players who have decided to team up and work together to be stronger in the game. An alliance is identified by its tag, which usual consists of a short hand for its name. An alliance has a leader and is composed of members of different custom ranks. Depending on the privileges attached to their rank members have access to various types of features on the alliance page. An alliance can be either a democracy (members can vote for their leader) or a dictature (no voting system). Alliance specific forums can also be created by members of sufficient rank and their access to members can be restricted according to their rank. +
+
+ At the beginning of the game you don't belong to any alliance. You can then choose among several options:
    +
  • Remain a lone gunman
  • +
  • Join an existing alliance
  • +
  • Create a new alliance
  • +
+ We'll have a closer look at the two last options in the corresponding manual section. +
+
+ The internal organisation of an alliance is usually set up by its leadership and we won't cover those matters in this manual. +
+ As member of an alliance and depending on your rank inside the alliance you may have access to various alliance management features. You are invited to read the manual sections covering the features you have access to to learn more about them. +
+
+ The alliance management pages provide you with useful information about the alliance in the same trend as the alliance information previously described or listings of members and controlled planets. It also offers means to manage members, alliance specific forums and ranks. The various sections of this part of the manual describe those features. +
+ +
+
+ diff --git a/manual/beta5/en/alliance_create.lwdoc b/manual/beta5/en/alliance_create.lwdoc new file mode 100644 index 0000000..2e04d59 --- /dev/null +++ b/manual/beta5/en/alliance_create.lwdoc @@ -0,0 +1,47 @@ + + + beta5 + en + Creating / Joining an Alliance +
+ At the beginning of the game you don't belong to any alliance. You can then choose among several options:
    +
  • Remain a lone gunman
  • +
  • Join an existing alliance
  • +
  • Create a new alliance
  • +
+ We'll have a closer look at the two last options in the following paragraphs. +
+
+ Before joining an alliance you have to know what the existing alliances are and which ones might be looking for new members. Several means at are your disposal:
    +
  • The general forums: among the general forums there is a recrutment forum in which alliances which are recruting new members can advertise. You can choose one from there
  • +
  • The rankings: on the ranking page, there is an alliance ranking section from which you can pick your future alliance.
  • +
  • The maps: there is an alliance display mode for the maps which reveals alliance tags on the map. You can select a neighbouring alliance to send a joining request to.
  • +
+ Once you've selected an alliance to join or in order to choose one you might be interested in getting more data on the alliance. The alliance information section of the alliance page allows you to do just that. +
+ Once you've made you decision about the alliance you want to join you may send a joining request. It's what the Join an alliance section of the page is for. In order to send a joining request you have to type the alliance tag in the relevant text field and click the Send request button. +
+ The section of the page now present a status on the current sent request. In case you'd change you mind you can click the Cancel Request button to cancel your request. +
+ A message will be sent to your account and stored in the Internal Transmission folder once your joining request is accepted or rejected. +
+ Keep also in mind that you can only send one request at once and can only be member of one alliance at a time. +
+
+ Providing the alliance tag in the text field and clicking the display information button reveals:
    +
  • Alliance name: full length name of the alliance
  • +
  • Leader: name of the player leading the alliance
  • +
  • Rank: rank of the alliance in the alliance ranking
  • +
  • Points: number of points for the alliance in the rankings
  • +
  • Planets: total number of planets controlled by members of this alliance
  • +
  • Avg. coordinates: average coodinates of the planets controlled by this alliance
  • +
+
+
+ In order to create a new alliance you have to provide two different elements in the relevant text fields of this section of the page:
    +
  • Alliance tag: it's the short hand used for joining request and diplays on the maps and so on
  • +
  • Alliance name: complete name of the alliance
  • +
+ You can now click the Create this alliance button. Your alliance will be created and you'll have a lot to do to manage it, as presented in other sections of this manual. +
+
diff --git a/manual/beta5/en/alliance_manage.lwdoc b/manual/beta5/en/alliance_manage.lwdoc new file mode 100644 index 0000000..cebb2d3 --- /dev/null +++ b/manual/beta5/en/alliance_manage.lwdoc @@ -0,0 +1,173 @@ + + + beta5 + en + Alliance Management +
+ The alliance management pages provide you with useful information about the alliance in the same trend as the alliance information previously described or listings of members and controlled planets. It also offers means to manage members, alliance specific forums and ranks. The various sections of this part of the manual describe those features. +
+
+
+ This section present the main elements concerning the alliance along with means to acquire data about other alliances and a way to leave it. +
+
+ This part of the page displays your status inside the alliance (leader or member) along with useful data similar to those displayed in the Alliance Information part. See this manual section for an exact list. +
+ If you are leader of the alliance and have a successor you can choose to step down by clicking the corresponding button in this part of the page. Your successor will automatically become the new leader of the alliance. +
+ The Leave button also allows you to leave the alliance whenever you want. +
+
+ The leader can change here some general settings for the alliance:
    +
  • Government: it can be either Dictatorial (members do have access to any voting facility to elect a new leader) or Democratic (members can vote for their leader). The goverment system can be chosen by using the radio button in front of the government system to select.
  • +
  • Successor: a member name can be put in the texfield. This member will automatically become the alliance leader if the current one loses all his planets, steps down or leaves the alliance.
  • +
+ Once data are changed in this section, two buttons appear. The Update button allows to take changes into account. The Reset button reverts to previous settings. +
+
+ It's the same system as when you have'nt joined an alliance yet. +
+
+
+
+ This section provides different listings linked with alliances:
    +
  • Alliance Planets: lists the planets belonging to the alliance
  • +
  • Alliance Members: lists the players who are members of the alliance
  • +
  • Planets under Attack: lists the planets which are part of the alliance and currently are under attack
  • +
+ It also offers common features for all lists. +
+
+ At the top of the page the Listing drop down list allows to switch among listings. +
+ You can also chose the number of elements to display per page with the coresponding drop down list. When several pages are necessary a specific drop down list appear to allow you to move between pages. +
+ A search facility is also provided. When it is relevant you can either choose to search a planet or a player name by selected the radio button before the item you're interested in. When filling in the search textfield the listing will be automatically limited to the items containing the string you've typed in wherever it may be in the whole name. +
+
+ This lists all planets in the alliance including for each of them:
    +
  • Coordinates: coordinates of the planets
  • +
  • Planet: name of the planet
  • +
  • Owner: name of the player who owns the planet
  • +
  • Factories: total number of factories on the planet
  • +
  • Turrets: total number of turrets on the planet
  • +
+ In this listing, clicking on the title of one column allows to order the list according to the values of that field. Depending on the direction of the arrow which appears the order is ascending or descending. Clicking again on the same title changes the order from ascending to descending and the other way around. +
+ A colour code is used to allow you to see if planets are under attack at a glance; these are displayed in red. +
+
+ This lists all members in the alliance including:
    +
  • Name: name of the player
  • +
  • Rank: rank of the player inside the alliance; see rank administration of this manual to learn more about the alliance rank system
  • +
+ In this listing, clicking on the title of one column allows to order the list according to the values of that field. Depending on the direction of the arrow which appears the order is ascending or descending. Clicking again on the same title changes the order from ascending to descending and the other way around. +
+ If you have sufficient privileges a checkbox is diplayed before each member name. Checking the box displays some actions features at the bottom of the page including two buttons:
    +
  • Kick: to kick the selected members out of the alliance
  • +
  • Change their rank: to change the rank of the selected members to the rank selected in the drop down list displayed afterwards.
  • +
+
+
+ This lists planets belonging to the alliance which are currently under attack along with useful information such as:
    +
  • Coordinates: coordinates of the planet under attack
  • +
  • Planet: name of the planet under attack
  • +
  • Owner: name of the player who owner the planet under attack
  • +
  • Def. Power: power of the fleets which defend the planet
  • +
  • Att. Power: power of the fleets which attack the planet
  • +
  • Defenders: list of names of players who are currently defending the planet
  • +
  • Attackers: list of names of players who are currently attacking the planet
  • +
+ For each planet under attack a colour code represents the current defense status compared to enemy fleets:
    +
  • Green: defending fleets have more than 3 times the enemy average power. It's very likely no support is needed and defending fleets should win
  • +
  • Red: attacking fleets have more than 3 times the defending average power. It's very like the defenses will be defeated
  • +
  • Grey: the average power difference between defending and attacking fleets isn't that big and the outcome of the battle isn't obvious
  • +
+ In this listing, clicking on the title of one column allows to order the list according to the values of that field. Depending on the direction of the arrow which appears the order is ascending or descending. Clicking again on the same title changes the order from ascending to descending and the other way around. +
+ You have to be aware that the information provided in this listing may not be 100% accurate. Indeed the are influenced by technologies of the communications area and are subject to electronic counter measures (encryption methods and such) as long as electronic counter counter measures (jamming). The accuracy of the data displayed depends both on the level of technology you and the enemy have in the field. +
+
+
+ When a player requests to join an alliance for which you can manage pending joining requests a message is sent to your account and stored in the Internal Transmissions folder. +
+ The Pending Requests part of the Alliance page displays the list of pending joining requests. To accept or reject a given player you have to check the checkbox in front of his name and click the relevant button: Accept or Reject. +
+
+
+ This section of the alliance management page allows you to perform administration tasks on the alliance specific forums. You can both create and manage those forums from here. +
+
+ The Create a forum link in the top right corner of the page directs you to a forum creating form you have to fill in properly in order to create a new forum. Before creating the forum you have to provide the following information:
    +
  • Forum name: in this textfield you have to type a new name for the future forum
  • +
  • New threads: you have to select who will be allowed to create new threads in the forum by clicking the radio button before the option you're interested in. It can be either Everyone or Moderators only
  • +
  • Initial position: this drop down list allows you to select where to place the forum in the forum management page compared to the other existing forums
  • +
  • Description: in this textarea you can type in a longer description of the forum
  • +
  • Forum access: in this section you have to decide for an access level for each of the customised ranks defined for the alliance. In order to do so you have to check the checkbox located before the rank(s) you want to change access for. Three buttons will appear at the bottom of the page to allow you to manage the access level of the given rank(s):
      +
    • No access: in order to forbid access to that forum to members of this rank click on the Give no access button
    • +
    • Standard access: in order to provide access with no extra privileges to the forum for members of that rank click the Give standard access button
    • +
    • Moderators: in order to give moderator privileges for that forum to the members of this rank click the Make moderator button
    • +
    +
  • +
+ You can now either click the OK button to create the forum or the Cancel one to cancel your changes. +
+
+ Once created, all forums are listed in the Alliance Forums page along with:
    +
  • Name and description: name and description of the forum along with useful links
  • +
  • New threads: information about who can create new threads in that forum
  • +
+ Along with each forum name and description a set of links allow to manage them:
    +
  • Edit: goes back to the forum creation form with prefilled data for the current forum so that you can make changes
  • +
  • Delete: allows you to delete the given forum
  • +
  • Move down: allows you to move the forum down in the list
  • +
  • Move up: allows you to move the forum up in the list
  • +
+
+
+
+
+ This section of the alliance management page allows you to both create and manage customised ranks of members of your alliance. These ranks are linked with a set of alliance features that you define here. +
+
+ The Create a rank link at the top right corner of the page directs you to a rank creation for you have to fill in. In order to create a rank you have to provide the following information:
    +
  • Designation: name of the rank as it will appear in the members' listing
  • +
  • List access: this drop down list allows you to select which alliance listing will be displayed for players of this rank. It can be either:
      +
    • Detailed planet list: all lists are available
    • +
    • Planet list: a less complete planet list is displayed
    • +
    • Member list: only the member list is available, the planet list is hidden
    • +
    • No access: no listing is displayed
    • +
    +
  • +
  • List of planets under attack: check this checkbox so that the player can see the list of planets currently under attack
  • +
  • Diplomatic contact: use this radio buttons to make members of this rank diplomatic contact for the alliance or not. As diplomatic contact members receive messages sent to the alliance
  • +
  • Can vote: use this radio button to allow members of this rank to vote or not in democratic alliances
  • +
  • Can apply for presidency: use this radio button to allow members of this rank to apply for presidency or not in democratic alliances
  • +
  • Member management: the following items allow you to decide what role in member management the players of this rank will have:
      +
    • Accept pending requests: use this radio button to allow members of this rank to accept pending joining request
    • +
    • Kick members: use this radio button to allow members of this rank to kick members of not. If the Only members of a specific rank option is chosen, a list of all currently existing ranks will be displayed with checkboxes before each rank. Check the boxes before the ranks you want the members of ths rank to be allowed to kick.
    • +
    • Change ranks: use this radio button to allow members of this rank to change the rank of other members or not. If the Only members of a specific rank option is chosen, a list of all currently existing ranks will be displayed with checkboxes before each rank. Check the boxes before the ranks you want the members of this rank to be allowed to promote / demote.
    • +
    +
  • +
  • Forum access: the following items allow you to decide what forum access the members of the new rank will have:
      +
    • Forum administrator: use this radio button to make members of the new rank forum administrators. As such they'd have all privileges on all forums and the following items will be hidden
    • +
    • Forum access: for each existing forum a radio button allows you to chose an access level for members of the new rank. It can be either No access, User or Moderator.
    • +
    +
  • +
+ Once you've made your modificaction you can either click the Ok button to create the new rank or the cancel button to cancel the changes. +
+
+ The Rank Administration page provides a list of all defined ranks for the alliance along with useful links including:
    +
  • Rank name: name of the rank as defined during its creation
  • +
  • Actions: actions which can be performed on the rank. It can be either edit or delete. The default rank Standard member can't be deleted but only edited
  • +
  • Members: number of members of this rank in the alliance
  • +
+ For each rank several actions are possible:
    +
  • [ + ]: clicking on this symbol before the name of the rank displays a short description based on the privileges attached to this rank
  • +
  • Edit: clicking on this link directs you to the rank creation page with prefilled values for the current rank so that you can make changes.
  • +
  • Delete: this link allows you to delete a rank. If the alliance has members who currently have this rank a drop down list of available ranks is displayed which allows you to choose to what rank to demote the members who have to rank to delete
  • +
+
+
+
diff --git a/manual/beta5/en/allies.lwdoc b/manual/beta5/en/allies.lwdoc new file mode 100644 index 0000000..bd95737 --- /dev/null +++ b/manual/beta5/en/allies.lwdoc @@ -0,0 +1,27 @@ + + + beta5 + en + Trusted Allies +
+ In Legacy Worlds you can entrust other players with control of your fleets while you're not online. This page allows you to manage the list of players you want to trust with your fleets in those circumstances along as to know which players might trust you with their fleets. +
+
+ Typing the name of a player in the provided textfield in this section of the page and clicking the Add button adds the player to your trusted allies list. You can add up to 5 players to the list. +
+
+ This list present your trusted allies in an ordered way. The different shades of green represent trust levels. When you're not online the player who can move your first is the first online player in the ordered trusted allies list, that is to say the online player with the higher trust level. +
+ A checkbox is displayed in front of each player name in the list. You have to check the checkbox in front of a name to select the corresponding player. You can perfom several actions on selected players:
    +
  • Remove: clicking on the Remove button removes the selected player from the trusted allies list
  • +
  • Up: clicking on the Up button moves the selected player up in the trusted allies list
  • +
  • Down: clicking on the down button moves the selected player down in the trusted allies list
  • +
+
+
+ This list present all players who trust you, including:
    +
  • Player: name of the player who has you in his trusted allies list
  • +
  • Trust Level: level of trust at which you are located in this player's list, that is to say your rank in his trusted allies list
  • +
+
+
diff --git a/manual/beta5/en/background.lwdoc b/manual/beta5/en/background.lwdoc new file mode 100644 index 0000000..a17a49c --- /dev/null +++ b/manual/beta5/en/background.lwdoc @@ -0,0 +1,17 @@ + + + beta5 + en + Background +
+ Land, resources, religion, ideology, colour of skin, power... any cause is worth it. A fight, a war. It could be now, it could be tomorrow. Does it really matter? In the end, the result is all the same: devasted cities, wastelands, undrinkable water, polluted soil and scattered survivors. After years of internal conflicts and devastating wars humanity has finally achieved its ultimate goal: the devastation of its home planet, Earth. What hope is there for those faced with an impossible task: survival on a radioactive piece of rock which could collapse into space dust at any time. The only option left for the remaining survivors of the holocaust: flee to the stars in the hope of finding a new home and rebuilding their civilisation. +
+
+ Hundreds of ships were launched into deep space. Their hope: finding a suitable planet, one that can sustain life as they knew it, one where they could settle and rebuild, one that they might take better care of than the previous one. They searched for years for such harbor, safe haven.Going deeper and deeper into the galaxy. Where noone has gone before. +
+
+ As commandant of a small group of colonists you've finally managed to find a planet matching the required parameters and you're about to start rebuilding on it. It will be up to you to make it grow into a new galactic empire within a hostile universe. To achieve that goal you'll have to find allies among the other Earth refugees who settled in the neighbouring star systems and fight those willing to spoil your hard work. +
+ It will also be up to you to provide your citizens with what they might need and encourage the scientists of the expedition in their research to achieve new technological breakthroughs. It will be up to you to fufill Earth's legacy... +
+
diff --git a/manual/beta5/en/battles.lwdoc b/manual/beta5/en/battles.lwdoc new file mode 100644 index 0000000..4c206bd --- /dev/null +++ b/manual/beta5/en/battles.lwdoc @@ -0,0 +1,60 @@ + + + beta5 + en + Battles +
+ Battles regulate the way military engagements occure in LegacyWorlds. The next parapgraphs describe how battles are managed in game. + When entering into battle mode, fleets remain unavailable until a battle tick has occured on the stellar object they are orbiting. +
+
+
+ Battles occure when the following conditions are met:
    +
  • Location: the fleets have to be at the same location. Same location means orbiting the same stellar object. Two fleets in hyperspace don't engage in battle because the particular nature of this parallel dimension renders weapons inefficient
  • +
  • Fleets' mode: the modes of the fleets have to be different ((one in defense and one in attack at least)
  • +
  • Vacation mode: in the case of an attack on a planet, the attacked planet has to belong to a player who isn't in vacation mode
  • +
  • Planet: a battle also occures when a fleet is in attack mode on a planet, be it defended by fleets or turrets or not
  • +
+
+
+ Battle computations correspond to the calculation of the outcome of a battle. They are regulated by the following rules:
    +
  • Battle computations take place at Battle ticks, every 4 hours
  • +
  • The losses of ships and turrets for each side are calculating depending on relative average power and fleet composition of each side
  • +
  • The primary factor taken into account is the relative power between attacking and defending fleets. The bigger the difference, the more losses the smaller fleet will sustain and the less losses the bigger fleet will have
  • +
  • The number of ships of each type being detroyed depends on fleet composition. The more ships of one category the more will be destroyed
  • +
+
+
+
+ Each time a battle tick during which you have been engaged in battle has occured, a battle report is sent in your Internal Transmissions folder by your Military Advisor. This report includes for each location a description of the fleets in presence and the outcome:
    +
  • Fleet categories: the report includes up to three categories of fleets:
      +
    • own fleets (green)
    • +
    • friendly fleets (blue)
    • +
    • enemy fleets (red)
    • +
  • +
  • Fleet composition: for each group of fleets is displayed the composition of the fleet before the battle (Start Column) and the losses (Losses column) for each ship category present and for Turrets if any, along with the same data in power of the fleet
  • +
  • Comment: at the bottom of the Battle Report the Military Advisor provides some insights on the next move to make
  • +
+
+
+
+ When attacking a planet with sufficient forces, a point is reached where all defenses have been destroyed, be they ships or turrets. At the next hour tick is then computed if anyone could take control of the planet. Owner change is decided based on the rules described in the next paragraphs. +
+
+ The control over the planet can only change if the ground assault troups have a isufficient control over the population. A single company can't control a whole planet, whatever elite they are, can't they? +
+ As a consequence a player has to have more GAs ships than the number of GA ships required to control the population, in order to take control of the planet. +
+ At the beginning of the game, the troups transported in 1 GA ship can control 200 population units. This number can be increased through technological advances such as:
    +
  • Exoskeleton
  • +
  • Nanofiber Armor
  • +
  • Self-repairing Exoskeleton
  • +
+
+
+ In some cases, several players with fleets including GA ships may be attacking the same planet at once. When several players are attacking the same planet, control over the planet is given to the one who has the capacity to control the more population. +
+ It doesn't necessarily mean that the player with the more GA ships will gain control. Everything depends on the level of technology each player involved has. +
+
+
diff --git a/manual/beta5/en/communications.lwdoc b/manual/beta5/en/communications.lwdoc new file mode 100644 index 0000000..f94f63b --- /dev/null +++ b/manual/beta5/en/communications.lwdoc @@ -0,0 +1,29 @@ + + + beta5 + en + Communications +
+ There are two major means of communication in LegacyWorlds: a messaging system and forums. These allow you to keep in touch with other players both inside your own alliance and outside of it. +
+
+ This page sums up your current status towards the messaging system and the forums. It allows to see at a glance where to read and where you attention is needed when things come to communications. This manual page describes the contents of this page. +
+
+ The Messaging System in Legacy Worlds is very similar to a simple mail client. It allows for folder creation to manage and store messages and offers a linear or threaded view of messages. In the bottom left part of the messaging system pages there is a link that allows you to switch to the forums. The various features are described in more details in the Messaging System section of the manual. +
+
+ Those forums are very similar to those you might find in any message board. + There are several categories of forums:
    +
  • Legacy Worlds Forums:- these are used by the staff to make announcements and by layers to report bugs and ask for new features.
  • +
  • General Forums:- these are used for general discussions about the game and beyond.
  • +
  • Legacy Worlds - Public beta 5:- these are public forums which are specific for Beta 5. They include alliance recruitment forums and such.
  • +
  • Alliance specific Forums:- these are the forums of each individual alliance. Their name correspond to the alliance name.
  • +
+ Forum features and forum categories are described more precisely in the Forums section of the manual. +
+ +
+
+
+ diff --git a/manual/beta5/en/communications_page.lwdoc b/manual/beta5/en/communications_page.lwdoc new file mode 100644 index 0000000..13fd190 --- /dev/null +++ b/manual/beta5/en/communications_page.lwdoc @@ -0,0 +1,55 @@ + + + beta5 + en + Communications Overview Page +
+ This page sums up your current status towards the messaging system and the forums. It allows to see at a glance where to read and where you attention is needed when things come to communications. This manual page describes the contents of this page. +
+ This page in split in two sections:
    +
  • Private Messages: this left part of the page presents an overview of the status of your messaging system
  • +
  • Forums: this right part of the part provides a summary about both general and alliance forums
  • +
+ The next parts of the manual page will describe each. +
+
+
+ This left part of the page presents data related to the messaging system. +
+ It first of all provides a Compose a Message which directs you to the message edition form where you can write a private message to send to either a player, a planet, or an alliance diplomatic staff. +
+ The next two sections of the page will be described in the next paragraphs. +
+
+ This part of the page presents the list of the three default folders that are available for all accounts:
    +
  • Inbox
  • +
  • Internal Transmissions
  • +
  • Outbox
  • +
+ For each of those folder, the name of the folder is the link to the contents of the folder. +
+ Next to the folder name, the total number of message contained in the folder is displayed. If unread messages are located in one folder, the number of unread messages is presented between brackets. +
+
+ This part of the page present the list of all the custom folders you have created, if any. For each of those folder, the name of the folder is the link to the contents of the folder. +
+ Next to the folder name, the total number of message contained in the folder is displayed. If unread messages are located in one folder, the number of unread messages is presented between brackets. +
+ Below the list of custom folders a Manage Custom folders link directs you to the folder management page. +
+
+
+
+ The right part of the page presents a list of all available forums. This list is split into two sections:
    +
  • General forums: those forums are available to all players. They are organised in categories that are also displayed in the list
  • +
  • Alliance forums: if you're a member of an alliance and the leadership of the alliance has created forums, the forums you have access to are listed here. Only specific members of the alliance have access to each alliance forum, depending on the access rules set up on the alliance forum management page.
  • +
+
+
+ For each forum or forum category in the list, the name is the a link to the forum page of this particular forum or forum category. +
+ Along the name is displayed the number of topics or threads in the forum. If there are unread topics, the number of unread threads is displayed after the number of topics between brackets. +
+
+
+ diff --git a/manual/beta5/en/diplomacy.lwdoc b/manual/beta5/en/diplomacy.lwdoc new file mode 100644 index 0000000..58073b0 --- /dev/null +++ b/manual/beta5/en/diplomacy.lwdoc @@ -0,0 +1,34 @@ + + + beta5 + en + Diplomacy +
+ Diplomacy covers a lot of concepts concerning in game interactions of players outside of the direct communication means. The major element is the alliance system which allows players to build up teams. The diplomatic section of the game allows you also to manage list of enemy and trusted players. Diplomacy is also a way to acquire technologies you can't research on your own and to exchange planets and fleets through the marketplace. The main Diplomacy page provides an overview of all these topics. +
+
+ This page provides an overview of your alliance if your part of one along with an overview of your allies and enemies and of your messaged. It includes shortcuts for the major elements in this area. The different sections of the page are the topic of the corresponding manual section. +
+
+ An alliance is a group of players who have decided to team up and work together to be stronger in the game. Alliance related topics are detailed in the Alliance section of the manual. +
+
+ This page allows you to keep a list of enemy players and alliance. This topic is discussed in more details in the Enemies manual section. +
+
+ In Legacy Worlds you can entrust other players with control of your fleets while you're not online. This page is presented in the manual in the Trusted Allies section. +
+
+ As the complete technological tree isn't available to one given player, you'll have to acquire some technologies from other players through diplomatic exchanges in order to obtain technologies you don't have access to. This feature is the topic of this manual section. +
+
+ They might not be diplomatic relations per se but economic relations might be considered as a first step towards establishing more solid agreements. As such the marketplace is the place where you can buy or accept and sell or give fleets and planets in LegacyWorlds. +
+ +
+
+
+
+
+
+ diff --git a/manual/beta5/en/diplomacy_page.lwdoc b/manual/beta5/en/diplomacy_page.lwdoc new file mode 100644 index 0000000..59d8bc5 --- /dev/null +++ b/manual/beta5/en/diplomacy_page.lwdoc @@ -0,0 +1,29 @@ + + + beta5 + en + Main Diplomacy Page +
+ This page provides an overview of your alliance if your part of one along with an overview of your allies and enemies and of your messaged. It includes shortcuts for the major elements in this area. The different sections of the page are the topic of the next paragraphs. +
+
+ This overview provides a reminder of the alliance tag and name, its number of planets and coordinates, its rank and number of points. It also displays you status inside the alliance and a link to the alliance management page. +
+ It also includes a section about alliance specific forums. It lists them with links to each forum and number of threads in each. +
+
+ This overview presents the number of players that:
    +
  • you have in your trusted allies list
  • +
  • have you in their trusted allies list
  • +
  • you have on your enemies list
  • +
+ This section also provides links to allies and enemies management pages. +
+
+ This section provides an overview of your messaging system including:
    +
  • Messages: total number of messages you have with a number of new ones; this title is a link to the message page
  • +
  • Internal Transmission: total number of internal transmissions you have with a number of new ones; this title is a link to the internal transmission folder
  • +
  • A link to the Compose a message page
  • +
+
+
diff --git a/manual/beta5/en/empire.lwdoc b/manual/beta5/en/empire.lwdoc new file mode 100644 index 0000000..fa3deeb --- /dev/null +++ b/manual/beta5/en/empire.lwdoc @@ -0,0 +1,272 @@ + + + beta5 + en + Empire +
+ Your empire is composed of a set of planets. Starting at first with one it will grow through conquest of uninhabited planets or enemy ones. The overall technological advance of your empire can be improved through >research. The fleets you build will provide it with defenses against hostiles and help with its expansion. Of course all of this costs money. +
+ In order to manage your empire you have to understand a set of basic concepts that are discribed in the next section of this manual page. +
+ The last section introduces the set of game pages that allow empire management in game and provides links to manual sections discribing those pages more precisely. +
+
+
+
+ The individual elements composing your empire are planets. Those planets are identified by a name and are located at a specific set of coordinates in the galaxy in which the game take place. At the beginning of the game you are provided with a single planet. +
+
+ It is up to you to get your empire growing by conquering or buying other planets. Those planets are either considered neutral (they aren't owned by any real player) or they might belong to another player. In order to conquer another planet you have to send fleets to that planet in sufficient quantity to destroy its defenses. Your fleets also have to include a sufficient amount of GA ships to control its population so that it joins your empire. +
+ In order to buy a planet you have to look at the marketplace and make a tempting enough offer so that its current owner agrees to sell it to you. +
+ Your friends can also give you planets. +
+
+
+
+ What makes great empires is their citizens. The population of a given planet represents the number of inhabitants of the planet. The population size of a planet has an influence on the base income of the planet and its happiness. +
+
+ Population growth is essential: the more population the more factories and turrets you can build without facing an happiness decrease. Each planet's population increases every day tick. +
+ The population increase calculation is influenced by several factors including:
    +
  • the planet's happiness
  • +
  • the maximum population the planet can hold
  • +
  • technologies you possess
  • +
+ The following technologies and laws have an influence on population growth:
    +
  • Advanced Hospitals
  • +
  • Corpse Reanimation
  • +
  • Forced Human Cloning
  • +
  • Nurishment Purification
  • +
+
+
+ The population a given planet can hold is limited. The limit is flexible around a fixed value. At first the maximum population of a planet is around 10,000 population units. Technologies can increase this value. Each technology increasing the maximum population size of a planet adds around 10,000 to the limit. Those technologies are:
    +
  • Arcologies
  • +
  • Self-sustained Arcologies
  • +
  • Singularity Housing
  • +
+ Since there are 3 technologies, the ultimate limit lies around 40,000. +
+
+
+
+ One major factor in LegacyWorlds is happiness. It reflect the overall satisfaction of your citizens. Be careful to keep them happy or they might revolt. Just as population growth and maximum size, happiness is influenced by several factors, that are presented in the floowing paragraphs of the manual. +
+
+ Citizens like when the attention of their leader is focused on them and solely on them. As the consequence, the happiness on each planet will decrease as your empire grows. The more planets you have in your empire the harder it will be too keep a high happiness. Theoritically the happiest empire would be one composed of a single planet. +
+
+ People don't like to be unemployed but they don't want to be overworked either. Therefore the number of factories on your planets has to be monitored. Up to one certain number of factories happiness will increase, but if you add more it will start to decrease. The "ideal" number of factories depends on the planet's population. +
+
+ People like to feel safe but they don't enjoy a police state. As a consequence the amount of turrets has also to be right depending on the planet population. Just like with factories increasing the number of turrets on one planet will increase its happiness up to a point where it will start having a negative effect. The "ideal" number of turrets also depends on the planet's population. +
+
+ Both your total amount of fleets and the size of the fleet stationned on one planet have an influence on happiness:
    +
  • Total fleet size: your total fleet size is compared to the average total fleet size in your protection zone. If it is higher you get a happiness bonus, and a malus if it's lower.
  • +
  • Fleet stationned on a planet: having a fleet stationned on a planet also increases its happiness. This happiness bonus is linked to the percentage of your total fleet that is stationned on that particular planet
  • +
+
+
+ Technologies you possess and laws you have enacted (or revoked) might also have an influence on happiness. Some increase happiness and other decrease it. +
+ Technologies and laws increasing happiness are:
    +
  • Advanced Communications
  • +
  • Ban Biological Drones
  • +
  • Biosphere Protection Pact
  • +
  • Civilian Communication Act
  • +
  • Civilian Transportation Act
  • +
  • Green Production
  • +
  • Legalize Space Weed
  • +
  • Safe Recreational Drugs
  • +
  • Wormholes
  • +
+ Technologies and laws decreasing happiness are:
    +
  • Biological Drones
  • +
  • Forced Human Cloning
  • +
  • Global Defense Bill
  • +
  • Martial law
  • +
  • Wild Capitalism
  • +
  • Wormhole Lockdown
  • +
+
+
+ Several decisions you might make in game might have an effect on the overall happiness of your empire, such as:
    +
  • Destroying a planet with a WormHole SuperNova decreases the happiness of all the remaning planets of the empire
  • +
  • Giving away / selling planets decreases the happiness of all your remaining planets
  • +
+ In those cases an happiness malus is applied immediately following the game action. This malus decreases slowly over time until the situation goes back to normal. +
+
+
+
+ When a planet's happiness gets too low, there is a high probability the population might revolt. When citizens revolt on a given planet, they go on strike and stop working in factories, riots occure and material destruction have to be deplored. +
+
+ When the happiness of a planet gets below 20%, the planet gets in a status where a revolt is possible. The lower the happiness the higher the probability a revolt could occure. +
+ Each hour tick the planet is tested for revolt and in some cases the revolt actually happens. The event is more likely if the revolt probability is high that is to say if the happiness is very low. +
+
+ What does it mean that a planet is revolting? It means that the citizens are quite unhappy with the way they are governed and that they express it. They go on strike and stop working in the factories. No income gets out of industrial factories and military factories' production is halted. They also riot which causes material destructions: turrets and factories are their targets and their number decreases. +
+ Destroying factories and turrets changes the happiness value of the planet. With luck, a single revolt episode might get the planet's happiness above 20%. If not there is still a probability it will revolt each hour tick until the situation is corrected. +
+
+
+
+ As in every society, when a planet has been run by the same government for some time, a corruption system> starts appearing. People resort to bribery to gain an advantage over other people, individuals use goverment money to achieve their own personal goals. +
+
+ In LegacyWorlds, each new planet starts with a corruption level of 0%. As soon as it get into the control of a player corruption level starts rising. The rate of increase is linked to the number of factories on the planet: the more factories the faster it grows. As soon as the corruption level of a planet reaches 10%, the factories on the planet start losing efficiency: income is reduced and military factories build slowlier as government officials use public money and factory workers to build swimming pools in their gardens. +
+
+ A planet with a high corruption level can see its corruption level decrease if it remains neutral for some time. The only option to restore a planet to a decent corruption level is to abandon it and wait. +
+
+
+
+ Two kinds of factories are available:
    +
  • Industrial factories contribute to you empire's income
  • +
  • Military factories allow to build warefare
  • +
+ Technologies have an influence on factories productivity and production. This manual section will cover all those topics. +
+
+ Industrial factories produces goods from raw materials. Those goods are sold within the empire and beyond its borders, which generates money. As a consequence the more industrial factories you have the more money you make. But there are workers in factories and they won't be too happy to have to work 3 jobs to get the planetary economy running. So there is a limit to the number of factories you can build without risking a revolt. +
+ Industrial factories can be built from the individual page of each planet you own on from the quick builder facility on the planets page. +
+
+ Military factories are specialised factories producing ships and turrets. The speed at wich items are built depends on the number of military factories on the planet. The more factories you have the faster it will go. But there are workers in factories and they won't be too happy to have to work 3 jobs to get the planetary weapon industry. So there is a limit to the number of factories you can build without risking a revolt. +
+ Military factories can be built from the individual page of each planet you own on from the quick builder facility on the planets page. +
+ The items available for construction in military factories depend on the technologies you possess. The following technologies provide you with new ships categories to build:
    +
  • Battle Cruisers
  • +
  • Cruisers
  • +
  • Fighters
  • +
+
+
+ Several technologies and laws have either a positive or negative influence on factories productivity. They can either affect only industrial factories or only military factories or both. +
+ Technologies and laws increasing productivity are:
    +
  • Adaptive Materials
  • +
  • Anti-matter Generators
  • +
  • Automated Factories
  • +
  • Bio-engineering
  • +
  • Biological Drones
  • +
  • Cloning Vats
  • +
  • Global Defense Bill
  • +
  • Hardened Alloys
  • +
  • Intelligent Materials
  • +
  • Martial law
  • +
  • Nanotechnologies
  • +
  • Robotics
  • +
  • Room Temperature Superconductors
  • +
  • Self-healing Materials
  • +
  • Wild Capitalism
  • +
  • Wormhole Lockdown
  • +
+ Technologies and laws decreasing productivity are:
    +
  • Ban Biological Drones
  • +
  • Biosphere Protection Pact
  • +
  • Global Defense Bill
  • +
  • Green Production
  • +
  • Increased Research Grants
  • +
  • Legalize Space Weed
  • +
  • Martial law
  • +
  • Science Golden Age
  • +
  • Wild Capitalism
  • +
  • Wormholes
  • +
+
+
+
+
+ At first your empire possesses a few basic technologies that allow for space travel and production. But in order to progress in the game you have to acquire new technologies, either by researching them inside your empire or by diplomatic exchanges. +
+
+ Each day tick the planets in your empire generate a certain amount of research points. This research output is linked to the planets' population. The number of points generated for each population unit can be modified by technologies and laws such as:
    +
  • Biological Computers (increase)
  • +
  • Increased Research Grants (increase)
  • +
  • Interstellar University (increase)
  • +
  • Miniaturised Particle Colliders (increase)
  • +
  • Nano-scale Computers (increase)
  • +
  • Quantum Computers (increase)
  • +
  • Science Golden Age (increase)
  • +
  • Wild Capitalism (decrease)
  • +
+ Those points are then used to research new technologies according to your research budget as set on the research page. +
+
+ Technologies that are in the part of the technology graph that you can access get naturally researched using your reseach points. +
+ Technologies that aren't in that part of the graph have to be acquired using the technology exchange tool in the diplomacy section of the research page. See the relevant section of the manual for more details. +
+
+
+
+ In game money has nothing to do with real money. It's an imaginary currency even if the sign used correspond to the euro sign (yes, yes, we're europeans...). Money is the main mean used in LegacyWorlds to acquire items, be it by building them or from other players. +
+
+ There are several ways to gain money:
    +
  • Each planet has a base income linked with its population
  • +
  • Industrial factories also provide income. See the relevant section of the manual to know more about this topic.
  • +
  • You can also sell planets and fleets, through the marketplace facility
  • +
  • You can also receive cash donations from other players
  • +
+ Several technologies have an influence on your income. +
+ Basically all technologies increasing the productivity of industrial factories increase you total income. See the factories section of this page to learn more about them. +
+ Some other technologies have a direct influence on your income:
    +
  • Economy Globalisation (increase)
  • +
  • Increased Research Grants (decrease)
  • +
  • Wild Capitalism (increase)
  • +
+
+
+ This money can be used in various means:
    +
  • Money is necessary to build planetary improvements like factories and turrets. Each item has a specific cost
  • +
  • You also have to buy the ships you build. Each category of ships has a particular price.
  • +
  • In order to implement a new technology that you have discovered you have to pay a certain fee
  • +
  • Money can also be used to buy fleets and planets from other players
  • +
  • You can also give money to friends
  • +
  • A part of your income is automatically diverted to pay for the upkeep of factories, fleets and turrets
  • +
+
+
+
+
+
+ This page provides you with a status of your empire at a glance along with shortcuts to the most important items you might be interested in viewing. It is split into several sections, providing insights about your planets, research progress, finantial situation and fleets status. +
+
+ This page provides your with an overview of the finantial situation of your empire. It is split into several sections that are detailed in a specific manual page. +
+
+ This page provides you with a general overview of all your planets at once along with a quick builder facility. A link in the top right corner of the page allows to switch between two views:
    +
  • List of controlled planets only
  • +
  • Quickbuilder facility and list of controlled planets
  • +
+ Each of those two elements are described in other paragraphs of the corresponding manual section. +
+
+ This page is the management center for any individual planet you own or an information page for other stellar objects. +
+
+ Some technologies in game provide you with the ability to build other spatial objects than just fleets: probes and beacons are just that and this manual page will provide you with all you need to know about them. +
+
+ +
+
+
+
+
+ diff --git a/manual/beta5/en/empire_overview.lwdoc b/manual/beta5/en/empire_overview.lwdoc new file mode 100644 index 0000000..3dc810b --- /dev/null +++ b/manual/beta5/en/empire_overview.lwdoc @@ -0,0 +1,68 @@ + + + beta5 + en + Empire Overview Page +
+ The empire overview page provides you with a status of your empire at a glance along with shortcuts to the most important items you might be interested in viewing. It is split into several sections. +
+
+ This section provides you with important facts about the planets which compose your empire:
    +
  • Planets owned: number of planets in your empire
  • +
  • Average happiness: average happiness calculated based on happiness on each of your planets
  • +
  • Average corruption: average corruption calculated based on corruption on each of your planets
  • +
  • Total population: sum of the populations of each of your planets
  • +
  • Average population: total population divided by the number of planets in your empire
  • +
  • Total factories: total number of factories on all your planets
  • +
  • Average factories: total number of factories divided by the number of planets in your empire
  • +
  • Total turrets: total number of turrets in your empire
  • +
  • Average turrets: total number of turrets divided by the number of planets you own
  • +
+ It also provided links to:
    +
  • The Planets overview page
  • +
  • Individual planet pages
  • +
+
+
+ This section provides you with informations about your research budget. For each research category, it displays the percentage of research points allocated along with the number of points per day this currently represents. +
+ It also provided you with a link to the research management page. +
+
+
+ This section is split into several paragraphs, each presenting one important aspect of your fleet power. +
+
+ This overview provides a general view of your fleets, including:
    +
  • Total fleet power: sum of the power of all the ships you possess
  • +
  • Fleet upkeep: total cost to sustain all your fleets
  • +
  • Number of fleets: total number of fleets
  • +
  • Fleets engaged in battle: number of fleets currently engaged in combat situations
  • +
  • View fleets: link to the Fleets management page
  • +
+
+
+ This section provides specific information about fleets located on your own planets, including:
    +
  • Number of fleets: total number of fleets located on your own planets
  • +
  • Fleets engaged in battle: total number of fleets engaged in combat on your own planets
  • +
+
+
+ This part of the page provides informations about fleets that aren't located on your own planets, including:
    +
  • Fleets on foreign planets: total number of fleets located on planets you don't own
  • +
  • Fleets engaged in battle: total number of fleets engaged in battle on planets you don't own
  • +
  • Moving fleets: total number of fleets in movement
  • +
  • Fleets waiting in Hyperspace: total number of fleets in stand-by in hyperspace
  • +
+
+
+ This section provides important statistics about your fleets, including:
    +
  • GA ships: total number of GA ships
  • +
  • Fighters: total number of Fighters
  • +
  • Cruisers: total number of Cruisers
  • +
  • Battle cruisers: total number of Battle Cruisers
  • +
  • Total ships: total number of ships
  • +
+
+
+
diff --git a/manual/beta5/en/enemies.lwdoc b/manual/beta5/en/enemies.lwdoc new file mode 100644 index 0000000..522120b --- /dev/null +++ b/manual/beta5/en/enemies.lwdoc @@ -0,0 +1,19 @@ + + + beta5 + en + Enemies +
+ This page allows you to keep a list of enemy players and alliance. This has for consequence that each time a fleet belonging to one of your enemy arrives at one of your planets it gets automatically switched to attack mode. Moreover it's impossible for them to switch their fleets to defense mode on your planets. +
+
+ In this section of the page you can add a new enemy to your list by typing its player name in the textfield and clicking the Add button. The player name get displayed in the list below. +
+ A checkbox is displayed before the name of each of the enemy players in your list. Checking those checkboxes make a new button appear. Clicking this Remove selected enemies button allows you to remove the players whose checkbox has be checked from the enemy list. +
+
+ In this section of the page you can add a new enemy to your list by typing its tag in the textfield and clicking the Add button. The alliance tag get displayed in the list below. +
+ A checkbox is displayed before the name of each of the enemy tags in your list. Checking those checkboxes make a new button appear. Clicking this Remove selected enemies button allows you to remove the alliances whose checkbox has be checked from the enemy list. +
+
diff --git a/manual/beta5/en/fleets.lwdoc b/manual/beta5/en/fleets.lwdoc new file mode 100644 index 0000000..97195fe --- /dev/null +++ b/manual/beta5/en/fleets.lwdoc @@ -0,0 +1,44 @@ + + + beta5 + en + Fleets +
+ Fleets are one of the major elements in LegacyWorlds, since they allow you to travel the universe, conquer new planets and defend yourself against enemies. + Fleets are sets of ships you can use either to >defend yourself or attack other planets. Each fleet can be composed of ships of several different categories. Those fleets can be moved from planet to planet according to a particular set of rules. When faced with enemy fleets they of course engage in battle. +
+ In order to have fleets you first of all have to possess the required technologies. You also have to build them. +
+ Fleets building is covered more precisely in the following manual sections:
    +
  • Military factories section of the empire manual page
  • +
  • Building warefare section of the individual planet page manual page
  • +
  • Quickbuilder section of the planets overview manual page
  • +
+
+ Fleets being one of the major concepts to grasp in LegacyWorlds, various fleets related topics will be studied in more details in different subsections of the manual. Those subsections are presented in the next paragraph. +
+
+
+ Fleets are composed of ships. It sounds only natural to start with ships. This section of the manual will present all topics linked with ships categories and caracteristics. +
+
+ The Fleets page is the main entry point to manage fleets. This part of the manual will present this page more deeply. The fleet page consists in three different sections:
    +
  • Top part: it includes a set of filters
  • +
  • Middle Part: it's the actual fleets list. It's either a complete list or the result of the choices you've made with the filters
  • +
  • Bottom Part: it's the Action section of the fleet page, where you can actually act on the fleets.
  • +
+ The fleets page manual section will describe each part of the page. +
+
+ Once you have selected at least one fleet in the fleets list of the fleets page, a list of links, corresponding to different actions you can perform with the fleets, is displayed. This list include a selection among the total list of possible actions, depending on the circumstances. Those actions will be presented in the Fleets actions part of the manual. +
+
+ The main goal of fleets is, obviously, to fight. The different rules surrounding the battle is the topic of the Battles section of the manual. +
+
+ +
+
+
+
+ diff --git a/manual/beta5/en/fleets_actions.lwdoc b/manual/beta5/en/fleets_actions.lwdoc new file mode 100644 index 0000000..fd1dfe6 --- /dev/null +++ b/manual/beta5/en/fleets_actions.lwdoc @@ -0,0 +1,37 @@ + + + beta5 + en + Fleets Actions +
+ Once you have selected at least one fleet in the fleets list, a list of links, corresponding to different actions you can perform with the fleets, is displayed at the bottom of the fleets page. This list include a selection among the total list of possible actions, depending on the circumstances. +
+
+ Once you have selected a set of fleets with the corresponding checkboxes, you may want to unselect them all at once without having to click on all the checkboxes one at a time. This feature does just that: click on this link to unselect all selected fleets. +
+
+ Clicking on this link opens an alert window where you can type in a new name for the fleet and change it. if several fleets are selected all selected fleets will be renamed with the new name. +
+
+ Clicking on this link directs you to a new page where you can manage the movements of the selected fleets. This topic is described more thoroughly in the fleets movements section of the manual. +
+
+ Clicking on this link directs you to a new page where you can define how to split the fleet. This topic is described more closely in the splitting fleets section of the manual. +
+
+ Clicking this link opens an alert box where you can type in the name of the new merged fleet and validation actually merges the fleets. +
+ Of course the fleets have to be at the same location. If fleets on several locations are selected, only possible merges are performed. +
+ The only other requirement is that all fleets have to be available. For instance you can't merge fleets unavailable because of battle but this action can be performed on moving fleets. +
+
+ Clicking this link directs you to a fleets sale page which is described more closely in the marketplace section of the manual. +
+
+ Clicking this link opens a confirmation alert box. If you validate the action the selected fleets will disappear from the game. +
+ +
+
+ diff --git a/manual/beta5/en/fleets_moving.lwdoc b/manual/beta5/en/fleets_moving.lwdoc new file mode 100644 index 0000000..bed3d31 --- /dev/null +++ b/manual/beta5/en/fleets_moving.lwdoc @@ -0,0 +1,199 @@ + + + beta5 + en + Fleets Movements +
+
+
+ When coming to fleets movements in LegacyWorlds, two different kinds of "space" have to be considered:
    +
  • normal space
  • +
  • hyperspace
  • +
+ Both types will be covered in the next parapgraphs. +
+
+ Normal space can be considered as the "real world" space, where stars, planets and other stellar objects are located. It's the space in which system ships can travel with their sub-light engines. But moving from one stellar system to another with that kind of engines would take centuries and researchers in LegacyWorlds had to find other means to allow space exploration to be more practical. That's where hyperspace travel gets in. +
+
+ The scientists in the LegacyWorlds universe soon realised space could also be apprehended on another plane that they called hyperspace. Hyperspace could be considered as another dimension, adjacent to the real one, where the distances between spatial bodies are significantly distorted. As a consequence, hyperspace travel is much faster than travel in normal space. The main drawbacks are that:
    +
  • jumping between hyperspace and normal space used tremendous amounts of energy which implies some specialised engines to open hyperspace windows
  • +
  • the lay-out of hyperspace is fluctuating and impossible to cartography. As a consequence moving ships following a defined trajectory reach their destination without problem. But ships stationned in hyperspace can derive from their location and lose clear knowlegde of their position and get definitively lost, unless they have a static point of reference in the form of a hyperspace beacon
  • +
+
+
+
+
+ Movement computation is the process that allows to define the position of a moving fleet. The exact rules behind this computation include general rules that are valid for all ravel types and others depend on the type of space the fleets are moving in. Rules for all movements, normal space and hyperspace travel are the topics of the next two paragraphs. +
+
+ All space travels are regulated according to those rules along with rules specific to the type of travel. These general rules are listed below:
    +
  • Movement computation and current position of every moving fleets is calculated every minute. This allows for an almost real time effect of fleets movements
  • +
  • In case of a change in the destination of a fleet, the new trajectory is calculated using its current location as starting point
  • +
  • Fleets passing by a planet can't be spotted by its owner unless they get into orbit around the planet
  • +
  • Fleets' speed is influenced by the type of objects they pass by: nebulas and planetary remains reduce the speed of ships crossing their orbits
  • +
+
+
+ Normal space travel is regulated by the following rules:
    +
  • Fleets always travel in normal space inside a given stellar system unless the fleet is hyperspace capable and an hyperspace stand-by delay has been set. In such a case the fleets travel in hyperspace but the travel time remains the same as for normal space travel
  • +
  • Between two stellar objects a fleet follows the most direct trajectory between the two
  • +
  • The fleet always crosses all the orbits between the two stellar objects (its origin nd its destination). As a consequence moving from the first orbit to the 6th takes much longer than from the first to the second
  • +
  • Travelling from one orbit to the orbit immediately next to it takes 12 min. The time to travel between two planets inside the same is then a multiple of 12, the multiplication factor depending on the number of orbits to cross
  • +
+
+
+ Hyperspace travel is regulated by the following rules:
    +
  • When going beyond the limits of a stellar system or a nebula, a fleet has to travel through hyperspace. Fleets that aren't hyperspace capable can't go beyond the limits of a stellar system
  • +
  • When leaving a stellar system, a fleet has to cross all orbits between its starting point and the edges of the system before travelling to the next one
  • +
  • A fleet moving between two stellar systems follows the most direct path to the destination system
  • +
  • When entering its destination system a fleet crosses again the necessary orbits until it reaches its destination planet
  • +
  • Whatever the origin and destination orbits in the stellar system are the time to go from one system to another is always the same
  • +
  • Hyperspace travel speed can be increased through technologies. This topic is covered in the ship speed of the ships section of the manual
  • +
+
+
+
+
+
+ There are two different means to access the fleets' orders page:
    +
  • On the individual page of each planet, planetary remains or nebula square, a Send Fleets link is available below the name of the stellar object. Clicking this link directs you to a specific page where you can select the fleets you want to send to that particular location and set up some parameters for the movement of the fleet
  • +
  • On the main fleet page, when at least one fleet is selected, a Change orders link is displayed in the bottom Actions section of the page. Clicking this link also directs you to the fleets' order change page
  • +
+
+
+
+ The page includes some actions links and three different sections:
    +
  • New orders: to define the orders you want the selected fleets to perform
  • +
  • Selected fleets: this list displays all fleets selected to perform the action defined in the New orders section
  • +
  • Available fleets: this lists includes all fleets you can control (bet it yours or those of your off-line trusted allies) which could follow the orders
  • +
+
+
+
+ In this part of the page you can define three kinds of orders:
    +
  • Destination: to choose where to send the fleet
  • +
  • Hyperspace stand-by orders: to get a fleet to stand-by in hyperspace
  • +
  • Fleet mode: to define the mode the fleet will be in upon arrival
  • +
+ Each type of order willl be detailed in the next paragraphs. +
+
+ The Destination line first defines the fleet's destination if any has already been set. The destination is preset for instance for moving fleets or when you access the page from the Send Fleets link of an individual stellar object page. +
+ To change the destination you have to click the Set destination link. You'll get directed to a new page describing current destination if any and providing a destination selection tool. This tool consists in a minimap and several means to change the system it is centered on:
    +
  • Minimap: this minimap is centered on the system in which is located your first planet and displays one stellar system at a time. Arrows are diplayed around the minimap if the selected fleets are hyperspace capable or if you have hyperspace capable fleets in case no particular fleet is selected yet. Using the arrows around the map allows you to change the focus of the minimap and move around that system
  • +
  • Centre on coordinates: you can center the minimap on a particular stellar system based on its coordinates. To do you you have to select the relevant radio button and type the coordinates of the stellar system in the provided textfields. Clicking the Move button centers the map on this system
  • +
  • Centre on own/allied planet: you can also center the minimap on one of your own planets or one of your trusted allies' planets. To do so, select the relevant radio button and choose the planet you're interested in in the drop down list. Clicking the Move button centers the minimap on the system the planet is located in
  • +
  • Centre on planet: you can also center the minimap on an stellar body based on its name. In that case select the corresponding radio button and type in the name of the stellar object in the provided textfield. Clicking the Move button centers the minimap on the system the planet is located in
  • +
+ Once you have centered the minimap you have to choose the particular stellar object you want to send your fleet to by clicking its name in the minimap. You can now either validate you destination by clicking the Confirm button or cancel your changes by clicking the Cancel button. +
+
+ Fleets around a stellar object can either:
    +
  • orbit it in clear sight in normal space
  • +
  • stay in its neighbourhood in hyperspace
  • +
+ The hyperspace stand-by orders line first of all indicates current orders, that is to say for how long a fleet is supposed to stay in hyperspace without moving at its current location or at its destination. +
+ A Set delay link allows you to define for how long the fleet should remain in hyperspace either at its current location or once it has reached its destination. Clicking the Set Delay link opens an alert box where you can type in the number of hour ticks the fleet should spend in hyperspace. For instance a delay of 1 means the fleet has to wait until the next hour tick and so on. +
+
+ The fleet mode might be considered as the battle readiness of the fleet. It might also be interpreted as the intention of the fleet: hostile or friendly. +
+ There are two different available modes for fleets in LegacyWorlds:
    +
  • Defense: the fleet will defend the destination stellar object
  • +
  • Attack: the fleet will attack the destination stellar object, attempting to take it over if it's a planet
  • +
+ The mode in which the fleet is sent can be changed by using the provided drop down list. You just have to select between Defense and Attack in the list. +
+ Keep in mind you can't send a fleet in attack mode to one of your own planets but no control is exerced on fleets sent to alliance planets or planets belonging to your trusted allies. +
+ The mode the fleet will have upon arrival at its destination isn't necessarily the mode it was sent with. The rules behind this mode upon arrival topic is discussed more closely in this paragraph. +
+
+
+
+ This part of the page presents the list of fleets that are selected to perfom the designed movement. It consists in a table describing each fleet using a colour code to represent fleets' ownership. Actions can also be performed on the list. +
+
+ The table includes for each fleet:
    +
  • Owner: name of the owner of the fleet
  • +
  • Name: name of the fleet
  • +
  • Haul: fill rate of the haul of capital ships carrying system ships. It's either N/A for system fleets or a percentage for fleets including capital ships. Be careful if the percentage isn't superior to 100%: in that case the fleet isn't hyperspace capable
  • +
  • Ships (G/F/C/B): fleet composition, including number of GA ships, Fighters, Cruisers and Battle Cruisers
  • +
  • Power: the power of the total fleet
  • +
  • Trajectory: this field presents the list of locations the fleets will go close by along its path to its destination. For stationnary fleets, the value is N/A. For moving fleets, the trajectory is represented with a drop down list describing current and future status and locations
  • +
+
+
+ The usual colour code is used in the list:
    +
  • Green: for your own fleets
  • +
  • Blue: for fleets belonging to off-line players who have you as trusted allies and that you can currently control
  • +
+
+
+ The only possible action in the list is to remove a fleet. Removing a fleet means that it won't perform the orders as the other fleets in the list. Previous orders for the fleets removed from the list are executed. +
+ In order to remove a fleet from the list you just have to click on its line. +
+
+
+
+ This part of the page presents the list of fleets that are available for selection to perfom the designed movement. It consists in a table describing each fleet using a colour code to represent fleets' ownership. Actions can also be performed on the list. +
+ The list of fleets depends on the set destination. For instance fleets that aren't hyperspace capable aren't displayed if the defined destination implies hyperspace travel. +
+
+ The table includes for each fleet:
    +
  • Owner: name of the owner of the fleet
  • +
  • Name: name of the fleet
  • +
  • Haul: fill rate of the haul of capital ships carrying system ships. It's either N/A for system fleets or a percentage for fleets including capital ships. Be careful if the percentage isn't superior to 100%: in that case the fleet isn't hyperspace capable
  • +
  • Ships (G/F/C/B): fleet composition, including number of GA ships, Fighters, Cruisers and Battle Cruisers
  • +
  • Power: the power of the total fleet
  • +
  • Current orders: the current status of the fleet (defending a planet, moving to a particular destination, attacking a planet and so on)
  • +
+
+
+ The usual colour code is used in the list:
    +
  • Green: for your own fleets
  • +
  • Blue: for fleets belonging to off-line players who have you as trusted allies and that you can currently control
  • +
+
+
+ The only possible action in the list is to add a fleet from the available fleets list to the selected fleets list. Adding a fleet to the selected fleets list means that the fleet will follow the same orders as the other selected fleets. +
+ In order to select a fleet you just have to click on its line. +
+
+
+ Once you are satisfied with the changes you have made in the different sections of the page you have to validate your orders. +
+ In order to validate your orders you have to click the Confirm link in the top right section of the page. Clicking the link actually sends the fleets and directs you back to the fleets page. +
+ In the same area of the page a Cancel link erases your changes and directs you back to the fleets page. +
+
+
+
+
+ Depending if you have set up an hyperspace stand-by delay or not two cases are possible:
    +
  • With hyperspace delay: the fleet doesn't get out of hyperspace into normal space immediately and remains in hyperspace until the defined number of hours ticks have passed by. It then jumps into normal space and gets into orbit. Getting into orbit is a complicate process which consumes a lot of energy. As such it causes the fleet getting out of hyperspace to remain unavailable at least until the following hour tick
  • +
  • Without hyperspace delay: the fleet directly gets out of hyperspace if it reached the planet through hyperspace, or settles in orbit around the planet if it arrived via normal space. In any case, the process implies it remains unavailable at least until the next hour tick
  • +
+
+
+ The mode of a fleet getting in orbit around a planet depends on various factors:
    +
  • In most cases: the fleet reaching orbit keeps the same mode as the one it was sent with
  • +
  • You already have fleets on the planet: if your different fleets don't have the same mode (some in attack and some in defense) the fleets get switched to the mode of the fleet with the highest power
  • +
  • Auto-attack mode: if the owner of the planet has you or your alliance in his enemies list whatever fleets you may send to any of his planets get automatically switched to attack mode
  • +
+
+
+ When a fleet reaches its destination (eventually getting out of hyperspace on it) and starts orbiting it, it remains unavailable for some time. Unavailability time depends on two cases:
    +
  • Arrival in attack mode or on a planet whose owner has you in his enemy list (be it you as a player or as member of a given alliance): the fleet is unavailable until a Battle tick occurs
  • +
  • Other cases: the fleet is unavailable until next Hour tick
  • +
+
+
+
diff --git a/manual/beta5/en/fleets_page.lwdoc b/manual/beta5/en/fleets_page.lwdoc new file mode 100644 index 0000000..62c9895 --- /dev/null +++ b/manual/beta5/en/fleets_page.lwdoc @@ -0,0 +1,161 @@ + + + beta5 + en + Fleets Page +
+ The Fleets page is the main entry point to manage fleets. This part of the manual will present this page more deeply. The fleet page consists in three different sections:
    +
  • Top part: it includes a set of filters
  • +
  • Middle Part: it's the actual fleets list. It's either a complete list or the result of the choices you've made with the filters
  • +
  • Bottom Part: it's the Action section of the fleet page, where you can actually act on the fleets
  • +
+ This manual section will describe the two first parts of the page. Fleets actions are the topic of another manual section that you can access here. +
+
+
+ In order to easily find a fleet you're interested in, you can either use the search feature or combine whatever set of filters. The filters and search features are described below. +
+
+ This filter allows you to select fleets depending on the category of locations there are on or moving to. You can choose to display fleets located on:
    +
  • All planets: no particular filter is applied
  • +
  • Own planets: only the fleets located on planets you own are displayed
  • +
  • Allied planets: only the fleets located on allied planets are displayed
  • +
  • Other planets: only the fleets located on other planets (that is to say planets or other stellar objects which don't fit in any other categories) are displayed
  • +
+
+
+ This filter allows you to select fleets depending on their status. You can choose to display fleets which status is:
    +
  • Any: no particular filter is applied and all fleets are displayed
  • +
  • Idle: only the fleets that are orbiting a planet and can actually move are displayed
  • +
  • Unavailable: only the fleets that are orbiting a planet and can't be moved (because they are engaged in battle and so on) are displayed
  • +
  • Moving: only the fleets that are currently flying between planets are displayed
  • +
  • H.S. Stand-By: only the fleets that are currently standing by in Hyperspace are displayed
  • +
  • On Sale: only the fleets that you are currently selling are displayed
  • +
  • Sold: only the fleets that you have sold but haven't been transfered to their new owner yet are displayed
  • +
+
+
+ This filter allows you to select fleets according to the mode they are in. You can either diplay fleets whose mode is:
    +
  • Any: no particular filter is applied and all fleets are displayed
  • +
  • Defending: only defending fleets are displayed
  • +
  • Attacking: only attacking fleets are displayed
  • +
+
+
+ This filters allows you to select fleets according to their owner. You can either display fleets whose owner is:
    +
  • Any: no particular filter is applied and all fleets are displayed
  • +
  • Myself: only the fleets you own are displayed
  • +
  • Trusted Allies: only the fleets belonging to players who have you in their trusted allies list are displayed
  • +
  • Others: only the fleets of other players (neither you nor those who have you as trusted allies) are displayed
  • +
+
+ +
+
+
+ The fleets list part of the page is divided into several sections, depending on the location the fleets are at. Those sections consist in:
    +
  • Own planets: you'll find here the list of all fleets located on planets you own
  • +
  • Allied planets: in this section are listed all fleets located on planets belonging to players who have you as trusted ally
  • +
  • Other planets: you'll find here all fleets located on other stellar objects, be it planets, planetary remains or nebula squares
  • +
  • Moving Fleets: in this section are displayed all fleets moving between different stellar objects
  • +
  • Fleets standing by: in this section are listed all fleets standing-by in hyperspace
  • +
+
+
+
+ For each of the previously described sections you'll find a table proving detailed fleets information for each location. The table includes:
    +
  • Detailed information for the location
  • +
  • Detailed information for each fleet at this location
  • +
+ The next parapgraphs will describe both sections of the table. +
+
+
+ The location part of the table provides a set of data on the location. Some are common to all stellar objects and others depend on the type of stellar object. All are listed in the next paragraphs. +
+
+ For all stellar objects, the location description part of the table includes:
    +
  • name: the name of the object
  • +
  • coordinates: the coordinates of the object on the map
  • +
+
+
+ For planets, the location description also displays:
    +
  • alliance tag: tag of the alliance the owner of the planet belongs to
  • +
  • owner: the name of the owner if the planet isn't yours but belongs to one of your trusted allies
  • +
  • population: the total population of the planet
  • +
  • turrets: the number of turrets located on the planet
  • +
  • turrets' power: the total power of the turrets on the planet
  • +
+ When attacking, below the fleets present on the location, one sentense presents the level of control over the population the player with the more control has. It can be either:
    +
  • "xxx could take the planet.": if all defenses were destroyed the player would have enough GA ships to gain control over the planet
  • +
  • "xxx would need x more GA Ships to take this planet.": even if the defenses are destroyed the player doesn't have enough GA sps to take over the planet
  • +
+
+
+ For other stellar objects, that is to say nebulas and planetary remains, the location description includes:
    +
  • nature of the object: it can be either a nebula or planetary remains
  • +
  • opacity or class: this represents the level of opacity of the object, which gives an idea of the speed reduction of fleets going through it
  • +
+
+
+
+
+ Below the description of the location you'll find a list of fleets present at that location. The data included in this list are described in the next paragraphs. +
+
+ The fleet list of a particular location first of all includes a set of general items:
    +
  • a checkbox that you have to check in order to select the coresponding fleet if you have the ability to do so
  • +
  • Owner: name of the owner of the fleet
  • +
  • Name: name of the fleet
  • +
+
+
+ This part of the list indicates the haul status of the fleet. The values can be of two kinds:
    +
  • N/A: this means the fleet doesn't include capital ships and that it can't carry any ship through hyperspace. Such a fleet can only travel to stellar objects located in the same stellar system it is currently located in
  • +
  • a percentage: this means the fleet includes capital ships that are hyperspace capable and the percentage represents how filled the hauls of those capital ships are. Two cases are then possible:
      +
    • the percentage is equal to or below 100%: the fleet is fully hyperspace capable and can move to other stellar system as is
    • +
    • the percentage is above 100%: there are too many system ships to carry through hyperspace for the number of capital ships you have in the fleet. You either have to remove system ships or add capital ships to the fleet so that the percentage gets equal or lower than 100% if you intend to send the fleet through hyperspace
    • +
    +
  • +
+
+
+ The table also provides information about the fleet's composition, including:
    +
  • GA Ships: the total number of GA ships in the fleet
  • +
  • Fighters: the total number of fighters in the fleet
  • +
  • Cruisers: the total number of cruisers in the fleet
  • +
  • Battle Cruisers: the total number of battle cruisers in the fleet
  • +
  • Power: the total power of all the ships in the fleet
  • +
+
+
+ The table also indicates the current status of the fleet. This status is composed of two parts, separated with a comma:
    +
  • First item: it indicates the battle mode of the fleet: either Defense or Attack
  • +
  • Second item: it indicates the availability of the fleet. You can either move it (Avail.) or have to wait before you can move it again (Unavail.)
  • +
+
+
+ A colour code for each fleet line is used in the fleet list to help you easily spot different categories of fleets:
    +
  • green: your own fleets
  • +
  • blue: fleets belonging to any other player and that are in the same battle mode as your own fleets (defending along with you or attacking along with you)
  • +
  • red: enemy fleets, that is to say fleets that aren't in the same battle mode as you(defending while you're attacking or the other way around)
  • +
+
+
+
+
+ Two operations are possible on a particular fleet:
    +
  • checking the checkbox at the beginning of the fleet's line allows to select the fleet and causes the list of available actions for the fleet to be displayed
  • +
  • clicking on the name of the fleet opens an alert window where you can type in a new name for the fleet and change it
  • +
+
+
+
diff --git a/manual/beta5/en/fleets_splitting.lwdoc b/manual/beta5/en/fleets_splitting.lwdoc new file mode 100644 index 0000000..3c769f6 --- /dev/null +++ b/manual/beta5/en/fleets_splitting.lwdoc @@ -0,0 +1,54 @@ + + + beta5 + en + Splitting Fleets +
+ Any fleet can be split into several different fleets. The exact splitting conditions follow:
    +
  • Fleets can be split whatever their location is (own, neutral or foreign planets, nebulas or planetary remains)
  • +
  • Moving fleets can be split
  • +
  • The only mandatory condition that has to be met is that the fleet's status is Available
  • +
+ In order to split a fleet you have to select it by checking the checkbox on the fleet's line in the fleets list of the fleets page. Then you have to click the split link that appears at the bottom of the fleets page among the other action links. You'll get directed to a special fleet splitting page. +
+
+ The splitting page is composed of two sections:
    +
  • Top section: this part of the page describes the fleet you are about to split. Its top part indicates the fleet's location. The bottom part consists in an abstract of the fleets' list tables which only includes the fleet being split
  • +
  • Bottom section: it's the actual splitting tool
  • +
+
+
+
+ The New fleets part of the page consists in a form that you have to fill in to split the fleets. This form consists in various items:
    +
  • Split type: to choose how the fleet will be split
  • +
  • Split parameters: to define precise splitting caracteristics
  • +
  • Split validation: to perform the split
  • +
+ Each topic will be covered in the next parapgraphs. +
+
+ The split type can be either:
    +
  • Manual: you have to choose precisely the number of ships of each type that you want to include in your new fleets in the corresponding textfields
  • +
  • Automatic: the system will automatically compute the number of ships to include in each fleet, taking into account the number of fleets you want to create and trying to create fleets with homogeneous compositions
  • +
+ In order to select a split type, select the radio button next to its name. +
+
+ The provided form allows to set various split parameters that are listed below:
    +
  • Amount of fleets: use this drop down list to select the number of fleets you want to have once the split is done
  • +
  • New name: use this textfield to type in the new name of the fleet. If several fleets are created, a number will be added to the name for each fleet
  • +
  • GA Ships: use this textfield to type in the number of GA ships to include for each new fleets (out of the total in the original fleet). This field is automatically filled in in automatic mode
  • +
  • Fighters (if you possess the corresponding technology): use this textfield to type in the number of fighters to include for each new fleets (out of the total in the original fleet). This field is automatically filled in in automatic mode
  • +
  • Cruisers (if you possess the corresponding technology): use this textfield to type in the number of Cruisers to include for each new fleets (out of the total in the original fleet). This field is automatically filled in in automatic mode
  • +
  • Battle Cruisers (if you possess the corresponding technology): use this textfield to type in the number of Battle Cruisers to include for each new fleets (out of the total in the original fleet). This field is automatically filled in in automatic mode
  • +
  • Haul used: this field is an informative field that indicates the number of haul spaces used by the ships you have included in each fleet. This number has to be inferior or equal to the Haul available value if you want the fleets to be hyperspace capable
  • +
  • Haul available: this field is an informative field that indicates the total number of hauls spaces provided by the capital ships included in each fleet. This number has to be superior or equal to the Haul used value in order for the fleets to be hyperspace capable
  • +
+
+
+ Once you are satisfied with what you have filled in you can click the Split fleet button to actually split the fleet. +
+ A Cancel button is also provided to go back to the fleets page without making any changes. +
+
+
diff --git a/manual/beta5/en/forums.lwdoc b/manual/beta5/en/forums.lwdoc new file mode 100644 index 0000000..ef964de --- /dev/null +++ b/manual/beta5/en/forums.lwdoc @@ -0,0 +1,214 @@ + + + beta5 + en + Forums +
+ Those forums are very similar to those you might find in any message board. + There are several categories of forums:
    +
  • Legacy Worlds Forums:- these are used by the staff to make announcements and by layers to report bugs and ask for new features.
  • +
  • General Forums:- these are used for general discussions about the game and beyond.
  • +
  • Legacy Worlds - Public beta 5:- these are public forums which are specific for Beta 5. They include alliance recruitment forums and such.
  • +
  • Alliance specific Forums:- these are the forums of each individual alliance. Their name correspond to the alliance name.
  • +
+
+
+ The left side panel of the page consists in a navigation menu to move between forums. It lists the following items:
    +
  • Overview:- this is a link to the forums overview section.
  • +
  • Legacy Worlds forums:- this is a link to that category of the forums' pages.
  • +
  • General forums:- this is a link to that category of the forums' pages.
  • +
  • Legacy Worlds - Public beta 5:- this is a link to that category of the forums' pages.
  • +
  • Alliance forums:- this link, corresponding to the alliance name, redirects to the alliance specific forums.
  • +
+ For each category a Latest messages link is displayed. This link allows to access the lastest message view for the forums in the category. Name of forums with new posts you haven't read yet are displayed in bold. The link corresponding to the page you are currently viewing is displayed in italic. +
+ Below this list, a Search the forums link redirects you to the search facility. +
+ At the bottom of the page, a Messages link allows to switch to the messaging system. +
+
+ For each forums category described in the introduction section of the forum manual page this overview page provides you with a list including:
    +
  • Category of forums:- the name of the category is a link to the specific page for this category.
  • +
  • Nature:- on the right side of the name and between brackets, this indicates if the forum category is general or alliance specific.
  • +
  • Forum name:- for each forum is displayed its name. That name is a link to the given forum's page. Below is indicated the forum's description.
  • +
  • Topics:- number of topics in the forum.
  • +
  • Posts:- number of posts in the forum.
  • +
  • Last Post:- name of the player who made the last post in the forum along with the date and time.
  • +
+
+
+
+ In order to read the forums you can either use the various last messages view or browse the topics. +
+
+ When clicked for the overview page, the Lastest messages link directs you to a view of the last messages in all forums. When clicked under a particular category page it directs you to a view of the last messages in the category. +
+ In all cases the Lastest messages view presents the following items:
    +
  • Latest Messages in:- indicates what forum category you are viewing the last messages.
  • +
  • Navigation panel:- on top and at the bottom of the page it includes:
      +
    • <- Previous page:- link to the previous page.
    • +
    • Posts per page:- you can use that drop down list to choose the number of posts to display on each page.
    • +
    • Next page ->:- link to the next page.
    • +
  • +
  • List of messages:- this list includes for each post:
      +
    • Forum in which the message has been posted.
    • +
    • Title of the message.
    • +
    • Posted by:- Author and date and time of the post.
    • +
    • Text of the post.
    • +
    • Action links:- on the right side of the header of the post various action links directs you to the relevant action pages:
        +
      • View forum: this link directs you to the forum page in which the message has been posted.
      • +
      • View topic: this page directs you to the topic page where all messages in the same thread are displayed.
      • +
      • Reply: this link directs you to the reply page.
      • +
      • Quote: this link directs you to the reply page with included quotes.
      • +
    • +
  • +
+
+
+ Clicking on the name of a forum directs you to this forum's main page. This page includes:
    +
  • Forum name
  • +
  • New topic:- either the new topic link which directs to the topic creation page or the "Only moderators can create new topics" sentence is displayed according to the forum settings.
  • +
  • Posts per page:- the corresponding drop down list allows you to choose how many topics are to be displayed on each page.
  • +
  • Page:- when it is required, a drop down list is displayed to allow you to choose what page to go to.
  • +
  • Posts list: this list includes for each post:
      +
    • Topic:- title of the topic. It's a link to the topic display page.
    • +
    • Replies:- number of replies to the first post.
    • +
    • First post:- author, date and time of the first post in the thread.
    • +
    • Last Post:- author, date and time of the last post in the thread.
    • +
  • +
+ Clicking on a topic name in the list directs you to the thread page for that specific topic. This page includes:
    +
  • Topic Name.
  • +
  • Posts per page:- use this drop down list to choose how many posts to display on each page.
  • +
  • Page:- when relevant, a drop down list is displayed to switch between pages of the posts.
  • +
  • List of messages in the thread. For each post the elements displayed are:
      +
    • Post title.
    • +
    • Posted:- time, date and author of the post.
    • +
    • Text of the message.
    • +
    • Action links:- along the header of the post, those action links directs you to the relevant action pages or perform the requested action.
        +
      • Reply:- directs you to the reply page.
      • +
      • Quote:- directs you to the reply page with included quotes.
      • +
      • Edit:- if you are the author of the post this link allows you to go to the edit page. This page is similar to the new topic page except all fields are prefilled with their current contents, thus allowing you to make corrections.
      • +
      • Delete:- if you are the author of the post, this link allows you to delete it.
      • +
    • +
  • +
+
+
+
+ If you have the rights to do so, a New topic link is displayed at the top of each forum page. Clicking on the link directs you to the new topic form. To post a new topic in the forum currently named at the top of the form, you have to fill in the following items:
    +
  • Topic title:- use the provided text field to type in the topic title.
  • +
  • Text:- use the provided text area to type your message.
  • +
  • Options:- two checkboxes that allow you to apply some specific options to the post:
      +
    • Enable graphical smileys:- checking this checkbox will replace all common text based smileys known to the system by their graphical counterparts.
    • +
    • Enable forum tags:- checking this checkbox is necessary to use forum specific tags like those required for quotes, text emphasis and such.
    • +
  • +
+ At the bottom of the form three buttons are provided to specify what to do with the text typed in and the options you've selected:
    +
  • Submit:- click this button to post the new forum topic.
  • +
  • Preview:- click this button to get a preview of what the post will look like in the forum.
  • +
  • Cancel:- click this button to cancel your changes and go back to the previous page.
  • +
+
+
+ In order to reply in a thread you can use either the Reply or the Quote links that are displayed alongside each post header. The behaviour is slightly different depending on which ones you choose:
    +
  • Reply:- a form similar to the one for new topics is displayed with a prefilled title that fits the current thread title. See the New Topic paragraph of the forum section of the manual for a description of this form use.
  • +
  • Quote:- a form similar to the one for new topics is displayed with a prefilled title that fits the current thread title and a prefilled body containing the post you replied to text with special quote forum markers. See the New Topic paragraph of the forum section of the manual for a description of this form use. Make sure the Enable forum tags checkbox is checked so that the quote is properly displayed as a quotation.
  • +
+ At the bottom of those forms, the Replying to... section presents the previous messages in the thread as reference. +
+
+ Moderator are provided with the means to enforce the moderation rules described in the General Rules section of the manual. If you have moderation privileges on a given forum, you are provided with a set of moderation tools. +
+ If you have moderation privileges, you have access to functionalities which allow you to delete individual posts or complete threads:
    +
  • Deleting a post: if you have moderation privileges, a "Delete" link is available in the right part of the posts' header. Clicking this link allows you to delete it. If the post is the last remaining one in a thread, the whole toic will be deleted.
  • +
  • Deleting a thread: in the threads list of the forum you have moderation privileges on, a checkbox is available in from of of each thread. In order to delete a thread, check the corresponding checkbox to select it. Then click the "Delete" button at the bottom of the page.
  • +
+
+
+ A sticky thread is a thread that remains on top of the threads list for the forum, whatever new posts may have been made in other threads. To switch a thread sticky, you have to options:
    +
  • New topic: if you're writing a new topic, you can make it sticky from the start by checking the "Sticky topic" checkbox at the bottom of the new topic form.
  • +
  • Existing topic: in order to switch an existing topic sticky, you first of all have to select it by checking the corresponding checkbox. Then click the "Switch sticky" button at the bottom of the page.
  • +
+ If you want a topic not to be sticky anymore, select it by checking its checkbox and click the "Switch Sticky" button. The topic gets back to normal topic status. +
+
+ If a thread has been posted in the wrong forum, you have the possibility to move it to another forum. In order to do so you first of all have to select the thread by selecting the corresponding checkbox. Then you have to select the destination forum in the forums drop down list at the bottom of the page. The click the "Move" button. +
+
+ As moderator you also have the ability to edit posts made by other players. To edit any post, just click the "Edit" link in the post's header. You'll be directed to the post edition form, which is similar to the form for new topics, with prefilled values. +
+
+
+ A "Search forums" link is provided at the bottom of the left forums panel. Clicking this link directs you to a search form where you can specify your search criteria:
    +
  • Text: use the provided textfield to type in the search string. The '*' character can be used as a wildcard to search for partial strings.
  • +
  • Search in: you can either search in posts titles or whole posts. To choose between the two, select the corresponding radio button.
  • +
  • Forum: you can either search in all forums or one specific forum. To define in what forum to search, select the entry you're interested in in the provided drop down list.
  • +
  • Sort by: The results of the search can be sorted according to Post Time or Post Title. Use the drop down list to choose which one to use. The sort can also be either ascending or descending. Select the relevant radio button to define the ordering you're interested in.
  • +
  • Display results: Results can be displayed as Posts or Topics. Choose the display you want by selecting the corresponding radio button.
  • +
+ Once you are satisfied with your setting, you can click the "Search" button to launch the search process. +
+ The results are displayed as a list very similar to any thread in any forum. The only differences are that each post also includes: +
    +
  • A new header line where the forum in which the post is located is indicated
  • +
  • A "View forum" link in the top right part of the header which directs you to the main page of the forum in which the post is
  • +
  • A "View topic" link in the top right part of the header which directs you to the page of the thread in which the post is
  • +
+
+
+
+ LegacyWorlds' forums include the possibility to apply text modifiers (to get bold text and so on) and can display some smileys with a graphical representation in order for the posts to look nicer. +
+
+ In order for graphical smileys to be displayed, you either have to enable the option in your preferences or enable it on a per post basis, in the post edition form. +
+ Most current smileys have a graphic conterpart: :), :P, ;) and so on. +
+
+ Forums tags allow you to include special items in post or to apply modifiers to texts. Single unit tags are to be used on their own. For tags composed of two elements, the text modifier applies to the text between the two tags. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TagMeaning
[b] Text [/b]"Text" gets displayed in bold.
[u] Text [/u]"Text" is underlined.
[i] Text [/i]"Text" is displayed in italic.
[quote] Text [/quote]"Text" is displayed as a note without title.
[quote=name] Text [/quote]"Text" is diplayed as a note, with title ("name said:").
[sep]A horizontal bar is drawn to replace the tag.
[item] Text [/item]"Text" is displayed as a bullet-point item.
[link=url] Text [/link]"Text" is displayed as a link that points to the "url" URL.
[code] Text [/code]"Text" is displayed as a fixed size font block. This can be used to insert ASCII art for instance.
+
+
+
diff --git a/manual/beta5/en/game_interface.lwdoc b/manual/beta5/en/game_interface.lwdoc new file mode 100644 index 0000000..3179192 --- /dev/null +++ b/manual/beta5/en/game_interface.lwdoc @@ -0,0 +1,154 @@ + + + beta5 + en + Game Interface +
+
+ Each in game page is split into three sections:
    +
  • Top banner: you'll find there the main menu of the game
  • +
  • Banner: here is located the title of the page you're currently on
  • +
  • Page body: the actual contents of the page are there. On all pages, a Help link on the top right corner of the page directs you to the relevant manual section
  • +
+
+
+ Most pages are automatically refreshed except the forums' pages. As a consequence it's useless to reload the pages on a regular basis: the game does it for you. +
+
+
+ In each game you are playing the top menu includes a link or button which takes you back to your account's page. But be careful: getting back to your account's page doesn't mean you have actually logged out from LegacyWorlds. If you want to benefit from features available to your allies when you're offline, make sure you have also logged out from your account, by clicking on the Log Out link on your account's page or by using the Log out link in the menu. +
+
+
+ The main menu consists in the menu bar on top of all in game pages. This game menu allows you to access all in game pages and features for the current game you're playing. Several game themes are available and the main menu lay-out depends on the theme you've selected in your preferences. A description of the menu depending on those themes is provided in the following paragraphs. +
+
+
+ The menu bar is split into two sections:
    +
  • Top part: this section of the menu bar provides general informations about the player and the game
  • +
  • Bottom part: this section of the menu bar provides you with menu entried linking with all the game pages along with icons for major shortcuts
  • +
+ The following sections of the manual will describe more precisely those two parts of the menu bar. +
+
+ The top part of the menu includes a set of data about the player and the current game, including:
    +
  • Player: Name of the player
  • +
  • Alliance: if the player belongs to an alliance the alliance tag is displayed between brackets
  • +
  • Current funds: amount of cash the player has in bank
  • +
  • Server Time: current time and date of the server, which is set to "Coordinated Universal Time" (abbreviated UTC)
  • +
+
+
+
+ The bottom part of the menu bar includes a set of menu entries for game pages as long as icons for major shortcuts. Those entries include:
    +
  • Overview
  • +
  • Empire
  • +
  • Diplomacy
  • +
  • Universe
  • +
  • Communications
  • +
  • Shortcut Icons
  • +
+ The next sections of the manual will present each entry in more details. +
+
+ The Overview menu entry directs you to the overview page which presents at a glance all important data about your empire and Legacy Worlds' universe. This menu entry also includes three submenu entries:
    +
  • Preferences: this entry directs you to the Preferences page where you can set up game specific preferences
  • +
  • My Account: this entry directs you to your account's main page, leaving the current game but not logging you out of LegacyWorlds
  • +
  • Log out: this links logs you out of the game
  • +
+
+
+ The Empire entry directs you to the Empire page which presents at a glance all important empire related information. This menu entry also includes several submenu entries:
    +
  • Planets: this entry directs you to the planets overview page which presents the list of all your planets and a quick builder facility. This entry also include as many submenu entries as you have planets. Each submenu entry directs you to the individual planet page for that particular planet
  • +
  • Fleets: this entry directs you to the fleets management page which presents all your fleets and allows you to control their movements and other fleets related actions
  • +
  • Beacons: this entry directs you to the Probes and Beacons page where you can manage those features
  • +
  • Research: this entry directs you to the main research page where you can manage research. It also includes four submenu entries corresponding to the various sections of the research page:
      +
    • Topics: to manage research topics
    • +
    • Laws: to manage laws
    • +
    • Budget: to manage research budget
    • +
    • Diplomacy: to manage research exchanges
    • +
  • +
  • Money: this entry directs you to the Money page, where is collected all information about the finantial status of your empire
  • +
+
+
+ The Diplomacy entry directs you to the main Diplomacy page which provides you with a diplomatic status of your empire. This entry also includes four submenu entries:
    +
  • Alliance: this entry directs you to the main alliance page where you can manage and access alliance related data
  • +
  • Marketplace: this entry directs you to the marketplace page, where is managed everything emplying selling or buying stuffs
  • +
  • Enemies: this entry directs you to the Enemies page where you can manage enemies lists
  • +
  • Trusted Allies: this entry directs you to the Trusted Allies page where you can manage a trusted allies list
  • +
+
+
+ The Universe entry directs you to the main Universe page which displays at a glance major facts about Legacy Worlds' universe. This entry also includes four submenu entries:
    +
  • Maps: this entry directs you to the main Maps pages which present a display of the galaxy. It also includes three submenu entries:
      +
    • Planets: this entry directs you to a map page with a grid diplay and planets identified by their name
    • +
    • Alliance: this entry directs you to a map page with a grid diplay and planets identified by their alliance tag
    • +
    • Listing: this entry directs you to a map presented in a list
    • +
  • +
  • Ticks: this entry directs you to the main Ticks page which presents the various ticks in the game
  • +
  • Rankings: this entry directs you to the main Rankings page which provided various rankings for players and alliances
  • +
  • Manual: this entry directs you to this manual main entry
  • +
+
+
+ The Communications entry directs you to the main Communications page which provides you with major data about your messages and forums. It also includes three submenus entries:
    +
  • Compose a message: this entry directs you to the the compose page of the messaging system where you can write a new message to be sent
  • +
  • Folders: this entry directs you to the folders page of the messaging system where you can manage and access you folders. This entry also includes submenu entries:
      +
    • Inbox: to go directly to your inbox folder
    • +
    • Transmissions: to go directly to your Internal Transmissions folder
    • +
    • Sent: to go directly to your Sent folder
    • +
    • A submenu entry for each custom folder: to go directly to the given folder's contents page
    • +
  • +
  • Forums: this entry directs you to the main forums page where you can access all the general forums and your alliance forums
  • +
+
+
+ On the right part of the page, a set of icons provide shortcuts to some important pages:
    +
  • Planet icon: the link on that icon directs you to the planets management page
  • +
  • Ships icon: the link on that icon directs you to the fleets management page
  • +
  • Map icon: the link on that icon directs you to the maps page
  • +
  • Alliance icon: the link on that icon directs you to the alliance page
  • +
  • Log out icon: the link on that icon logs you out
  • +
+
+
+
+
+ This theme is very similar to the LegacyWorlds Beta 5 one and contains exactly the same data. The only difference is that the two parts are inverted: the menu bar is above the player and game information instead of being below them. +
+
+
+ The LegacyWorlds Classic theme correspond to the theme used in Beta 1-4. It consists in a table containing three rows of links. The next sections wil describe the contains of each row. +
+
+ The top row contains four cells corresponding to the four following links:
    +
  • Overview: the Overview link directs you to the overview page which presents at a glance all important data about your empire and Legacy Worlds' universe
  • +
  • Fleets: this link directs you to the fleets management page which presents all your fleets and allows you to control their movements and other fleets related actions
  • +
  • Alliance: this link directs you to the main alliance page where you can manage and access alliance related data
  • +
  • Messages: this link directs you to the messaging system page where you can write a new message to be sent, manage and access your messages folders
  • +
+
+
+ The middle row contains four cells corresponding to the four following links:
    +
  • Planets: this link directs you to the planets overview page which presents the list of all your planets and a quick builder facility
  • +
  • Research: this link directs you to the main research page where you can manage research
  • +
  • Marketplace: this link directs you to the marketplace page, where is managed everything emplying selling or buying stuffs
  • +
  • Forums: this link directs you to the main forums page where you can access all the general forums and your alliance forums
  • +
+
+
+ The bottom row contains eight cells corresponding to the eight following links:
    +
  • Money: this link directs you to the Money page, where is collected all information about the finantial status of your empire
  • +
  • Beacons: this link directs you to the Probes and Beacons page where you can manage those features
  • +
  • Maps: this link directs you to the main Maps pages which present a display of the galaxy
  • +
  • Rankings: this link directs you to the main Rankings page which provided various rankings for players and alliances
  • +
  • Enemies: this link directs you to the Enemies page where you can manage enemies lists
  • +
  • Trusted Allies: this link directs you to the Trusted Allies page where you can manage a trusted allies list
  • +
  • Preferences: this link directs you to the Preferences page where you can set up game specific preferences
  • +
  • Log out: this links logs you out of the game
  • +
+
+
+
+
diff --git a/manual/beta5/en/game_overview.lwdoc b/manual/beta5/en/game_overview.lwdoc new file mode 100644 index 0000000..e416b62 --- /dev/null +++ b/manual/beta5/en/game_overview.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Game Overview +
+ Legacy Worlds is an online multiplayer intergalactic war game. Your goal as a player: build up an empire and defeat the other players. How to achieve that: through technological research, alliances with other players and of course conquests. +
+ Legacy Worlds is a tick based game. This means events are controlled at given intervals of time called ticks. +
+ Legacy Worlds is a text based game so don't expect any fancy graphics. +
+
+ At first started as a University project by one of the developers it's now developed by a little team which felt it was a pity to let it go to waste and disappoint its current players. +
+ The current team includes:
    +
  • El Christoph: original design and game concept
  • +
  • TSeeker: lead developer and game design
  • +
  • Ju: game design, game manual, developer
  • +
  • Sycophant: game design, random ideas, orthograph fascist
  • +
+
+
+ This version is Beta 5 and is as the previous ones completely free. Our goal isn't to make money or even to have players to contribute for the costs implied by hosting an online game. It's to have fun as much as our players. +
+ The server hosting the game is currently lended by one of the developers who also shares his bandwidth with all of you players. The domain name has been purchased by another of the developers. +
+
diff --git a/manual/beta5/en/game_rules.lwdoc b/manual/beta5/en/game_rules.lwdoc new file mode 100644 index 0000000..6c2e5e4 --- /dev/null +++ b/manual/beta5/en/game_rules.lwdoc @@ -0,0 +1,43 @@ + + + beta5 + en + Game Rules +
+ As in every game there are rules in LegacyWorlds. Those rules apply to all players and are listed in the following sections of the manual. +
+ Breaking any of those rules has a simple consequence: your account will be deleted and an explanation e-mail will be sent to the e-mail address associated with the account. +
+
+ As stated in the general rules, it's forbidden for one person to hold several accounts. If multi accounts are found by the staff all the suspicious accounts will be deleted. +
+ Occasional password sharing is allowed but abusing this authorisation will be met by the same consequence as multiple accounts holding. +
+
+ Accessing someone else' account without their knowledge nor authorisation is not only illegal, it's cheating. +
+ If any player can be proved to have done so his account will be deleted. +
+
+ Using an open proxy to connect to the game server is not only a security threat for our (TSeeker's and Ju's) network but also a suspicious behaviour from a player. Who would need to hide who he is unless he intends to do something that is against the rules? +
+ As a consequence all accounts connecting from open proxies will be deleted without warning. +
+ If you have no idea about what I might be talking about the probability this rule concerns you is close to 0 so don't bother.... +
+
+ Forum moderation rules are set up to try and prevent players from having an improper behaviour in the game community. But if those aren't sufficient, harsher measures can be taken. +
+ If a player gets banned more than 5 times from the forums, his account will be deleted. +
+
+ It is strictly forbidden to create external tools for personal or alliance purposes. +
+ If you are interested in building any tool related to the game, be it an irc bot, an alliance management feature or whatever system that easily allows to perform game related tasks, please contact the staff. We don't disagree with the concept of tools per se as long as they are accessible to all players. A lot of tools ideas are already foreseen for a future beta version or even this one. It would be completely stupid to develop the same thing twice . + As a consequence all tools should be either:
    +
  • built by the game staff and integrated into the game interface so that any player can use them
  • +
  • developed by players authorised to do so. In that case, they have to ask an authorisation from the staff so that the same tools aren't developped twice. If the development is authorised they can be granted the means to easily build the tool and host it on the game server if required software is available. The tool will in any case have to be made publicly available to all players, once it has been reviewed and approved by the staff.
  • +
+ Players who develop tools without authorisation will have their account deleted. +
+
diff --git a/manual/beta5/en/general_game.lwdoc b/manual/beta5/en/general_game.lwdoc new file mode 100644 index 0000000..76ce6db --- /dev/null +++ b/manual/beta5/en/general_game.lwdoc @@ -0,0 +1,37 @@ + + + beta5 + en + General Game Information +
+ This section of the manual presents some general information about the game including account creation and games management along with a presentation of the main interface and overview page. +
+
+ The LegacyWorlds home page consists in four different sections:
    +
  • a top banner containing a set of links
  • +
  • a left panel containing various forms and information
  • +
  • a middle section which consists in the body of the page
  • +
  • a bottom banner containing another set of links and credits
  • +
+ This part of the manual presents those different sections more precisely. +
+
+ Each player in LegacyWorlds has a single account which provides him access to all possible games he wishes to play. This section of the manual explains in details how to create and activate an account along with account deletion and how to manage games. +
+
+ Each in game page is split into three sections:
    +
  • Top banner: you'll find there the main menu of the game
  • +
  • Banner: here is located the title of the page you're currently on
  • +
  • Page body: the actual contents of the page are there
  • +
+ In this manual section, more precision are provided about all those different parts of the interface. +
+
+ The overview page is the first page you get directed to when logging into a LegacyWorlds Beta 5 game. It provides a short or a more complete display of important facts about what is going on in game. +
+ +
+
+
+
+ diff --git a/manual/beta5/en/general_rules.lwdoc b/manual/beta5/en/general_rules.lwdoc new file mode 100644 index 0000000..0b8ebfb --- /dev/null +++ b/manual/beta5/en/general_rules.lwdoc @@ -0,0 +1,39 @@ + + + beta5 + en + General Rules +
+ As in every game there are rules. Those rules are to be agreed upon and followed by both sides: players and people running the game. Those are the topic of this section. +
+ Some general elements that didn't fit in any other section of the manual are also presented here. +
+
+ As in every system where you have an account and where you interact with other people some general good practice rules apply or should apply. Those are presented in this manual section. +
+
+ Along with the above good practices, some rules apply to your account in Legacy Worlds. Those are summed up in this accounts section of the manual. +
+
+ As in every game there are rules. Those rules are detailes in the Game Rules section of the manual. +
+
+ In order for players to get to know each others and to play better as members of an alliance or simply of the LegacyWorlds community the game is set with some internal forums. Those forums are a tool for you to use and as all tools allowing people to say what they have to say some simple rules apply. +
+ So far we've always had a policy of free speech but as everyone knows one's freedom ends where begins someone else's freedom. +
+ As a consequence:
    +
  1. Public forums are moderated by the staff and a set of volunteers appointed by staff members
  2. +
  3. Alliance forums are to be moderated by the alliance's leader and eventually other alliance members appointed by the alliance leader. If posts / threads that don't comply with the moderation rules are brought to the staff's attention, the alliance's leader will be held personally responsible. This means the same punishment will be applied to the alliance's leader as to the offender
  4. +
+
+ + +
+
+
+
+
+ diff --git a/manual/beta5/en/glossary.lwdoc b/manual/beta5/en/glossary.lwdoc new file mode 100644 index 0000000..f88ba74 --- /dev/null +++ b/manual/beta5/en/glossary.lwdoc @@ -0,0 +1,160 @@ + + + beta5 + en + Glossary +
+ A user in computing context is one who uses a computer system. Users may need to identify themselves for the purposes of accounting, security, logging and resource management. In order to identify oneself, a user has an account (a user account) and a username, and in most cases also a password (see below). Users employ the user interface to access systems, and the process of identification is often referred to as log in. + In LegacyWorlds each user has a single account that allows him to play as many LegacyWorlds game as he wishes among the available games. +
+
+ Income, generally defined, is the money that is received as a result of the normal business activities of an individual or a business. +
+ In the case of LegacyWorlds the base income represents the basic amount generated by a given planet, without taking into account factories output. +
+
+ Battle cruisers are large and fast heavy combat and transport ships. +
+
+ A capital ship is a ship equipped with hyperspace engines. As such it can travel from one stellar system to the next through hyperspace. Capital ships have hauls allowing them to transport a certain number of system ships. +
+
+ A civilian technology is a technology which purpose is to improve the well-being of the citizens of the empire and the empire's efficiency. +
+
+ In broad terms, corruption is the misuse by government officials of their governmental powers for illegitimate, usually secret, private enrichment. Misuse of government power for other purposes, like repression of political opponents and general police brutality, is not considered political corruption. +
+ In game, it causes the reduction of factories' productivity. +
+
+ Cruisers are heavy combat and transport capital ships. +
+
+ The current funds of your empire represents the finantial capital it owns at the moment. It's the amount of money that can be used to build or buy items or implement technologies. +
+
+ The daily profits of your empire corresponds to the amount of money it earns each day. It's the difference between your income and the various upkeep costs you have to pay to sustain your fleets. +
+
+ Generally, an empire is defined as a state that extends dominion over areas and populations that are culturally and ethnically distinct from the culture at the center of power. Like other states, an empire maintains its political structure at least partly by coercion. +
+ In the LegacyWorlds universe, an empire is constituted by the set of planets that are controlled by a given player. +
+
+ A factory or manufacturing plant is a large industrial building where workers manufacture goods or supervise machines processing one product into another. Most modern factories have large warehouses or warehouse-like facilities that contain heavy equipment used for assembly line production. Archetypically, factories gather and concentrate resources, workers, capital and plant. +
+ In LegacyWorlds there are two types of factories:
    +
  • Industrial Factories produce goods that are sold and provide income to your planets
  • +
  • Military Factories are dedicated to producing warfare
  • +
+
+
+ Fighters are light attack system ships. +
+
+ A fleet is a large formation of warships. In order to control your ships, you have them organised into fleets and you give orders to each of those fleets. +
+
+ Each ship in LegacyWorld has a power. This power represents both its firepower and resistance to attacks. The total power of a fleet represents the combined power of the ships composing it. +
+
+ A fundamental technology is a technology which goal is to improve the knowledge of the empire. As such a fundamental technology might not have direct application. +
+
+ GA Ships (shorthand for Ground Assault Ships) are light attack system ships equipped with troup pods. As such they are the only ships capable of taking over a planet. +
+
+ A game in LegacyWorlds consists in a universe where a group of players play according to a set of rules using a given interface and a set of tools. Several LegacyWorlds games may be running at the same time, each possibly answering to different game parameters. Those various games might be for instance different betas. +
+
+ Happiness is an emotional or affective state that feels good or pleasing. Happiness is often correlated to the presence of favorable events (such as a promotion, a marriage, lottery winnings, etc.) and the absence of troubles or bad luck (such as accidents, getting fired, divorce, conflicts, etc.). +
+ Happines is a concept used in LegacyWorlds to represent the overall satisfaction of one planet's population. +
+
+ Hyperspace is any region of space co-existing with our own universe (in some cases displaced in an extra spatial dimension) which may be entered using some sort of space-altering device. While hyperspace is in some way anchored to the normal universe, its properties are not the same as normal space, so traveling in hyperspace is largely inequivalent to traveling in normal space. This allows of faster than light (FTL) travel: while the shortest distance between two points in normal space is a straight line, hyperspace allows those points to be closer together, or a curved line in normal space to be straight, etc. +
+
+ Internal Transmissions are messages that are automatically sent to your Internal Transmissions folder when important game events occur, such as battles, alliance related events, scientific assistance offers and the like. +
+
+ Law is the set of rules or norms of conduct which forbid, permit or mandate specified actions and relationships among people and organizations. +
+ In Legacyworlds researches might provide you with laws. Enacting those laws has an effect either positive or negative on game parameters. +
+
+ A map is a simplified depiction of a space, a navigational aid which highlights relations between objects within that space. A map is a two-dimensional, geometrically accurate representation of a three-dimensional space, in the case of LegacyWorlds the game universe, that is to say a galaxy. +
+ Different versions of the map of the universe are available, providing various useful information about your empire and its surroundings. +
+
+ A military technology is a technology with direct military applications. It can either improve ships' speed, defenses or firepower. +
+
+ A nebula is an interstellar cloud of dust, gas and plasma. Some are the birthplace of stars. They are formed when very diffuse molecular clouds begin to collapse under their own gravity, often due to the influence of a nearby supernova explosion. The cloud collapses and fragments, forming sometimes hundreds of new stars. The newly-formed stars ionize the surrounding gas to produce an emission nebula. Other nebulae are formed by the death of stars; a star that undergoes the transition to a white dwarf blows off its outer layer to form a planetary nebula. Novae and supernovae can also create nebulae known as nova remnants and supernova remnants respectively. +
+ In the LegacyWorlds universe, nebulae are sectors of space where ships travel slowlier than in the rest of the universe. +
+
+ A neutral planet is a planet that doesn't belong to any player. It doesn't mean it doesn't have a government, just that it's not controlled by a player. Neutral planets don't build anything on their own and have the number of turrets or factories they had when they were created or abandoned. +
+
+ A P-* planet is a planet just as every other planet. All planets are generated with a name of the form P-[xxx] where xxx represents any combination of letters and numbers. As players have the ability to rename planets, P- planets are often neutral planets. +
+
+ A planet is a celestial body that is in orbit around a star or stellar remnants; has a mass below the limiting mass for thermonuclear fusion of deuterium; and is above the minimum mass/size requirement for planetary status in our solar system. +
+ In the LegacyWorlds universe, there are 6 planets in each stellar system. Each can be colonised as part of one player's empire. +
+
+ In LegacyWorlds the Wormhole Supernova technology allows you to destroy a planet. The principle of the technology is to get a wormhole to collapse inside the planet, getting it to explode from the inside. The matter constituting the planet doesn't vanish and a field of debris is created, making travel through this region of space more difficult and as such slower. +
+
+ Ranking is the process of positioning players on an ordinal scale in relation to others. A list arranged in this way is said to be in rank order. Different rankings are providing depending on the topic they are related to. +
+
+ Each day the planets in each empire generate a certain amount of research points. This amount is linked to their population. Each technology in the technology graph costs a certain amount of research points and the points you've gained are used to make progresses in your researches. +
+
+ As all technologies aren't available to all players, some means of exchanging technologies are necessary. Scientific assistance can be given or received to/from other players in the form of specific technologies or research points. +
+
+ The Server Time is the time set on the LegacyWorlds server. It is set to the Coordinated Universal Time (UTC). +
+ Coordinated Universal Time (UTC) is a high-precision atomic time standard. UTC has uniform seconds defined by International Atomic Time (TAI), with leap seconds announced at irregular intervals to compensate for the earth's slowing rotation, and other discrepancies. The leap seconds allow UTC to closely track Universal Time (UT), which is a time standard based on the earth's angular rotation, rather than a uniform passage of seconds. +
+
+ In the LegacyWorlds' context, a ship is obviously a starship or a spaceship. A starship is a spaceship designed for interstellar travel, specifically between star systems. Science fiction abounds with tales of such ships. Space-going vessels that are not intended for travel between star systems are often referred to as spaceships. For a more precise description of the different classes of ships available, look at the Fleets page of the manual. +
+
+ A stellar system is a system comprised of a star or group of stars, and, perhaps, planetary systems of smaller bodies (such as planets or asteroids), in gravitational association. The solar system is the stellar system comprised of Sol and other bodies, such as Earth, in orbit around it. +
+ In the LegacyWorlds universe, there are 6 planets in each stellar system. Each can be colonised as part of one player's empire. +
+
+ A system ship is a ship that isn't equipped with hyperspace engine. As a consequence it can only travel in the stellar system it is located in unless it is carried by a capital ship. +
+
+ Despite its cultural pervasiveness, technology is an elusive concept. It can refer to material objects, such as machines, hardware or utensils, but it can also encompass broader themes, such as systems, methods of organization, and techniques. +
+ In the LegacyWorlds' universe a set of technologies are available for research, allowing for the evolution of youe empire. +
+ +
+ A tick is a moment in time during which an automatic game action occurs. At various time intervals, specialised scripts are triggered wich update different game data. Ticks are what make the game dynamic without it to be run real time, which allows for players not to be online all the time and reduces bandwidth. +
+
+ A turret is usually a rotating weapon platform. This can be mounted on a fortified building or structure such as an anti-naval land battery, or on an armoured fighting vehicle, a naval ship, or a military aircraft. +
+ In LegacyWorlds' universe, turrets are stationnary defenses that can be set up on planets to help destroy enemy fleets orbiting the planet. +
+
+ Upkeep represents the necessary care and management of equipment and operations. All mechanical equipment and organizations need continual maintenance to forestall a total system breakdown. This maintenance has a cost that is also called upkeep. +
+ In LegacyWorlds you have to pay an upkeep for factories, turrets and fleets. +
+
+ In physics, a wormhole (also known as Abbreviated Space) is a hypothetical topological feature of spacetime that is essentially a "shortcut" or "abbreviation" through space and time. A wormhole has at least two mouths which are connected to a single throat. If the wormhole is traversable, matter can 'travel' from one mouth to the other by passing through the throat. + In the LegacyWorlds universe, wormholes aren't theoretical anymore and various technologies are based on building and sustaining stable wormholes. +
+
diff --git a/manual/beta5/en/good_practices.lwdoc b/manual/beta5/en/good_practices.lwdoc new file mode 100644 index 0000000..41a9263 --- /dev/null +++ b/manual/beta5/en/good_practices.lwdoc @@ -0,0 +1,27 @@ + + + beta5 + en + Good Practices +
+ As in every system where you have an account and where you interact with other people some general good practice rules apply or should apply. Those are presented in this manual section. +
+
+ You'll find here a few advices about your password to access Legacy Worlds:
    +
  • Your password, as for everything requiring one, has to be complex enough if you don't want anyone to guess it and use your account without your knowledge. A short password is also easier to guess than a long one. The name of your dog is a very bad idea. A random combination of letters and digits along with special characters usually makes a good password
  • +
  • Don't use the same password as for a banking site, work or university login, email account and so on. If someone was to guess your Legacy Worlds account information he would then be able to access all those other accounts and might be able to do things that may cost a lot more than a few fleets or in game cash
  • +
  • Think about changing your password on a regular basis. This also helps reduce the risks of your account being used by someone else
  • +
  • Make sure you sign off before closing your browser's window. By doing so you prevent anyone who might have access to the same computer from using your account if they don't know your account information
  • +
+
+
+ The other players in the game aren't only nicks on the Internet. There are real persons with feelings beyond those nicks. As such you owe them the respect that is owed to every human being. Whatever their colour, their faith, their nationality, their religion, their opinions, their social behaviour and so on there is no point in saying anything that might hurt their feelings nor in exchanging insults. +
+ Your are also invited to avoid any offensive alliance or planet names. If the game administrators were to find any of those:
    +
  • The alliance would be disbanded by the game administrators
  • +
  • The planet would be taken from the player who owns it, neutralised and renamed as a P-* planet. Whatever fleets the former may have had orbiting the planet would be disbanded
  • +
+
+ Keep in mind it's only a game and that there is no use in ressorting to degrading comments over a game. +
+
diff --git a/manual/beta5/en/home_page.lwdoc b/manual/beta5/en/home_page.lwdoc new file mode 100644 index 0000000..3833eae --- /dev/null +++ b/manual/beta5/en/home_page.lwdoc @@ -0,0 +1,135 @@ + + + beta5 + en + Legacy Worlds Home Page +
+ The LegacyWorlds home page consists in three different sections:
    +
  • a top banner containing a set of links
  • +
  • a left panel containing various forms and information
  • +
  • a middle section which consists in the body of the page
  • +
+ The following sections of the manual will present those various items. +
+
+
+ The top banner consists in the set of links that allow you to navigate between the various pages of the external LegacyWorlds web site:
    +
  • Home: to go back to the home page
  • +
  • Create Account: to go to the account creation form; this link is only present if you're not logged in yet
  • +
  • Manual: to go to the manual page
  • +
  • Rankings: to go to the external Rankings page
  • +
+
+
+
+ The left panel can be split into two sections:
    +
  • Top part: it includes account related data
  • +
  • Bottom part: it presents some rankings or manual browsing facilities
  • +
+
+
+ The top part of this panel includes account related information. +
+ When you are not logged in, it presents three sets of data:
    +
  • login form: in order to log in, type you username and password in the corresponding fields and click on the Log in -> button. You will then log in into your account
  • +
  • players information: the number of online players and registered players are displayed below the login form
  • +
  • Terms of use: ait's a link to a special page describing shortly the terms of use for the page
  • +
+
+ When you are logged in, this section presents the name of the account with which you have logged in. Below you'll find a link to the account's preferences pages and the terms of use page and a Log out link. Clicking on the log out button logs out the account and directs you to a special log out page. +
+
+ On most pages, the bottom part of the left panel presents the Top Ten of the Public Beta 5 Overall round rankings, including Player name and corresponding number of Points. +
+ The very bottom part of the left panel provides the version number of the newest LegacyWorlds game. +
+ On the manual page, it contains manual navigation tools, that will be explained along with the manual page. +
+
+
+
+
+
+ The home page for the game has two different layouts depending if you're logged in or not. Each will be described in the next parapgraphs. +
+
+ If you're not logged in, this page presents a quick introduction to the game. It also provides a link to the website of the team who built the game: DeepClone Development. +
+ Below this introduction, you can find an Online Games Directories title. This part of the page lists online review of the game on the following websites: + The bottom part of the page includes a set of links allow you to access other DeepClone Development games related items:
    +
  • F1 Manager Pro: another online game developed by the team
  • +
  • Legacy Worlds Beta 4: this link directs you to the main page of the previous version of the game (beta 4) if you wish to play it. This older version will be kept running for a few more months
  • +
  • external forums: since beta 1-4 didn't have any included forums, external forums on a website hosting message boards were created. This link directs you to this external forum.
  • +
+
+
+
+ If you are logged in, this page is split into three sections:
    +
  • A games related section
  • +
  • A vacation mode related section
  • +
  • A closing account related section
  • +
+ The next paragraphs will cover all these topics. +
+
+ The games related section presents the list of games you are currently playing, including:
    +
  • Game name: the name of the game
  • +
  • Planets: the number of planets you own in this particular game
  • +
  • Cash: the amount of money you own in this game
  • +
+ Clicking on the name of a game will log you in this game. +
+
+ Vacation mode allows you to take a break from the game. Entering vacation mode doesn't deleted your account nor your game data and you're still able to access the pages of the games you're playing. Being on vacation only means that your assets are protected but that you can't perform any game actions. +
+ This part of the page first of all provides you with some information about your vacation status:
    +
  • number of vacation credits: vacation credits correspond to period of 6h during which you can stay in vacation
  • +
  • Corresponding period of time: the period of time corresponding to your amount of vacation credits
  • +
+ Below is displayed a Enter Vacation mode button. Clicking the button starts the process of entering vacation mode which is described more precisely in the vacation mode section of the manual. +
+
+ Closing your account allows you to permanently remove all game data link to your account. +
+ This part of the page first of all explains what closing the account means. Below is displayed a Close my account button. Clicking the button gets you to the forms that allow you to actually close your account, which is explained more precisely in the closing account section of the manual. +
+
+
+
+
+ Once on the manual page, you have access both to the manual itself and to a set of tools in the bottom part of the left panel. Both will be presented here. +
+
+ All manual pages are organised in the same fashion:
    +
  • The title of the manual section is displayed at the top of the page
  • +
  • Below the title, the contents of the sections are displayed in a bulleted list. Each item in the list is an internal link to the corresponding part of the page
  • +
  • The main parts of each section (top level bullets in the contents) are separated by horizontal lines
  • +
  • In the body of the page, each title is followed with a Top link on the right. Clicking this link will get you back to the top of the page
  • +
+
+
+ There are three kinds of navigation tools that allow you to browse the manual:
    +
  • A set of navigation links: those links (Top, Previous, Up, Next) allow you to go back and forth in your navigation session
  • +
  • A search facility: type in a keyword in the provided textfield and click on the Search button. This will direct you to a special result page displaying the manual sections including your search string with links to access them.
  • +
  • A contents list: This list includes a set of manual topics corresponding each to one manual page. Clicking on one of those topics will display the corresponding page.
  • +
+
+
+
+ The Rankings page allows you to access all rankings for any LegacyWorlds game. The top of the page consists in a form where you can choose the rankings to display, by selecting:
    +
  • Game: the first drop down list allows you to select the game for which you want to see the rankings
  • +
  • Rankings: the second drop down list allows you to select the type of rankings to display
  • +
+ Clicking the Display Rankings button updates the rankings list in the bottom part of the page. This list includes for each player in the rankings:
    +
  • Rank: number of the rank
  • +
  • Name: name of the player
  • +
  • Points: number of points the player has for this particular ranking
  • +
+
+
+ +
+ diff --git a/manual/beta5/en/law_discrimination.lwdoc b/manual/beta5/en/law_discrimination.lwdoc new file mode 100644 index 0000000..6acef8a --- /dev/null +++ b/manual/beta5/en/law_discrimination.lwdoc @@ -0,0 +1,54 @@ + + + beta5 + en + Law: discrimination +
+ [...] +
+
+
+ (Act no. 2001-1066 of 16 November 2001 Article 1 Official Journal of 17 November 2001) +
+ (Act no. 2002-303 of 4 March 2002 Article 4 Official Journal of 5 March 2002) +
+ Discrimination comprises any distinction applied between natural persons by reason of their origin, sex, family situation, physical appearance or patronymic, state of health, handicap, genetic characteristics, sexual morals or orientation, political opinions, union activities, or their membership or non-membership, true or supposed, of a given ethnic group, nation, race or religion. +
+ Discrimination also comprises any distinction applied between legal persons by reason of the origin, sex, family situation, physical appearance or patronymic, state of health, handicap, genetic characteristics, sexual morals or orientation, political opinions, union activities, membership or non-membership, true or supposed, of a given ethnic group, nation, race or religion of one or more members of these legal persons. +
+
+ Act no. 2001-1066 of 16 November 2001 Article 1 Official Journal of 17 November 2001 +
+ Ordinance No. 2000-916 of 19 September 2000 Article 3 Official Journal of 22 September into force 1 January 2002 +
+ Discrimination defined by article 225-1, committed against a natural or legal person, is punished by two years? imprisonment and a fine of ? 30,000 where it consists:
    +
  1. of the refusal to supply goods or services;
  2. +
  3. of obstructing the normal exercise of any given economic activity;
  4. +
  5. of the refusal to hire, to sanction or to dismiss a person;
  6. +
  7. of subjecting the supply of goods or services to a condition based on one of the factors referred to under article 225-1;
  8. +
  9. of subjecting an offer of employment to a condition based on one of the factors referred to under article 225-1.
  10. +
+
+
+ (Act no. 2002-303. of 4 March 2002 Article 4 Official Journal of 5 March 2002) +
+ The provisions of the previous article do not apply to:
    +
  1. discrimination based on state of health, when it consists of operations aimed at the prevention and coverage of the risk of death, of risks for the physical integrity of the person, or the risk of incapacity to work or invalidity. However, when it is based on the consideration of predictive genetic tests relating to an illness that has not yet commenced or the genetic predisposition towards an illness, this discrimination is punished by the penalties provided for by the previous article;
  2. +
  3. discrimination based on state of health or handicap, if it consists of a refusal to hire or dismiss based on a medically established incapacity, according to either the provisions of title IV of book II of the Labour Code, or of the laws defining the statutory framework of the public service;
  4. +
  5. recruitment discrimination based on gender when the fact of being male or female constitutes the determining factor in the exercise of an employment or professional activity, in accordance with the provisions of the Labour Code or of the laws defining the statutory framework of the public service.
  6. +
+
+
+ Legal persons may incur criminal liability for the offence defined under article 225-2, pursuant to the conditions set out under article 121-2. The penalties incurred by legal persons are:
    +
  1. a fine, pursuant to the conditions set out under article 131-38;
  2. +
  3. the penalties enumerated under 2, 3, 4, 5, 8 and 9 of article 131-39.
  4. +
+ The prohibition referred to in 2 of article 131-39 applies to the activity in the exercise of which or on the occasion of the exercise of which the offence was committed. +
+ [...] +
+
+
+
+
+
diff --git a/manual/beta5/en/law_spam.lwdoc b/manual/beta5/en/law_spam.lwdoc new file mode 100644 index 0000000..906ad86 --- /dev/null +++ b/manual/beta5/en/law_spam.lwdoc @@ -0,0 +1,21 @@ + + + beta5 + en + Law: spam +
+ [...] +
+ (inserted by Order no. 2001-741 of 23 August 2001 art. 5 and art. 12 Journal officiel of 25 August 2001) +
+ Direct canvassing by a business, by means of automatic calling machines or faxes or electronic messages, of consumers who have not expressed their agreement to receive such calls is prohibited. +
+ Distance communication methods, other than those mentioned in the previous paragraph, involving personal communication, may only be used where the consumer has not raised an objection. +
+ The conditions under which the consumer expresses his agreement to receive the calls mentioned in the first paragraph, information that the professional must supply to the consumer regarding his opportunity to raise an objections as well as the conditions under which objection registers are kept are prescribed by Council of State decree. +
+ [...] +
+
+
+
diff --git a/manual/beta5/en/law_unauthorised_access.lwdoc b/manual/beta5/en/law_unauthorised_access.lwdoc new file mode 100644 index 0000000..ee0ce9b --- /dev/null +++ b/manual/beta5/en/law_unauthorised_access.lwdoc @@ -0,0 +1,57 @@ + + + beta5 + en + Law: unauthorised access +
+ [...] +
+
+ Ordinance no. 2000-916 of 19th September 2000 Article 3 Official Journal of 22nd September 2000 came into force the 1st January 2002 +
+ Fraudulently accessing or remaining within all or part of an automated data processing system is punished by one year's imprisonment and a fine of 15,000 euros. +
+ Where this behaviour causes the suppression or modification of data contained in that system, or any alteration of the functioning of that system, the sentence is two years' imprisonment and a fine of 30,000 euros. +
+
+ Ordinance no. 2000-916 of 19th September 2000 Article 3 Official Journal of 22nd September 2000 came into force the 1st January 2002 +
+ Obstruction or interference with the functioning of an automated data processing system is punished by three years' imprisonment and a fine of 45,000 euros. +
+
+ Ordinance no. 2000-916 of 19th September 2000 Article 3 Official Journal of 22nd September 2000 came into force the 1st January 2002 +
+ The fraudulent introduction of data into an automated data processing system or the fraudulent suppression or modification of the data that it contains is punished by three years' imprisonment and a fine of 45,000 euros. +
+
+ The participation in a group or conspiracy established with a view to the preparation of one or more offences set out under articles 323-1 to 323-3, and demonstrated by one or more material actions, is punished by the penalties prescribed for offence in preparation or the one that carries the heaviest penalty. +
+
+ Natural persons convicted of any of the offences provided for under the present Chapter also incur the following additional penalties:
    +
  1. forfeiture of civic, civil and family rights, pursuant to the conditions set out under article 131-26;
  2. +
  3. prohibition, pursuant to the conditions set out under article 131-27 to hold public office or to undertake the social or professional activity in the course of which or on the occasion of the performance of which the offence was committed, for a maximum period of five years;
  4. +
  5. confiscation of the thing which was used or intended for the commission of the offence, or of the thing which is the product of it, with the exception of articles subject to restitution;
  6. +
  7. mandatory closure, for a maximum period of five years of the business premises or of one or more of the premises of the undertaking used to commit the offences;
  8. +
  9. disqualification from public tenders for a maximum period of five years;
  10. +
  11. prohibition to draw cheques, except those allowing the withdrawal of funds by the drawer from the drawee or certified cheques, for a maximum period of five years;
  12. +
  13. public display or dissemination of the decision, in accordance with the conditions set out under article 131-35.
  14. +
+
+
+ Legal persons may incur criminal liability for the offences referred to under the present Chapter pursuant to the conditions set out under article 121-2. +
+ The penalties incurred by legal persons are:
    +
  1. a fine, pursuant to the conditions set out under article 131-38;
  2. +
  3. the penalties referred to under article 131-39.
  4. +
+ The prohibition referred to under 2 of article 131-39 applies to the activity in the course of which or on the occasion of the performance of which the offence was committed. +
+
+ Attempt to commit the misdemeanours referred to under articles 323-1 to 323-3 is subject to the same penalties. +
+ [...] +
+
+
+
+
diff --git a/manual/beta5/en/legal.lwdoc b/manual/beta5/en/legal.lwdoc new file mode 100644 index 0000000..f86aa76 --- /dev/null +++ b/manual/beta5/en/legal.lwdoc @@ -0,0 +1,41 @@ + + + beta5 + en + Legal Issues + + + + + + +
+
+
+ diff --git a/manual/beta5/en/main.lwdoc b/manual/beta5/en/main.lwdoc new file mode 100644 index 0000000..e43dc18 --- /dev/null +++ b/manual/beta5/en/main.lwdoc @@ -0,0 +1,11 @@ + + + beta5 + en + Manual +
+
+
+
+
+ diff --git a/manual/beta5/en/maps.lwdoc b/manual/beta5/en/maps.lwdoc new file mode 100644 index 0000000..db200fc --- /dev/null +++ b/manual/beta5/en/maps.lwdoc @@ -0,0 +1,131 @@ + + + beta5 + en + Maps +
+ LegacyWorlds takes place in a far far away galaxy. The maps allow you to get a grasp on the lay-out of this galaxy. +
+ This manual section will first of all introduce basic concepts about the galaxy and then present the various available maps along with the associated controls. +
+
+
+
+ A galaxy is a huge gravitationally bound system of stars, interstellar gas and dust, plasma, and (possibly) unseen dark matter. Stars in the galaxy all orbit a common center of gravity. +
+ Representing a galaxy as it should be would be of course very difficult to read as distances between objects are huge compared to the size of the objects. Moreover galaxies aren't exact planes and distances between objects vary from object to object. +
+
+
+ For ease of reading and understanding the galaxy in which the game takes place is represented inside a grid, which consists in a distorted view of reality. +
+ Two objects are in adjacent grid squares because they are relatively close to one another in reality. But it doesn't mean the distances aren't tremendous. +
+
+ Each square in the galaxy grid contains 6 objects. Those objects may be planets, planetary remains or parts of a nebula. +
+ Within a planetary system, planets orbit the central star in elliptical orbits. Eah object orbits the star at a different distance. Thus each object is said to be located on a different orbit. Orbits are ordered according to the distance to the star. The first orbit is the closest while the 6th is the furthest. +
+ Even if no planetary system exists inside a nebula the same orbit system is applied in the game galaxy. As a consequence nebula squares are also split in 6 orbits, representing each one region of the nebula square. +
+ In the grid representation of LegacyWorlds galaxy, the object located on the first orbit is the first one in the list of items in the stellar system grid square and so on. +
+
+
+ The LegacyWorlds galaxy is also an expanding galaxy. It's still young and contains enough dense molecular clouds to produce new generations of stars, thus increasing its size. The in game universe expansion is triggered by new players joining the game in order to provide them with enough room to settle in. +
+
+
+ As mentionned earlier various stellar objects are present in the galaxy:
    +
  • planets
  • +
  • planetary remains
  • +
  • nebula
  • +
+ All those objects are discussed in mode details in the Types of stellar objects section of the individual planet page manual page. +
+
+
+
+ In order to help you navigate your ships in LegacyWorlds galaxy three kinds of maps are available:
    +
  • Planets Map: this map displays planet names for each planet
  • +
  • Alliance Map: this map displays alliances tag for each planet
  • +
  • Listing: this presents the planets in the galaxy as a list
  • +
+ Common features are also available for all kinds of maps. +
+
+
+ All maps present two features that work the same in all cases:
    +
  • Maps controls: these controls allow you to customise the display of the maps
  • +
  • Maps caption: the caption explains the various colour codes used on the map
  • +
+ The next paragraphs explain both topics more precisely. +
+
+ The common controls at the top of the page allow you to choose the map to display and its parameters. More precisely those features include:
    +
  • Display: the drop down menu allows you to select the type of map you want to see among the three available modes
  • +
  • Grid size: the drop don menu allows you to change the size of the grid to diplay, from 1 system to 7x7 systems
  • +
  • Center on: selecting the corresponding radio button or typing something on the same line as a radio button allows you to chose to center the map either on:
      +
    • coordinates: you have to type the X and Y coordinates to center the map on in the two provided textfields
    • +
    • own planet: the drop down list allows you to select the name of a planet you own on which you want the map to be centered
    • +
    • planet: type the name of a planet in the provided textfield allows you to center the map on this planet
    • +
  • +
+ The Update Diplay button allows to take into account the modifications you might have made on the map parameters. +
+
+
+ A caption is also presented. the map colours include:
    +
  • Green: your own planets
  • +
  • Blue: planets belonging to members of your alliance
  • +
  • White: other planets, that is to say planets belonging to non alliance members, neutral planets and so on
  • +
  • Yellow: planetary remains
  • +
  • Shades of red: those aren't planets but nebula squares. Each level of red represent an opacity level or opacity class for the corresponding nebula sector. You can't own nebula squares but can travel through them and station fleets on them but the speed of your ships is reduced depending on the opacity of the nebula square
  • +
+
+
+
+
+ This is the standard display. It includes a grid of the requested size presenting X and Y coordinates. +
+ On the borders of the grid clicking on arrows allows you to move the focus of the displayed grid on the complete map. Left and right arrows allow you to move along the X axis whereas top and bottom arrows allow you to move along the Y axis. +
+
+ In stellar systems are presented for each planet:
    +
  • a planet image
  • +
  • a planet name: the name is a link to an individual planet page. The contents of the individual planet page depends on the level of information you have on the planet, that is to say whether it's yours, belongs to your alliance, you have fleets on it and so on
  • +
+ In nebula squares are displayed:
    +
  • in shades of red the names of the different nebula sectors
  • +
  • the background image depends on the average opacity of the nebula square
  • +
+
+
+
+
+ This display is a specialised one that allow you to grasp where various alliances are settled in. It includes a grid of the requested size presenting X and Y coordinates. +
+ On the borders of the grid clicking on arrows allows you to move the focus of the displayed grid on the complete map. Left and right arrows allow you to move along the X axis whereas top and bottom arrows allow you to move along the Y axis. +
+
+ In stellar systems are presented for each planet:
    +
  • a planet image
  • +
  • an alliance tag. Untagged planets, that is to say planets which don't belong to any alliance, are presented with empty brackets
  • +
+ In nebula squares are displayed:
    +
  • in shades of red the text N/A for all different nebula sectors
  • +
  • the background image depends on the average opacity of the nebula square
  • +
+
+
+
+ In listing mode planets in the selected area are presented as a list which includes for each planet:
    +
  • Coordinates: coordinates of the planet
  • +
  • Planet: planet image and name. The planet name is a link to an individual planet page. The contents of this page depends on the level of information you have on the planet, that is to say whether it's yours, belongs to your alliance, you have fleets on it and so on.
  • +
  • Tag: tag of the alliance to which the planet belongs. Empty brackets are displayed for untagged planets, that is to say planets which don't belong to any alliance. A - is displayed for nebula sectors
  • +
  • Opacity: it represents the density of matter in the area which influences ships speed. The opacity is mostly interesting for nebula sectors where it represents the class of the nebula. It's 0 for viable planets and 1 for planetary remains, that is to say planets which have been destroyed.
  • +
+ As in all lists clicking on the title of one column allows you to order the items in the list according to the values of that field, either in descending or ascending order. Clicking again on the same title allows to switch between descending and ascending order. +
+
+
diff --git a/manual/beta5/en/market_buy.lwdoc b/manual/beta5/en/market_buy.lwdoc new file mode 100644 index 0000000..d94c5e8 --- /dev/null +++ b/manual/beta5/en/market_buy.lwdoc @@ -0,0 +1,191 @@ + + + beta5 + en + Buying and accepting a gift +
+ When items are on sale, salers need customers. This part of the manual explains how to make buying offers, how to accept gifts and so on. +
+ Two kinds of sales offers exist in Legacy Worlds:
    +
  • Public Offers: the sale offers are made publicly and all players can buy items offered for sale
  • +
  • Direct offers: the sale or gift offers are only made to one player (you) and noone else is aware of them except you a,d of course the seller
  • +
+ Each category is managed from a particular subpage of the marketplace page through the corresponding link at the top of the page body. The next sections will describe each case. +
+
+
+ Public sales offers are accessible through the Public Offers subpage of the marketplace page. The offers are displayed in a geograpical basis. This means that all offers aren't displayed at once. Only offers in a particular region of space are listed. +
+ As a consequence, the subpage is split in two sections:
    +
  • The left part of the page lists planets and fleets sales offers in the region of space that is currently in focus
  • +
  • The right part of the page provides a tool that allows to navigate the galaxy and change the area of space that is under focus
  • +
+ The next sections will describe each part of the subpage. +
+
+ The right part of the page provides four different means to change the focus of the sales search:
    +
  • Minimap: this minimap is centered on the system in which is located your first planet and displays one stellar system at a time. Arrows are diplayed around the minimap. Using the arrows around the map allows you to change the focus of the minimap and move around that system
  • +
  • Centre on coordinates: you can center the minimap on a particular stellar system based on its coordinates. To do you you have to select the relevant radio button and the coordinates of the stellar system in the provided textfields. Clicking the Change Display button centers the map on this system
  • +
  • Centre on own planet: you can also center the minimap on one of your own planets. To do so, select the relevant radio button and choose the planet you're interested in in the drop down list. Clicking the Change Display button centers the minimap on the system the planet is located in
  • +
  • Centre on planet: you can also center the minimap on an stellar body based on its name. In that case select the corresponding radio button and type in the name of the stellar object in the provided textfield. Clicking the Change Display button centers the minimap on the system the stellar object is located in
  • +
+ You also have the possibility to define the size of the focus area. This size is represented by a distance around the stellar system displayed on the minimap. You can change this distance by using the Distance drop down list. Possible values are between 1 and 7. +
+ For each change you make in the form (except using the arrows in the minimap) don't forget to click the Update Display button to validate your changes. +
+
+
+ All current planets and fleets sales offers in the selected area of space are listed on the left part of this subpage, if any. The rest of this section will detail both lists. +
+
+
+ The list of planet sales offers can be split into two parts:
    +
  • Top part: it includes a set of controls that allow you to easily navigate into the list
  • +
  • List per se: it's the actual list of pending offers
  • +
+ The next sections of the manual will cover both parts of the list. +
+
+ On top of the list of pending offers a set of controls offer navigation means into the list. +
+ On the left side of the page, a drop down list allows to select a number of items to display on each page of the list. This number of items can be either 5, 10, 15, 20 or 25. If there are more items in total than this number, the list will be displayed onto several internal pages specific to the list without modifications of the rest of the Public Offers page. +
+ In the middle of the page is provided a search facility which allows you to search for particular planets. This search system consist in a textfield where you can type in a search string. Any planet which name contains the search string, wherever it may be located in the planet name, will match the search and be displayed. +
+ If there are more items in the list than have to be displayed on each list page, a drop down list appears on the right part of the page. This drop down list allows to select the list page to display. +
+
+ Below the set of control are listed all planets sales offers. This list includes for each offer:
    +
  • Planet: the name of the planet on sale. This name is a link to the individual planet page
  • +
  • Coords.: the coordinates of the planet
  • +
  • Pop.: the total population of the planet
  • +
  • Turrets: the total number of turrets on the planet
  • +
  • Fact.: the total number of factories on the planet
  • +
  • Fleets: if a fleet is bundled with the planet, the number of ships of each category is displayed with the following code:
      +
    • G for GA ships
    • +
    • F for fighters
    • +
    • C for cruisers
    • +
    • B for battle cruisers
    • +
    If no fleet is bundled the No Fleet string is displayed
  • +
  • Owner: the name of the player currently owning the planet
  • +
  • Expiration: if the offer expires over time, an expiration time and date are displayed. Otherwise this field contains the Never string.
  • +
  • Price: for public sales, the price is the buying price, for auction the price is the last bid
  • +
  • Action: two actions are possible:
      +
    • For public sales: clicking the Buy link opens an alert confirmation window where you have to confirm to buy the planet at the displayed price
    • +
    • For Auctions sales: click the Place Bid link opens an alert window where you can type in your bid. That bid has to be higher than the previous one. The player who placed the higher bid when the expiration time is reached buys the planet
    • +
  • +
+ Just as for most lists in game, clicking on the header of one column order the list items according to the value of this flied. Clicking again on the same header changes the ordering from ascending to descending and the other way around. +
+
+
+
+ The list of fleets sales offers can be split into two parts:
    +
  • Top part: it includes a set of controls that allow you to easily navigate into the list
  • +
  • List per se: it's the actual list of pending offers
  • +
+ The next sections of the manual will cover both parts of the list. +
+
+ On top of the list of pending offers a set of controls offer navigation means into the list. +
+ On the left side of the page, a drop down list allows to select a number of items to display on each page of the list. This number of items can be either 5, 10, 15, 20 or 25. If there are more items in total than this number, the list will be displayed onto several internal pages specific to the list without modifications of the rest of the Public Offers page. +
+ In the middle of the page is provided a search facility which allows you to search for particular locations where flets are on sale. This search system consist in a textfield where you can type in a search string. Any location which name contains the search string, wherever it may be located in the location name, will match the search and be displayed. +
+ If there are more items in the list than have to be displayed on each list page, a drop down list appears on the right part of the page. This drop down list allows to select the list page to display. +
+
+ Below the set of control are listed all fleets sales offers. This list includes for each offer:
    +
  • Location: the name of the location where fleets are on sale. This name is a link to the individual page of this location
  • +
  • Coords.: the coordinates of the location
  • +
  • G.A.S.: the number of GA ships in the fleet
  • +
  • Fgt.: the number of fighters in the fleet
  • +
  • Cru.: the number of cruisers in the fleet
  • +
  • B.Cru.: the number of battle cruisers in the fleet
  • +
  • Owner: the name of the player currently owning the fleet
  • +
  • Expiration: if the offer expires over time, an expiration time and date are displayed. Otherwise this field contains the Never string.
  • +
  • Price: for public sales, the price is the buying price, for auction the price is the last bid
  • +
  • Action: two actions are possible:
      +
    • For public sales: clicking the Buy link opens an alert confirmation window where you have to confirm to buy the fleet at the displayed price
    • +
    • For Auctions sales: click the Place Bid link opens an alert window where you can type in your bid. That bid has to be higher than the previous one. The player who placed the higher bid when the expiration time is reached buys the fleet
    • +
  • +
+ Just as for most lists in game, clicking on the header of one column order the list items according to the value of this flied. Clicking again on the same header changes the ordering from ascending to descending and the other way around. +
+
+
+
+
+
+ All current and past sales and gift offers that were made personally to you are listed on Direct Offers part of the marketplace page. This section is accessible through the Direct Offers link on top of the marketplace page. The rest of this section will detail both lists. +
+
+
+ The list of pending sale or gift offers can be split into two parts:
    +
  • Top part: it includes a set of controls that allow you to easily navigate into the list
  • +
  • List per se: it's the actual list of pending offers
  • +
+ The next sections of the manual will cover both parts of the list. +
+
+ On top of the list of pending offers a set of controls offer navigation means into the list. +
+ On the left side of the page, a drop down list allows to select a number of items to display on each page of the list. This number of items can be either 5, 10, 15, 20 or 25. If there are more items in total than this number, the list will be displayed onto several internal pages specific to the list without modifications of the rest of the Direct Offers page. +
+ In the middle of the page is provided a search facility which allows you to search for particular planets on sale or on which fleets are beeing sold. This search system consist in a textfield where you can type in a search string. Any planet which name contains the search string, wherever it may be located in the planet name, will match the search and be displayed. +
+ If there are more items in the list than have to be displayed on each list page, a drop down list appears on the right part of the page. This drop down list allows to select the list page to display. +
+
+ Below the set of control are listed all pending offers. This list includes for each offer:
    +
  • Received: the time and date when the offer was made
  • +
  • Sender: the name of the player who made the offer to you
  • +
  • Price: the price at which the offer has been made. This field is Free for gifts
  • +
  • To Player: the player to whom the offer has been made. This field is N/A for public and auction sales
  • +
  • Details: a sentense describing the item on sale:
      +
    • For planets: it includes planet name and coordinates, population, number of turrets and factories
    • +
    • For fleets: it includes the number of ships of each category along with the total fleet power
    • +
    • For planets with bundled fleets: it includes the information provided for both planet and fleets
    • +
  • +
  • Accept offer link: clicking this link opens an alert box where you have to confirm you want to accept the offer and make the sale or gift effective
  • +
  • Decline offer link: clicking on this link opens a confirmation alert box. Confirming the action in this alert box rejects the offers.
  • +
+ Just as for most lists in game, clicking on the header of one column order the list items according to the value of this flied. Clicking again on the same header changes the ordering from ascending to descending and the other way around. +
+
+
+
+ The sales or gift offers history can be split into two parts:
    +
  • Top part: it includes a set of controls that allow you to easily navigate into the list
  • +
  • List per se: it's the actual list of offers history
  • +
+ The next sections of the manual will cover both parts of the list. +
+
+ On top of the list of offers history a set of controls offer navigation means into the list. +
+ On the left side of the page, a drop down list allows to select a number of items to display on each page of the list. This number of items can be either 5, 10, 15, 20 or 25. If there are more items in total than this number, the list will be displayed onto several internal pages specific to the list without modifications of the rest of the Direct Offers page. +
+ In the middle of the page is provided a search facility which allows you to search for particular planets or fleets offers. This search system consist in a textfield where you can type in a search string. Any planet which name contains the search string, wherever it may be located in the planet name, will match the search and be displayed. +
+ If there are more items in the list than have to be displayed on each list page, a drop down list appears on the right part of the page. This drop down list allows to select the list page to display. +
+
+ Below the set of control are listed all offers in the history list. This list includes for each offer:
    +
  • Last Change: the time and date when the last change on the offer has occured. It might be a cancellation of someone accepting the offer for instance
  • +
  • Received: the time and date when the offer was received
  • +
  • Status: the outcome of the offer. The offer may have been cancelled or accepted or rejected
  • +
  • Sender: the name of the player who made the offer
  • +
  • Details: a sentense describing the item on sale:
      +
    • For planets: it includes planet name and coordinates, population, number of turrets and factories
    • +
    • For fleets: it includes the number of ships of each category along with the total fleet power
    • +
    • For planets with bundled fleets: it includes the information provided for both planet and fleets
    • +
  • +
+ Just as for most lists in game, clicking on the header of one column order the list items according to the value of this field. Clicking again on the same header changes the ordering from ascending to descending and the other way around. +
+
+
+
+ diff --git a/manual/beta5/en/market_sell.lwdoc b/manual/beta5/en/market_sell.lwdoc new file mode 100644 index 0000000..47d9625 --- /dev/null +++ b/manual/beta5/en/market_sell.lwdoc @@ -0,0 +1,190 @@ + + + beta5 + en + Selling and Giving +
+ In order for exchanges to take place it is obvious that some items for sale are necessary. This part of the manual will cover this topic. It will go through the process of putting planets and fleets on sale, be it publicly or privately, going through the offers you're making or have already made and making gifts. +
+
+
+ In order to put an item on sale, you first of all have to choose the item. The process is a bit different depending on the type of item you want to sell:
    +
  • Planets: on the individual page of each planet, a Sell/Give link is displayed below the planet name among other action links. Clicking on this link directs you to the planet sale page
  • +
  • Fleets: on the Fleets page, once you have selected the fleet with corresponding checkbox, a Sell link appears among the other action links. Clicking on the link directs you the fleets selling page. It is also possible to sell a fleet bundled with a planet. This topic will be covered along with planets sales
  • +
+
+
+
+ Once you have clicked on the Sell/Give link of a planet you get directed to a special page with similar items with the individual planet page. This page is split into four sections:
    +
  • Top planet section
  • +
  • Planet Overview
  • +
  • Give / Sale Planet
  • +
  • Bundled Fleets
  • +
+ The next paragraphs will go through the different sections and their role in the sale process. +
+
+ This part of the page is almost the same as for the individual planet page. See this section of the manual for more details. The only difference stands with the planetary control links below the planet name. There are only two sale related links:
    +
  • Confirm Sale: once you are statisfied with the choices you've made in the sale form, click on the link to confirm the sale and make it "official". Clicking the link will open a confirmation alert window where you have to confirm again in order to validate the sale offer
  • +
  • Cancel: click on this link to remove all changes you've made to the form and go back to the individual planet page of the planet
  • +
+
+
+ This part of the page is identical to the Planet Overview section of the Individual Planet Page. See this section of the manual for more details. +
+
+ This part of the page is the actual selling tool. The left part of the page consists in a set of radio buttons corresponding each to one category of sale. Depending on the choice you make, the right part differs because it's specific to the type of sale. The different possible values for the radio button are:
    +
  • Gift: in the case of a gift you're giving a planet to another player without any finantial compensation. The offer is targetted to a particular player. The data you have to fill in in the right part of the form are:
      +
    • Offer expires: use this drop down list to select the expiration time between the values Never, 6 hours, 12 hours, 1 day, 2 days, 3 days, 4 days and 5 days
    • +
    • Target player: use this text field to type in the name of the player to whom the offer has to be made
    • +
  • +
  • Direct Sale: in the case of a direct sale, the sale offer is only made to one player but there is a price attached to the planet. The target player is the only other player who is aware of the sale offer. The data you have to fill in in the right part of the form are:
      +
    • Offer expires: use this drop down list to select the expiration time between the values Never, 6 hours, 12 hours, 1 day, 2 days, 3 days, 4 days and 5 days
    • +
    • Target player: use this text field to type in the name of the player to whom the offer has to be made
    • +
    • Price: use this text field to type in the amount of cash you wish to obtain in exchange for the planet
    • +
  • +
  • Public Sale: in the case of a public sale, the sale offer is made in public and all players can see it. The first player who offers to buy the planet is the one with whom the sale is concluded. The data you have to fill in in the right part of the form are:
      +
    • Offer expires: use this drop down list to select the expiration time between the values Never, 6 hours, 12 hours, 1 day, 2 days, 3 days, 4 days and 5 days
    • +
    • Price: use this text field to type in the amount of cash you wish to obtain in exchange for the planet
    • +
  • +
  • Auction Sale: in the case of an auction sale, the offer is also publicly available to all players. But you don't define a fix price for the planet. You can only set a minimum bid. The player who has the higher bid when the offer expires buys the planet for the price corresponding to his bid. The data you have to fill in in the right part of the form are:
      +
    • Offer expires: use this drop down list to select the expiration time between the values 6 hours, 12 hours, 1 day, 2 days, 3 days, 4 days and 5 days, The value Never is invalid in this case.
    • +
    • Minimum bid: use this text field to type in the minimum price you want to sell the planet to
    • +
  • +
+
+
+ When putting a planet for sell or offering a planet as a gift you have the possibility to also offer the fleets orbiting the planet. The price for the planet then correspond to a bundle price for both the fleet and the planet. That's what this section of the manual is all about. +
+ This part of the page provides a list of all fleets you own that are orbiting the planet. This list includes the following fields:
    +
  • Name: the current name of the fleet
  • +
  • G.A. Ships: the number of GA ships in the fleet
  • +
  • Fighters: the number of fighters in the fleet
  • +
  • Cruisers: the number of cruisers in the fleet
  • +
  • Battle Cruisers:the number of battle cruisers in the fleet
  • +
  • Power: the total power of the fleet
  • +
+ In front of each fleet there is a checkbox. Checking the checkbox selects the fleet as having to be sold or given along with the planet. +
+
+
+
+ Once you have selected fleets on the fleet page and clicked on the Sell link, you get directed to a special fleet selling page. In the top right section of the page are located the page action links:
    +
  • Cancel: this link is always present and clicking it removed all modifications you might have made and directs you back to the fleets page
  • +
  • Confirm: clicking this link validates the offer and you get directed back to the fleets page. It only appears when all form for all locations are filled in properly
  • +
+
+ For each set of Planet name / coordinates is displayed:
    +
  • Selected Fleets: a list of all fleets you have selected for sale at this location
  • +
  • Sale details: a form allowing to define the sale parameters
  • +
+
+
+ For each location this list presents for each fleet to be sold or given:
    +
  • Fleet Name: the current name of the fleet
  • +
  • G.A. Ships: the number of GA ships in the fleet
  • +
  • Fighters: the number of fighters in the fleet
  • +
  • Cruisers: the number of cruisers in the fleet
  • +
  • Battle Cruisers:the number of battle cruisers in the fleet
  • +
  • Power: the total power of the fleet
  • +
+ At the bottom of the list a line presents the total for all fleets at this location. +
+
+ This part of the page is the actual selling tool. The left part of the page consists in a set of radio buttons corresponding each to one category of sale. Depending on the choice you make, the right part differs because it's specific to the type of sale. The different possible values for the radio button are:
    +
  • Gift: in the case of a gift you're giving a fleet to another player without any finantial compensation. The offer is targetted to a particular player. The data you have to fill in in the right part of the form are:
      +
    • Offer expires: use this drop down list to select the expiration time between the values Never, 6 hours, 12 hours, 1 day, 2 days, 3 days, 4 days and 5 days
    • +
    • Target player: use this text field to type in the name of the player to whom the offer has to be made
    • +
  • +
  • Direct Sale: in the case of a direct sale, the sale offer is only made to one player but there is a price attached to the fleet. The target player is the only other player who is aware of the sale offer. The data you have to fill in in the right part of the form are:
      +
    • Offer expires: use this drop down list to select the expiration time between the values Never, 6 hours, 12 hours, 1 day, 2 days, 3 days, 4 days and 5 days
    • +
    • Target player: use this text field to type in the name of the player to whom the offer has to be made
    • +
    • Price: use this text field to type in the amount of cash you wish to obtain in exchange for the fleet
    • +
  • +
  • Public Sale: in the case of a public sale, the sale offer is made in public and all players can see it. The first player who offers to buy the fleet is the one with whom the sale is concluded. The data you have to fill in in the right part of the form are:
      +
    • Offer expires: use this drop down list to select the expiration time between the values Never, 6 hours, 12 hours, 1 day, 2 days, 3 days, 4 days and 5 days
    • +
    • Price: use this text field to type in the amount of cash you wish to obtain in exchange for the fleet
    • +
  • +
  • Auction Sale: in the case of an auction sale, the offer is also publicly available to all players. But you don't define a fix price for the fleet. You can only set a minimum bid. The player who has the higher bid when the offer expires buys the fleet for the price corresponding to his bid. The data you have to fill in in the right part of the form are:
      +
    • Offer expires: use this drop down list to select the expiration time between the values 6 hours, 12 hours, 1 day, 2 days, 3 days, 4 days and 5 days, The value Never is invalid in this case.
    • +
    • Minimum bid: use this text field to type in the minimum price you want to sell the fleet to
    • +
  • +
+
+
+
+
+
+ All current and past sale and gift offers are listed on the Sent Offer part of the marketplace page. This section is accessible through the Sent Offers link on top of the marketplace page. The rest of this section will detail both lists. +
+
+
+ The list of pending sale or gift offers can be split into two parts:
    +
  • Top part: it includes a set of controls that allow you to easily navigate into the list
  • +
  • List per se: it's the actual list of pending offers
  • +
+ The next sections of the manual will cover both parts of the list. +
+
+ On top of the list of pending offers a set of controls offer navigation means into the list. +
+ On the left side of the page, a drop down list allows to select a number of items to display on each page of the list. This number of items can be either 5, 10, 15, 20 or 25. If there are more items in total than this number, the list will be displayed onto several internal pages specific to the list without modifications of the rest of the Sent Offers page. +
+ In the middle of the page is provided a search facility which allows you to search for particular planets on sale or on which fleets are beeing sold. This search system consist in a textfield where you can type in a search string. Any planet which name contains the search string, wherever it may be located in the planet name, will match the search and be displayed. +
+ If there are more items in the list than have to be displayed on each list page, a drop down list appears on the right part of the page. This drop down list allows to select the list page to display. +
+
+ Below the set of control are listed all pending offers. This list includes for each offer:
    +
  • Date: the time and date when the offer was made
  • +
  • Offer Type: the type of the offer. It can be either Gift, Private Sale, Public Sale or Auction Sale
  • +
  • Price: the price at which the offer has been made. This field is N/A for gifts and correspond to the current bid for auction sale
  • +
  • Expiration: the date and time when the offer expire
  • +
  • To Player: the player to whom the offer has been made. This field is N/A for public and auction sales
  • +
  • Details: a sentense describing the item on sale:
      +
    • For planets: it includes planet name and coordinates, population, number of turrets and factories
    • +
    • For fleets: it includes the number of ships of each category along with the total fleet power
    • +
    • For planets with bundled fleets: it includes the information provided for both planet and fleets
    • +
  • +
  • Cancel link: clicking on this link opens a confirmation alert box. Confirming the action in this alert box cancel the offers.
  • +
+ Just as for most lists in game, clicking on the header of one column order the list items according to the value of this flied. Clicking again on the same header changes the ordering from ascending to descending and the other way around. +
+
+
+
+ The sales or gift offers history can be split into two parts:
    +
  • Top part: it includes a set of controls that allow you to easily navigate into the list
  • +
  • List per se: it's the actual list of offers history
  • +
+ The next sections of the manual will cover both parts of the list. +
+
+ On top of the list of offers history a set of controls offer navigation means into the list. +
+ On the left side of the page, a drop down list allows to select a number of items to display on each page of the list. This number of items can be either 5, 10, 15, 20 or 25. If there are more items in total than this number, the list will be displayed onto several internal pages specific to the list without modifications of the rest of the Sent Offers page. +
+ In the middle of the page is provided a search facility which allows you to search for particular planets or fleets offers. This search system consist in a textfield where you can type in a search string. Any planet which name contains the search string, wherever it may be located in the planet name, will match the search and be displayed. +
+ If there are more items in the list than have to be displayed on each list page, a drop down list appears on the right part of the page. This drop down list allows to select the list page to display. +
+
+ Below the set of control are listed all offers in the history list. This list includes for each offer:
    +
  • Last Change: the time and date when the last change on the offer has occured. It might be a cancellation of someone accepting the offer for instance
  • +
  • Status: the outcome of the offer. The offer may have been cancelled or accepted or the item has been sold
  • +
  • Sent: the time and date when the offer was made
  • +
  • Offer Type: the type of the offer. It can be either Gift, Private Sale, Public Sale or Auction Sale
  • +
  • Price: the price at which the offer has been made. This field is N/A for gifts and correspond to the current bid for auction sale
  • +
  • To Player: the player to whom the offer has been made. This field is N/A for public and auction sales
  • +
  • Details: a sentense describing the item on sale:
      +
    • For planets: it includes planet name and coordinates, population, number of turrets and factories
    • +
    • For fleets: it includes the number of ships of each category along with the total fleet power
    • +
    • For planets with bundled fleets: it includes the information provided for both planet and fleets
    • +
  • +
+ Just as for most lists in game, clicking on the header of one column order the list items according to the value of this field. Clicking again on the same header changes the ordering from ascending to descending and the other way around. +
+
+
+
+ diff --git a/manual/beta5/en/marketplace.lwdoc b/manual/beta5/en/marketplace.lwdoc new file mode 100644 index 0000000..1ff5af5 --- /dev/null +++ b/manual/beta5/en/marketplace.lwdoc @@ -0,0 +1,45 @@ + + + beta5 + en + Marketplace +
+ They might not be diplomatic relations per se but economic relations might be considered as a first step towards establishing more solid agreements. As such the marketplace is the place where you can buy or accept and sell or give fleets and planets in LegacyWorlds. +
+ The marketplace page is split into three subpages:
    +
  • Public Offers: this page presents the planets and fleets that are publicly offered for bidding by their owners. That's also where you can make buying offers.
  • +
  • Firect Offers: this page lists the planets and fleets that are directly offered to you and no other players, along with a transaction history
  • +
  • Sent Offers: this page lists the sales and gifts offers you've made. Those may be still pending or part of the transactions history
  • +
+ Those subpages are accessible through the links at the top of the body of the page. +
+ The next sections of this manual page will go through those different pages while presenting the processes of selling and buying items. +
+
+ In order for exchanges to take place it is obvious that some items for sale are necessary. This part of the manual will cover this topic. It will go through the process of putting planets and fleets on sale, be it publicly or privately, going through the offers you're making or have already made and making gifts. +
+
+ When items are on sale, salers need customers. This part of the manual explains how to make buying offers, how to accept gifts and so on. +
+
+
+ After a sale has been concluded two possibilities exist to cancel the sale:
    +
  • If the buyer doesn't have enough money to purchase the item when the sale is concluded the sale is automatically cancelled
  • +
  • If the seller loses the fleet for sale in battle or loses control over the planet for sale the sale is cancelled
  • +
  • The seller has 4 hours to cancel the transfer after the sale has been concluded. To cancel the transfer:
      +
    • For planets: go to the individual planet page of the planet and click on the Cancel sale link among the planetary controls links below the planet name
    • +
    • For fleets: go to the fleets page and select the fleet for which you want to cancel the sale with the corresponding checkbox. Then click on the Cancel sale link among the action links at the bottom of the page
    • +
  • +
+
+
+ There are no guarantees surrounding sales except that the requested price is provided to the seller and the control over the item (planet or fleet) is transfered to the buyer. This means that there might be a fleet waiting to destroy the sold fleet or retake the sold planet. +
+ In the end everything is a matter of honesty and reputation. If a player has a habit of retaking planets he has just sold the word will spead and it his doubtful other players will buy items from him very often. Just keep that in mind... +
+
+ +
+
+ + diff --git a/manual/beta5/en/messages.lwdoc b/manual/beta5/en/messages.lwdoc new file mode 100644 index 0000000..9b3b8ac --- /dev/null +++ b/manual/beta5/en/messages.lwdoc @@ -0,0 +1,79 @@ + + + beta5 + en + Messaging System +
+ The Messaging System in Legacy Worlds is very similar to a simple mail client. It allows for folder creation to manage and store messages and offers a linear or threaded view of messages. In the bottom left part of the messaging system pages there is a link that allows you to switch to the forums. +
+
+ Clicking on the Compose a message link on the Messages page directs you to a message form. You have to fill in the various elements in order to send a message:
    +
  • Message:- the drop down list allow you to select if you want to send the message to:
      +
    • a player
    • +
    • an alliance
    • +
    • a planet
    • +
  • +
  • to:- you have to type in the text field the name of the recipient: player name, planet name or an alliance tag. In case of an alliance the message is sent to members with diplomatic privileges.
  • +
  • Subject:- type in the provided text field the subject of the message.
  • +
  • Message:- type in the provided textarea the body of the message.
  • +
+ The Send message button allows you to send the message to its recipients. +
+
+
+ Clicking on the Folders link on the messages page directs you to the folder management section of the page. This pages presents a list of all folders for your account including three default folders:
    +
  • Inbox:- received messages get stored here.
  • +
  • Internal Transmissions:- messages automatically sent by the game get stored here, including diplomatic messages, alliance specific messages and so on.
  • +
  • Sent Messages:- messages you have sent get stored in this folder.
  • +
+ The three default folders are permanent folders and can't be deleted. +
+
+ You can create a new folder by typing the new folder name in the provided text field and clicking on the Create button. The newly created folder will then be added to the folders list. +
+
+ In the folders list a checkbox is displayed in front of each folder. Checking the checkbox allows you to perform actions on the folders. The action section of the page is displayed once you've checked a checkbox and offers:
    +
  • Flush:- the flush button is available for all folders. Clicking the Flush button empties all selected folders.
  • +
  • Delete:- the delete button is only available for custom folders. Clicking the Delete button deletes the selected folders and their contents.
  • +
+
+
+
+
+ A list of all folders is displayed in the left part of the page. When unread messages are present in a given folder, the name of the folder is presented in bold with the number of unread messages between parenthesis. Clicking on the name of a folder allows to visualise the list of the messages it contains. This list presents several options:
    +
  • Messages per page:- the drop down list allows you to choose how many messages to display on each page.
  • +
  • Threaded view:- check this checkbox allows you to switch to thread view mode. Leaving the checkbox unchecked presents a linear display.
  • +
  • Page:- in case it's required this drop down list appears and allows you to navigate between pages of messages in the given folder.
  • +
+ The list also present various fields for each message:
    +
  • Subject:- subject of the message.
  • +
  • Date:- date and time the message was sent or received.
  • +
  • From:- author of the message.
  • +
  • To:- recipient of the message.
  • +
+ Clicking on the title of the columns allow you to order the messages according to that field either in descending or ascending order. Clicking several times on the same column header allows to switch betseen ascending and descending order. +
+
+ In order to read a particular message you have to click on its subject. The body of the message is then displayed along with a [ Reply ] link. Clicking again on the subject of the message hides the body of the message again. +
+ Clicking on the reply link when the body of the message is displayed directs you to the Compose a message page with prefilled recipient and subject fields. See the Compose a message section of the manual for more information about this process. +
+
+ In any given folder, checkboxes are present:
    +
  • in front of the subject header:- checking this checkbox selects all messages in the folder.
  • +
  • in front of each message:- checking this checkbox selects the corresponding message.
  • +
+ Once messages have been selected several actions become available at the bottom of the page:
    +
  • Delete:- clicking the Delete button deletes selected messages.
  • +
  • Move:- clicking the Move button moves the selected messages to the folder chosen in the drop down list.
  • +
+
+
+
+ Those are particular messages sent by the game under various circumstances:
    +
  • Game wide information messages.
  • +
  • Alliance wide information messages.
  • +
  • Empire specific information messages:- scientific exchange offers etc.
  • +
+
+
diff --git a/manual/beta5/en/mod_rules.lwdoc b/manual/beta5/en/mod_rules.lwdoc new file mode 100644 index 0000000..b83cca0 --- /dev/null +++ b/manual/beta5/en/mod_rules.lwdoc @@ -0,0 +1,77 @@ + + + beta5 + en + Forum Moderation Rules +
+ In order for players to get to know each others and to play better as members of an alliance or simply of the LegacyWorlds community the game is set with some internal forums. Those forums are a tool for you to use and as all tools allowing people to say what they have to say some simple rules apply. +
+ So far we've always had a policy of free speech but as everyone knows one's freedom ends where begins someone else's freedom. +
+ As a consequence:
    +
  1. Public forums are moderated by the staff and a set of volunteers appointed by staff members
  2. +
  3. Alliance forums are to be moderated by the alliance's leader and eventually other alliance members appointed by the alliance leader. If posts / threads that don't comply with the moderation rules are brought to the staff's attention, the alliance's leader will be held personally responsible. This means the same punishment will be applied to the alliance's leader as to the offender
  4. +
+
+
+
+ Those rules apply to:
    +
  1. Posts in public forums
  2. +
  3. Posts in alliance forums
  4. +
  5. Forum Signatures
  6. +
  7. Private Messages if their contents are brought to the staff's attention
  8. +
+ If you have any questions or concerns regarding these rules, feel free to contact either Tseeker or Ju or Sycophant or El_Cristoph. +
+ The actual rules follow. +
+
+ Offensive, vulgar, obscene, threatening, or hateful posts will not be allowed. As this has already been stated in the general rules in the manual, the server being hosted in France, those are considered as criminal offenses according to french law. Not that any of us would like to report you to the police, but people hosting public websites can be held responsible for what is being posted and you wouldn't want your dear staff to end up in jail if someone was to report it, would you? +
+ Posts that violate include, but are not limited to:
    +
  • Posting insults
  • +
  • Posting unrespectful comments towards other players because of their colour, their gender, their faith, their nationality, their religion, their opinions, their social behaviour and so on
  • +
  • Posting pornographic or obscene material
  • +
  • Posting links to pornographic or obscene pictures, stories, movies or material
  • +
  • Posting links to a website that is potentially damaging to your system or personal details by accessing it
  • +
  • Threatening another player
  • +
  • Repeated Flames
  • +
+ This is quite objective, but as always the Moderators have final say on the matter. This rule generally applies to trolls. We're not here to babysit you, folks, and we're not going to ban someone because they insulted you once. +
+
+ Unsolicited and/or multiple posting of threads/replies for the purposes of advertising or multiple posts with little to no content within a short period of time will not be allowed. No, we're not going to delete a post from a valid forum member advertising his or her new website *if done only once*. Members should not clutter the forum with useless posts, pushing posts with actual content off the front page. "Useless" is subjective, and up to the mod's discretion. +
+
+ Long strings of text without line breaks(Ex.typing 100 D's with no spaces) can throw off the formatting of the page. If you intend to use a long string, please add line breaks. Anything larger tends to throw off the formatting of the entire page, and can really play havoc with those of us who use the smaller resolutions. +
+
+ Yes, we all know the general forum gets the most visitors. However, if your question/comment is about Bugs, please direct it to the Bugs forum. The same goes for eclectic etc. Threads found in the wrong forum will be moved. +
+
+
+ Each violation contains a progression of penalties. They are ordered in such a way as that number 1 will be the reprisal for the first offense in that category, number 2 will be the next step up should you become a repeat offender, etc. Please note that depending on the severity of the action, as determined by the administrative staff, the progression of penalties may be completely bypassed where the result is immediate, permanent suspension from the forums and even the game. +
    +
  1. Warning: A verbal warning from the mods, over either the messaging system, the e-mail address listed for your account, or through a response to the offending post
  2. +
  3. Probation: You are placed on probation. We will watch you, any further offense will result in immediate suspension. The admins and mods will keep track of probationary members
  4. +
  5. Suspension: You are suspended from the forums for one week. After one week you will again be in a probationary state, if you should commit the violation again, you will be banned immediately
  6. +
  7. Banning: Self-explanatory. You are banned from the forums. We will notify you by e-mail or private message if you have these methods available should this situation arise. If you do not have these methods available to us, it becomes your responsibilty to contact one of the mods for an explanation as to why you are banned. Banned members will be kept track of
  8. +
+ Note also that should one person consistently collect warnings for numerous offenses yet not progress beyond the warning stage in any one of them, moderators reserve the right to determine an appropriate penalty for the number and severity of warnings. This should not be a game to see how many warnings you can collect without being punished. +
+
+ Moderators and staff members are human beings just like you. They have feelings and are due to:
    +
  • punish more severely some particular offenses because they consider them as more important
  • +
  • punish more severely some players because they have hard feelings towards them
  • +
+ As a consequence we consider you should have the right to complain if you consider you've been treated unfairly. To do so you should contact the staff and explain the situation. In such cases there are two options:
    +
  1. either the staff considers the person who punished you went too far and the punishment will be lifted. The moderator will be flagged as having gone too far once
  2. +
  3. the staff agrees with the person who punished you and no further step will be taken except you'll be flagged as having complained for no valid reason once
  4. +
+ Those flags will be used to help in the moderation system:
    +
  1. If a moderator punishes players for no reason more than 5 times, he'll be removed from the moderation team
  2. +
  3. If you complain for no valid reason more than 5 times you'll be definitively banned from the forums
  4. +
+ Moderating forums is a difficult task and everything should be done so that everyone can have faith in the moderating system, be him moderator or a simple player... +
+
diff --git a/manual/beta5/en/money.lwdoc b/manual/beta5/en/money.lwdoc new file mode 100644 index 0000000..85dd487 --- /dev/null +++ b/manual/beta5/en/money.lwdoc @@ -0,0 +1,47 @@ + + + beta5 + en + Money Page +
+ In game money has nothing to do with real money. It's an imaginary currency even if the sign used correspond to the euro sign (yes, yes, we're europeans...). Money is the main mean used in LegacyWorlds to acquire items, be it by building them or from other players. +
+ General information about the concept of money is provided in the corresponding section of the empire manual page. This maunal section is focused on the Money page of the game interface. +
+
+ This part of the page keeps you updated on the current amount of money you have in bank and that you can use to build fleets, improve your planets, implement technologies or enact laws. +
+ Once your account is old enough, it also provides you with a form with which you can transfer cash to another player. To transfer cash to another player fill in the amount to transfer in the first provided text field and the player name in the second one. Then click the Ok button. +
+
+ This section of the page provides you with a quick overview of your profits and losses with three important figures:
    +
  • Planet Income: sum of the incomes of all your planets
  • +
  • Fleet Upkeep: sum of all the costs implied to sustain all your fleets
  • +
  • Daily Profits: difference between the Planet income and the Fleet upkeep. It's the amount of money you currently earn each day. This cash is delivered twice a day during cash ticks.
  • +
+ The two first items are links to the relevant detailled sections of the page. +
+
+ This list details for each of your planets:
    +
  • Planet: name of the given planet
  • +
  • Base income: income derived from the population of the planet
  • +
  • Industrial Factories: number of industrial factories on the planet
  • +
  • Factory Income: income generated by the industrial factories on the planet
  • +
  • Factory Upkeep: costs to maintain all the factories (industrial and military) on the planet
  • +
  • Turret Upkeep: cost to maintain all the turrets on the planet
  • +
  • Expense: sum of the costs previously listed
  • +
  • Profit: sum of the profits previously listed minus the expenses
  • +
+ At the bottom of the list is displayed the Total Daily Income which sums all the planets' profits. +
+
+ This list details for each of your fleets:
    +
  • Name: name of the fleet as defined in the Fleet page
  • +
  • Location: place at which the fleet is located or towards which it is moving
  • +
  • Distance: distance between the current place the fleet is and its destination
  • +
  • Delay: time left in hyperspace before reaching the destination
  • +
  • Upkeep: current cost to sustain this fleet
  • +
+ At the bottom of the list is displayed the Total Fleet Upkeep which sums all the fleets' upkeeps. +
+
diff --git a/manual/beta5/en/overview.lwdoc b/manual/beta5/en/overview.lwdoc new file mode 100644 index 0000000..ad59f6e --- /dev/null +++ b/manual/beta5/en/overview.lwdoc @@ -0,0 +1,30 @@ + + + beta5 + en + Overview +
+ After years of internal conflicts and devastating wars humanity has finally achieved its ultimate goal: destroy its home planet, Earth. The only option left for the remaining survivors of the holocaust: flee to the stars in the hope of finding a new home and rebuild their civilisation. Hundreds of ships were launched into deep space looking for suitable planets and searched for years for some capable of sustaining life. +
+ As commandant of a small group of colonists you've finally managed to find a planet matching the required parameters and you're about to start rebuilding on it. It will be up to you to make it grow into a new galactic empire in a hostile universe. To achieve that goal you'll have to find allies among the other Earth refugees who settled in the neighbouring star systems and fight those willing to spoil your hard work. +
+ It will also be up to you to provide your citizens with what they might need and encourage the scientists of the expedition in their research to achieve new technological breakthroughts. It will be up to you to fufill Earth's legacy... +
+
+ Legacy Worlds is an online multiplayer intergalactic war game. Your goal as a player: build up an empire and defeat the other players. How to achieve that: through technological advancement, alliances with other players and of course conquests. +
+ Legacy Worlds is a tick based game. This means events are controlled at given intervals of time called ticks. +
+ Legacy Worlds is a text based game so don't expect any fancy graphics. +
+ At first started as an university project by one of the developers it's now developed by a little team which felt it was a pity to let it go to waste and disappoint its current players. +
+ This version is Beta 5 and is as the previous ones completely free. +
+
+ This tutorial presents a few tips to help you getting started with the game. It's not the ultimate guide to Legacy Worlds and its scope is limited to your first days of gameplay. Becoming the best player in the game will then be up to you. +
+
+ The manual for the game itself is split among a few sections. You can find out more about these topics on the Manual Topics page. +
+
diff --git a/manual/beta5/en/overview_page.lwdoc b/manual/beta5/en/overview_page.lwdoc new file mode 100644 index 0000000..9228c93 --- /dev/null +++ b/manual/beta5/en/overview_page.lwdoc @@ -0,0 +1,105 @@ + + + beta5 + en + Overview Page +
+ The overview page provides you with a summary of the situation of your empire along with general information about the universe. +
+ This overview page exists in two versions:
    +
  • Short Overview
  • +
  • Complete Overview
  • +
+ The difference between the two is of course that the complete overview includes more information and more precise data than the short one. +
+ You can switch between short and complete overview by clicking on the link in the top right corner of the page. +
+ The following sections of the manual will describe each of them. +
+
+ This overview provides you with a few simple facts about your empire and the universe. +
+ Among empire related data you may find:
    +
  • Messages: an update about pending messages along with links to the relevant mail boxes if you have new messages. A link to get directly to the Compose page of the messaging system is also provided
  • +
  • Planets: this section sums up the status of your planets. It displays the number of planets you own, the total population of your empire and the total number of factories you have built
  • +
  • Fleets: this section indicates the total average power of your fleets
  • +
  • Money: here is displayed your total daily profit
  • +
+
+
+ This section provides a few useful facts about the universe:
    +
  • Forums: this section informs you if you have unread topics in either general or alliance specific forums. It also provides links to the page of each of those forums' categories
  • +
  • Planets: this section indicates the total number of planets in the universe
  • +
  • Next ticks: here is provided a countdown to the next tick of each category: battle, cash, day and hour tick.
  • +
  • Rankings: here are displayed your two major rankings: general and round rankings
  • +
+
+
+
+ This overview provides you more detailed information about the same topics as the short overview. +
+
+ Here you will find some information about your messaging system's status. For each of of your default folders (Inbox, Internal Transmissions and Outbox) you will find:
    +
  • Name of the folder, consisting in a direct link to that folder
  • +
  • Number of messages in the folder
  • +
  • Number of unread messages in the folder if any
  • +
+ A Compose link which directs you to the compose page of the messaging system is also available. +
+
+ In the Planets section of the overview, you will find:
    +
  • Planets owned: total number of planets in your empire, along with the average happiness
  • +
  • Average corruption: average corruption calculated based on corruption on each of your planets
  • +
  • Total population: population of your whole empire along with a per planet average
  • +
  • Total factories: count of all your military and industrial factories throughtout your whole empire along with a per planet average
  • +
  • Total turrets: total count of the turrets on all your planets with a per planet average
  • +
+ This section also include a More Details links which directs you to the Planets general page. +
+
+ In this section, you will find general information about your fleets:
    +
  • total fleet power
  • +
  • number of fleets and number of fleets engaged in battle if any
  • +
  • a More Details link to access directly the fleets page
  • +
+
+
+ This part of the complete overview indicates the number of new technologies (that is technologies you haven't implemented yet) that you have discovered. It also provides a direct link to the Research page. +
+
+ This section consists in a finantial fact sheet for your empire. It includes:
    +
  • Income: total daily income of your empire
  • +
  • Fleet upkeep: amount of money you have to spend each day to sustain your fleets
  • +
  • Daily Profit: amount of money you actually earn each day, once you have paid for your fleet upkeep
  • +
+
+
+
+
+ This section of the Universe overview provides you with a summary of the forums' status. +
+ It includes a list of the various forums categories with a view link which directs you to the page of this forums category. All forums in each category are listed. For each forum:
    +
  • The name of the forum consists in a link to the forum's main page
  • +
  • Next to the name, the number of topics in the forum is indicated
  • +
  • If relevant, the number of unread topic or topics with unread posts is displayed
  • +
+
+
+ Here you will find some data about the universe in which everything happens:
    +
  • Total number of planets
  • +
  • Number of grid squares in the universe which aren't actually occupied stellar systems with planets but by nebulas
  • +
+ It also includes some universe related links:
    +
  • Maps: to access directly the maps page
  • +
  • More Details: to go to the Universe main page
  • +
+
+
+ As on the short overview, this part includes a countdown to the next tick of each category: Battle tick, Cash tick, Day tick and Hour tick. +
+
+ In this section, you will find the list of all rankings with your current rank in each ranking. It also provides a direct More Details link to access the Rankings page. +
+
+
+
diff --git a/manual/beta5/en/planet.lwdoc b/manual/beta5/en/planet.lwdoc new file mode 100644 index 0000000..7590c88 --- /dev/null +++ b/manual/beta5/en/planet.lwdoc @@ -0,0 +1,215 @@ + + + beta5 + en + Individual Planet Page +
+ General concepts about planets are provided in the planet section of the empire manual page. The manual section you're reading now focuses on the Individual Planet page and presents its contents. +
+ This page is the management page for any individual planet you own or an information page for other stellar objects. +
+
+
+ In LegacyWorlds you can find three kinds of stellar objects. The next paragraphs will describe each type. +
+
+ Planets are the basic components of you empire. They are the most interesting stellar objects for us and the next two sections of this manual page concern them. +
+ To learn more about basic concepts about planets, see this manual section. +
+
+ When a planet has been detroyed by a Wormhole Supernova, the material the planet is composed of doesn't just disapear. Those are planetary remains which are indicated in yellow on the maps. +
+ You can fly through planetary remains with your fleets and even station there but of course all the junk in the area have a nasty effect: it's quite difficult to navigate among floating rocks and your fleets are slowed down. This is represented by the opacity level of the planetary remains. +
+ You can of course access the corresponding detailed page of any grid area of the map occupied by planetary remains. This page will indicate:
    +
  • the fleets present there if you have yourself some fleets on that particular planetary remains
  • +
  • the opacity level
  • +
+
+
+ In the universe everything isn't filled with planetary systems. There are also giant gas clouds called nebulas. The are displayed in shade of red on the maps. +
+ Just as for planetary remains, it's quite difficult to navigate through gas clouds and crossing nebulas with a fleet slows it down. The speed of the fleet crossing a nebula depends on its opacity: denser the cloud, slower the fleet. +
+ When looking at the page of a nebula square, you'll see:
    +
  • the fleets present if you have a fleet at those coordinates
  • +
  • the opacity level of the square
  • +
+
+
+
+
+ When accessing the individual page of a planet, the amount of information varies. Depending on the status of the planet towards you, the level of information you have access to changes. The next parapgraphs will present the data displayed in each case. +
+
+ In that case you have access to a complete planetary management page which is described in the next section of this manual page. +
+
+ In the case of alliance planets when you have access to the planets list, the individual planet page of an allied planet includes:
    +
  • Coordinates of the planet on the map
  • +
  • Alliance the owner of the planet belongs to (that is your own alliance)
  • +
  • Total number of factories on the planet
  • +
  • Total number of Turrets on the planet
  • +
+ You have also access to three action links:
    +
  • Send Fleets: click this link to send fleets to that particular planet. Sending fleets is covered in the fleets section of the manual
  • +
  • Centre Map: clicking this link directs you to the map page with a map centered on that particular planet
  • +
  • Message: clicking this link opens the compose form of the messaging system with the planet name prefilled as recipient. This allows you to send a private message to the planet's owner
  • +
+
+
+ In the case of alliance planets, when you don't have access to the planets list, the amount of information provided on the individual planet page is very limited. It is equivalent to the data provided for any other planet. +
+
+ If you have fleets stationed on a planet, the data provided are a bit more extended than for any random planet. You have access to:
    +
  • Coordinates of the planet on the map
  • +
  • Alliance the owner of the planet belongs to
  • +
  • fleet size if any fleet is present
  • +
  • Total number of Turrets on the planet
  • +
  • Population of the planet
  • +
+ You have also access to three action links:
    +
  • Send Fleets: click this link to send fleets to that particular planet. Sending fleets is covered in the fleets section of the manual
  • +
  • Centre Map: clicking this link directs you to the map page with a map centered on that particular planet
  • +
  • Message: clicking this link opens the compose form of the messaging system with the planet name prefilled as recipient. This allows you to send a private message to the planet's owner
  • +
+
+
+ In the case of an alliance planet on which you have fleets stationned the level of information provided consists in a combination of your particular alliance planets case (if you have planets list access or not) and the planets you have fleets on case. +
+
+ In cases which don't fit in any other category you have access to very few data:
    +
  • Coordinates of the planet on the map
  • +
  • Alliance the owner of the planet belongs to if any
  • +
+ You have also access to three action links:
    +
  • Send Fleets: click this link to send fleets to that particular planet. Sending fleets is covered in the fleets section of the manual
  • +
  • Centre Map: clicking this link directs you to the map page with a map centered on that particular planet
  • +
  • Message: clicking this link opens the compose form of the messaging system with the planet name prefilled as recipient. This allows you to send a private message to the planet's owner
  • +
+
> +
+
+
+ The individual planet page is the specific management page for a given planet. It provides detailed information on the planet along with a set of controls to navigate among planets and manage one given planet. +
+
+ In the top right corner of the page, some general controls allow you to navigate among planetary pages:
    +
  • The Select planet drop down list allows you to switch between planets. Selecting the name of another planet you own in the list gets you directly to its individual planet page
  • +
  • The Planet list link gets you back to the Planets main page which lists all your planets
  • +
+
+
+
+ In the top left part of the page, below the planet name, a list of action links provide planet wide functions. The next paragraphs will detail each link. +
+
+ You can click this link to send fleets to that particular planet. Sending fleets is covered in the fleets section of the manual. +
+
+ Clicking this link directs you to the map page with a map centered on that particular planet. +
+
+ clicking on this link allows you to abandon a planet once you have confirmed the action in the alert box that pops up. The link gets then replaced with the time remaining before the planet is actually out of your control and a Cancel link. Clicking the Cancel link of course stops the abandon process. +
+ You cannot abandon your planet if you only have one. This link is only displayed if you have at least 2 planets. +
+
+ Once you've owned a given planet for more than two weeks, a new Rename link appears. Clicking on the link opens an alert box with a textfield where you can type in the new name of the planet. Once you're satisfied with the name you can click the OK button to change the planet's name. the Cancel button of course cancels the change and closes the alert box. +
+ Once you have renamed a planet you have to wait two more weeks before being able to rename it again. +
+
+ Clicking on this link gets you to a particular version of the planet page where you can set specific sale/gift parameters. This topic is described more precisely in the marketplace section of the manual. +
+ You cannot sell your planet if you only have one or if your account is younger than 10 days. This link is only displayed if your account is older than 10 days and you have at least 2 planets. +
+
+ Once you have access to the Wormhole Supernova technology and of course implemented it, you can see a new link which allows you to detroy the planet with a wormhole Supernova. +
+ Be careful though: citizens of your empire may not appreciate that you destroyed one of your planets and this action has a bad, lasting effect on the happiness of your population. +
+
+
+ This part of the page provides some facts about the planet, including:
    +
  • Coordinates: coordinates of the planet on the map
  • +
  • Alliance: tag of the alliance the planet belongs to
  • +
  • Population: total population size of the planet
  • +
  • Turrets: number of turrets on the planet along with a Destroy link. Clicking this link opens an alert window where you can select a number of turrets to destroy
  • +
  • Planet Income: amount of money generated by the planet each day
  • +
  • Happiness: percentage of happiness for the planet
  • +
  • Corruption: percentage of corruption of the planet
  • +
  • Industrial factories: number of industrial factories on the planet
  • +
  • Military factories: number of military factories on the planet
  • +
  • Fleets standing by: in this section the average power of your fleets stationed on the planet is indicated in green. Average power of friendly fleets is in blue and average power of enemy fleets in red
  • +
+
+
+ Industrial factories produces goods from raw materials. Those goods are sold within the empire and beyond its borders, which generates money. As a consequence the more industrial factories you have the more money you make. But there are workers in factories and they won't be too happy to have to work 3 jobs to get the planetary economy running. So there is a limit to the number of factories you can build without risking a revolt. +
+ The top part of this section indicates the number of industrial factories currently present on your planet, along with the price to build one new industrial factory. +
+ Below that section, you can manage factories. To change the number of industrial factories on the planet, use the texfield to indicate an amount of factories to build / destroy and click the relevant button: Increase or Decrease. +
+ You have to be aware that the number of industrial factories you can destroy is limited to 10% of the number you owned 24h before the destruction attempt. For instance if you had 100 industrial factories 24h ago, buit 20 12h ago and try to destroy some now you can destroy up to 30. +
+
+ Military factories are specialised factories producing ships and turrets. The speed at wich items are built depends on the number of military factories on the planet. The more factories you have the faster it will go. But there are workers in factories and they won't be too happy to have to work 3 jobs to get the planetary weapon industry. So there is a limit to the number of factories you can build without risking a revolt. +
+ The top part of this section indicates the number of military factories currently present on your planet, along with the price to build one new military factory. +
+ Below that section, you can manage factories. To change the number of military factories on the planet, use the texfield to indicate an amount of factories to build / destroy and click the relevant button: Increase or Decrease. +
+ You have to be aware that the number of military factories you can destroy is limited to 10% of the number you owned 24h before the destruction attempt. For instance if you had 100 military factories 24h ago, buit 20 12h ago and try to destroy some now you can destroy up to 30. +
+
+
+ This part of the page is composed of two sections:
    +
  • tools to add elements to a build queue in the left part
  • +
  • build queue management tools in the right part
  • +
+ The next paragraphs will describe their usage. +
+
+ In order to build new items, you have to select the type of item you want to build. +
+ Depending on your technology level, different items are available. Basically you can find turrets (stationary defenses) and Ground Assault (GA) ships (system ships carrying troups which allow them to take control over planets). The following technologies provide you access to other types of ships:
    +
  • Battle Cruisers
  • +
  • Cruisers
  • +
  • Fighters
  • +
+
+ Ship types are described more precisely in the ships part of the manual. +
+ In order to select one item, you have to select the radio button before its name. The cost for one item of each category is indicated along the name. Once you have selected one item, use the Quantity textfield to indicate the number of items to build. Then click the Add button to add the items to the build queue. +
+
+
+ The build queue consist in:
    +
  • a list of items being built
  • +
  • action buttons
  • +
+ The next parapgraphs will cover both topics. +
+
+ The list of items being buit includes:
    +
  • Qty: number of items in the queue
  • +
  • Type: category of items being built
  • +
  • Time to build - Ind.: individual build time for this bundle of items. The time is rounded up to be expressed in hours
  • +
  • Time to build - Cum.: cumulated build time for all items in the queue up to this one. This cumulative build time is the sum of build times for previous items without rounding. This represents the fact that if the full hour tick time isn't required to finish one item, work starts on the next. As a consequence, cumulative build time may be inferior to the sum of all individual build times
  • +
+
+
+ Available build queue actions are:
    +
  • Up: to move one item up in the queue. To do so, select one item by checking the checkbox on its line and click the Up button.
  • +
  • Down: to move one item up in the queue. To do so, select one item by checking the checkbox on its line and click the Down button.
  • +
  • Cancel: to remove one item from the queue. To do so, select one item by checking the checkbox on its line and click the Cancel button.
  • +
  • Flush: to remove all items in the build queue. To do so, just click the Flush button.
  • +
+ You also have the ability to replace one item in the queue with another. In order to do so, select the item to replace by checking its checkbox in the build queue. Then proceed as if you were to add a new item to the queue but click the Replace button instead of the Add button. +
+
+
+
+
diff --git a/manual/beta5/en/planets.lwdoc b/manual/beta5/en/planets.lwdoc new file mode 100644 index 0000000..556a814 --- /dev/null +++ b/manual/beta5/en/planets.lwdoc @@ -0,0 +1,59 @@ + + + beta5 + en + Planets Overview Page +
+ General concepts about planets are provided in the planet section of the empire manual page. The manual section you're reading now focuses on the Planets page and presents its contents. +
+ The Planets page provides you with a general overview of all your planets at once along with a quick builder facility. A link in the top right corner of the page allows to switch between two views:
    +
  • List of controlled planets only
  • +
  • Quickbuilder facility and list of controlled planets
  • +
+ Each of those two elements are described in the other paragraphs of this manual section. +
+
+ This section of the page displays a list of all planets in your empire along with:
    +
  • Planet: name of the concerned planet. It's a link to the corresponding planet page
  • +
  • Coords: coordinates of the planet
  • +
  • Population: population of the planet in millions of unhabitants
  • +
  • Happiness: percentage of happiness for the planet
  • +
  • Industrial: number of industrial factories on the planet
  • +
  • Turrets: number of turrets on the planet
  • +
  • Military: number of military factories on the planet
  • +
  • Profit: daily profit generated by the planet
  • +
  • Currently Building: list of the first elements in the build queue along with the time to completion. A link at the top of the page allows to switch between a view with cumulated build times and a view with individual build times.
  • +
+
+
+
+ Switching to quick builder mode modifies slightly the controlled planets list and reveals a new section of the page. Using those tools you can manage factories and ships construction and destruction on several planets at once. +
+
+ In order to do some actions at the planetary level on several planets at once, you first of all have to select the planets you're interested in. This can be achieved by checking the checkboxes which are displayed in front of each planet name. Quick links are also provided at the top of the planets list:
    +
  • select all: to select all planets
  • +
  • unselect all: to unselect all planets
  • +
  • invert: to invert the selection
  • +
+ The operation to perform at the planet level may be chosen with radio buttons in the corresponding section of the quick builder. Clicking any element on the same line as a radio button selects it. The different possible operations are:
    +
  • Operations on factories: Using the corresponding drop down lists you can either build or destroy the number of industrial or military factories that you chose in the textfield.
  • +
  • Adding to build queues: using the corresponding drop down list you can add to the build queues> the number of ships of the selected category or turrets that you chose in the texfield.
  • +
  • Flushing build queues: using this option you can remove all elements from the build queues
  • +
+ The buttons at the bottom of the quick builder perfom the defined planet operation on all selected planets. +
+
+ In order to perform actions on some specific elements in the build queue you first of all have to select them. To do so you have to check the checkboxes present in front the build queue items you want to change in the controlled planets list. +
+ The operation to perform on the selected items may be chosen with radio buttons in the corresponding section of the quick builder. Clicking any element on the same line as a radio button selects it. The different possible operations are:
    +
  • Delete: to remove the selected items from the build queues
  • +
  • Move: using the relevant drop down list you can either move down or up in the queue the items you have selected
  • +
  • Replace: using the relevant drop down list you can replace the selected items with the number of ships of the chosen category or turrets that you have put in the textfield
  • +
+ The buttonsbuttons at the bottom of the quick builder perfom the defined planet operation on all selected items. +
+
+ The two buttons at the bottom of the quick builder section both perfom the defined actions. The only difference is that the left one (Execute action) leaves the quick builder displayed whereas the right one (Execute and hide) switch also the diplay to list only mode. +
+
+
diff --git a/manual/beta5/en/preferences.lwdoc b/manual/beta5/en/preferences.lwdoc new file mode 100644 index 0000000..30d7532 --- /dev/null +++ b/manual/beta5/en/preferences.lwdoc @@ -0,0 +1,160 @@ + + + beta5 + en + Preferences +
+ Various elements in Legacy Worlds can be customised. Some preferences are specific to one account and others are in game features that are defined on a per game basis. +
+
+ The account preferences page is available through the Preferences link displayed at the bottom of game selection page you reach when logging in. The account preferences page allows for customisation of the various generic elements presented in the table below. + + + + + + + + + + + + + + + + + + + + + + + + + + +
ElementDescriptionPossible values
E-Mail AddressE-Mail Address of the player - used to send account activation data and game related messagesAny valid E-Mail address, as long as it hasn't already been used to create another account
Colour SchemeThe colour of the theme to use in game
    +
  • Red
  • +
  • Green
  • +
  • Blue
  • +
  • Grey
  • +
  • Purple
  • +
  • Yellow
  • +
LanguageLanguage in which the game interface and internal messages are displayed
    +
  • English
  • +
  • French
  • +
Font SizeThe font size to use in game
    +
  • Tiny
  • +
  • Small
  • +
  • Normal
  • +
  • Big
  • +
  • Huge
  • +
+ Some forum specific preferences can also be set up as presented in the table below. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ElementDescriptionPossible values
Topics/pageNumber of Forum Topics to display on each page
    +
  • 10
  • +
  • 20
  • +
  • 30
  • +
  • 40
  • +
  • 50
  • +
Graphical smileysThis option allows to display or not graphical smileys in forums
    +
  • Disabled
  • +
  • Enabled
  • +
Diplay ModeThis option allows to switch between threaded and linear vues of a forum topic
    +
  • Linear
  • +
  • Threaded
  • +
Messages/pageNumber of messages in a topic to display on each page
    +
  • 10
  • +
  • 20
  • +
  • 30
  • +
  • 40
  • +
  • 50
  • +
Forum tagsThis option allows you to define if you want forum tags to be decoded into text modifiers or not
    +
  • Disabled
  • +
  • Enabled
  • +
Messaged orderOrder in which to display the messages according to time
    +
  • Oldest First
  • +
  • Newest First
  • +
SignatureMessage to display at the bottom of the messages you postAny valid chain of characters
+ The form at the bottom of the page allows you to change your password for your account. +
+
+
+ Those preferences are specific to the the current game and are accessible only for inside a game. They influence in game elements only. For Beta 5, the form includes the same elemnts as for the account preferences along with a few more items which are presented in the table below: + + + + + + + + + + + + + + + + +
ElementDescriptionPossible values
TooltipsTime period during which tooltips have to be displayed
    +
  • Disabled
  • +
  • 0.5 second
  • +
  • 1 second
  • +
  • 1.5 second
  • +
  • 2 second
  • +
  • 2.5 second
  • +
  • 3 second
  • +
Theme The theme to use for the menu bar
    +
  • LegacyWorlds Beta 5
  • +
  • Beta 5 Reversed
  • +
  • LegacyWorlds Classic
  • +
+
+
+ The bottom part of the in game preferences page provides you with a form to leave the given game. Clicking on the Leave LegacyWorlds Beta 5 button will start a 24h count down. If you don't click the DO NOT leave LegacyWorlds Beta 5 button within this 24h delay, your game data will be deleted. But note that this won't delete your account. The account still exists but isn't playing that particular game anymore. +
+
+
diff --git a/manual/beta5/en/probes.lwdoc b/manual/beta5/en/probes.lwdoc new file mode 100644 index 0000000..26713e1 --- /dev/null +++ b/manual/beta5/en/probes.lwdoc @@ -0,0 +1,20 @@ + + + beta5 + en + Probes and Beacons Page +
+ Some technologies in game provide you with the ability to build other spatial objects than just ships:
    +
  • Beacons: those are some kind of satellites that can be placed around a planet. The basic model is placed in hyperspace and provides some kind of anchor or fixed reference point for ships located in hyperspace around the planet, thus limiting ships losses. More advanced models, with scanning abilities and such will be available in further versions of the game.
  • +
  • Probes: those small unmanned vessels can be sent to other planets to scan for the fleets and other defenses, along with planetary improvements. Those will be available in further versions of the game.
  • +
+
+
+ This page presents a list of all the planets you own along with some beacon related information. For each planet, it includes
    +
  • Planet name: the name of the planet is a link to the planet's individual page
  • +
  • Coordinates: the coordinates of the planet on the map
  • +
  • Beacon information: it's either No beacon if there is no beacon around the planet, or the type of beacon
  • +
  • Beacon upgrade information: if you have the required technology to install or upgrade a beacon, a button is available, to install or upgrade the beacon, at a cost. If not, an information message states there is no upgrade available.
  • +
+
+
diff --git a/manual/beta5/en/rankings.lwdoc b/manual/beta5/en/rankings.lwdoc new file mode 100644 index 0000000..5d2f850 --- /dev/null +++ b/manual/beta5/en/rankings.lwdoc @@ -0,0 +1,105 @@ + + + beta5 + en + Rankings +
+ Rankings are a way to keep track of the various players' progress in the game and to compare their strength. There are different kinds of rankings which are presented in the various sections of the Rankings page. +
+ On top of the Rankings page various links allow you to navigate beween the different rankings pages:
    +
  • Summary: this page provides you wil a general overview of your own rankings
  • +
  • General Rankings: this page presents a list of all players for the general ranking
  • +
  • Detailed Rankings: this page offers a list of all players for the various rankings composing the general ranking
  • +
  • Alliance Rankings: this page offers a view of all alliances and their ranking
  • +
  • Overall Round Rankings: this page presents a list of all players for the overall round ranking
  • +
  • Inflicted Damage Rankings: this page presents a list of all players for the inflicted damage ranking
  • +
+
+
+ This page provides you wil a general overview of your own rankings. For each ranking your rank and the corresponding amount of points are provided. It includes:
    +
  • General ranking: this ranking corresponds to a combination of your civilisation, military and financial rankings. It represents your current advancement in the game.
  • +
  • Overall Round Ranking: this ranking is calculated based on your previous general rankings. It allows for a long term estimate of your strength and advancement.
  • +
  • Civilization Ranking: this ranking represents the advancement level of the society in your empire. It takes into account technology level, population and happiness.
  • +
  • Military Ranking: this ranking allows to assess the military strength of your empire. Its calculation is based on the number of turrets and military factories you own along with your fleet fire power.
  • +
  • Financial Ranking: this ranking correspond to the economic health of your empire. It takes into account your banked cash, your empire's income and the number of industrial factories you own.
  • +
+
+
+ This page provides you with the general rankings for all players. General rankings consists of a combination of civilisation, military and financial rankings, thus providing a current image of each player's strength and advancement in the game. +
+ The top part of the page includes some navigation facilities:
    +
  • Number of players per page: the left drop down list allows you to choose how many players you want displayed on each page.
  • +
  • Page to display: when relevant, a drop down list which allows you to switch between pages is displayed.
  • +
  • Search feature: when filling in the search textfield the listing will be automatically limited to the items containing the string you've typed in, wherever it may be in the whole player name.
  • +
+
+ The bottom part of the page consists of the listing itself, containing: +
    +
  • Player Name: name of the corresponding player
  • +
  • Rankings/Points: rank in the rankings for the player along with the corresponding amont of points
  • +
+ In this list your own name is displayed in bold. Clicking on the columns titles allows for sorting according to the corresponding field. Clicking again on the same title switches between ascending and descending sorts. +
+
+ This page provides you with the detailed rankings for all players. Detailed rankings consists of civilisation, military and financial rankings, thus providing a current detailed image of each player's strength and advancement in each field. +
+ The top part of the page includes some navigation facilities:
    +
  • Number of players per page: the left drop down list allows you to choose how many players you want displayed on each page.
  • +
  • Page to display: when relevant, a drop down list which allows you to switch between pages is displayed.
  • +
  • Search feature: when filling in the search textfield the listing will be automatically limited to the items containing the string you've typed in, wherever it may be in the whole player name.
  • +
+
+ The bottom part of the page consists of the listing itself, containing: +
    +
  • Player Name: name of the corresponding player
  • +
  • Civilisation Rankings/Points: rank in the rankings for the player along with the corresponding amont of points
  • +
  • Military Rankings/Points: rank in the rankings for the player along with the corresponding amont of points
  • +
  • Financial Rankings/Points: rank in the rankings for the player along with the corresponding amont of points
  • +
+ In this list your own name is displayed in bold. Clicking on the columns titles allows for sorting according to the corresponding field. Clicking again on the same title switches between ascending and descending sorts. +
+
+ This page provides you with the rankings for all alliances. Alliance rankings provide a current image of each alliance's strength and advancement in the game. +
+ The top part of the page includes some navigation facilities: +
    +
  • Number of alliances per page: the left drop down list allows you to choose how many alliances you want displayed on each page.
  • +
  • Page to display: when relevant, a drop down list which allows you to switch between pages is displayed.
  • +
  • Search feature: when filling in the search textfield the listing will be automatically limited to the items containing the string you've typed in, wherever it may be in the whole alliance tag.
  • +
+
+ The bottom part of the page consists of the listing itself, containing:
    +
  • Alliance Name: name of the corresponding alliance
  • +
  • Rankings/Points: rank in the rankings for the alliance along with the corresponding amont of points
  • +
+
+
+ This page provides you with the overall round rankings for all players. Overall round rankings are calculated based on previous general rankings for each player. It allows for a long term estimate of strength and advancement in the game. +
+ The top part of the page includes some navigation facilities:
    +
  • Number of players per page: the left drop down list allows you to choose how many players you want displayed on each page.
  • +
  • Page to display: when relevant, a drop down list which allows you to switch between pages is displayed.
  • +
  • Search feature: when filling in the search textfield the listing will be automatically limited to the items containing the string you've typed in, wherever it may be in the whole player name.
  • +
+
+ The bottom part of the page consists of the listing itself, containing:
    +
  • Player Name: name of the corresponding player
  • +
  • Rankings/Points: rank in the rankings for the player along with the corresponding amont of points
  • +
+ In this list your own name is displayed in bold. Clicking on the columns titles allows for sorting according to the corresponding field. Clicking again on the same title switches between ascending and descending sorts. +
+
+ This page provides you with the inflicted damage ranking for all players. Inflicted Damage Rankings are based on fleet power destroyed by the player's fleets. +
+ The top part of the page includes some navigation facilities:
    +
  • Number of players per page: the left drop down list allows you to choose how many players you want displayed on each page.
  • +
  • Page to display: when relevant, a drop down list which allows you to switch between pages is displayed.
  • +
  • Search feature: when filling in the search textfield the listing will be automatically limited to the items containing the string you've typed in, wherever it may be in the whole player name.
  • +
+ The bottom part of the page consists of the listing itself, containing:
    +
  • Player Name: name of the corresponding player
  • +
  • Rankings/Points: rank in the rankings for the player along with the corresponding amont of points
  • +
+ In this list your own name is displayed in bold. Clicking on the columns titles allows for sorting according to the corresponding field. Clicking again on the same title switches between ascending and descending sorts. +
+
diff --git a/manual/beta5/en/ships.lwdoc b/manual/beta5/en/ships.lwdoc new file mode 100644 index 0000000..f53c609 --- /dev/null +++ b/manual/beta5/en/ships.lwdoc @@ -0,0 +1,192 @@ + + + beta5 + en + Ships +
+ Fleets are composed of ships. It sounds only natural to start with ships. This section of the manual will present the different ships categories and caracteristics and how to build ships. +
+
+
+ In LegacyWorlds, several categories of ships and military devices are available:
    +
  • Turrets
  • +
  • GA ship
  • +
  • Fighter
  • +
  • Cruiser
  • +
  • Battle cruiser
  • +
+ Each category of ships has particularities and special abilities which makes it unique and more efficient for one purpose or another. The rest of this manual section will present those ships categories and ships carateristics. +
+
+ There different categories of ships are listed in the table below. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescriptionClassInitial PowerHaul SizeRoom usedInitial SpeedUpkeepBuild Price
TurretStationary DefenseN/A10N/AN/AN/A 20400
GA shipSimple transport vessel for ground troupsSystem Ship 5N/A3140750
FighterSmall attack shipSystem Ship10N/A1150500
CruiserLong range attack and transport vesselCapital Ship4020N/A15005000
Battle CruiserHeavy long range attack and transport vesselCapital Ship8015N/A2150015000
+
+
+ There are three major categories of military devices in LegacyWorlds. The class of a military device defines its travel abilities. Thoses classes are described in the list below:
    +
  • Turrets: turrets are stationary defenses. As such they can't go anywhere. Isn't it logical?
  • +
  • System ships: system ships can only fly inside a star system. They have to be transported in the hauls of capital ships in order to travel in hyperspace
  • +
  • Capital ships: capital ships are hyperspace capable. This means they can move between several stellar systems. They can also transport system ships in their hauls
  • +
+
+
+
+ The power of a ship represents both its firepower and the amount of damage it can sustain. The figures provided are initial figures for basic ships models. Those basic design can be upgraded due to technological advances. The next parapgraphs will list technologies influencing firepower and resistance to damage. +
+
+
+ The influence technologies have on firepower depend on ship classes. Some technologies have an effect on all classes and other on only one class. +
+
+ The following technologies increase the firepower of all classes of ships:
    +
  • Cybernetic Interfaces
  • +
  • Matter Anti-matter Missiles
  • +
+ This one decreases it:
    +
  • Civilian Transportation Act
  • +
+
+
+ These technologies increase the firepower of turrets and turrets only:
    +
  • Automated Turrets
  • +
  • Biological Turrets
  • +
  • Sensor Turrets
  • +
+
+
+ These technologies increase the firepower of ships of the Capital Ships class:
    +
  • Biological Hyperspace Engines
  • +
  • Biological Propulsion Systems
  • +
+
+
+
+ This other set of technologies increases ships' and turrets' resistance to damage:
    +
  • Adaptive Materials
  • +
  • Adaptive Plating
  • +
  • Advanced Materials
  • +
  • Force fields
  • +
  • Hardened Alloys
  • +
  • Intelligent Materials
  • +
  • Medical Bays
  • +
  • Resurrection tanks
  • +
  • Self-healing Materials
  • +
+
+
+
+ System ships have to be transported into the haul of capital ships in order to travel in hyperspace. +
+ Each type of system ship has a different size. As such the number of haul spaces they use in capital ships differ. +
+ Each type of capital ship has a different haul size. As such the number of system ships they can carry varies. +
+ In order to help you to build hyperspace capable fleets, the fleet page and the fleets splitting tool display the percentage of fleet haul that is used. +
+
+ The speed of a ship is represented by the maximum distance it can travel in one hour tick time. For most ships this distance is 1 (a ship can travel a whole stellar system in one hour). +
+ The speed of capital ships can be increased through technological advances. Each technology in the list increases the speed of capital ships by 1:
    +
  • Matter Anti-matter Engines
  • +
  • Space-time folding
  • +
+
+
+ The upkeep of a ship is the amount of money you have to pay each day to maintain the ship. The Fleet Upkeep Expenses section of the Money manual page covers this topic more thoroughly. +
+
+ The build price of a ship class is the price you have to pay to build one ship of the category. See the next section of this manual page to learn more about ship building. +
+
+ GA ships, as troups transport ships, are the only ships that can take control of a planet. Each GA ship transports the troups capable to control a certain amount of population. The bigger the population, the more GA ships are required to take control of the planet. +
+ At the beginning of the game, the GA ships you can build are able to control 200 population units. This figure can be increased through technology. Each technology in the following list increases the population size a single GA ship can control by 75:
    +
  • Exoskeleton
  • +
  • Nanofiber Armor
  • +
  • Self-repairing Exoskeleton
  • +
+
+
+
+
+ In order to build ships you have to possess the required technology:
    +
  • The Fighter technology allows you to build fighters
  • +
  • The Cruiser technology allows you to build cruisers
  • +
  • The Battle Cruiser technology allows you to build battle cruisers
  • +
+
+
+ Ships are built in military factories. Both those factories and ships can be built on each planet separately or by the means of the quick builder facility. In order to know more about those topics, have a look at the following manual sections:
    +
  • Military Factories section of the empire manual page
  • +
  • Building warefare section of the individual planet page manual page
  • +
  • Quickbuilder section of the planets overview manual page
  • +
+
+
+
diff --git a/manual/beta5/en/tech_1.lwdoc b/manual/beta5/en/tech_1.lwdoc new file mode 100644 index 0000000..5f3ca03 --- /dev/null +++ b/manual/beta5/en/tech_1.lwdoc @@ -0,0 +1,25 @@ + + + beta5 + en + Fighters +
+ Sir! We have successfully researched a new type of ship, the Fighter! These ships are faster and more efficient at combating enemy ships than our current Ground Assault ships. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: No
  • +
  • Cost: &euro;50,000
  • +
  • Effects:
      +
    • Provides the ability to build ships of the Fighter class
    • +
  • +
+
+
+ Required by:
    +
  • Martial law
  • +
  • Cruisers
  • +
+
+
diff --git a/manual/beta5/en/tech_10.lwdoc b/manual/beta5/en/tech_10.lwdoc new file mode 100644 index 0000000..bec4375 --- /dev/null +++ b/manual/beta5/en/tech_10.lwdoc @@ -0,0 +1,29 @@ + + + beta5 + en + Advanced Hospitals +
+ Sir! We have successfully researched a new public service! We hope that these "Advanced Hospitals" will improve the health of our subjects so that they are less likely to bite the dust at an unprofitable moment. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: No
  • +
  • Cost: &euro;50,000
  • +
  • Effects:
      +
    • Increases population growth
    • +
  • +
+
+
+ Depends on:
    +
  • Bio-engineering
  • +
+ Required by:
    +
  • Cloning Techniques
  • +
  • Nourishment Purification
  • +
  • Surgical Robots
  • +
+
+
diff --git a/manual/beta5/en/tech_11.lwdoc b/manual/beta5/en/tech_11.lwdoc new file mode 100644 index 0000000..6c12121 --- /dev/null +++ b/manual/beta5/en/tech_11.lwdoc @@ -0,0 +1,27 @@ + + + beta5 + en + High-efficiency Hydroponics +
+ Recent progress in bio-engineering has allowed for new farming techniques, that are safer for the planet and allow for further studies in green technologies. +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: No
  • +
  • Cost: &euro;50,000
  • +
  • Effects: N/A
  • +
+
+
+ Depends on:
    +
  • Bio-engineering
  • +
+ Required by:
    +
  • Safe Recreational Drugs
  • +
  • Nourishment Purification
  • +
  • Green Production
  • +
+
+
diff --git a/manual/beta5/en/tech_12.lwdoc b/manual/beta5/en/tech_12.lwdoc new file mode 100644 index 0000000..144f281 --- /dev/null +++ b/manual/beta5/en/tech_12.lwdoc @@ -0,0 +1,27 @@ + + + beta5 + en + Safe Recreational Drugs +
+ Recent progress in the field of farming has allowed our scientists to develop safe recreational drugs such as the so-called "Space weed". This will improve commerce and make the population happier, since these drugs can be used instead of traditional anaesthetics in our hospitals. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;80,000
  • +
  • Effects:
      +
    • Increases happiness
    • +
  • +
+
+
+ Depends on:
    +
  • High-efficiency Hydroponics
  • +
+ Required by:
    +
  • Legalize Space Weed
  • +
+
+
diff --git a/manual/beta5/en/tech_13.lwdoc b/manual/beta5/en/tech_13.lwdoc new file mode 100644 index 0000000..2a47ee7 --- /dev/null +++ b/manual/beta5/en/tech_13.lwdoc @@ -0,0 +1,26 @@ + + + beta5 + en + Legalize Space Weed +
+ Yeah, maaan ... I mean, Sir ... Enacting this law will allow every citizen in our empire to smoke Space Weed as they see fit, without any harmful effect for their health. I mean... huh, What was I saying again? ... Ah, yes! Well, they might be a bit inefficient, because they'll still be stoned, but they'll be happy! +
+
+
    +
  • Category: Law
  • +
  • Optional: Yes
  • +
  • Cost: &euro;15,000
  • +
  • Effects:
      +
    • Increases happiness
    • +
    • Decreases Industrial Factories productivity
    • +
    • Decreases Military Factories productivity
    • +
  • +
+
+
+ Depends on:
    +
  • Safe Recreational Drugs
  • +
+
+
diff --git a/manual/beta5/en/tech_14.lwdoc b/manual/beta5/en/tech_14.lwdoc new file mode 100644 index 0000000..7ec7aa7 --- /dev/null +++ b/manual/beta5/en/tech_14.lwdoc @@ -0,0 +1,26 @@ + + + beta5 + en + Cloning Techniques +
+ Sir! Their stem cells analysis have finally brought our scientists a new breakthrough. Our researchers have succeeded in cloning various lifeforms. +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: Yes
  • +
  • Cost: &euro;100,000
  • +
  • Effects: N/A
  • +
+
+
+ Depends on:
    +
  • Advanced Hospitals
  • +
+ Required by:
    +
  • Lifeform Engineering
  • +
  • Cloning Vats
  • +
+
+
diff --git a/manual/beta5/en/tech_15.lwdoc b/manual/beta5/en/tech_15.lwdoc new file mode 100644 index 0000000..b169c37 --- /dev/null +++ b/manual/beta5/en/tech_15.lwdoc @@ -0,0 +1,30 @@ + + + beta5 + en + Nano-scale Computers +
+ Sir, their recent progresses in nanotechnologies has allowed our scientists to create a new generation of computers. This new miniaturised computer allows us to improve research efficiency and opens a brand new field of research. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;100,000
  • +
  • Effects:
      +
    • Increases research output
    • +
  • +
+
+
+ Depends on:
    +
  • Room Temperature Superconductors
  • +
  • Nanotechnologies
  • +
+ Required by:
    +
  • Quantum Computers
  • +
  • Hyperspace Theory
  • +
  • Cybernetic Interfaces
  • +
+
+
diff --git a/manual/beta5/en/tech_16.lwdoc b/manual/beta5/en/tech_16.lwdoc new file mode 100644 index 0000000..ef20646 --- /dev/null +++ b/manual/beta5/en/tech_16.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Quantum Gravitation +
+ These advances in the field of quantum theory will open a new area for further studies. +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: No
  • +
  • Cost: &euro;100,000
  • +
  • Effects: N/A
  • +
+
+
+ Depends on:
    +
  • Hyperspace Basics
  • +
+ Required by:
    +
  • Miniaturised Particle Colliders
  • +
  • Quantum Computers
  • +
  • Hyperspace Theory
  • +
  • Experimental Anti-Matter Production
  • +
+
+
diff --git a/manual/beta5/en/tech_17.lwdoc b/manual/beta5/en/tech_17.lwdoc new file mode 100644 index 0000000..d3e742e --- /dev/null +++ b/manual/beta5/en/tech_17.lwdoc @@ -0,0 +1,29 @@ + + + beta5 + en + Miniaturised Particle Colliders +
+ These new and small particle colliders increase research in many areas, increasing lab outputs. +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: Yes
  • +
  • Cost: &euro;100,000
  • +
  • Effects:
      +
    • Increases research output
    • +
  • +
+
+
+ Depends on:
    +
  • Advanced Materials
  • +
  • Quantum Gravitation
  • +
+ Required by:
    +
  • Increased Research Grants
  • +
  • Science Golden Age
  • +
+
+
diff --git a/manual/beta5/en/tech_18.lwdoc b/manual/beta5/en/tech_18.lwdoc new file mode 100644 index 0000000..ee47474 --- /dev/null +++ b/manual/beta5/en/tech_18.lwdoc @@ -0,0 +1,29 @@ + + + beta5 + en + Advanced Communications +
+ Having achieved major breakthroughs in electronic research, our scientists are now able to apply this research to the communications field, where some interesting developments are expected. +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: Yes
  • +
  • Cost: &euro;150,000
  • +
  • Effects:
      +
    • Increases happiness
    • +
  • +
+
+
+ Depends on:
    +
  • Room Temperature Superconductors
  • +
+ Required by:
    +
  • Economy Globalisation
  • +
  • Wide Band Jamming
  • +
  • Fast Burst Transmission
  • +
+
+
diff --git a/manual/beta5/en/tech_2.lwdoc b/manual/beta5/en/tech_2.lwdoc new file mode 100644 index 0000000..e9328d8 --- /dev/null +++ b/manual/beta5/en/tech_2.lwdoc @@ -0,0 +1,24 @@ + + + beta5 + en + Hyperspace Basics +
+ Sir! Our scientists have made major progress in understanding the basics behind Hyperspace theory. Those basic hyperspace capabilities are the very beginning of a new area in space flight and allow a wide range of new experiments. +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: No
  • +
  • Cost: &euro;15,000
  • +
  • Effects: N/A
  • +
+
+
+ Required by:
    +
  • Cruisers
  • +
  • Quantum Gravitation
  • +
  • Hyperspace Beacon
  • +
+
+
diff --git a/manual/beta5/en/tech_20.lwdoc b/manual/beta5/en/tech_20.lwdoc new file mode 100644 index 0000000..3ddac39 --- /dev/null +++ b/manual/beta5/en/tech_20.lwdoc @@ -0,0 +1,30 @@ + + + beta5 + en + Quantum Computers +
+ Based on the new discoveries in the quantum theory field, these new computers are much more efficient and faster. Equipping our labs will be expensive but the advantages of the increased research speed should be incredible. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;500,000
  • +
  • Effects:
      +
    • Increases research output
    • +
  • +
+
+
+ Depends on:
    +
  • Nano-scale Computers
  • +
  • Quantum Gravitation
  • +
+ Required by:
    +
  • Economy Globalisation
  • +
  • Biological Computers
  • +
  • Quantum Encryption
  • +
+
+
diff --git a/manual/beta5/en/tech_21.lwdoc b/manual/beta5/en/tech_21.lwdoc new file mode 100644 index 0000000..62c560c --- /dev/null +++ b/manual/beta5/en/tech_21.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Economy Globalisation +
+ Recent advances in both communications and computer capacities have allowed us to set up an empire wide economic system that should increase our planets base income. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;1,000,000
  • +
  • Effects:
      +
    • Increases planetary base income
    • +
  • +
+
+
+ Depends on:
    +
  • Advanced Communications
  • +
  • Quantum Computers
  • +
+ Required by:
    +
  • Wild Capitalism
  • +
+
+
diff --git a/manual/beta5/en/tech_22.lwdoc b/manual/beta5/en/tech_22.lwdoc new file mode 100644 index 0000000..e94c082 --- /dev/null +++ b/manual/beta5/en/tech_22.lwdoc @@ -0,0 +1,29 @@ + + + beta5 + en + Hyperspace Theory +
+ Our scientists have developed and tested a complete advanced theory regarding the structure of Hyperspace! We need to upgrade their labs for them to continue researches in this field. +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: Yes
  • +
  • Cost: &euro;500,000
  • +
  • Effects: N/A
  • +
+
+
+ Depends on:
    +
  • Nano-scale Computers
  • +
  • Quantum Gravitation
  • +
+ Required by:
    +
  • Temporal Mechanics
  • +
  • Entropy Generator
  • +
  • Force fields
  • +
  • Hyperspace Probing Beacons
  • +
+
+
diff --git a/manual/beta5/en/tech_23.lwdoc b/manual/beta5/en/tech_23.lwdoc new file mode 100644 index 0000000..f10964d --- /dev/null +++ b/manual/beta5/en/tech_23.lwdoc @@ -0,0 +1,26 @@ + + + beta5 + en + Increased Research Grants +
+ This law allows to divert a higher percentage of income towards research thus allowing faster discoveries but reducing income. +
+
+
    +
  • Category: Law
  • +
  • Optional: Yes
  • +
  • Cost: &euro;30,000
  • +
  • Effects:
      +
    • Increases research output
    • +
    • Decreases Industrial Factories productivity
    • +
    • Decreases planetary base income
    • +
  • +
+
+
+ Depends on:
    +
  • Miniaturised Particle Colliders
  • +
+
+
diff --git a/manual/beta5/en/tech_24.lwdoc b/manual/beta5/en/tech_24.lwdoc new file mode 100644 index 0000000..294a191 --- /dev/null +++ b/manual/beta5/en/tech_24.lwdoc @@ -0,0 +1,32 @@ + + + beta5 + en + Hardened Alloys +
+ These new improved alloys are more resistant and will allow for less losses in battle as well as future advances in industry research. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;100,000
  • +
  • Effects:
      +
    • Increases Industrial Factories productivity
    • +
    • Increases planetary base income
    • +
    • Reduces battle losses
    • +
  • +
+
+
+ Depends on:
    +
  • Nanotechnologies
  • +
+ Required by:
    +
  • Experimental Anti-Matter Production
  • +
  • Arcologies
  • +
  • Robotics
  • +
  • Adaptive Materials
  • +
+
+
diff --git a/manual/beta5/en/tech_25.lwdoc b/manual/beta5/en/tech_25.lwdoc new file mode 100644 index 0000000..bd9505c --- /dev/null +++ b/manual/beta5/en/tech_25.lwdoc @@ -0,0 +1,26 @@ + + + beta5 + en + Experimental Anti-Matter Production +
+ Recent advances in new alloy production and a better knowledge of quantum theory have allowed our scientists to produce anti-matter for the first time. Research should be continued in this area since the applications could be tremendous. +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: Yes
  • +
  • Cost: &euro;500,000
  • +
  • Effects: N/A
  • +
+
+
+ Depends on:
    +
  • Quantum Gravitation
  • +
  • Hardened Alloys
  • +
+ Required by:
    +
  • Mass Anti-matter Production
  • +
+
+
diff --git a/manual/beta5/en/tech_26.lwdoc b/manual/beta5/en/tech_26.lwdoc new file mode 100644 index 0000000..8356917 --- /dev/null +++ b/manual/beta5/en/tech_26.lwdoc @@ -0,0 +1,27 @@ + + + beta5 + en + Nanofiber Armor +
+ Sir! Applying nanotechnologies to the military field our scientists have managed to produce nano-fiber armors for our ground troups. Since our soldiers will be better protected against rioters we'll need to send less of them on the ground to take control of a planet. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;100,000
  • +
  • Effects:
      +
    • Increases G.A. ship efficiency
    • +
  • +
+
+
+ Depends on:
    +
  • Nanotechnologies
  • +
+ Required by:
    +
  • Exoskeleton
  • +
+
+
diff --git a/manual/beta5/en/tech_27.lwdoc b/manual/beta5/en/tech_27.lwdoc new file mode 100644 index 0000000..54ec839 --- /dev/null +++ b/manual/beta5/en/tech_27.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Lifeform Engineering +
+ Our scientists have devised a method to design lifeforms from scratch! Although this breakthrough has no direct application, further research should be funded, the potential gains are tremendous! +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: Yes
  • +
  • Cost: &euro;500,000
  • +
  • Effects: N/A
  • +
+
+
+ Depends on:
    +
  • Nanotechnologies
  • +
  • Cloning Techniques
  • +
+ Required by:
    +
  • Sentient Lifeform Engineering
  • +
  • Self-healing Materials
  • +
  • Lifeform Energy Manipulation
  • +
+
+
diff --git a/manual/beta5/en/tech_28.lwdoc b/manual/beta5/en/tech_28.lwdoc new file mode 100644 index 0000000..d614f9c --- /dev/null +++ b/manual/beta5/en/tech_28.lwdoc @@ -0,0 +1,26 @@ + + + beta5 + en + Sentient Lifeform Engineering +
+ Sir! Our biologists have finally managed to create intelligent lifeforms from scratch, thus opening a brand new field of studies. +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: Yes
  • +
  • Cost: &euro;800,000
  • +
  • Effects: N/A
  • +
+
+
+ Depends on:
    +
  • Lifeform Engineering
  • +
+ Required by:
    +
  • Biological Computers
  • +
  • Biological Drones
  • +
+
+
diff --git a/manual/beta5/en/tech_29.lwdoc b/manual/beta5/en/tech_29.lwdoc new file mode 100644 index 0000000..c861220 --- /dev/null +++ b/manual/beta5/en/tech_29.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Cloning Vats +
+ Our scientists have perfected their cloning techniques, allowing them to grow real clones in vats. Our industrial production could be greatly improved using this technology! +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;200,000
  • +
  • Effects:
      +
    • Increases Industrial Factories production
    • +
  • +
+
+
+ Depends on:
    +
  • Cloning Techniques
  • +
+ Required by:
    +
  • Corpse Reanimation
  • +
  • Forced Human Cloning
  • +
+
+
diff --git a/manual/beta5/en/tech_3.lwdoc b/manual/beta5/en/tech_3.lwdoc new file mode 100644 index 0000000..71eb91b --- /dev/null +++ b/manual/beta5/en/tech_3.lwdoc @@ -0,0 +1,26 @@ + + + beta5 + en + Advanced Materials +
+ Sir! Our scientists just discovered new production methods that will allow us to create hardened materials! Our factories must be upgraded in order to start producing those new alloys. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: No
  • +
  • Cost: &euro;20,000
  • +
  • Effects:
      +
    • Reduces battle losses
    • +
  • +
+
+
+ Required by:
    +
  • Room Temperature Superconductors
  • +
  • Nanotechnologies
  • +
  • Miniaturised Particle Colliders
  • +
+
+
diff --git a/manual/beta5/en/tech_30.lwdoc b/manual/beta5/en/tech_30.lwdoc new file mode 100644 index 0000000..1fb6552 --- /dev/null +++ b/manual/beta5/en/tech_30.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Nourishment Purification +
+ Sir! We have successfully researched a new way to improve food, Nourishment purification! This process will give our subjects a healthier lifespan by removing all the nasties in their food. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;1,500,000
  • +
  • Effects:
      +
    • Increases population growth
    • +
  • +
+
+
+ Depends on:
    +
  • Advanced Hospitals
  • +
  • High-efficiency Hydroponics
  • +
+ Required by:
    +
  • Corpse Reanimation
  • +
+
+
diff --git a/manual/beta5/en/tech_31.lwdoc b/manual/beta5/en/tech_31.lwdoc new file mode 100644 index 0000000..3cdd74e --- /dev/null +++ b/manual/beta5/en/tech_31.lwdoc @@ -0,0 +1,30 @@ + + + beta5 + en + Green Production +
+ These new production methods are safer for planetary ecology, making the population happier but slightly reducing factory productivity. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;400,000
  • +
  • Effects:
      +
    • Increases happiness
    • +
    • Decreases Industrial Factories productivity
    • +
    • Decreases Military Factories productivity
    • +
  • +
+
+
+ Depends on:
    +
  • High-efficiency Hydroponics
  • +
+ Required by:
    +
  • Biosphere Protection Pact
  • +
  • Arcologies
  • +
+
+
diff --git a/manual/beta5/en/tech_32.lwdoc b/manual/beta5/en/tech_32.lwdoc new file mode 100644 index 0000000..60f36c5 --- /dev/null +++ b/manual/beta5/en/tech_32.lwdoc @@ -0,0 +1,26 @@ + + + beta5 + en + Biosphere Protection Pact +
+ Enforcing this law forces the industrial sector to use greener production methods that make the population happier but reduce productivity. +
+
+
    +
  • Category: Law
  • +
  • Optional: Yes
  • +
  • Cost: &euro;60,000
  • +
  • Effects:
      +
    • Increases happiness
    • +
    • Decreases Industrial Factories productivity
    • +
    • Decreases Military Factories productivity
    • +
  • +
+
+
+ Depends on:
    +
  • Green Production
  • +
+
+
diff --git a/manual/beta5/en/tech_33.lwdoc b/manual/beta5/en/tech_33.lwdoc new file mode 100644 index 0000000..4213776 --- /dev/null +++ b/manual/beta5/en/tech_33.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Corpse Reanimation +
+ Sir! Our scientists have established a new technology, Corpse Reanimation! This technology will mean about 60% of our subjects will be able to be brought back to life after death through the use of modified cloning vats. This should lead to a decrease in total death and an increase in workers for our factories. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;1,500,000
  • +
  • Effects:
      +
    • Increases population growth
    • +
  • +
+
+
+ Depends on:
    +
  • Cloning Vats
  • +
  • Nourishment Purification
  • +
+ Required by:
    +
  • Resurrection tanks
  • +
+
+
diff --git a/manual/beta5/en/tech_34.lwdoc b/manual/beta5/en/tech_34.lwdoc new file mode 100644 index 0000000..ee2a400 --- /dev/null +++ b/manual/beta5/en/tech_34.lwdoc @@ -0,0 +1,25 @@ + + + beta5 + en + Forced Human Cloning +
+ Sir! We could enact a law that would allow our government to clone citizens and boost our population growth! Our people wouldn't be too happy about it though... +
+
+
    +
  • Category: Law
  • +
  • Optional: Yes
  • +
  • Cost: &euro;80,000
  • +
  • Effects:
      +
    • Decreases happiness
    • +
    • Increases population growth
    • +
  • +
+
+
+ Depends on:
    +
  • Cloning Vats
  • +
+
+
diff --git a/manual/beta5/en/tech_35.lwdoc b/manual/beta5/en/tech_35.lwdoc new file mode 100644 index 0000000..5b87243 --- /dev/null +++ b/manual/beta5/en/tech_35.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Arcologies +
+ Sir! We are now able to build arcologies, which will allow us to house loads more citizens on our empire's planets! +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;800,000
  • +
  • Effects:
      +
    • Increases planet maximum population
    • +
  • +
+
+
+ Depends on:
    +
  • Hardened Alloys
  • +
  • Green Production
  • +
+ Required by:
    +
  • Singularity Housing
  • +
+
+
diff --git a/manual/beta5/en/tech_36.lwdoc b/manual/beta5/en/tech_36.lwdoc new file mode 100644 index 0000000..8737e60 --- /dev/null +++ b/manual/beta5/en/tech_36.lwdoc @@ -0,0 +1,31 @@ + + + beta5 + en + Robotics +
+ Recent advances in electronics and new materials have allowed our engineers to design autonomous robots that will help our workers and improve the production of our factories. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;600,000
  • +
  • Effects:
      +
    • Increases Industrial Factories productivity
    • +
    • Increases Military Factories productivity
    • +
  • +
+
+
+ Depends on:
    +
  • Room Temperature Superconductors
  • +
  • Hardened Alloys
  • +
+ Required by:
    +
  • Surgical Robots
  • +
  • Biological Drones
  • +
  • Automated Turrets
  • +
+
+
diff --git a/manual/beta5/en/tech_37.lwdoc b/manual/beta5/en/tech_37.lwdoc new file mode 100644 index 0000000..27d21a9 --- /dev/null +++ b/manual/beta5/en/tech_37.lwdoc @@ -0,0 +1,31 @@ + + + beta5 + en + Adaptive Materials +
+ New developments in research have allowed our scientists to create materials that adapt to the needs of our civilians, thus providing even more resistant alloys. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;300,000
  • +
  • Effects:
      +
    • Increases Industrial Factories productivity
    • +
    • Increases Military Factories productivity
    • +
    • Reduces battle losses
    • +
  • +
+
+
+ Depends on:
    +
  • Hardened Alloys
  • +
+ Required by:
    +
  • Self-healing Materials
  • +
  • Force fields
  • +
  • Battle Cruisers
  • +
+
+
diff --git a/manual/beta5/en/tech_38.lwdoc b/manual/beta5/en/tech_38.lwdoc new file mode 100644 index 0000000..bf120d6 --- /dev/null +++ b/manual/beta5/en/tech_38.lwdoc @@ -0,0 +1,29 @@ + + + beta5 + en + Cybernetic Interfaces +
+ Sir! Our scientists have found a way to interface the human brain with a machine. This new technology allows a direct interface between electronic and biological systems, making our ships more reactive to their pilots' commands. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;500,000
  • +
  • Effects:
      +
    • Increases fleet power
    • +
  • +
+
+
+ Depends on:
    +
  • Bio-engineering
  • +
  • Nano-scale Computers
  • +
+ Required by:
    +
  • Exoskeleton
  • +
  • Biological Propulsion Systems
  • +
+
+
diff --git a/manual/beta5/en/tech_39.lwdoc b/manual/beta5/en/tech_39.lwdoc new file mode 100644 index 0000000..0b592d9 --- /dev/null +++ b/manual/beta5/en/tech_39.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Exoskeleton +
+ Sir! Our scientists have managed to improve even further the equipment of our ground troups. With those new exoskeletons we will require to send even less GA ships to get the inhabitants of another planet to share our views.. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;700,000
  • +
  • Effects:
      +
    • Increases G.A. ship efficiency
    • +
  • +
+
+
+ Depends on:
    +
  • Nanofiber Armor
  • +
  • Cybernetic Interfaces
  • +
+ Required by:
    +
  • Self-repairing Exoskeleton
  • +
+
+
diff --git a/manual/beta5/en/tech_4.lwdoc b/manual/beta5/en/tech_4.lwdoc new file mode 100644 index 0000000..1769c4e --- /dev/null +++ b/manual/beta5/en/tech_4.lwdoc @@ -0,0 +1,26 @@ + + + beta5 + en + Bio-engineering +
+ Sir! Our scientists worked hard to improve our empire's knowledge of bio-engineering, which will greatly improve the production of basic goods, as well as pave the way for further advances. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: No
  • +
  • Cost: &euro;30,000
  • +
  • Effects:
      +
    • Increases Industrial Factories productivity
    • +
  • +
+
+
+ Required by:
    +
  • Advanced Hospitals
  • +
  • High-efficiency Hydroponics
  • +
  • Cybernetic Interfaces
  • +
+
+
diff --git a/manual/beta5/en/tech_40.lwdoc b/manual/beta5/en/tech_40.lwdoc new file mode 100644 index 0000000..bfbf6d6 --- /dev/null +++ b/manual/beta5/en/tech_40.lwdoc @@ -0,0 +1,27 @@ + + + beta5 + en + Temporal Mechanics +
+ The progress of our scientists has enabled us to better understand temporal phenomenons, which should allow for a wide range of new discoveries. Further funding of this area of study is a necessity. +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: Yes
  • +
  • Cost: &euro;1,000,000
  • +
  • Effects: N/A
  • +
+
+
+ Depends on:
    +
  • Hyperspace Theory
  • +
+ Required by:
    +
  • Space-time folding
  • +
  • Phase Neutraliser
  • +
  • Multiphasic Transmission
  • +
+
+
diff --git a/manual/beta5/en/tech_41.lwdoc b/manual/beta5/en/tech_41.lwdoc new file mode 100644 index 0000000..0161a40 --- /dev/null +++ b/manual/beta5/en/tech_41.lwdoc @@ -0,0 +1,30 @@ + + + beta5 + en + Space-time folding +
+ Sir! Our scientists have pushed the limit of Hyperspace theory! This will allow us to design better, faster ship engines and further advances are to be expected! However, upgrading our fleets as well as our laboratories might be a bit expensive... +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: Yes
  • +
  • Cost: &euro;2,000,000
  • +
  • Effects:
      +
    • Increases capital ships speed
    • +
  • +
+
+
+ Depends on:
    +
  • Temporal Mechanics
  • +
+ Required by:
    +
  • Battle Cruisers
  • +
  • Wormhole Theory
  • +
  • Singularity Housing
  • +
  • Biological Subspace Control
  • +
+
+
diff --git a/manual/beta5/en/tech_42.lwdoc b/manual/beta5/en/tech_42.lwdoc new file mode 100644 index 0000000..d788e4c --- /dev/null +++ b/manual/beta5/en/tech_42.lwdoc @@ -0,0 +1,30 @@ + + + beta5 + en + Biological Computers +
+ Our scientists have found a way to integrate intelligent lifeforms into our computers, thus improving calculation capabilities. Upgrading our computers, though expensive, should allow our current research projects to reach completion faster. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;1,500,000
  • +
  • Effects:
      +
    • Increases research output
    • +
  • +
+
+
+ Depends on:
    +
  • Quantum Computers
  • +
  • Sentient Lifeform Engineering
  • +
+ Required by:
    +
  • Science Golden Age
  • +
  • Interstellar University
  • +
  • Intelligent Materials
  • +
+
+
diff --git a/manual/beta5/en/tech_43.lwdoc b/manual/beta5/en/tech_43.lwdoc new file mode 100644 index 0000000..87e07b2 --- /dev/null +++ b/manual/beta5/en/tech_43.lwdoc @@ -0,0 +1,27 @@ + + + beta5 + en + Science Golden Age +
+ Enacting this law permits us to divert more resources towards research at the expense of other necessities. +
+
+
    +
  • Category: Law
  • +
  • Optional: Yes
  • +
  • Cost: &euro;100,000
  • +
  • Effects:
      +
    • Increases research output
    • +
    • Decreases Industrial Factories productivity
    • +
    • Decreases Military Factories productivity
    • +
  • +
+
+
+ Depends on:
    +
  • Miniaturised Particle Colliders
  • +
  • Biological Computers
  • +
+
+
diff --git a/manual/beta5/en/tech_44.lwdoc b/manual/beta5/en/tech_44.lwdoc new file mode 100644 index 0000000..ce03f38 --- /dev/null +++ b/manual/beta5/en/tech_44.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Wide Band Jamming +
+ This basic jamming technology allows us to try and prevent the planets we attack from transmitting data to their allies. Therefore defensive procedures can potentially be disrupted. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;500,000
  • +
  • Effects:
      +
    • Increases jamming capabilities
    • +
  • +
+
+
+ Depends on:
    +
  • Advanced Communications
  • +
+ Required by:
    +
  • Entropy Generator
  • +
  • Civilian Communication Act
  • +
+
+
diff --git a/manual/beta5/en/tech_45.lwdoc b/manual/beta5/en/tech_45.lwdoc new file mode 100644 index 0000000..773d3ca --- /dev/null +++ b/manual/beta5/en/tech_45.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Fast Burst Transmission +
+ This new technology renders our stellar and interstellar communications less prone to jamming and interception. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;500,000
  • +
  • Effects:
      +
    • Increases communications efficiency
    • +
  • +
+
+
+ Depends on:
    +
  • Advanced Communications
  • +
+ Required by:
    +
  • Hyperspace Beacon
  • +
  • Quantum Encryption
  • +
+
+
diff --git a/manual/beta5/en/tech_46.lwdoc b/manual/beta5/en/tech_46.lwdoc new file mode 100644 index 0000000..818234e --- /dev/null +++ b/manual/beta5/en/tech_46.lwdoc @@ -0,0 +1,29 @@ + + + beta5 + en + Hyperspace Beacon +
+ Placed in hyperspace around our planets this beacon provides an "anchor" for your ships and those of your alliance, thus reducing the losses in fleet stationed in hyperspace. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;1,000,000
  • +
  • Effects:
      +
    • Provides the ability to build Hyperspace Beacons
    • +
  • +
+
+
+ Depends on:
    +
  • Hyperspace Basics
  • +
  • Fast Burst Transmission
  • +
+ Required by:
    +
  • Hyperspace Probing Beacon
  • +
  • Sensor Turrets
  • +
+
+
diff --git a/manual/beta5/en/tech_48.lwdoc b/manual/beta5/en/tech_48.lwdoc new file mode 100644 index 0000000..a9408e3 --- /dev/null +++ b/manual/beta5/en/tech_48.lwdoc @@ -0,0 +1,30 @@ + + + beta5 + en + Quantum Encryption +
+ Quantum computers have allowed tremendous progress in encryption algorithms. We can now be more efficient in preventing the enemy from disrupting our communications. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;1,500,000
  • +
  • Effects:
      +
    • Increases communications efficiency
    • +
  • +
+
+
+ Depends on:
    +
  • Quantum Computers
  • +
  • Fast Burst Transmission
  • +
+ Required by:
    +
  • Civilian Communication Act
  • +
  • Interstellar University
  • +
  • Multiphasic Transmission
  • +
+
+
diff --git a/manual/beta5/en/tech_49.lwdoc b/manual/beta5/en/tech_49.lwdoc new file mode 100644 index 0000000..3e2e5c5 --- /dev/null +++ b/manual/beta5/en/tech_49.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Entropy Generator +
+ By applying hyperspace theory to telecommunications we've found a new way to disrupt enemy communications, making it even harder for them to transmit accurate data to their allies. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;1,500,000
  • +
  • Effects:
      +
    • Increases jamming capabilities
    • +
  • +
+
+
+ Depends on:
    +
  • Hyperspace Theory
  • +
  • Wide Band Jamming
  • +
+ Required by:
    +
  • Phase Neutraliser
  • +
+
+
diff --git a/manual/beta5/en/tech_5.lwdoc b/manual/beta5/en/tech_5.lwdoc new file mode 100644 index 0000000..1f1bb5a --- /dev/null +++ b/manual/beta5/en/tech_5.lwdoc @@ -0,0 +1,26 @@ + + + beta5 + en + Martial law +
+ Sir! We can enact Martial Law to force our people to work in the military's best interest! They probably won't be too happy about it, and our economy might suffer, but who cares? Our military production will be greatly improved! +
+
+
    +
  • Category: Law
  • +
  • Optional: No
  • +
  • Cost: &euro;5,000
  • +
  • Effects:
      +
    • Increases Military Factories productivity
    • +
    • Decreases Industrial Factories productivity
    • +
    • Decreases happiness
    • +
  • +
+
+
+ Depends on:
    +
  • Fighters
  • +
+
+
diff --git a/manual/beta5/en/tech_50.lwdoc b/manual/beta5/en/tech_50.lwdoc new file mode 100644 index 0000000..4904881 --- /dev/null +++ b/manual/beta5/en/tech_50.lwdoc @@ -0,0 +1,26 @@ + + + beta5 + en + Surgical Robots +
+ This technology provides new advances in the medical field, allowing for further researches in this area. Further funding should bring quite interesting breakthroughs. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;800,000
  • +
  • Effects: N/A
  • +
+
+
+ Depends on:
    +
  • Advanced Hospitals
  • +
  • Robotics
  • +
+ Required by:
    +
  • Medical Bays
  • +
+
+
diff --git a/manual/beta5/en/tech_51.lwdoc b/manual/beta5/en/tech_51.lwdoc new file mode 100644 index 0000000..a765ae3 --- /dev/null +++ b/manual/beta5/en/tech_51.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Medical Bays +
+ Our engineers have modified the designs for our capital ships in order to include highly advanced medical bays, in which the pilots can be healed when they are wounded in combat. This improvement will reduce our losses, but our fleets and factories must be upgraded first. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;1,500,000
  • +
  • Effects:
      +
    • Reduces battle losses
    • +
  • +
+
+
+ Depends on:
    +
  • Cruisers
  • +
  • Surgical Robots
  • +
+ Required by:
    +
  • Resurrection tanks
  • +
+
+
diff --git a/manual/beta5/en/tech_52.lwdoc b/manual/beta5/en/tech_52.lwdoc new file mode 100644 index 0000000..3eb76d9 --- /dev/null +++ b/manual/beta5/en/tech_52.lwdoc @@ -0,0 +1,25 @@ + + + beta5 + en + Resurrection tanks +
+ Our engineers have updated the designs for our capital ships. The medical bays will now integrate the equipment required to raise our pilots from the dead, further reducing battle losses. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;3,000,000
  • +
  • Effects:
      +
    • Reduces battle losses
    • +
  • +
+
+
+ Depends on:
    +
  • Corpse Reanimation
  • +
  • Medical Bays
  • +
+
+
diff --git a/manual/beta5/en/tech_53.lwdoc b/manual/beta5/en/tech_53.lwdoc new file mode 100644 index 0000000..b8e5613 --- /dev/null +++ b/manual/beta5/en/tech_53.lwdoc @@ -0,0 +1,32 @@ + + + beta5 + en + Self-healing Materials +
+ Sir! Our scientists have have found a way to grow advanced, self-healing materials. This new technology will allow us to provide regeneration and auto-repair capabilities to both our ships and factories. We will thus greatly reduce our losses in battle and gain productivity in our industrial sector. +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: Yes
  • +
  • Cost: &euro;1,500,000
  • +
  • Effects:
      +
    • Increases Industrial Factories productivity
    • +
    • Increases Military Factories productivity
    • +
    • Reduces battle losses
    • +
  • +
+
+
+ Depends on:
    +
  • Lifeform Engineering
  • +
  • Adaptive Materials
  • +
+ Required by:
    +
  • Self-repairing Exoskeleton
  • +
  • Intelligent Materials
  • +
  • Anti-matter Generators
  • +
+
+
diff --git a/manual/beta5/en/tech_54.lwdoc b/manual/beta5/en/tech_54.lwdoc new file mode 100644 index 0000000..fab0bbc --- /dev/null +++ b/manual/beta5/en/tech_54.lwdoc @@ -0,0 +1,25 @@ + + + beta5 + en + Self-repairing Exoskeleton +
+ Sir! Incorporating the newest alloys to our soldiers exoskeletons, our scientists have set up self-repairing exoskeletons. Even more efficient against attacking crowds, this new armor generation will reduce even more the number of GA ships required to take control of a foreign planet. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;2,000,000
  • +
  • Effects:
      +
    • Increases G.A. ship efficiency
    • +
  • +
+
+
+ Depends on:
    +
  • Exoskeleton
  • +
  • Self-healing Materials
  • +
+
+
diff --git a/manual/beta5/en/tech_55.lwdoc b/manual/beta5/en/tech_55.lwdoc new file mode 100644 index 0000000..b62bd10 --- /dev/null +++ b/manual/beta5/en/tech_55.lwdoc @@ -0,0 +1,31 @@ + + + beta5 + en + Biological Drones +
+ This technology allows industry to use specifically designed lifeforms to replace workers in the factories. These lifeforms will be much more efficient than human beings, but our citizens might not like getting sacked in favour of their new replacements. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;2,000,000
  • +
  • Effects:
      +
    • Increases Industrial Factories productivity
    • +
    • Increases Military Factories productivity
    • +
    • Decreases happiness
    • +
  • +
+
+
+ Depends on:
    +
  • Sentient Lifeform Engineering
  • +
  • Robotics
  • +
+ Required by:
    +
  • Ban Biological Drones
  • +
  • Automated Factories
  • +
+
+
diff --git a/manual/beta5/en/tech_56.lwdoc b/manual/beta5/en/tech_56.lwdoc new file mode 100644 index 0000000..3bc838c --- /dev/null +++ b/manual/beta5/en/tech_56.lwdoc @@ -0,0 +1,26 @@ + + + beta5 + en + Ban Biological Drones +
+ To fight the decrease in happiness caused by biological drones, we can enact a law that bans their presence in our empire. This law counters all effects of biological drones and restores factories to their previous level of productivity. +
+
+
    +
  • Category: Law
  • +
  • Optional: Yes
  • +
  • Cost: &euro;70,000
  • +
  • Effects:
      +
    • Decreases Industrial Factories productivity
    • +
    • Decreases Military Factories productivity
    • +
    • Increases happiness
    • +
  • +
+
+
+ Depends on:
    +
  • Biological Drones
  • +
+
+
diff --git a/manual/beta5/en/tech_57.lwdoc b/manual/beta5/en/tech_57.lwdoc new file mode 100644 index 0000000..f9ce0cc --- /dev/null +++ b/manual/beta5/en/tech_57.lwdoc @@ -0,0 +1,29 @@ + + + beta5 + en + Force fields +
+ Our scientists have found a way to create force fields. The direct military application is the addition of shields to our current fleets, which will reduce losses. +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: Yes
  • +
  • Cost: &euro;1,500,000
  • +
  • Effects:
      +
    • Reduces battle losses
    • +
  • +
+
+
+ Depends on:
    +
  • Hyperspace Theory
  • +
  • Adaptive Materials
  • +
+ Required by:
    +
  • Mass Anti-matter Production
  • +
  • Singularity Housing
  • +
+
+
diff --git a/manual/beta5/en/tech_58.lwdoc b/manual/beta5/en/tech_58.lwdoc new file mode 100644 index 0000000..cd93c8a --- /dev/null +++ b/manual/beta5/en/tech_58.lwdoc @@ -0,0 +1,27 @@ + + + beta5 + en + Lifeform Energy Manipulation +
+ Sir! Our scientists have managed to engineer lifeforms capabable of manipulating energy. This new discovery lets us foresee some astonishing future breakthroughs. +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: Yes
  • +
  • Cost: &euro;1,500,000
  • +
  • Effects: N/A
  • +
+
+
+ Depends on:
    +
  • Lifeform Engineering
  • +
+ Required by:
    +
  • Biological Propulsion Systems
  • +
  • Adaptive Plating
  • +
  • Biological Subspace Control
  • +
+
+
diff --git a/manual/beta5/en/tech_59.lwdoc b/manual/beta5/en/tech_59.lwdoc new file mode 100644 index 0000000..0a6c428 --- /dev/null +++ b/manual/beta5/en/tech_59.lwdoc @@ -0,0 +1,27 @@ + + + beta5 + en + Civilian Communication Act +
+ Passing this law will permit civilians access to our militaries' advanced communication networks. Civilians will be happier since they can keep in touch with friends, but this civilian use of military installations could disrupt anti-jamming and jamming systems! +
+
+
    +
  • Category: Law
  • +
  • Optional: Yes
  • +
  • Cost: &euro;120,000
  • +
  • Effects:
      +
    • Increases happiness
    • +
    • Decreases jamming abilities
    • +
    • Decreases communications efficiency
    • +
  • +
+
+
+ Depends on:
    +
  • Wide Band Jamming
  • +
  • Quantum Encryption
  • +
+
+
diff --git a/manual/beta5/en/tech_6.lwdoc b/manual/beta5/en/tech_6.lwdoc new file mode 100644 index 0000000..68e1e3b --- /dev/null +++ b/manual/beta5/en/tech_6.lwdoc @@ -0,0 +1,31 @@ + + + beta5 + en + Cruisers +
+ Sir! We have successfully researched a new type of ship, the Cruiser! These are able to travel outside of our solar system thanks to the recent developments in Hyperspace technology, and can carry our current ships in their holds. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: No
  • +
  • Cost: &euro;500,000
  • +
  • Effects:
      +
    • Provides the ability to build ships of the Cruiser class
    • +
  • +
+
+
+ Depends on:
    +
  • Fighters
  • +
  • Hyperspace Basics
  • +
+ Required by:
    +
  • Civilian Transportation Act
  • +
  • Medical Bays
  • +
  • Battle Cruisers
  • +
  • Biological Propulsion Systems
  • +
+
+
diff --git a/manual/beta5/en/tech_60.lwdoc b/manual/beta5/en/tech_60.lwdoc new file mode 100644 index 0000000..c3fc5f0 --- /dev/null +++ b/manual/beta5/en/tech_60.lwdoc @@ -0,0 +1,25 @@ + + + beta5 + en + Hyperspace Probing Beacon +
+ Adding probing systems to hyperspace beacons, this technology allows for early detection of enemy ships stationed in hyperspace around our planets. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;3,000,000
  • +
  • Effects:
      +
    • Provides the ability to build Hyperspace Probing Beacons
    • +
  • +
+
+
+ Depends on:
    +
  • Hyperspace Beacon
  • +
  • Hyperspace Theory
  • +
+
+
diff --git a/manual/beta5/en/tech_61.lwdoc b/manual/beta5/en/tech_61.lwdoc new file mode 100644 index 0000000..6720640 --- /dev/null +++ b/manual/beta5/en/tech_61.lwdoc @@ -0,0 +1,29 @@ + + + beta5 + en + Automated Turrets +
+ Sir! Including robotics in our turrets will improve their accuracy and firing efficiency. With these new turrets we will be able to better defend our planets. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;500,000
  • +
  • Effects:
      +
    • Increases turret power
    • +
  • +
+
+
+ Depends on:
    +
  • Robotics
  • +
+ Required by:
    +
  • Sensor Turrets
  • +
  • Global Defense Bill
  • +
  • Biological Turrets
  • +
+
+
diff --git a/manual/beta5/en/tech_62.lwdoc b/manual/beta5/en/tech_62.lwdoc new file mode 100644 index 0000000..6aada66 --- /dev/null +++ b/manual/beta5/en/tech_62.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Interstellar University +
+ Sir! Thanks to our recent improvements in communications and computing capacities we've brought education to a new scale. Our interstellar universities will allow us to better adapt education programs to students' needs and to improve cooperation between our research labs. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;2,500,000
  • +
  • Effects:
      +
    • Increases research output
    • +
  • +
+
+
+ Depends on:
    +
  • Biological Computers
  • +
  • Quantum Encryption
  • +
+ Required by:
    +
  • Wormhole Theory
  • +
+
+
diff --git a/manual/beta5/en/tech_63.lwdoc b/manual/beta5/en/tech_63.lwdoc new file mode 100644 index 0000000..63ca243 --- /dev/null +++ b/manual/beta5/en/tech_63.lwdoc @@ -0,0 +1,31 @@ + + + beta5 + en + Battle Cruisers +
+ Sir! We have successfully researched a new type of ship, the Battle Cruiser! These ships are an improvement on our Cruisers as they are faster and more deadly. However, they are considerably more expensive to build and carry less of our system ships. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;2,500,000
  • +
  • Effects:
      +
    • Provides the ability to build ships of the Battle Cruiser class
    • +
  • +
+
+
+ Depends on:
    +
  • Cruisers
  • +
  • Adaptive Materials
  • +
  • Space-time folding
  • +
+ Required by:
    +
  • Global Defense Bill
  • +
  • Biological Hyperspace Engines
  • +
  • Matter Anti-matter Missiles
  • +
+
+
diff --git a/manual/beta5/en/tech_64.lwdoc b/manual/beta5/en/tech_64.lwdoc new file mode 100644 index 0000000..bc1bdd8 --- /dev/null +++ b/manual/beta5/en/tech_64.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Phase Neutraliser +
+ Applying temporal mechanics to communications, our scientists have managed to find better ways to disrupt enemy data flows, thus reducing their chances of accurate data being sent to their allies. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;5,000,000
  • +
  • Effects:
      +
    • Increases jamming capabilities
    • +
  • +
+
+
+ Depends on:
    +
  • Temporal Mechanics
  • +
  • Entropy Generator
  • +
+ Required by:
    +
  • Localised Wormhole Destabilisation
  • +
+
+
diff --git a/manual/beta5/en/tech_65.lwdoc b/manual/beta5/en/tech_65.lwdoc new file mode 100644 index 0000000..973463e --- /dev/null +++ b/manual/beta5/en/tech_65.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Multiphasic Transmission +
+ Applying temporal mechanics to communication, our scientists have managed to use it to transmit data in fluctuating phases. This further protects our communications from enemy jamming technologies. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;5,000,000
  • +
  • Effects:
      +
    • Increases communications efficiency
    • +
  • +
+
+
+ Depends on:
    +
  • Temporal Mechanics
  • +
  • Quantum Encryption
  • +
+ Required by:
    +
  • Subspace Data conduit
  • +
+
+
diff --git a/manual/beta5/en/tech_68.lwdoc b/manual/beta5/en/tech_68.lwdoc new file mode 100644 index 0000000..f75d4f3 --- /dev/null +++ b/manual/beta5/en/tech_68.lwdoc @@ -0,0 +1,25 @@ + + + beta5 + en + Sensor Turrets +
+ Sire dude! Equipping our turrets with sensors designed out of our probe technology, we can gain in the accuracy of our aiming, thus becoming more efficient. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;2,000,000
  • +
  • Effects:
      +
    • Increases turret power
    • +
  • +
+
+
+ Depends on:
    +
  • Hyperspace Beacon
  • +
  • Automated Turrets
  • +
+
+
diff --git a/manual/beta5/en/tech_69.lwdoc b/manual/beta5/en/tech_69.lwdoc new file mode 100644 index 0000000..a96ab5b --- /dev/null +++ b/manual/beta5/en/tech_69.lwdoc @@ -0,0 +1,27 @@ + + + beta5 + en + Global Defense Bill +
+ This new law goes even further than the Martial Law to cope with the military's will. But our citizens will even less appreciate it. +
+
+
    +
  • Category: Law
  • +
  • Optional: Yes
  • +
  • Cost: &euro;150,000
  • +
  • Effects:
      +
    • Decreases happiness
    • +
    • Decreases Industrial Factories productivity
    • +
    • Increases Military Factories productivity
    • +
  • +
+
+
+ Depends on:
    +
  • Automated Turrets
  • +
  • Battle Cruisers
  • +
+
+
diff --git a/manual/beta5/en/tech_7.lwdoc b/manual/beta5/en/tech_7.lwdoc new file mode 100644 index 0000000..8e9aab6 --- /dev/null +++ b/manual/beta5/en/tech_7.lwdoc @@ -0,0 +1,25 @@ + + + beta5 + en + Civilian Transportation Act +
+ This law grants our citizens the right to use military ships to move between planets. This increases happiness but reduces battle efficiency of our ships since they have civilians on board. +
+
+
    +
  • Category: Law
  • +
  • Optional: No
  • +
  • Cost: &euro;50,000
  • +
  • Effects:
      +
    • Increases happiness
    • +
    • Decreases fleet power
    • +
  • +
+
+
+ Depends on:
    +
  • Cruisers
  • +
+
+
diff --git a/manual/beta5/en/tech_70.lwdoc b/manual/beta5/en/tech_70.lwdoc new file mode 100644 index 0000000..aa83d3c --- /dev/null +++ b/manual/beta5/en/tech_70.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Wormhole Theory +
+ Going even further than space-time folding, our scientists have written a theory that, when put into practice, would allow us to manipulate wormholes. Of course further studies are required to reach any real application. +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: Yes
  • +
  • Cost: &euro;5,000,000
  • +
  • Effects: N/A
  • +
+
+
+ Depends on:
    +
  • Space-time folding
  • +
  • Interstellar University
  • +
+ Required by:
    +
  • Wormhole Collapsing
  • +
  • Wormholes
  • +
  • Subspace Data conduit
  • +
+
+
diff --git a/manual/beta5/en/tech_71.lwdoc b/manual/beta5/en/tech_71.lwdoc new file mode 100644 index 0000000..d6188cc --- /dev/null +++ b/manual/beta5/en/tech_71.lwdoc @@ -0,0 +1,30 @@ + + + beta5 + en + Biological Propulsion Systems +
+ Sir! Our scientists have found a way to create and grow artificial lifeforms capable of basic space flight. These can be used to replace our most simple ships in order to gain efficiency. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;4,000,000
  • +
  • Effects:
      +
    • Increases G.A. ships power
    • +
    • Increases fighters power
    • +
  • +
+
+
+ Depends on:
    +
  • Cruisers
  • +
  • Cybernetic Interfaces
  • +
  • Lifeform Energy Manipulation
  • +
+ Required by:
    +
  • Biological Hyperspace Engines
  • +
+
+
diff --git a/manual/beta5/en/tech_72.lwdoc b/manual/beta5/en/tech_72.lwdoc new file mode 100644 index 0000000..78e5c68 --- /dev/null +++ b/manual/beta5/en/tech_72.lwdoc @@ -0,0 +1,27 @@ + + + beta5 + en + Mass Anti-matter Production +
+ Using newly acquired technologies our scientists have achieved mass production of anti-matter thus opening a brand new field of applications. +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: Yes
  • +
  • Cost: &euro;2,500,000
  • +
  • Effects: N/A
  • +
+
+
+ Depends on:
    +
  • Experimental Anti-Matter Production
  • +
  • Force fields
  • +
+ Required by:
    +
  • Anti-matter Generators
  • +
  • Matter Anti-matter Engines
  • +
+
+
diff --git a/manual/beta5/en/tech_73.lwdoc b/manual/beta5/en/tech_73.lwdoc new file mode 100644 index 0000000..bcc8ebf --- /dev/null +++ b/manual/beta5/en/tech_73.lwdoc @@ -0,0 +1,29 @@ + + + beta5 + en + Singularity Housing +
+ Using the principles of Space-time folding, our engineers have improved our arcology design. Hyperspace and force-field generators must be integrated into our existing arcologies in order to allow our planets to house even more citizens. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;3,800,000
  • +
  • Effects:
      +
    • Increases planet maximum population
    • +
  • +
+
+
+ Depends on:
    +
  • Arcologies
  • +
  • Space-time folding
  • +
  • Force fields
  • +
+ Required by:
    +
  • Self-sustained Arcologies
  • +
+
+
diff --git a/manual/beta5/en/tech_74.lwdoc b/manual/beta5/en/tech_74.lwdoc new file mode 100644 index 0000000..49b6d64 --- /dev/null +++ b/manual/beta5/en/tech_74.lwdoc @@ -0,0 +1,33 @@ + + + beta5 + en + Intelligent Materials +
+ Bringing sentience to the materials they use, our scientists provide us with wonderful new ways of building stuff. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;4,000,000
  • +
  • Effects:
      +
    • Increases Industrial Factories productivity
    • +
    • Increases Military Factories productivity
    • +
    • Reduces battle losses
    • +
  • +
+
+
+ Depends on:
    +
  • Biological Computers
  • +
  • Self-healing Materials
  • +
+ Required by:
    +
  • Automated Factories
  • +
  • Adaptive Plating
  • +
  • Self-sustained Arcologies
  • +
  • Biological Turrets
  • +
+
+
diff --git a/manual/beta5/en/tech_75.lwdoc b/manual/beta5/en/tech_75.lwdoc new file mode 100644 index 0000000..292945f --- /dev/null +++ b/manual/beta5/en/tech_75.lwdoc @@ -0,0 +1,29 @@ + + + beta5 + en + Automated Factories +
+ Sir! Using sentient materials in our factories will allow us to gain production efficiency. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;7,000,000
  • +
  • Effects:
      +
    • Increases Industrial Factories productivity
    • +
    • Increases Military Factories productivity
    • +
  • +
+
+
+ Depends on:
    +
  • Biological Drones
  • +
  • Intelligent Materials
  • +
+ Required by:
    +
  • Wild Capitalism
  • +
+
+
diff --git a/manual/beta5/en/tech_76.lwdoc b/manual/beta5/en/tech_76.lwdoc new file mode 100644 index 0000000..8f26108 --- /dev/null +++ b/manual/beta5/en/tech_76.lwdoc @@ -0,0 +1,29 @@ + + + beta5 + en + Wild Capitalism +
+ This law allows the industrial sector to use any means necessary to increase profit. As a consequence, base planetary income and industrial factory benefits are increased but military factories, research and happiness suffer from it. +
+
+
    +
  • Category: Law
  • +
  • Optional: Yes
  • +
  • Cost: &euro;200,000
  • +
  • Effects:
      +
    • Increases planetary base income
    • +
    • Increases Industrial Factories productivity
    • +
    • Decreases Military Factories productivity
    • +
    • Decreases research output
    • +
    • Decreases happiness
    • +
  • +
+
+
+ Depends on:
    +
  • Economy Globalisation
  • +
  • Automated Factories
  • +
+
+
diff --git a/manual/beta5/en/tech_77.lwdoc b/manual/beta5/en/tech_77.lwdoc new file mode 100644 index 0000000..92bede0 --- /dev/null +++ b/manual/beta5/en/tech_77.lwdoc @@ -0,0 +1,25 @@ + + + beta5 + en + Adaptive Plating +
+ Sir! Our scientists have come up with a new way to reduce battle damage. They have integrated intelligent materials in our ships' plating thus offering them better defensive abilities. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;10,000,000
  • +
  • Effects:
      +
    • Reduces battle losses
    • +
  • +
+
+
+ Depends on:
    +
  • Lifeform Energy Manipulation
  • +
  • Intelligent Materials
  • +
+
+
diff --git a/manual/beta5/en/tech_78.lwdoc b/manual/beta5/en/tech_78.lwdoc new file mode 100644 index 0000000..89c27ce --- /dev/null +++ b/manual/beta5/en/tech_78.lwdoc @@ -0,0 +1,29 @@ + + + beta5 + en + Anti-matter Generators +
+ Sir! Our scientists have designed a new way to produce energy. Equipping our factories with these anti-matter generators will improve our productivity. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;4,000,000
  • +
  • Effects:
      +
    • Increases Industrial Factories productivity
    • +
    • Increases Military Factories Productivity
    • +
  • +
+
+
+ Depends on:
    +
  • Self-healing Materials
  • +
  • Mass Anti-matter Production
  • +
+ Required by:
    +
  • Matter Anti-matter Missiles
  • +
+
+
diff --git a/manual/beta5/en/tech_79.lwdoc b/manual/beta5/en/tech_79.lwdoc new file mode 100644 index 0000000..fd57c7d --- /dev/null +++ b/manual/beta5/en/tech_79.lwdoc @@ -0,0 +1,27 @@ + + + beta5 + en + Biological Subspace Control +
+ Sir! Our scientists have engineered some new astonishing lifeforms. These artificial creatures are capable of controlling subspace fields. This major discovery opens a new era for our future studies. +
+
+
    +
  • Category: Fundamental research
  • +
  • Optional: Yes
  • +
  • Cost: &euro;4,000,000
  • +
  • Effects: N/A
  • +
+
+
+ Depends on:
    +
  • Space-time folding
  • +
  • Lifeform Energy Manipulation
  • +
+ Required by:
    +
  • Self-sustained Arcologies
  • +
  • Biological Hyperspace Engines
  • +
+
+
diff --git a/manual/beta5/en/tech_8.lwdoc b/manual/beta5/en/tech_8.lwdoc new file mode 100644 index 0000000..be8e779 --- /dev/null +++ b/manual/beta5/en/tech_8.lwdoc @@ -0,0 +1,30 @@ + + + beta5 + en + Room Temperature Superconductors +
+ Sir! Our scientists have worked on a new type of electronic circuitry that will allow us to greatly improve the efficiency of our factories, using those room temperature superconductors. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: No
  • +
  • Cost: &euro;40,000
  • +
  • Effects:
      +
    • Increases Industrial Factories productivity
    • +
    • Increases Military Factories Productivity
    • +
  • +
+
+
+ Depends on:
    +
  • Advanced Materials
  • +
+ Required by:
    +
  • Nano-scale Computers
  • +
  • Advanced Communications
  • +
  • Robotics
  • +
+
+
diff --git a/manual/beta5/en/tech_80.lwdoc b/manual/beta5/en/tech_80.lwdoc new file mode 100644 index 0000000..4c4a611 --- /dev/null +++ b/manual/beta5/en/tech_80.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Wormhole Collapsing +
+ Sir, our scientists have devised a new defense. This technology allows our planets to build counter-measures that will allow them to prevent unwanted hyperspace windows from forming in orbit. This should prevent 10% of an enemy fleet from exiting hyperspace above the planet and delay them in hyperspace for 1 more hour. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;10,000,000
  • +
  • Effects:
      +
    • Prevents Hyperspace exit
    • +
  • +
+
+
+ Depends on:
    +
  • Wormhole Theory
  • +
+ Required by:
    +
  • Localised Wormhole Destabilisation
  • +
  • Wormhole Supernova
  • +
+
+
diff --git a/manual/beta5/en/tech_81.lwdoc b/manual/beta5/en/tech_81.lwdoc new file mode 100644 index 0000000..5cc43ad --- /dev/null +++ b/manual/beta5/en/tech_81.lwdoc @@ -0,0 +1,29 @@ + + + beta5 + en + Wormholes +
+ This technology applies space-time folding principles to spacebats, allowing your citizens to move freely between the planets in your empire. They will now be living in ecstacy as they will be able to visit their families, friends and dolphins. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;10,000,000
  • +
  • Effects:
      +
    • increases happiness
    • +
    • decreases Industrial Factories productivity
    • +
  • +
+
+
+ Depends on:
    +
  • Wormhole Theory
  • +
+ Required by:
    +
  • Wormhole Lockdown
  • +
  • Wormhole Supernova
  • +
+
+
diff --git a/manual/beta5/en/tech_82.lwdoc b/manual/beta5/en/tech_82.lwdoc new file mode 100644 index 0000000..8e8334d --- /dev/null +++ b/manual/beta5/en/tech_82.lwdoc @@ -0,0 +1,25 @@ + + + beta5 + en + Subspace Data conduit +
+ Applying wormhole theory to communications has allowed our scientists to create subspace conduits to transmit data, thus hiding it even better from enemy disruption techniques. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;12,000,000
  • +
  • Effects:
      +
    • Increases communications efficiency
    • +
  • +
+
+
+ Depends on:
    +
  • Multiphasic Transmission
  • +
  • Wormhole Theory
  • +
+
+
diff --git a/manual/beta5/en/tech_83.lwdoc b/manual/beta5/en/tech_83.lwdoc new file mode 100644 index 0000000..5b5d9b1 --- /dev/null +++ b/manual/beta5/en/tech_83.lwdoc @@ -0,0 +1,26 @@ + + + beta5 + en + Self-sustained Arcologies +
+ Sir! By combining their expertise in hyperspace theory and biological engineering, our scientists have found a way to create "grown" housing that will provide room and nourishment for our citizens. This will allow us to sustain more people on our planets. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;10,000,000
  • +
  • Effects:
      +
    • Increases planet maximum population
    • +
  • +
+
+
+ Depends on:
    +
  • Singularity Housing
  • +
  • Intelligent Materials
  • +
  • Biological Subspace Control
  • +
+
+
diff --git a/manual/beta5/en/tech_84.lwdoc b/manual/beta5/en/tech_84.lwdoc new file mode 100644 index 0000000..f4830b6 --- /dev/null +++ b/manual/beta5/en/tech_84.lwdoc @@ -0,0 +1,27 @@ + + + beta5 + en + Matter Anti-matter Engines +
+ Sir! Our scientists have found a new application for matter anti-matter reactions: propulsion systems! This new line of engines should increase our ships efficiency greatly. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;5,000,000
  • +
  • Effects:
      +
    • Increases capital ship speed
    • +
  • +
+
+
+ Depends on:
    +
  • Mass Anti-matter Production
  • +
+ Required by:
    +
  • Biological Hyperspace Engines
  • +
+
+
diff --git a/manual/beta5/en/tech_85.lwdoc b/manual/beta5/en/tech_85.lwdoc new file mode 100644 index 0000000..f996255 --- /dev/null +++ b/manual/beta5/en/tech_85.lwdoc @@ -0,0 +1,25 @@ + + + beta5 + en + Biological Turrets +
+ Building our turrets with intelligent materials should allow us to take advantage of their sentience to gain in accuracy and fire power. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;8,000,000
  • +
  • Effects:
      +
    • Increases turret power
    • +
  • +
+
+
+ Depends on:
    +
  • Automated Turrets
  • +
  • Intelligent Materials
  • +
+
+
diff --git a/manual/beta5/en/tech_86.lwdoc b/manual/beta5/en/tech_86.lwdoc new file mode 100644 index 0000000..111ac86 --- /dev/null +++ b/manual/beta5/en/tech_86.lwdoc @@ -0,0 +1,25 @@ + + + beta5 + en + Localised Wormhole Destabilisation +
+ Applying wormhole collapsing technologies in a localised way allows us to disrupt subspace data conduits and other long range communication, rending them less efficient. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;12,000,000
  • +
  • Effects:
      +
    • Increases jamming capabilities
    • +
  • +
+
+
+ Depends on:
    +
  • Phase Neutraliser
  • +
  • Wormhole Collapsing
  • +
+
+
diff --git a/manual/beta5/en/tech_87.lwdoc b/manual/beta5/en/tech_87.lwdoc new file mode 100644 index 0000000..65e9de4 --- /dev/null +++ b/manual/beta5/en/tech_87.lwdoc @@ -0,0 +1,25 @@ + + + beta5 + en + Wormhole Lockdown +
+ This law prevents your citizens from using the planetary gateways, cancelling the effects of the wormhole technology. +
+
+
    +
  • Category: Law
  • +
  • Optional: Yes
  • +
  • Cost: &euro;170,000
  • +
  • Effects:
      +
    • Increases Industrial Factories productivity
    • +
    • Decreases happiness
    • +
  • +
+
+
+ Depends on:
    +
  • Wormholes
  • +
+
+
diff --git a/manual/beta5/en/tech_88.lwdoc b/manual/beta5/en/tech_88.lwdoc new file mode 100644 index 0000000..e409305 --- /dev/null +++ b/manual/beta5/en/tech_88.lwdoc @@ -0,0 +1,25 @@ + + + beta5 + en + Wormhole Supernova +
+ Sir! With this technology we can initiate a chain reaction on a planetary wormhole that will cause it to flare up and destroy anything in the vicinity, including the planet and scaring the living hell out of the planet's neighbours. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;15,000,000
  • +
  • Effects:
      +
    • Provides the ability to destroy planets
    • +
  • +
+
+
+ Depends on:
    +
  • Wormhole Collapsing
  • +
  • Wormholes
  • +
+
+
diff --git a/manual/beta5/en/tech_89.lwdoc b/manual/beta5/en/tech_89.lwdoc new file mode 100644 index 0000000..82fbefb --- /dev/null +++ b/manual/beta5/en/tech_89.lwdoc @@ -0,0 +1,28 @@ + + + beta5 + en + Biological Hyperspace Engines +
+ Sir! Our scientists have found a way to grow and nurture organic hyperspace engines. We will now be able to grow living capital ships, improving our fleets' efficiency. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;10,000,000
  • +
  • Effects:
      +
    • Increases Cruiser power
    • +
    • Increases Battle Cruiser power
    • +
  • +
+
+
+ Depends on:
    +
  • Battle Cruisers
  • +
  • Biological Propulsion Systems
  • +
  • Biological Subspace Control
  • +
  • Matter Anti-matter Engines
  • +
+
+
diff --git a/manual/beta5/en/tech_9.lwdoc b/manual/beta5/en/tech_9.lwdoc new file mode 100644 index 0000000..ec1a36e --- /dev/null +++ b/manual/beta5/en/tech_9.lwdoc @@ -0,0 +1,31 @@ + + + beta5 + en + Nanotechnologies +
+ Sir! Our scientists have made major progress in miniaturisation. With this technology our researchers have gained the capability to work at the "nano" level. +
+
+
    +
  • Category: Civilian technology
  • +
  • Optional: No
  • +
  • Cost: &euro;40,000
  • +
  • Effects:
      +
    • Increases Industrial Factories productivity
    • +
    • Increases Militart Factories productivity
    • +
  • +
+
+
+ Depends on:
    +
  • Advanced Materials
  • +
+ Required by:
    +
  • Nano-scale Computers
  • +
  • Hardened Alloys
  • +
  • Nanofiber Armor
  • +
  • Lifeform Engineering
  • +
+
+
diff --git a/manual/beta5/en/tech_90.lwdoc b/manual/beta5/en/tech_90.lwdoc new file mode 100644 index 0000000..968a321 --- /dev/null +++ b/manual/beta5/en/tech_90.lwdoc @@ -0,0 +1,25 @@ + + + beta5 + en + Matter Anti-matter Missiles +
+ Using matter / anti-matter reactions in our warheads should greatly increase the damage caused by our ships. +
+
+
    +
  • Category: Military technology
  • +
  • Optional: Yes
  • +
  • Cost: &euro;10,000,000
  • +
  • Effects:
      +
    • Increases fleet power
    • +
  • +
+
+
+ Depends on:
    +
  • Battle Cruisers
  • +
  • Anti-matter Generators
  • +
+
+
diff --git a/manual/beta5/en/tech_exchange.lwdoc b/manual/beta5/en/tech_exchange.lwdoc new file mode 100644 index 0000000..e08a94a --- /dev/null +++ b/manual/beta5/en/tech_exchange.lwdoc @@ -0,0 +1,46 @@ + + + beta5 + en + Research Exchanges +
+ As the complete technological tree isn't available to one given player, you'll have to acquire some technologies from other players through diplomatic exchanges in order to obtain technologies you don't have access to. +
+ Technologies exchanges are managed through the Diplomacy section of the research page. It is accessible by clicking the "Diplomacy" link on top of the Research page. +
+ In order to use this feature you have to match the following criteria:
    +
  • Your account has to be more than 10 days old.
  • +
  • Technologies can only be exchanged between players in the same protection zone.
  • +
  • You haven't already sent an offer within the last 24h.
  • +
+
+
+ The top part of the "Diplomacy" section of the research page allows you to send scientific assistance offers to other players. You can provide this assistance by two different means:
    +
  • Research assistance: by selecting the corresponding radio button, you'll offer part of your own research points to the player you're making the offer to. The number of research points offered is the smallest number between half of the target's.
  • +
  • A particular technology: by selecting the other radio button and choosing a technology in the provided drop down list you'll offer this technology to the target player.
  • +
+ You also have to choose the player you're making the offer to. Use the textfield next to "player" to type in his name. You can also define an amount of money to ask for in compensation by typing in the amount of cash you want in return in the corresponding textfield. Leaving the field blank corresponds to a gift. +
+ To validate your offer click the "Send" button. You can also cancel your changes by clicking the "Cancel" button. +
+
+ Each time another player sends you a scientific assistance offer, an Internal Transmission by your Scientific Advisor is sent to your mailbox. In this message, a "More Details" link directs you to the "Diplomacy" section of the Research Page. Details of the offer are displayed in the "Received Assistance Offers" section of the page. +
+ For each offer is displayed:
    +
  • Sender
  • +
  • Date and time of the offer
  • +
  • Nature of the offer
  • +
  • Your current status toward that particular offer. In particular it indicates if you have already discovered the technology or already accepted an offer within the last 24h...
  • +
  • Buttons to either accept the offer or decline it (Accept" and Decline" button)
  • +
+ Be careful that you can only accept one offer within 24h. Scientific assistance offers also expire on their own after 24h. +
+
+ The bottom right part of the "Diplomacy" section of the Research page also includes an History of previous research assistance offers. For each offer this history includes:
    +
  • Sender
  • +
  • Nature of the offer
  • +
  • Time and date of the offer
  • +
  • The action that has been take towards the offer: accepted, declined or expired
  • +
+
+
diff --git a/manual/beta5/en/tech_list.lwdoc b/manual/beta5/en/tech_list.lwdoc new file mode 100644 index 0000000..3b7c017 --- /dev/null +++ b/manual/beta5/en/tech_list.lwdoc @@ -0,0 +1,284 @@ + + + beta5 + en + List of technologies +
+
    +
  • Advanced Communications
  • +
  • Biological Subspace Control
  • +
  • Cloning Techniques
  • +
  • Experimental Anti-Matter Production
  • +
  • Force fields
  • +
  • High-efficiency Hydroponics
  • +
  • Hyperspace Basics
  • +
  • Hyperspace Theory
  • +
  • Lifeform Energy Manipulation
  • +
  • Lifeform Engineering
  • +
  • Mass Anti-matter Production
  • +
  • Miniaturised Particle Colliders
  • +
  • Quantum Gravitation
  • +
  • Self-healing Materials
  • +
  • Sentient Lifeform Engineering
  • +
  • Space-time folding
  • +
  • Temporal Mechanics
  • +
  • Wormhole Theory
  • +
+
+
+
    +
  • Adaptive Plating
  • +
  • Automated Turrets
  • +
  • Battle Cruisers
  • +
  • Biological Hyperspace Engines
  • +
  • Biological Propulsion Systems
  • +
  • Biological Turrets
  • +
  • Cruisers
  • +
  • Cybernetic Interfaces
  • +
  • Entropy Generator
  • +
  • Exoskeleton
  • +
  • Fast Burst Transmission
  • +
  • Fighters
  • +
  • Hyperspace Beacon
  • +
  • Hyperspace Probing Beacon
  • +
  • Localised Wormhole Destabilisation
  • +
  • Matter Anti-matter Engines
  • +
  • Matter Anti-matter Missiles
  • +
  • Medical Bays
  • +
  • Multiphasic Transmission
  • +
  • Nanofiber Armor
  • +
  • Phase Neutraliser
  • +
  • Quantum Encryption
  • +
  • Resurrection tanks
  • +
  • Self-repairing Exoskeleton
  • +
  • Sensor Turrets
  • +
  • Subspace Data conduit
  • +
  • Wide Band Jamming
  • +
  • Wormhole Collapsing
  • +
  • Wormhole Supernova
  • +
+
+
+
    +
  • Adaptive Materials
  • +
  • Advanced Hospitals
  • +
  • Advanced Materials
  • +
  • Anti-matter Generators
  • +
  • Arcologies
  • +
  • Automated Factories
  • +
  • Bio-engineering
  • +
  • Biological Computers
  • +
  • Biological Drones
  • +
  • Cloning Vats
  • +
  • Corpse Reanimation
  • +
  • Economy Globalisation
  • +
  • Green Production
  • +
  • Hardened Alloys
  • +
  • Intelligent Materials
  • +
  • Interstellar University
  • +
  • Nano-scale Computers
  • +
  • Nanotechnologies
  • +
  • Nourishment Purification
  • +
  • Quantum Computers
  • +
  • Robotics
  • +
  • Room Temperature Superconductors
  • +
  • Safe Recreational Drugs
  • +
  • Self-sustained Arcologies
  • +
  • Singularity Housing
  • +
  • Surgical Robots
  • +
  • Wormholes
  • +
+
+
+
    +
  • Ban Biological Drones
  • +
  • Biosphere Protection Pact
  • +
  • Civilian Communication Act
  • +
  • Civilian Transportation Act
  • +
  • Forced Human Cloning
  • +
  • Global Defense Bill
  • +
  • Increased Research Grants
  • +
  • Legalize Space Weed
  • +
  • Martial law
  • +
  • Science Golden Age
  • +
  • Wild Capitalism
  • +
  • Wormhole Lockdown
  • +
+
+
+
    +
  • Adaptive Materials
  • +
  • Adaptive Plating
  • +
  • Advanced Communications
  • +
  • Advanced Hospitals
  • +
  • Advanced Materials
  • +
  • Anti-matter Generators
  • +
  • Arcologies
  • +
  • Automated Factories
  • +
  • Automated Turrets
  • +
  • Ban Biological Drones
  • +
  • Battle Cruisers
  • +
  • Bio-engineering
  • +
  • Biological Computers
  • +
  • Biological Drones
  • +
  • Biological Hyperspace Engines
  • +
  • Biological Propulsion Systems
  • +
  • Biological Subspace Control
  • +
  • Biological Turrets
  • +
  • Biosphere Protection Pact
  • +
  • Civilian Communication Act
  • +
  • Civilian Transportation Act
  • +
  • Cloning Techniques
  • +
  • Cloning Vats
  • +
  • Corpse Reanimation
  • +
  • Cruisers
  • +
  • Cybernetic Interfaces
  • +
  • Economy Globalisation
  • +
  • Entropy Generator
  • +
  • Exoskeleton
  • +
  • Experimental Anti-Matter Production
  • +
  • Fast Burst Transmission
  • +
  • Fighters
  • +
  • Force fields
  • +
  • Forced Human Cloning
  • +
  • Global Defense Bill
  • +
  • Green Production
  • +
  • Hardened Alloys
  • +
  • High-efficiency Hydroponics
  • +
  • Hyperspace Basics
  • +
  • Hyperspace Beacon
  • +
  • Hyperspace Probing Beacon
  • +
  • Hyperspace Theory
  • +
  • Increased Research Grants
  • +
  • Intelligent Materials
  • +
  • Interstellar University
  • +
  • Legalize Space Weed
  • +
  • Lifeform Energy Manipulation
  • +
  • Lifeform Engineering
  • +
  • Localised Wormhole Destabilisation
  • +
  • Martial law
  • +
  • Mass Anti-matter Production
  • +
  • Matter Anti-matter Engines
  • +
  • Matter Anti-matter Missiles
  • +
  • Medical Bays
  • +
  • Miniaturised Particle Colliders
  • +
  • Multiphasic Transmission
  • +
  • Nano-scale Computers
  • +
  • Nanofiber Armor
  • +
  • Nanotechnologies
  • +
  • Nourishment Purification
  • +
  • Phase Neutraliser
  • +
  • Quantum Computers
  • +
  • Quantum Encryption
  • +
  • Quantum Gravitation
  • +
  • Resurrection tanks
  • +
  • Robotics
  • +
  • Room Temperature Superconductors
  • +
  • Safe Recreational Drugs
  • +
  • Science Golden Age
  • +
  • Self-healing Materials
  • +
  • Self-repairing Exoskeleton
  • +
  • Self-sustained Arcologies
  • +
  • Sensor Turrets
  • +
  • Sentient Lifeform Engineering
  • +
  • Singularity Housing
  • +
  • Space-time folding
  • +
  • Subspace Data conduit
  • +
  • Surgical Robots
  • +
  • Temporal Mechanics
  • +
  • Wide Band Jamming
  • +
  • Wild Capitalism
  • +
  • Wormhole Collapsing
  • +
  • Wormhole Lockdown
  • +
  • Wormhole Super Nova
  • +
  • Wormhole Theory
  • +
  • Wormholes
  • +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ diff --git a/manual/beta5/en/technology.lwdoc b/manual/beta5/en/technology.lwdoc new file mode 100644 index 0000000..3ca1692 --- /dev/null +++ b/manual/beta5/en/technology.lwdoc @@ -0,0 +1,218 @@ + + + beta5 + en + Technology +
+ Technology allows you to improve your empire as a whole. This manual section convers all technology related topics such as:
    +
  • Research topics: this paragraph describes all technologies and the way they are organised in the technology graph
  • +
  • Research process: this part of the page introduces you to the way research progresses, from unknown technologies to technologies in use in your empire
  • +
  • Laws: this section explains how some special technologies allow you to influence the way things are handled in your empire through the legal system
  • +
  • Research budget: this paragraph deals with the influence you might have on what is being researched by setting a particular budget
  • +
  • Diplomacy: the last part of the manual page introduce the topic of technology exchanges that are covered in more details here, as part of the diplomacy manual page
  • +
+
+
+
+ In the LegacyWorlds universe a set of technologies are available for the players to research. Each of those technologies may or may not have an influence on some game parameters. +
+ Technologies are organised according to two different systems:
    +
  • Research Categories: technologies are organised according to the class of parameters they influence
  • +
  • Dependencies Graph: technologies usually depend on one another which means you need to possess technology A to be able to research technology B. Those dependencies correspond to links between technologies and the whole set of technologies along with those links compose the technology graph
  • +
+ Both classification systems are presented in more details in the following paragraphs along with a list of all available technologies. +
+
+
+ The various research topics available in the game are organised in three categories:
    +
  • Fundamental research
  • +
  • Military resarch
  • +
  • Civilian research
  • +
+ The next paragraphs will cover each category more precisely. +
+
+ Fundamental research has as its primary objective the advancement of knowledge. It is exploratory by nature. It is conducted without any practical end in mind, although it may have unexpected results pointing to practical applications. Through theory generation, fundamental research provides the foundation for further, sometimes applied research. +
+ Technologies in the fundamental category are basic technologies or theories that are important for your scientists to make further progress in their studies. They don't necessarily provide any improvement in your empire but they may be required by other studies. +
+ In general fundamental technologies have an influence on the game such as:
    +
  • Increasing research output
  • +
  • Providing access to new fields of research without any real influence on game parameters
  • +
  • Wide range of influences on various military or civilian parameters: in those cases the technologies were flagged as fundamental because of their exploratory nature
  • +
+ A list of all fundamental technologies is available here. +
+
+ Military research consists in applied research. Applied research is done to solve specific, practical questions; its primary aim is not to gain knowledge for its own sake. Applied research in the military field provides technologies with direct military applications. +
+ Those military technologies usually consist in:
    +
  • new kinds of ships
  • +
  • improvements on those ships
  • +
  • technologies providing a better defense of your empire
  • +
  • technologies providing means to better attack an enemy planet or alliance
  • +
  • means to increase factories production
  • +
+ A list of all military technologies is available here. +
+
+ Civilian research consists in applied research. Applied research is done to solve specific, practical questions; its primary aim is not to gain knowledge for its own sake. Applied research in the civilian field provide technologies with direct civilian applications. +
+ Civilian technologies usually consists in:
    +
  • new means to host more citizen on one planet
  • +
  • means to increase the happiness of the unhabitants of your empire
  • +
  • improvement of the population growth
  • +
  • technologies increasing factories efficiency
  • +
+ A list of all civilian technologies is available here. +
+
+
+
+ The research topics are also organised in a dependency graph. It means you may need to have researched some other topics before being able to research another new one. A list of all research topics is provided here. +
+ Clicking on the same of one technology in the list directs you to the individual manual page for the technology. This page is described in more details in the next paragraph. +
+
+
+ The individual page of a technology is split into several sections:
    +
  • Top part
  • +
  • Description
  • +
  • Details
  • +
  • Dependencies
  • +
+ The next paragraphs will describe each section. +
+
+ The top part of the individual page of a technology provides:
    +
  • The name of the research topic as title of the page
  • +
  • Links to the other sections of the page in a Contents list
  • +
+
+
+ This section presents the description of the technology as it appears in-game. This description correspond to an approximation of the technology's effects. +
+
+ This section of the page provides details about the technology, including:
    +
  • Category: this indicates in what category this topic belongs (civilian, military or fundamental) and if it's a law or a standard technology
  • +
  • Optional: research topics are mandatory (this field's value is No) if you are sure to have them in the subsection of the technologies tree you have access to. Other topics (you may not have them in your part of the graph) are optional (field set to Yes)
  • +
  • Cost: price you have to pay to implement the technology
  • +
  • Effects: influence of the technology of enacting the law on game parameters
  • +
+
+
+ This part of the page allows you to figure out where the technology is placed in the technology dependencies graph by providing:
    +
  • Depends on: technologies you have to implement prior to being able to research this one
  • +
  • Required by: technologies that you can't research if you haven't first implemented this one
  • +
+
+
+
+
+ The technology list allows you to browse available technologies, following the dependency graph. You can view the list of fundamental research topics, military technologies or civilian technologies. If you care for a nice headache, you can take a look at the complete list as well. The lists are all sorted in alphabetical order. +
+
+
+
+ During the research process research points are used in the various research categories to advance in the research process of various technologies. Along the way technologies go through various steps:
    +
  • Unknown Technologies: technologies that are available in game but that your researchers haven't studied enough yet
  • +
  • Forseen Breakthroughs: research topics your scientists are working on and are close to completion
  • +
  • New technologies: technologies your scientists have discovered but that aren't in use in your empire yet
  • +
  • Implemented technologies: technologies currently in use in your empire
  • +
+ The next paragraphs will describe each stage and the associated features more closely. +
+
+ Unknow technologies are technologies that are available in game. They may be unkown to you for several reasons:
    +
  • Your researchers are working on them but they aren't advanced enough in the research process for the technologies to be listed as forseen breakthroughs
  • +
  • You don't have the required dependencies implemented yet and your researchers can't work on those subjects yet because they aren't advanced enough technologically
  • +
  • Those technologies aren't part of the technology graph section you have access to. You'll have to acquire them through diplomatic exchanges
  • +
+ Unknown technologies aren't displayed in the Topics section of the Rsearch page contrary to technologies at other research stages. +
+
+ Forseen breakthroughs are research topics your scientists are currently working on. Technologies appear in this list when the required dependencies have been fulfilled and that at least 75% of the required research points have been consumed. +
+ Both technologies and laws appear in this list but once completed each category (technologies or laws) is displayed in the relevant section of the page: Topics for standard technologies and Laws for laws. +
+ Technologies you acquired through diplomatic exchanges get also added to the list with 75% of research completion. +
+ Forseen breakthroughs are displayed in a single list for all categories in the Topics section of the Research page. For each technology is displayed:
    +
  • Technology: only the name of the technology is basically displayed. Clicking on its name allows to display the description or hide it by clicking it again
  • +
  • Type: category in which the technology is classified (Military, Civilian or Fundamental)
  • +
  • Cost: price to pay to implement the technology
  • +
+
+
+ New technologies are technologies that your scientists have completely discovered. It means that your empire possesses the knowledge associated with the technology. But they can't be used in your empire and there effects aren't applied until they are implemented. +
+ New technologies discovered by your scientists are displayed in the New technologies list of the Topics section of the Technology page. +
+ New technologies are listed on the Topics section of the Technology page below the New technologies title. The list includes:
    +
  • Technology: only the name of the technology is basically displayed. Clicking on its name allows to display the description or hide it by clicking it again
  • +
  • Type: category in which the technology is classified (Military, Civilian or Fundamental)
  • +
  • Cost: price to pay to implement the technology
  • +
  • Implement technology: clicking this link pays the required fee to implement the technology and start using it in your empire
  • +
+
+
+ Implementing technologies implies paying a fixed fee to upgrade planetary improvements and fleets so that they use the new technology. +
+ Once you have chosen to implement a technology, it becomes available for use in your empire. Its effects are applied on all your planets and fleets and so on. +
+ Implemented technologies are listed at the bottom of the Topics section of the Technology page. This list is split in three columns , one for each research category(Military, Civilian or Fundamental). As for the other lists, only the name of the technology is displayed and clicking its name allows to show / hide the description. +
+
+
+
+ Some research topics don't provide you with standard technologies that you can implement so that they can be used in your empire. They provides you with access to laws. +
+ The legal system in LegacyWorlds is quite simple. Once the research points required to research a law have been consumed the law doesn't get listed among the new technologies but among the available laws in the Laws section of the Research page. Laws can be enacted and revoked and have an empire wide influence. Using the legal system allows you to give general directions to your empire's development, depending on the effects of the laws you have enacted or revoked. +
+ Enacting a law has some beneficial effects but also some drawbacks that have to be carefully weighted before enacting it. Enacting a law represents also a finantial cost and it can't be revoked for a 5 days period of time once enacted. +
+
+ Check out the list of available laws, sorted alphabetically for your browsing pleasure. +
+
+ Once it has been discovered, a law can have two different status and can be moved from one category to another at will but with a 5 days delay between each action and a cost. Those two status are:
    +
  • Enacted laws: laws you have chose to enact are listed in this category. Their effects are currently being applied in your empire. You can choose to revoke it by clicking the relevant link
  • +
  • Available laws: newly dicovered laws and laws that you have previously revoked are listed here. They aren't used in your empire but you can choose to enact them by clicking the relevant link in order to benefit from their effect. Of course you have to have enough money to pay for the enacting fee to enact the law
  • +
+
+
+
+
+ Each day tick you get granted a certain amount of research points depending on your empire's total population. You have the possibility to balance the amount of research points used in each research category: Military, Civilian or Fundamental. +
+ But keep in mind that:
    +
  • you might need technologies in one category in order to achieve some breakthroughs in the others
  • +
  • Research points are used primarily in the areas you have set them to be used for. If it is impossible to use all of them in those categories given current implemented technologies, those research points will be assigned in other categories until it is impossible to discover new technologies
  • +
  • If no new technologies can be researched, remaining research points are lost
  • +
+
+
+ For each research category you are provided with 5 buttons to balance your research budget, the total remaning automatically at 100%:
    +
  • double left arrow: decrease the percentage of research points for this category by 10. The two others are increased so that the total remains at 100%
  • +
  • left arrow: decrease the percentage of research points for this category by 1. The two others are increased so that the total remains at 100%
  • +
  • right arrow: increase the percentage of research points for this category by 1. The two others are decreased so that the total remains at 100%
  • +
  • double right arrow: increase the percentage of research points for this category by 10. The two others are decreased so that the total remains at 100%
  • +
  • lock: clicking once on a lock has for effect that this category isn't influenced by the changes made on the other categories. The research points percentage for it remains locked at its current value. Clicking once again on it removes the lock and the category is once again taken into account in the calculations to keep the total percentage of research points at 100%
  • +
+
+
+
+
+ At the beginning of the game, each player gets assigned a subsection of the technology graph. This subset consists usually of about half the total graph. It is the list of technologies that is basically accessible to the player, that is to say that his scientists can research and discover on their own. +
+ The subset of the technology graph assigned to each player is generated randomly and always includes complete branches of the tree. That is to say no technology is included for which the player can't research the required dependencies. +
+ Technologies that aren't in your predefined subset of the technology graph have to be obtained from other players. +
+
+ In order to acquire technologies that your empire can't research you have to acquire them by using the Diplomacy tool of the Research page. See this manual section for more details. +
+
+ +
+ diff --git a/manual/beta5/en/ticks.lwdoc b/manual/beta5/en/ticks.lwdoc new file mode 100644 index 0000000..6176547 --- /dev/null +++ b/manual/beta5/en/ticks.lwdoc @@ -0,0 +1,79 @@ + + + beta5 + en + Ticks +
+ Legacy Worlds is a tick based game. Ticks are specific instants in time when automated events are triggered. They correspond to moments when game data are updated. + As such different kinds of ticks happen at different time intervals and play a different role in the game dynamics. +
+
+ The different ticks involved are presented in the table below: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TickTimeEvents
BattleEvery 4 hours
    +
  • Battle computations
  • +
  • Battle Unavaibility of fleets ends
  • +
HourEvery hour
    +
  • Fleets arrival Unavaibility ends
  • +
  • Hyperspace stand-by delay of fleets get reduced
  • +
MovementEver Minute
    +
  • Fleet position calculation
  • +
Cashtwice a day
    +
  • + Income calculation and distribution +
  • +
DayOnce a day
    +
  • Population growth
  • +
  • Planet updates
  • +
  • Ranking updates
  • +
+
+
+
+ The Ticks page provides information about the major ticks involved in game:
    +
  • Battle Tick
  • +
  • Cash Tick
  • +
  • Day Tick
  • +
  • Hour Tick
  • +
+ No display is provides for the Hour Tick, given the occurence of the tick is almost instantaneous. +
+
+ For each tick is provided:
    +
  • Description of the tick
  • +
  • Date and time of the previous tick of the same category
  • +
  • Date and time of the next tick of the same category along with a timer to the next tick
  • +
+
+
+
diff --git a/manual/beta5/en/topics.lwdoc b/manual/beta5/en/topics.lwdoc new file mode 100644 index 0000000..9384c36 --- /dev/null +++ b/manual/beta5/en/topics.lwdoc @@ -0,0 +1,57 @@ + + + beta5 + en + Manual Topics +
+ This section of the manual presents some general information about the game including account creation and games management along with a presentation of the main interface and overview page. +
+
+ Your empire is composed of a set of planets. Starting at first with one it will grow through conquest of uninhabited planets or enemy ones. The overall technological advance of your empire can be improved through research. The fleets you build will provide it with defenses against hostiles and help with its expansion. Of course all of this costs money. But don't be too greedy because corruption might settle on your planets. +
+ What makes great empires is their citizens. Population growth is as such essential. One major factor in LegacyWorlds is happiness. It reflects the overall satisfaction of your citizens. Be careful to keep them happy or they might revolt. +
+
+ Fleets are sets of ships you can use either to defend yourself or attack other planets. Each fleet can be composed of ships of several different categories. Those fleets can be moved from planet to planet according to a particular set of rules. When faced with enemy fleets they of course engage in battle. +
+ In order to have fleets you first of all have to build them. You also have to possess the required technologies. +
+
+ Technology allows you to improve your empire as a whole. A certain amount of research points is granted to you each day tick according to your empire's population. Using those points in various research fields allows for new scientific breakthroughs. Implementing those newly discovered technologies allows your whole empire to benefit from them. Scientific knowledge also provides you access to laws that you can choose to enact or not. Those laws and technologies apply modifiers to various parameters in the game, either positively or negatively. +
+ Not all research topics may be accessible to your empire and you might have to use diplomatic exchanges to acquire some technologies. +
+
+ Legacy Worlds' universe is a galaxy far far away where the suvivors from the Earth's annihilation are trying to rebuild their civilisation. Maps of the area are available. +
+ Legacy Worlds being a tick based game, information about the various ticks is also part of the universe. The various rankings of the players are also present along with the manual you're reading now. +
+
+ Diplomacy covers a lot of concepts concerning in game interactions of players outside of the direct communication means. The major element is the alliance system which allows players to build up teams. The diplomatic section of the game also allows you to manage list of enemy and trusted players. Diplomacy is also a way to acquire technologies you can't research on your own. +
+
+ There are two major means of communication in LegacyWorlds: a messaging system and forums. Those allow you to keep in touch with other players both inside your own alliance and outside of it. +
+
+ Various elements in Legacy Worlds can be customised. Some preferences are specific to one account and others are in-game features that are defined on a per game basis. +
+
+ As in every game there are rules. Those rules are to be agreed upon and followed by both sides: players and people running the game. Those are the topic of this section. +
+ Some general elements that didn't fit in any other section of the manual are also presented here. +
+
+ A glossary, that is a list of terms along with their definition, is provided so that you can grasp the meaning of a concept used in the LegacyWorlds' universe at a glace. +
+ +
+
+
+
+
+
+
+
+
+
+ diff --git a/manual/beta5/en/tutorial.lwdoc b/manual/beta5/en/tutorial.lwdoc new file mode 100644 index 0000000..882cbae --- /dev/null +++ b/manual/beta5/en/tutorial.lwdoc @@ -0,0 +1,81 @@ + + + beta5 + en + Tutorial +
+ This tutorial presents a few tips to help you getting started with the game. It's not the ultimate guide to Legacy Worlds and its scope is limited to your first days of gameplay. Becoming the best player in the game will then be up to you. +
+
+ Legacy Worlds is a ticks based text based galactic war game. As leader of a little group of suvivors from the earth annihilation you are expected to build up an empire among the stars, in a remote galaxy, for your followers. This empire, including only one planet at the begining of the game, can grow through conquest and commerce. Taking over new planets by force is achieved thanks to fleets that you can move from planet to planet. Research provides you new technologies to improve your fleets and your empire. +
+
+
+ When joining a Legacy Worlds game you get provided with a single planet. Before expanding your newborn empire you first of all can improve that newly acquired property. +
+
+ In order to be able to build fleets, implement technologies or conduct transactions with other players you first of all need money. Your planet provides you with a base income linked with its population. Building industrial factories also generates income. Income is generated twice a day during the Money tick. +
+
+ In order to protect your planet against potential invaders you have to set up defenses. Static defenses are provided by turrets. Fleets stationned on a planet also defend it. You build both turrets and ships with military factories. Items in the military factories' build queue get produced during hour ticks. In order to increase the construction speed of your turrets and fleets you can increase the number of military factories on your planet. +
+
+ Be careful about the amount of factories you build on your planets. Factories have an influence on happiness. Happiness is a major factor in the game and represents the satisfaction of the citizens of your planet. If they are too unhappy they might revolt and destroy planetary improvements. Happiness calculation is dynamic and occurs each time you do anything influencing happiness. +
+
+ A high happiness is also important because of its influence on population growth. Population size increase happens once a day during day ticks. The bigger the population the more ressources of all kinds you have. +
+
+ Technological advances are provided through research. Each day at the day tick your amount of research points is calculated and automatically used to research new technologies in the three available categories: fundamental, civilian and military. The only influence you have on research budget is balancing the percentage of research points used in each category. Once a new technology has been discovered you have to implement it, which has a financial cost, before using it in your empire. Technologies have a wide variety of effects on the game parameters and also provide you with new kinds of ships. +
+ Research also provides you with laws that you can enact to temporarily influence some game parameters. +
+ Keep a close eye on the research page in order to be able to implement new technologies and progress in the technology graph. +
+ By default you only have access to about 2/5 of all research topics in the technology graph. The others have to be acquired from other players through diplomacy. In order to get a new technology from another player you have to have the technologies it depends on. +
+
+ The general overview page provides you with all major information about your empire. Some overview pages are also provided for various major categories of informations like Empire, Diplomacy, Universe or Communications. All important events also generate specific messages in the Internal Transmissions folder of the messaging system. +
+
+
+
+
+ There are several means to acquire new planets:
    +
  • Buying it: some planets might be offered for bidding in the marketplace
  • +
  • Getting it as a donation: it is possible to give a planet to another player
  • +
  • Taking it over: it's the most common way which will we described in more details below
  • +
+
+
+ In order to take over a planet you first of all have to send fleets to it. Fleets movements are regulated by a set of rules. +
+ System ships (GA ships and fighters) can only travel in the same stellar system. If you want to send your fleets to another system your fleet has to include capital ships (cruisers and battle cruisers) in a sufficient amount so that they can carry all your system ships in their hauls. +
+ The time required for your fleet to travel from one planet to another depends on the distance between the two planets of course but also on the type of ships you are sending and technological advances you may have. +
+
+ Once you have reached the target planet you have to attack it in order to destroy its defenses. To do so you have to switch your fleet status to attack and have sufficient fire power compared to the defending forces. Battle calculations occur every 4h during Battle ticks. +
+ Once the defending fleets have been destroyed you will gain control of the planet at the following hour tick if your fleet includes enough GA ships to control the planet's population. Depending on your technological advancement the number of GA ships required compared to the planet's population changes. +
+
+
+ Fighting alone in the galaxy isn't easy and it's often best to team up with other players. In game teaming up means creating or joining an alliance. Players in the same alliance get provided with information about their fellow comrades and their empires that are useful to help each others in case of attack. Alliances can also set up alliance specific forums to ease communication among members. +
+ The leader of the alliance has advanced control over the alliance configuration. He can define various ranks with various privileges among the members to allow specific access to alliance listings and forums depending on members' rank. +
+
+
+ The in-game communication system includes first of all a messaging system. This messaging system is very similar to a simple mail client. It allows you to send messages to players, planets or alliances' diplomatic staff. You can also create folders to manage your messages. +
+ The second major communication mean is the forums. Those forums include general forums, game specific forums and alliance forums. The forums are the place to look for help, report bugs, request features, look for an alliance, keep informed about the game and so on... Those forums have a behaviour similar to that of a simple message board. +
+ Along inside communications an IRC network dedicated to the game has been set up. The server to connect to is irc.legacyworlds.com on the default port. Look at you IRC client manual to know more about IRC set-up and access. +
+
+ In order to learn more about more advanced features and progress in your use of those presented here you can read the various manual topics. +
+ A help forum is also provided for you to ask questions. Before posting any new question though make sure it has not already been asked... +
+
diff --git a/manual/beta5/en/universe.lwdoc b/manual/beta5/en/universe.lwdoc new file mode 100644 index 0000000..141d755 --- /dev/null +++ b/manual/beta5/en/universe.lwdoc @@ -0,0 +1,38 @@ + + + beta5 + en + Universe +
+ Legacy Worlds' universe includes everything related to places, time and people involved at the scene where the game takes place. As such universe menu item directs you to the set of pages describing all those topics, including:
    +
  • Universe Overview page": this page offers you at a glance all required information about the game universe
  • +
  • Maps: the game takes place in a galaxy far far away where the suvivors from the Earth's annihilation are trying to rebuild their civilisation. Maps of the area are available as part of the universe data
  • +
  • Ticks: Legacy Worlds being a tick based game, information about the various ticks is also part of the universe. That would be the time dimension of the universe
  • +
  • Rankings: there are also people in this universe, the players. The various rankings of the players are also present in the universe section of the game
  • +
  • Manual: The rules regulating the way the game works are also part of the universe. That's why the manual you're reading now is placed here
  • +
+
+
+ This page provides you with a status of Legacy Worlds' universe at a glance along with shortcuts to the most important items you might be interested in viewing. It is split into several sections that are described more precisely in the corresponding manual section. +
+
+ Legacy Worlds' galaxy is split in a grid. Each square in the grid represent a stellar system or a portion of a nebula. Each square contains 6 elements, either 6 planets or 6 nebula sectors. +
+ In order to help you navigate your ships in that galaxy three kinds of maps are available. +
+ Common navigation features are also available for all kinds of maps to help you browse them. +
+ All those features are explained in details in the maps manual section. +
+
+ Legacy Worlds is a tick based game. As such different kinds of ticks happen at different time intervals and play a different role in the game dynamics. More precisions on ticks are given in the Ticks manual section. +
+
+ Rankings are a way to keep track of the various players' progress in the game and to compare their strength. There are different kinds of rankings which are presented in the various sections of the Rankings manual page. +
+ +
+
+
+
+ diff --git a/manual/beta5/en/universe_page.lwdoc b/manual/beta5/en/universe_page.lwdoc new file mode 100644 index 0000000..f6e9041 --- /dev/null +++ b/manual/beta5/en/universe_page.lwdoc @@ -0,0 +1,39 @@ + + + beta5 + en + Universe Overview Page +
+ This page provides you with a status of Legacy Worlds' universe at a glance along with shortcuts to the most important items you might be interested in viewing. It is split into several sections. +
+
+ This section provides you with general information about the galaxy in which the game takes place including:
    +
  • Planets: total number of planets in the galaxy
  • +
  • Neutral planets: number of neutral planets in the galaxy
  • +
  • Systems occupied by nebulas: number of systems which aren't occupied by stellar systems but by nebula squares
  • +
  • Average turrets/planet: average number of turrets on the planets in the galaxy
  • +
  • Average factories/planet: average number of factories on the planets in the galaxy
  • +
+
+
+ This part of the page informs you about the time at wich the next ticks will occure:
    +
  • Next Battle Tick
  • +
  • Next Hour Tick
  • +
  • Next Cash Tick
  • +
  • Next Day Tick
  • +
+ It also provides a More Details link to the ticks page. +
+
+ This section sums up your current status in the game rankings, presenting:
    +
  • your current general ranking and the associated number of points
  • +
  • your current financial ranking and the associated number of points
  • +
  • your current civilisation ranking and the associated number of points
  • +
  • your current military ranking and the associated number of points
  • +
  • you current inflicted damage ranking and the associated number of points
  • +
  • your overall round ranking and the associated number of points
  • +
  • the current number of players in the rankings
  • +
+ It also provides a More Details link to the Rankings page. +
+
diff --git a/manual/beta5/en/vacation_mode.lwdoc b/manual/beta5/en/vacation_mode.lwdoc new file mode 100644 index 0000000..d33b69b --- /dev/null +++ b/manual/beta5/en/vacation_mode.lwdoc @@ -0,0 +1,133 @@ + + + beta5 + en + Vacation mode +
+ While playing a LegacyWorlds' game you might need to take a break from the game for some time, because you're on holidays, or in a place where no internet access (really that still actually exists) or simply because you're tired of playing. In such cases you might not want to lose all the hard work you've put in your empire because someone attacks it while you're not around for instance. The vacation mode is just for you. +
+ Basically vacation mode allows you to keep your assets protected while you're away but everything is slowed down in your empire and you can't perform any game actions except read your messages and the forums. The next sections of the manual will explain all vacation mode related topics. +
+
+
+ In order to enter vacation mode you first of all have to access the set of forms that allow you to activate vacation mode. Those two topics will be covered in the next paragraphs. +
+
+ The page section where you can activate vacation mode is located on the home page of the game when you are logged in. In order to access the page you have two options:
    +
  • if you're not logged in, follow the logging in procedure. The middle section of the page displayed in the body of the home page should be about vacation mode. Below an introduction about the topic, Is displayed an Enter vacation mode button. Clicking the button opens the forms that lead to the path to vacation
  • +
  • if you are logged in and playing a LegacyWorlds game, clicking on the My Account menu item gets you to the logged in home page of the game. As in the previous case below the introduction about the topic, is displayed the Enter vacation mode button. Clicking the button opens the forms that lead to the path to vacation
  • +
+
+
+ Clicking the Enter vacation mode button opens a new page displaying some information about the vacation mode. At the bottom of the page are displayed two buttons:
    +
  • Yes, start the countdown: clicking the button starts the countdown to the beginning of the real vacation period. Until the countdown is finished that the game is still accessible normally
  • +
  • No, don't activate vacation mode: clicking the button cancels the action and gets you back to the home page
  • +
+ Once you have clicked the Yes, start the countdown button the logged in home page presents a Cancel vacation mode button instead of the Enter vacation mode one. The button will stay in place for the next 24 to 30h (until vacation mode actually starts). +
+
+
+
+ In order to get out of vacation mode you first of all have to access the set of forms that allow you to de-activate vacation mode. Those two topics will be covered in the next paragraphs. +
+
+ The page section where you can de-activate vacation mode is located on the home page of the game when you are logged in. In order to access the page you have two options:
    +
  • if you're not logged in, follow the logging in procedure. The middle section of the page displayed in the body of the home page should be about vacation mode. Below an introduction about the topic, Is displayed a Leave vacation mode button. Clicking the button opens the forms that lead the to path out of vacation
  • +
  • if you are logged in and looking at the data of a LegacyWorlds game, clicking on the My Account menu item gets you to the logged in home page of the game. As in the previous case below the introduction about the topic, is displayed the Leave vacation mode button. Clicking the button opens the forms that lead to the path out of vacation
  • +
+
+
+ Clicking the Leave vacation mode button opens a new page displaying some information about the vacation mode. At the bottom of the page are displayed two buttons:
    +
  • Yes, exit vacation mode: clicking the button gets you out of the vacation period
  • +
  • No, stay in vacation mode: clicking the button cancels the action and gets you back to the home page. You remain in vacation mode
  • +
+ Once you have clicked the "Yes, exit vacation mode button everything gets back to normal. +
+
+
+
+ Vacation mode is controlled through vacation credits which allow to keep track of the amount of vacation time you're entitled to. Being on vacation has various effects on both your game data and other players' actions. All those topics will be covered in the next manual paragraphs. +
+
+ Vacation credits allow to keep track of the amount of vacation time you're entitled to. Vacation credits gain and usage obey a set of rules that are listed below:
    +
  • each account gains one vacation credit each day of gameplay (no vacation credits are gained while you're on vacation or while your account is closed
  • +
  • one vacation credit corresponds to 6h of vacation time
  • +
  • the maximum number of vacation credits you might have is 240 (that is up to two months of vacation period)
  • +
  • while you're in vacation mode, one vacation credit is used every 6h until you get out of vacation mode or get down to 0 vacation credit. In the last case your account gets automatically reverted back to "normal" mode
  • +
+
+
+
+ While you're in vacation mode you can still log in into your account and access any game you're playing. But some game features aren't accessible and other are altered. The next parapgraphs will list what you can do and what you can't do while on vacation mode along with the other effects it might have on your empire. +
+
+ While you're on vacation you can:
    +
  • Log in into your account
  • +
  • Access any game you're playing
  • +
  • See all in game pages
  • +
  • Use the messaging system
  • +
  • Browse and post in the forums as usual
  • +
  • Change your preferences
  • +
+
+
+ All game related actions, that is to say actions which have a real effect on the game are disabled. A non exhaustive list of actions you can't accomplish while you're on vacation could be:
    +
  • Build or destroy planetary improvements (factories or turrets)
  • +
  • Build ships
  • +
  • Perform any fleets related actions
  • +
  • Upgrade beacons
  • +
  • Implement technologies
  • +
  • Give or receive cash donations
  • +
  • Join or leave an alliance
  • +
  • Manage your alliance (create or modify ranks, add forums or even create a new alliance)
  • +
  • Modify your Trusted Allies and Enemies lists
  • +
  • Sell, buy, give, accept as gift an item in the marketplace
  • +
+
+
+
+ While you're in vacation mode several game parameters get altered and your assets get protected to a certain extent. The next parapgraphs will list all alterations in your in game data that aren't directly visible in the game interface. +
+
+ While you're in vacation mode, your citizen feel like they're on holidays, with their mighty leader away. As a consequence they don't work as hard a usual and this has quite an effect on production:
    +
  • Population growth is divided by 4
  • +
  • Daily profit is divided by 4
  • +
  • Military production is divided by 4
  • +
  • Research output is divided by 4
  • +
+
+
+ While you're in vacation mode your planets are also protected to a certain extent. The specific rules around this protection feature are explained below. +
+ If a planet you own was under attack when you enter vacation mode the battle gets to its end as usual. +
+ If you lose control over the planet it won't be given back to you because you've entered vacation mode at the time. +
+ If you keep control of the planet when the battle is over, the planet then enters full protection. +
+ All planets you own where no battle is occuring are fully protected. This means that even if a battle occurs over the planet you can't lose control over the planet. +
+
+ The protection status of your fleets depends on their location when you enter vacation mode. +
+ Moving fleets or fleets in hyperspace stand-by fly or drop out of hyperspace normally as if you weren't in vacation mode. +
+ For fleets located on your own planets the system is the same as for the planets. This means that fleets located on planets where battles are in progress when you enter vacation mode can get killed during battle ticks as usual. +
+ The remaining fleets (if any) get in full protection mode at the end of the battle. +
+ Fleets on your own planets where no battle is in progress are protected and are considered neutral during any future battle occuring at that location while you're on vacation. This means that they don't get killed during battle ticks. +
+
+
+
+ The influence an account in vacation mode has on the rest of the players is very limited. The only elements to keep in mind are:
    +
  • You can't donate money to players in vacation mode
  • +
  • Players in vacation mode won't be able to accept your technologies, planets or fleets exchanges
  • +
  • Your fleets can fly to planets whose owner is in vacation mode
  • +
  • The fleets belonging to a player in vacation mode can get killed if they aren't located on his own planets
  • +
  • You can't kill the fleets of a player in vacation mode if they are on his own planets nor take his planets but battles can occurr in orbit. The only effect vacation mode has is that the owner's fleets are considered neutral and no owner change can occur
  • +
+
+
+
diff --git a/misc/Legacy Worlds.wdgt/Default.png b/misc/Legacy Worlds.wdgt/Default.png new file mode 100755 index 0000000000000000000000000000000000000000..1ec4ed477be5f5b276643e7170446f81f68d50ff GIT binary patch literal 17069 zcmb`vcT`i~x4;P^MQI`eB2`h4UZocspwdL7NtX@@y|)M`NS9s$1gR>WKtk^wLXi?k z2tgq9&`G2Z-`~vp&Ajzy)|&U$`y(rR-L>w$=j^@jXYX_F-Z$#4wmR+2dpAi)NN8Wb zdijoogjD6~=Rieqb>zPA+49vN#YYYGmn57dsw8!r16j3KM>xDxjl5pC0)e(JUL-F( zY^}U(ZMc0My&SmTJJ@oozt(!YWAn&`goK;q^~-1P{ZRN8vntyGZ{)GX>in+_r(oln z=W6}TurHLXL(EA`FF$d7?ywevb5mLJek4(ewQi3{?33=GUg3!dW%cvW)T`ad z^(2hsG)Q>jhwCIfS-XS(fZHGz7-^CZ?R2EKEyH1tqQKEaJ>dw1eMS!L9H(Yu2vnDAIOSxnbbPb$2fIi}(<`c4)FeucBD-s50l18r$% zsUcNrXyt?ZyMRKI!W>2{$Z7r#UtZ!awUoB^GhT`z)Gz5n*sApb4({Q?Kz@C-7- zfM+CU7`z=B3?0+Y44CFNIn0U>{%S`g=8=3Yqz}tXax)W~;JFBV-)jB-&+F9#BCUVa zp1=)9xyGI3ooL0TeR2VjGg=O7I!OEO&4SL*7cBu5DHg%C z1hSyfitu?$YAG$UT3IJF1#E#q+F1(xq<&U$1V)Oco$!m}X2~Bwbi=wF z>c1XROX1o)R}55+2sJBWD75abf8i-p`?~VuH}y(ZHJoJrTvTK4W??Fw2#>18V<(xr z7}^b3Q3L`C^tG3nnp&|WA@;Tb3J5i!uQN!Ns|3d3HvN!!0-Jy$jkFtCKGq02&j z$R0q_=nAR}{RtB!frK zp)~JKAr3<|PmZ*@>YTRwsp2*3#Qwie($kIaH)*O3AB^h|1A!9FKZlbX#Q-vhp0@@T zqpi0Qh`J@X`8%~sg(Z*TF8r%f`vSE~52J_p9{JOLQD8D`325%eNO6V)T_}Kb2Y2G~ z7cL~Q(G*qWE>h0@!V>#HBGD7C6j$#V*NQFSeT6xz zDh}%X6?^2aon*jB!o@PH(g z*YSQ4XEAu_$w0Y3Pf}^byc<@lr=IQA^_-3cHIc6VydkWZ#65X4NP4R!lWK>J7qhH+ z1cQssT}heC_@lcMiDCs2j>fYRa)g2p+nm7Aj*4Qo{c2kkc`46=GK^##Q&&Ed_w>N$ zXT~t#5QTk0B^fU?V$5jW+-14{M{*OdLxW7Li<)(Xuxv08_7q(a>T^S(`9cz4(0BFN zG8=NLT+Fqdtym=P2fi(6+*t~XtPf>Y2Om((2qk3M9bfpgJa1}+dkY=v>5ur_u{;dM z%lFbOoqo*;p1dELY0aiN3+5f<(6r>^;I+nO9h|byPGw8!9kT`6eS9Ryo5qr8?=DJ> z?E$Dy8imLeV1_x8(KK@Lv8(p2ZMz}3QkKVL zTP);C;bqtGp|m!rPF;EdJk=Rqi;#O>wpjHwz*GKKnY`k=gSD^e1$UceF=drHVoTwME zTkNnvay6bZS5Q7Cn-gkGTD4RXa-yTmk25s%!;UbkBN~;Q3|6@SBRXSc+hK^~D$_y- z!&4Nxc*{(>a#bfjoxr-Y6^NgFkk)YAs5zzMfYt;j^gm&jHy3f0mM0|Oa^NMW{7u0- zEo)E_4=&NXYb%F*yo91F*0SSQ?f^!VhkQI~cK`l_Tui7O6&r+L&(4EHLknPkbeqhv z4kJ31z>%Fi%p4i|C^n$nOTe|S0FmL@hzyi%dkx^rNX~_?*DHd*AWH~MA@?h0w|wM; zkaZj6vky$IE!XVDCbOIlhFz_cZSXAphqJrdw<NF6mW7Od8W_9>Oz`(nAA29~=w1Bj@0BLDd_djYB=&7yP^>=7xEQ(L<&lVZ6#^`w zHgc2=xh;k5m9y8(TrDl5rf%!1BI9u&gfPS*Y9ZRc>T~iNXEH$MCRVo1Z;-`9^4fSv z+EkW)?^t$~PTDzltLt48K#uP+#a#Ax;K-QtYFnΝ#+wb;tz-w4JCM(zXg(RT$g- zfjfy>IXlb&;2CIkL54#e#v`^es6rPn(swQF?2Ih(+E$VGOOtM zg$eByY0*c+l|xMLby_7vE0hx5-pUK9>u~V*lyUtGp6oOX58Yefm&lD3V&5^ie#hr& z49h$9XaPi#zWUG9XYv9BpalyB{HTg81LlBcLLHe&l`6oeiMbcD{Y$IL$A&IfV8`wp z%YwdwSJbYIkan~#=#XBSk86~2L(+_fa8#G>0m|)d&73DC`V91rDfH%?agEbIG1N5s z@9&Nq^V9E7(T51KW3pzlbTK7(+RJJD6ht!M!Q>92}y{@KqQ;sVY^ zNxvTjxxlu!>wZ&D2}k`BDiTDAy|EvAnM^z7T{j%c!=K{0{LOl|jK6O_qpa`5PF`B) z-gPij7OvC*$;SSXq$EL z$?dGE@(#y8eNF9$i|43LKN)&W9l8POWy%}{i^6-XJVq5PPsQwC+9tO`iFC-G56K3( zvJx(WJIn^@guQ3OQ9_NQP3OOuf!he_Yg^1ODfJFM(t0fm{cL5Y$fVzNI?u{v1zOsy zZ9j}T@#)&XFqaP1dNe2YZn<1mtz>>EKaR8i{BDSz`=s_h8Gq2aFE7RA#h;9_{bPCG zBS)&0;j+i@yz@ZTBusuF-(|kCN8f97Zh>6e7rW!zD8xV;ssF*O#or;7vqph7*OQnt zIw=Gr;&=dAK5G8FsWEx$*RC4U^kKM242*c%s$#@brt(h^B|a@u@xvLE4!l>=3KeNb{Q5pP)QWiPZZnkN2!fvp+V3@#Ak zqwD)26^8mtyM(jw?R~!$8j5ju+LO+XllkCfG4vYk(tgAN7_oTid9m;6k_K14SS-05 zY4g`g#YVQJV_)p6OP5rd8$*<-D|E*5`6=F2Sw13P>`D7>1-E;8*K@+3%{a={|(2y+n499_t!k@Sz(zoTP=(%SZ+1tr6BNj}dvr1#Pl zN<1FD-TL*s^^vF-ckE2gf=_x!Hv0n}!&N0s3AFEHK?t~#*CSeT_K~ce4r0qHmZrh6 zHCZ}MxS10s98pP^PITEcb(DGGAUnL()zmp*pRW)uZo-iUK-)O(4Qo@q8T<_tja5yo z<-gAJ+o3u@eteS)HspZLGj0vT4J11l`c50MhhV88C!3d8GW`&)3-b`_C_WUfg}!n- z_y%HQ3gZ%k9C?D!aPnU~>f=lbQUutdWa3Ww`c47%!L&F94GQ9SztjGccm6rX7A73^ z-78yiW!lR0(nC7vKSv2Fml~iW`<>GL(_P|j&h=ae%; z0Dnh1*pOCOjIo-@!2B5{@vbym-wKj58_nQ{U#IX6-{S%ys>H|mVyH}ud98I^^y=;S zPaF67>Qu+|q^FcXF{ss8E#W6<>{@Yt{v4CN`fhVjhd=J)wlQ6NYVb05_qj~IPOT&% zKJf@sQl%V7;c%?8wSek_Tu#zs8f*7Ydv{jUt-v~0-g`v^4NFghtuC6lAG3eBy?j#6 z1MV4T~m&rwGMk_libmMUS^=B>QPF$)2UYcUP88umBQ?0i&f~6D*!b1zA=P< z%IIPP=s3xt=U=k36$==vFz?wPasB8?P^_0bUr7g@M_F(Vop$_KJUuvz1VYb1>Anz@ zK&wxkzV)n&({?ufmQwt(<p~i0mgq(+vV`Kwa%xk=4B3JUn zSBlvWqNjxjZ(uzrglNWhRy#7p7%${`N`u={UKQcSj{I&u?efkUN?b>^yzFJ)a4_=A zO&PeoO4&5?csLvQ0{dz+=(Me;;|aUzc@5s7O6hVQ-b|zqo?7=*Ij{~)I8#2vAlj~o ze?d4}XMvvr2L^v(P-D(E&Dr<9Bzw+ee8@<2)y-nx&?@_U)Qf){cQQ2Vdhacf#>uU8y>SyG=$y$MIeM>|M?r3> z4@}&-IIo#%302W)ex2K&js#4|sscT!UDye8n+$=B@setYSqhQM=ayRk~8z=thW z z1?UU9eb{@u($t=QOR+3XC`_u2?9t4Z@yzguCk_}XZi@}4XYJguY4pPbMM~(%!wAs_ zlAGO%G&-eC)D7>q*6Ph_R69*efm~h1Ag$MJRbc1^|EvFd{qxTq-4FGEk;=Y7fV1oIgkfpue!FZFrJ$)yY zr5biTWA<$zt6juVci1<92P|1#-;i7_{G!Hr&JlUVw-w$$P}y9{l2ZeH^-Yb zx1>p#R(m}8UFz{^A>>Y)JFn>0{U1S~yccb6<=vgPZ4@&WGY;wN3+yCBUsu^ZNc@gv zU?qQ7tOtR$FAgrXFOqCFS+v*rhIAoCUC%e#)>5%Z5K@p!(&TJ+Wy@b`6Wuc~P;G?> z3|lOh=y6AWa;C9XYkkbCzOKaf0hnDW%W)l<(o0pUtTLDoSEwLueGtimFs6~?@aIwf zQWXTN?D9xPDZYq}8|i&Vzdd96<4`P(?a)Z}SMzV-3Bk?Y=vA?AQD5)_yeJ1@S6k)Y z&8d9Gq%LI#9@`_6YwOa9X`@w(<_4%ba*wZG*90}P-u^5NFa{K2BL~?igetb^^je7D z`{s|dWJWEH57X&LKY14B;(oE&)GT? zhMVF2POi+ZKMKHMb+NtwhJfi&?QF@!Q|v54Bj9{6>9I=E>Vrgg8}pU~ zbWz(aBk!u*&EgMVY#U;ySb0lqjl2yQj(oP2(?{a{ZltEakP$t>aj>=r2NDg$v0qOqt+6o^t50x+2z|HOEWYycWv=S3)j!z!yCLj16Lxx!sp@CmN z-Um8S4?GBOV01gsOA{n}1s_MBh33@x&FXSOUr-Nbu*FiqW;+dSAQxctqFjGvn``>BleD3BNF%(6HO8O3UMsnLo-&UZLNA4@&ww zwEi`w!x-sk;_)XXCN!Vir?2zOdCz7dJ4k{}c%zi289o^{(F0XpKQq%!QRd~4g3i~C zWUx(^vbQDO-a#5;<>{ znPJh7>(pK2=~<-Lo#fJ}^ytQoh?uR4}VKdA8^_^E&J*7pWa~ zOmkN zeE#bA#{JJJkXz3cJK5DN%o{>DuJ2y>ii)+A`U$MfXCso@``1%4=Ka{jeLk$tG4ii2 zOP&O4%L23&+jw1vAB);eBHstUbR0>=PV%SC>A6T31jiQD=``K5kX4?Ct!7`ys?Y0r zluw<%56spp{t-8Vx6b%Uy^^~qZ@8)n`Fv)ggPrKtp}Jpq_W9PkkmpqQaeV(PLUsB+ z6e%p|1F2wON&mil>V`~J3IlG}X$RIT->~d{NbqOgXLtW}S4a5n;LG-Gk(a@DY#zmE z)tp+^otFGw?LNXBh#18SwUwn?ztiTaDes}o8nzE;OHH5chu%uDH@I-dv7QdUY4IpD z7hk$aTrENBZ-QLFR2%XD#vdSya)s@U_`OF3_)mc_`*?U;u#Nm|Z-WIV@2Pn=;o+@`>u>l``a?uk)D^nOsUH04AlJm%HA3k`Cxwm(u*jZW$#-6@g~#+Z z&97#OpNq{;3w^u%NcBEmnIO7qmlQ~_j~(!%zh9lNx4O0+e1uZn{K}#kS)V}(de>TS z^xdCS?$7Pxwth+3L}ak{PWGl>sQeQ7pr^_SRvCX9lKRLwvN?mjczd>_B9mtOhhycf zMJ<*8GTndXt!U|0)lnNHBEs{JCe;3yJPRtz-g#VeJH^lCmJ=xdSA$B!dBMH1*-Kz* z`$Ut}S4FqF`>(i~q=a58-u^iZ#Kj$QkIK{z5{b%4)svuNov6XwoO{5)H6wJT=niYFpEfl z-Btiy3Y|M25vB%$-f(8mQg-DL3|vQbAqm@Unr?xpdOFKVdbN`WX4tjGlqRWvT)LV6 zKOyDcZ24~s-Q+1;kI4SN?6%jtBz8VVF3Qzu7EeBryU)1#Oaf8DLcyWkmM|e>&3RLr zDhuF-ywj1&x9?i#ymFn6K4jYgOC6j!+r@VTZBp)_C6$F!)IW>`_D4PfT50F} z1*GG7FHH+ocpf)q(sKds0?8;v(X{vi_@2h!~U zvqp}6truVGbD+Gg@hKR%e*{o$i`ooZIr$9YGiDI{*Wqr}T1`@F7bX3?y^`h~w+tRf zE%Jig)3b(W<-{8VMJ7$~UHW3>5{O&GS@Hg0=DuZ7NVR&S=d#ozHq*tx?acIQFT#!M zBTD;yN*g{dfhcvPM?&jRCJsCS64gu4^A>~0%ihnzJejODq}sDn9lV0+e=u~Ah3TWrcVORIhu2-+0@=bY|;iMc%aD9aQy6rXx4VCN3$jxPC1q?@_PeJddSiEZM9o^JLq;cCRz2FuiVEw5v@(}OON)}KkP?Q!@$G-lHER+F_c;O5Wbd{n=)i5{d0W{7p30V9*!b99L@?R-41VSj=8m32fBftqr(l zcFtmUpFg=0StgpPVfT&c{8I8&#n1!aBiog%?9xq)Z`TuDuBbz`_tSZhv8S6J*~KbJ z5$RgfYCeds|LFZ+Vfg=4VJ%*Z>U-ipQQ2uwhez@MdJq@|`fvY7Ex>=6_}{kvp9#8# zA*u_L7Z&-qYo^pmeeSDI3>n2eXjY&6li8;=F}SKTzjToE`Bss!(rB`y34a3W01+ky zJZ;fp`JoX$;GM0zQgz1TU#x|^&$rx_dBhMi*^4&$?Gkvb>g~*wIZDN>GYo`D-otWs ztWN|iDz76Fce1&63>c@4a*Q}FkT?~Qm{f0Nes%C;(8=j;cbAY!@TZR}Sf-vgWba0Q zM!i{Bd5{55=k)B5jDFdAJ}+;pI$it1UFU41f9XTN65^ZB7 z(kM(Lzc_Jk>4~TqFkTA=xiM+P*A6LiXa#<`c36De>uWGXLaXpX?v`Y zy~(B5)jj^2x@x6fgR-DVnsw$U`{#N4x6}Ol4vHTJ6L>oMB6nobT*+UC;_gx_$4TT7 zltc1NlAJwV<3Mh;G0C1mnMNxMJ7R;BzZn>u{P z@)=_asr@VCDN#27zZH2ZxBqJ~){6SzS!i*~bE7_Mfl*5Ahc7>xWn=4kt3|%W=Md$Fj?RR?w-m+#B8=Q1&lL}NH*d08WZo90Va z*5a#;x`aG>tGohhS2-g6`#3`S!w~izkaGGU#V<$SQF8Mz21+7qu`1rzJXsC;I zw7i9_(?g8@fpWn}r}N@!{Bnk*jm$gIW=mya1sSsP3<@0aGfGLiO=_|Lcx$(uXN&N85 z5{VI8a1_HSgX^%tBx_>3vN00NxR*xp6|rR$fohk{{2QSenJOdXU3R*H&n{TWBX#z3 zu8WLXi@WaFe$b|SfPy8*NFgLf*%L*hQOiFLVL12iodew5(xv{-pL7Qc$Z$Ik9vUWH;3#q&Bz#0c{ZP1`CW>*LDbQm6o*QTG}QUz&*+~?b@9%Fb7 zjlY&7`tJKm=vVdU+{h3KY7mSS)Q6qzURsS1uPm|_QGuyjdd0L=D++^Ob?`#InNRU? z&MRC`O|Y>k(1AzgkKEvP4GppY0SG%j7QfLdJh#I{Ou-K(@f8k*_PyQKA)k7m0l0xzH)y@ZAp6<^G!R-07s(yBNLgY@{?mvYEG8-$;P$r06 z6OY7c55C~4M=6Eqq#@R~pS3fojWu@xu*(2KqIN?_@6g?vu-z+$Fx?(a0l z+78XL!>Koht3Tid#X4_J!-ovUd^bvZB!9&J(2@LhzZ4(g=5Zt1nuNIuL+r?6@$|K^ zN@~hLM(kzD?b8>}tV2tL`AaediI0|o&_=nmP}?W|iFn4vpk@qlpEL1`X!3W?{&~XiH}Vfp zhQNp)tC_)eH7#hr;E{E-K`9%X>ku0D>ff;YkAz^)&L9M(CGgp=F!jokelz186zS=Z zK|*gz;y;G{igfwkT6gB3<;#wcz!F`B6LNAW2JAQ)6&_LBZB-U9iI)xKA)7Ye-Uu=e ziC)LamE1FY#hd6;Co>u5aHqD;&owq#9oTKuWd!I+`pe7jZs)?qUVDn}+e2 zB@ao9YTUVl`S)`EpIw@NNBaM{(9L^e&m7;1&z|SB z1aecM=i#TtI^7YBqc?c%btUC;6dE`&2LRu=(76kRxvF!Q)6`JSc;w3VTRa9!@k#A2 z?VPR6X>vzBLdW;V<=eGC(9o_rGk+ip$)~hC_OjQ43~5NFxyH3ngjZfD10f7F>JJrY zyYOSvKCk%A6P^Yj@rQZETvS6T_2DI9(bku}&9<5WcKrJG_BFPxo3>_|!x1N&C=!?i zX2rh$ThTge*o*Z(2VajW&3HS7V|?o-Digo2MleQARUh@0c&20j$a-rm!%NXye+eSr zzh|#)hE9T*Os07v!a2QRXKAfhmG9iXIwkBiXnX%-)En02xzv_TaFcXuZHGuv z#iHaaUt@0=tmL1>eZu_zx&KltM)J2&<){=OUE(lmL4megjeY7OyG4t(Rb0DB>XtU8 zy`Ced^v!rlSp3}zg~mfQOm|xL9GWd${4blt3s6>>mDot}@tcYL%vTeewD@U-#}p;% zw^-3=MChCtWLc25?v1GA5Se5kR!<=2y?Kk$OcdNVL)Q5C7ZWu>-TQoFGhawU z-|6(2{ISuF-|M!`o8tX$KO(El>S3uXQ$rGfn-p$_#sf>IwVBfiuJD|RaMgni6&6P? zV1@7BB6{ojkR*HCb@5kRs;WfR>Jk6m=JVo&5$>AJoHqM4-;02h;P#C76#EqGXwafB zVwtNhkZfGq-l+9tQ#SaI3b3UGj1$CG$@-lknGNTo?Sd(%VNKn)EA4-;ot%lS(UkQw zx{r#}F(rGyZIu3#+au)~B_+3CZ~D3^Zz^_wsEj@5dTf-apq{~&QfLE<@at*WNX_oD{eTYyZQ4K#ZbCX4^sDI4=&IX2 zNeftvqaPA8h0xmg=-??IIa*ui?cPEfmSfmRV9sSb!3_4Im-bc0bVrxopbaQM=IUKX z3keMTp_C?qp0SNon+X|G=}@XGEWKp$e*cd5{F*Ygz;}zz@UztSbE83i?5zUfC>k5< zj-{>|)#JP61ggUM+f-2D(loyH;^o zLw{zRk2!l*Ltf9{t6+Z4Cm5Y{*=z1NZclNEtL(5+&1s`dmHO7KULW{E(zvbX4X0RQ z-w&Z#S*b+IU$~fqcgZ)xP8To1;DP z!5YusQDCKAm+P`?Y?n0I0d`%(+K|F$EuXSexUNf0-4Yhv{ynfk*QAP!%JbB}dg0;utht~R1e^HQoL~R+_VCtxM@7Ksph3EE>`9(1R zMphmRlF`KRL|b~z<#{v*rZdEqZIh+?l+)24ZiyrWae3io@r8#J$G^2{OEo#a8Tz&5 z1;J;UO{%nC{ASBh%$(7S+m(QSKFi7R_Wn{{L+>3C76 zwfydK2%hWIxJFJLDo;AAnJ`SYWG+X2_ZPq3yHs5%H8mKHX8h)F6f7PM6>862D2Figt*^ za7tU7Cbm43Bm)6JrFD$*k2qTYE_7%4mj8b7_HMX>dgKwIsdJrS8q~re;mPkE9&ys# zxOO=EwO_$K0`}h9$QZMyD|p%5%6i73L`Gv8LxamyAWshPkzgythJhu8c7m8oMlTgV zF%xsoeJLJJ%*Xo%;UU5EbeyNwcGP)hWDc8y;Sy80GhJ#c z2lBRD4;K~xGQ{b>cezA&PktU5ii>9Helygng+j{$%Bh9to;1eN4HJAPeacPLEyVWTA=?bcl0qT? zImk3C%FvWZmg%Z*K@AhUsyIr3vV@QVM%>;0PGhSZ55Vx!fX&jj0}BwJrOJREt+f4u z%RtAMjH3-M_kg_Chn9qczSxR6sO?N{r99ZmpqkY-*Q7UQdUVc;cy1|F{xcq`6WB>! z0ZBrnBquQ9y?)*`CU{49(gHfj$FK!CY2vy7b-!2ioxRx~q zWH$|7Uvs$;gxd78abXbFt|}uXa%=!S zzPoPT#URzL@w>cd?8y!1EPMF(tRhN5MZb0d+&0j5AR?G=WD8yu>~~z!Hla1@x#RF> z`W3dng|_?L=6G`cLAks@`uk`vUNY(rRM2(T?VwlELngXa`RD#79)(d$*f8lnw;9fW zS<0r;^iL+aN$d-j>wm5>-`$E%5%6ZHrI$Bj13Rnna?cyR+k_R6oz^0tQr&#PU9V~p z{Aq9ynZ0PpZ9uw_ssAYwd%ma>!Yny#N%Dz!`3H;~t5Bg=@DoKnnW>0m6#kZ5Yc)%J z^1b_)q+I5G$+Yy3BRUUWZ;4>mB?>By1Pzo4y&GGfQ|AbhcdPU|_zZ6HO7m<^^EZez zNVfC5O>S6y3^Mr6Xe1D6G_uam7o}RM!eCn^c+WW;Y}K<>u5vz zr^GMfAdEf5OTjx98w*v+pn=lw@SR7M;T{MX@=^bJ+e5VgJ(tGmVvE0wHpvyUg^xSq=xYf4nF2$eB6{u>27f zXb|J^^M_SHn!;*UjFHNU$rnPMjOMcFhoueYqRJ&^plAHi0E-)85Y&5By&yz~y^z^a zI(5C=X@IT@SR7Ga&iZz2sgnd!&U^wwA+#G3K0h0LuI)%aTv>G=mX__KvrBJgxoi9? z3n25np~BCg@@}1mv7@X#|MY-4K#$^_l{jqtc9=h|x}+JH3;9T>o7XejjuC=MPA}iy zR?LECfZnL}40YkHz*D*5!LFWl=!9q`n+Uc+_+f7ewn2Qas(honCn$o+?AES)rwm6`+{feR5m4SE3wX@-na{Y-ffFU z;)Lfu3SKT_l54RO6eoH(b5-LQjl+*FoK07fI=H(XnSpke*CBtMS@yugdY`a_f}QZa z8|@d}KKifW-160q_4p^5N(e!>p{>O(Wi_ZrR0N2jke7s4`j=ZNYnY@N>%){2cMm2O zx?2Y?;?>n{z2c!&nlyHq>y9(2iRxC}1n{1Mcg}FpZ{0%j@K7tW-KvsVu3F0xt10hwLoC*hjJJQb#g z!fz|MJVu#sGFFoFTzL4~0|SM^_F{_EE1KTKPnrgh$c1l~*ZdsHw{D`gaI3n{uaw2f zEYzNm77KQ+y&Ipun(^d;ryx1<*tT=s_m6Vi!<3d)(o>3DHZ9BmuacexA0BRiiDo7gSkxB z$MG2hNHb{%q;Hk{v7!O|Qc!v*on$d`)cmA}t9gxm`FHb<$>lgQ5y5B|v(vtKlP0Fz zhm)=8q`;>+xS+9W3eCHZlP&_Q%$|Si9(c?yxa-cG^vvq^0T0`~20xx2gZr{Cnz1mw zCu@yXY5@U#DZyeM(wcH_ivb|Lc-oitFA-G-sz-NF&}{S=i|paWimjjxLv6xIK-}*& zxmq(2d!qgb1ND}EnnJ#8W_@ME9mUl^HRzM9R! z^}Tlf)TQXQ(5Zpdt3Cme|4$=%8`9x;1xwI*OK+fk+s8=YC)U7|$J`~Ne5${lrh zAom%;^_Y^&1?dYhVmCcyR`6qo$=)sQo()^v7lcd0zG^bz8XIA(xD(vQJ!i|4zk=}b zW?qDgwVc&dkSeU)vK&~iJT3YVQp=3gmU+XhD<0J;jifSD#30KpN*SiKY-{xpZxD8$ z|K2IRC{y>%C_%`X18s0XfCjqUth-yfWfxohypIR{5uT1;fAXXMw`gHy>-*wdrRmBw z{CU%aVH&(j+5h$xEb?dP$dJC5sKpJ1cpb=P0;Cmd9ZxyvdU<}iz#+z7EvNDTy;BqJ zEY@^Qh}mVx))v#ihCtHaerhE|K`_qz1BhW}YaH;aH9y1v)P_Uu`@tOZhGtrkjT2v7d!t|anY&svI+B;cz?Vo++yWP zOLXV?>J#tR*E!kA+4yBqTK<}NEF?ir=R%JSyK!VAq;#y;RoHj{3^0g`<`JFj>tL-( zl)Obn9Qh1hbYc?sb@sp{%6LY9D;|T*DLa_T&7N-r! z=Ugo=8h)*d1((Rc8=Rz+>M+@Qs~BhG)`IxVqS&7Hho3YPk2mU4t0_LmY#8ykTIeNK zuLbOWAbB9Ah$W6_SA}7uur21E>U8d#&tSi#h}Xq6u@=@ziq_3(K}cNWsw6cApk0leyZiz66#?WP(oa$)3H12FKx8 zS!vLy`o%PyOty1z*P*)p(P)mz?W_W1$bPUiI3z?)eUAxeu{7Tg~1PE{QIYUSD$&PahzJ#hnlRy?1^WPWAUKqkav;`RMF&Fm?2i71NPbvd? zW!$aJdI!saHEFX#1jSXMa@kdHpi6;VAOm9mQI&B4aQj@dsns{g4R6LR(`PSwHdoo4 z=+)0=aO~fD#ul<$4nWGxxKzmoojIUoQ9LH5!Eg@6h@KAXsvdnyck-$)&_}l?)RVSO z*{ZNL!EIBvBYy5q7fRA2neMznPs~>)jpR0JW64^#6bS0f(ha+0R zhMboIbHOW&zSw^GrK^6pBZftbzb$jw9n6NV--NdoHWG#Dtgh$=?a>D?E!E+_j??+R z8DC@qTfM3|S#~?TK0omKIHSV}@JGx#Jdx|k$!1t`*=cn;@wxWBS0!PG6r);n8Ff)< z#m`W(P3TT6*;#7RN(bKo=}r`k38*!M4VCX9qj80zG{$-vl$v zk|l39laO4y|Ic>;F!9VlQa4WNEPIb|_ zW$`$nXa|+O+)5#-ia3%IgXlZDn`M)I$;jBd7-F2he%YB+FZEZ!8tBG#-vG3@TA%CQ zFSsuHgRO$6*w2c>%y;VF5&^6|<}2nZ ze9~`q<9};Dc~JV0Al}QufW+DMB;kMF0A}*Ry;NAIa8RSpnS9%(z>VqoY3U)ezh5Z; z$g7S#^g92ITIbr$Cd;Oi;c0XLo%CuShU}`70URUf+GS9E{cV zei0jYo-?}J5xS1&p*Fa^w{^GFx{f0N=`D-TiD8<>MF7KvtFBsPpAq}^wHntk7bcXJ ziRmVh2EM7#7ZVO|r>h|G`8u(B(FM}enS)%??SXELpz3a%Uwv(Y}2dkSnbla|wGPX;yh=hoQ zkYE9b9b(m+q~nA{Y}te@?6UzDNU#D4l#UpLkVuQDZFf3uySm$rY`5pJ=6d73_xwML zGyUhj`|KXZx zHX{=icVAq4J%+nj091e}R`KEA?W6y*#QU}Hh=^N=Fu(<1N&^B1Vn}VobiL&0DkBpV z5J){~pi_Jz5*iQ-L<*Ry2g=&W@(Gx-3lQP*-yfh~IPn(Gh+P8%RRN$fFs1`8J781T z5ttnnEUS<>QjjH??Lp8;R#HTDZ0i3GgmpAS;6(ig0Yu2MNl-ok;R;zk0c0&y{fGFU ze>hA(cjBG+o=8;zJOH>X!BrPrY=fnM2!q7f<+C)8q{J>hQ;i%4c~M3KKSFB#O7n^= z`7DN-*h%V&AGy+mGKgigYG%gv{+zsoZ=sh!o(mi)%mn8a)%@o%pWh zwyF=9!5VnD3FbaT7!U%vRcO1A=qE2tn)8%4Bk=^25hLTp+tNmqwUN-LG}xxdpz(ht z-^j8Ipd6;`LCiL&HG`}Fe1e`T@fOdBa0OKTA*$Y8Fb@DRPAr?q)l5chY$<52bfu(7 zAdUU%j^qi;7=5m`%P}WesexFoG<{tC;SxO+;w`=@QVsyqe*{x(Ld*aHqjbC{fW{4y z>NP!e5_fOhd*mco95+fzF;ZknOz;?8SjR`8s9v;{&N2|Sz~vUo?rq%s&p)IuM7+f} zMTVU|s{TDl)rUyw<|m3n#)wSqsn%qY@Ym>Mq+!`K0u?o1QX`ZZ4{NkmjctiC2$v{2 zw@`LI!M$^TI+jM+lYnl~FJbz3z~vUi3;|}V;{p-bafmqG=RUCC0*0t(B7Pa{gohos zu)AkJ@oxdSkGOXJdBBc(Z2vyk&Z#y>h#Jey#jeM-6a#>ewPt{P63?IfOZm*4w|GXx z6hN_g7u9eDVoIl&m7aP6M0o5oX@)Q3XCw)CJxQDdpGF_5cbpFJ{Rf#eHP;pMB;YY zF(iDCz&;O6NC1rLTYpm=I*2%ulmT+9U6kEhpnMAV-uZSyuU6w8S3uS4IX%~f z_q!A_aLQWrcy=)q>7jr}xJe{dQfs9%zO_P=I38K_R5QpDeP};; z7($c*_j8>o`fa-Jyw(|-t4Y(Pwru#w0p z9b^O=$Y@wZ-`5hNi7rvpMN+K=r7}dhZuKaQs6Nuy+~gDO;iDK7j8giZGf>nz?@86+(Za*@mJqEjjL~+2D!e%`@#QZzI7UhU!JOM z5&|K<|IJrIe}D14F9m|cIofd#zqA8a-#U#S|Ml@md~pKdiOK@<#B{L8&yU#lN6w<; z?P=ugxn|B=ctZqc2vc=Hs1ghZL{3BP+Y;1-o%MwDZkvwJ1>1(IuX^62j9YaiL=I7k$3v+pe}*mmcZH$xkNiMZ1f%M&c%zsSX*KM`cD8RS#@ zAYknO>YqtMJmC_i*aTPG5UIj$Xz{6wEYyI0GU0v#cEcc!D5Co1Aby^BCy(t`eKFWA zZgyvmFCUu5U}(_kRe1T}6!y=z6?ewPpE~M^w-Nf0TAL`ob*9ksa`UDZsJ#;qh8e8X z#8v<R1QTOS#fzy<<#rux?A!xOBA)lNN)=_cZ5XSUu zq(tqA{~g81>A9fyG}}vb!IZv?@y*DQB&nMR5nQ|5#iR8C&+nVWq33r5gW^T#$vZkU@RDZ>$s2Au21>RTYlkhN#f zn%r*oXq*%9x^Y*Z;uGEO-npAB5ciQWr3lB2~IiHsrje?j*VC~Lu#+c{J<#7z}7-8v+4JZzO3ju0fhn19bM1CEn7pxdd)ZP5{Ra z?}9Oc_deRdh0E(GE5_@u?!@$D=D5pp@u>w%bh;%5LxT^lY@?NDSbS|)gd5C}co)|lF-#x|Dg73sL0YU8wsg;F3|7oMUJHvs z&PbvO@cbC*lOx~*dYRTi{+a9 zv+vaZFY0$_t-K6{^Q1nOupQrz$sF|FsfD@l&Z%Sb&U&tfjq_Z3;@Dgm^Q7mx<#pA6 zG$=M^r#H|2X4}rwg5lP!44=I(AsC!R?~g}V)PvJHBKJTywsGw3pLmlEs?&RW01@Pw;P??sly@(!g+AW>z_wd_Z4Novv6mJsk4#m<`UQE z-}h2OVD*Vsn1b}NqLByg&e~?dNJN<@#W{hnN;mn=lAR$|?wPh?iuas&lGdpKd)djJ zy}0T)ZypfF(O=$$nW+{&x!uLe`XGq<>c$YC-0omzs)Zx3?t%!gw6MqNyH%&N6qhN9 ziAKO1Lv2-kpvI{$kw7#p{#NISNpx1kV6Gu66E6xRvn0j@i4~3dBS~b}Hl=Rm1cFN+ zucOzmaQM(J%} zxNKVVl9F^nqVuXAgp24xk<{>!j-Ytc4hW@@4vpQS5kd{BL@pC)c9RS zmUvaM?jpv@Kh=P^22nyHCc#e}^dQ)NVi+rR&w@vS`mV>o!r6<~e_UGFgA+&hM$ThS z{OfbeE6(EX#nQrFr|(vhpu_06oLbuv2vrM)Kmax&CCTL$xawKrUH_M_lOO}pgfr_L zMVVr_2MSb1x6N`O`IsDO?3V@xyk#RF-5mknt}h7AzHf>0dtZMM`{pO{aBYB_ceWkq zjvRme);3nw2Y7Dp4*c$KykLp^{-Z>+b;yd6%|Zf}MTMq3R0}|wD{!%mYGAGX#+ARN z5J{%I1rn#(gpnj8e8$YzpCm;{3^0v^;l;Jpk=>jB@dr=LV!iyE{|F($`yZ`hSQzY? zopj#9N{CvE&i#rN)8GEu3)nL|iNVm|!sXRC8&Sb97eNjs9-_na6q(K?hC&4c35+RF z6>H$Ci>$q~78s~{U@4+vSBiWjj;H<7T7(&iS5XT|eX7g8Gbk#&|M7abd;a1|8Xi-4 z?EIyb(C%j+t)i?rb-{EbNV{kX`_-dJ5j+FJ23&4Bk=;htnobiyhkolvLdd~3bp{r( znJ%eS=G3xuBS|6B87km zAZzVx&fA*dx{V@-ZP$rSNi$hc8Fs}s0!k8S5-Pz&r2`{-c@7X6Eqv5EcU)~_tn*9j z0n>hTAhw~tW@Id16=5KzK-Is4V(SXH+(K*W;P|}lFP;8@WUXl+n@~C+9m!^tbdeaL zCM+sFNNFh@=`tU$^d;2lHI>(j5-T6#@)`eFx!^aGYNcdUEBq0)1~qSALEx(#NFZFn z3|G)!c@M*l4*(DbYZu1nZ7G`sC=1ywQl%4K-)NmiYS%>91XG&e)=V7IxawOR)1CoQOF8%K_$ZoKsZ|05-67+2 zRdbz_1O*vKX{xu-_0zd8^|Z;(0lOT^tsAv4;=r#fHGwH6!O+IK<$RJ zII38?R%YCEbSeU6X`V*YJBd1z^lVV#c41VF5T)Wt)}neN(ny(wAVMr+3bTd$T)j2u-;s;of`RZ^eg(&b(?eoGFs$hiI=SAneI zD7gYY05D)Pv_#vwhN^c5rr2E@MAn``p3eYzD>1AB4QXlz zQ$|YvC024O1@DhTg{t^a7pRp6H55lSF{~u}Sck}BhA`z8s{RAZZQa{|FtUj`^dJ7@ z=^Q2S*S@3Q6;t&<`6OC9_9JWWMwaCO<@&&n>Y7jkIf`>IMo-pi?0x+l(WrIx{rbX2 zQk9>s95*@R5rQ5D%tM&|LloQBQ1 + + + + AllowNetworkAccess + + CFBundleDisplayName + Legacy Worlds + CFBundleIdentifier + com.apple.widget.legacyworlds + CFBundleVersion + 1.0 + MainHTML + LW.html + + diff --git a/misc/Legacy Worlds.wdgt/LW.css b/misc/Legacy Worlds.wdgt/LW.css new file mode 100755 index 0000000..7cc81d0 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/LW.css @@ -0,0 +1,109 @@ +body { + margin: 0; +} + +#mainDiv { + position: absolute; + top: 35px; + left: 7px; + width: 307px; + height: 290px; + margin: 0; + padding: 0; +} + +#debugLink { + position: absolute; + top: 326px; + left: 30px; + width: 271px; + height: 14px; + margin: 0; + padding: 0; + color: white; + font-size: 12px; + text-align: center; +} + +#debugDiv { + display: none; + position: absolute; + top: 35px; + left: 7px; + width: 307px; + height: 290px; + margin: 0; + padding: 0; + color: white; + font-size: 12px; + z-index: 1; + border: 1px solid #7f7f7f; +} + +#debugTable { + width: 301px; + height: 284px; + margin: 3px; + padding: 0; + border: 0px; +} + +#debugTable td { + color: white; + font-size: 12px; +} + +tr#dbgHdr { + height: 14px; +} + +#mainTable { + width: 305px; + height: 288px; + margin: 1px; + padding: 0; + border: 1px solid #7f7f7f; + border-collapse: collapse; +} + +#mtTitle { + text-align: center; + vertical-align: middle; + height: 30px; + font-size: 18px; + color: white; + font-weight: bold; +} + +#mtMenu { + text-align: center; + vertical-align: middle; + height: 30px; + border: 1px solid #7f7f7f; + font-size: 12px; + color: white; +} + +#mtContents { + color: white; + vertical-align: middle; + text-align: center; + font-size: 13px; +} + +a, a:active, a:visited { + text-decoration: underline; + font-style: italic; + color: white; +} + +.table { + margin: 0% 4%; + width: 96%; + border: 0; +} + +.table td { + color: white; + font-size: 13px; +} diff --git a/misc/Legacy Worlds.wdgt/LW.html b/misc/Legacy Worlds.wdgt/LW.html new file mode 100755 index 0000000..12a0134 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/LW.html @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/Legacy Worlds.wdgt/images/blue.png b/misc/Legacy Worlds.wdgt/images/blue.png new file mode 100755 index 0000000000000000000000000000000000000000..1ec4ed477be5f5b276643e7170446f81f68d50ff GIT binary patch literal 17069 zcmb`vcT`i~x4;P^MQI`eB2`h4UZocspwdL7NtX@@y|)M`NS9s$1gR>WKtk^wLXi?k z2tgq9&`G2Z-`~vp&Ajzy)|&U$`y(rR-L>w$=j^@jXYX_F-Z$#4wmR+2dpAi)NN8Wb zdijoogjD6~=Rieqb>zPA+49vN#YYYGmn57dsw8!r16j3KM>xDxjl5pC0)e(JUL-F( zY^}U(ZMc0My&SmTJJ@oozt(!YWAn&`goK;q^~-1P{ZRN8vntyGZ{)GX>in+_r(oln z=W6}TurHLXL(EA`FF$d7?ywevb5mLJek4(ewQi3{?33=GUg3!dW%cvW)T`ad z^(2hsG)Q>jhwCIfS-XS(fZHGz7-^CZ?R2EKEyH1tqQKEaJ>dw1eMS!L9H(Yu2vnDAIOSxnbbPb$2fIi}(<`c4)FeucBD-s50l18r$% zsUcNrXyt?ZyMRKI!W>2{$Z7r#UtZ!awUoB^GhT`z)Gz5n*sApb4({Q?Kz@C-7- zfM+CU7`z=B3?0+Y44CFNIn0U>{%S`g=8=3Yqz}tXax)W~;JFBV-)jB-&+F9#BCUVa zp1=)9xyGI3ooL0TeR2VjGg=O7I!OEO&4SL*7cBu5DHg%C z1hSyfitu?$YAG$UT3IJF1#E#q+F1(xq<&U$1V)Oco$!m}X2~Bwbi=wF z>c1XROX1o)R}55+2sJBWD75abf8i-p`?~VuH}y(ZHJoJrTvTK4W??Fw2#>18V<(xr z7}^b3Q3L`C^tG3nnp&|WA@;Tb3J5i!uQN!Ns|3d3HvN!!0-Jy$jkFtCKGq02&j z$R0q_=nAR}{RtB!frK zp)~JKAr3<|PmZ*@>YTRwsp2*3#Qwie($kIaH)*O3AB^h|1A!9FKZlbX#Q-vhp0@@T zqpi0Qh`J@X`8%~sg(Z*TF8r%f`vSE~52J_p9{JOLQD8D`325%eNO6V)T_}Kb2Y2G~ z7cL~Q(G*qWE>h0@!V>#HBGD7C6j$#V*NQFSeT6xz zDh}%X6?^2aon*jB!o@PH(g z*YSQ4XEAu_$w0Y3Pf}^byc<@lr=IQA^_-3cHIc6VydkWZ#65X4NP4R!lWK>J7qhH+ z1cQssT}heC_@lcMiDCs2j>fYRa)g2p+nm7Aj*4Qo{c2kkc`46=GK^##Q&&Ed_w>N$ zXT~t#5QTk0B^fU?V$5jW+-14{M{*OdLxW7Li<)(Xuxv08_7q(a>T^S(`9cz4(0BFN zG8=NLT+Fqdtym=P2fi(6+*t~XtPf>Y2Om((2qk3M9bfpgJa1}+dkY=v>5ur_u{;dM z%lFbOoqo*;p1dELY0aiN3+5f<(6r>^;I+nO9h|byPGw8!9kT`6eS9Ryo5qr8?=DJ> z?E$Dy8imLeV1_x8(KK@Lv8(p2ZMz}3QkKVL zTP);C;bqtGp|m!rPF;EdJk=Rqi;#O>wpjHwz*GKKnY`k=gSD^e1$UceF=drHVoTwME zTkNnvay6bZS5Q7Cn-gkGTD4RXa-yTmk25s%!;UbkBN~;Q3|6@SBRXSc+hK^~D$_y- z!&4Nxc*{(>a#bfjoxr-Y6^NgFkk)YAs5zzMfYt;j^gm&jHy3f0mM0|Oa^NMW{7u0- zEo)E_4=&NXYb%F*yo91F*0SSQ?f^!VhkQI~cK`l_Tui7O6&r+L&(4EHLknPkbeqhv z4kJ31z>%Fi%p4i|C^n$nOTe|S0FmL@hzyi%dkx^rNX~_?*DHd*AWH~MA@?h0w|wM; zkaZj6vky$IE!XVDCbOIlhFz_cZSXAphqJrdw<NF6mW7Od8W_9>Oz`(nAA29~=w1Bj@0BLDd_djYB=&7yP^>=7xEQ(L<&lVZ6#^`w zHgc2=xh;k5m9y8(TrDl5rf%!1BI9u&gfPS*Y9ZRc>T~iNXEH$MCRVo1Z;-`9^4fSv z+EkW)?^t$~PTDzltLt48K#uP+#a#Ax;K-QtYFnΝ#+wb;tz-w4JCM(zXg(RT$g- zfjfy>IXlb&;2CIkL54#e#v`^es6rPn(swQF?2Ih(+E$VGOOtM zg$eByY0*c+l|xMLby_7vE0hx5-pUK9>u~V*lyUtGp6oOX58Yefm&lD3V&5^ie#hr& z49h$9XaPi#zWUG9XYv9BpalyB{HTg81LlBcLLHe&l`6oeiMbcD{Y$IL$A&IfV8`wp z%YwdwSJbYIkan~#=#XBSk86~2L(+_fa8#G>0m|)d&73DC`V91rDfH%?agEbIG1N5s z@9&Nq^V9E7(T51KW3pzlbTK7(+RJJD6ht!M!Q>92}y{@KqQ;sVY^ zNxvTjxxlu!>wZ&D2}k`BDiTDAy|EvAnM^z7T{j%c!=K{0{LOl|jK6O_qpa`5PF`B) z-gPij7OvC*$;SSXq$EL z$?dGE@(#y8eNF9$i|43LKN)&W9l8POWy%}{i^6-XJVq5PPsQwC+9tO`iFC-G56K3( zvJx(WJIn^@guQ3OQ9_NQP3OOuf!he_Yg^1ODfJFM(t0fm{cL5Y$fVzNI?u{v1zOsy zZ9j}T@#)&XFqaP1dNe2YZn<1mtz>>EKaR8i{BDSz`=s_h8Gq2aFE7RA#h;9_{bPCG zBS)&0;j+i@yz@ZTBusuF-(|kCN8f97Zh>6e7rW!zD8xV;ssF*O#or;7vqph7*OQnt zIw=Gr;&=dAK5G8FsWEx$*RC4U^kKM242*c%s$#@brt(h^B|a@u@xvLE4!l>=3KeNb{Q5pP)QWiPZZnkN2!fvp+V3@#Ak zqwD)26^8mtyM(jw?R~!$8j5ju+LO+XllkCfG4vYk(tgAN7_oTid9m;6k_K14SS-05 zY4g`g#YVQJV_)p6OP5rd8$*<-D|E*5`6=F2Sw13P>`D7>1-E;8*K@+3%{a={|(2y+n499_t!k@Sz(zoTP=(%SZ+1tr6BNj}dvr1#Pl zN<1FD-TL*s^^vF-ckE2gf=_x!Hv0n}!&N0s3AFEHK?t~#*CSeT_K~ce4r0qHmZrh6 zHCZ}MxS10s98pP^PITEcb(DGGAUnL()zmp*pRW)uZo-iUK-)O(4Qo@q8T<_tja5yo z<-gAJ+o3u@eteS)HspZLGj0vT4J11l`c50MhhV88C!3d8GW`&)3-b`_C_WUfg}!n- z_y%HQ3gZ%k9C?D!aPnU~>f=lbQUutdWa3Ww`c47%!L&F94GQ9SztjGccm6rX7A73^ z-78yiW!lR0(nC7vKSv2Fml~iW`<>GL(_P|j&h=ae%; z0Dnh1*pOCOjIo-@!2B5{@vbym-wKj58_nQ{U#IX6-{S%ys>H|mVyH}ud98I^^y=;S zPaF67>Qu+|q^FcXF{ss8E#W6<>{@Yt{v4CN`fhVjhd=J)wlQ6NYVb05_qj~IPOT&% zKJf@sQl%V7;c%?8wSek_Tu#zs8f*7Ydv{jUt-v~0-g`v^4NFghtuC6lAG3eBy?j#6 z1MV4T~m&rwGMk_libmMUS^=B>QPF$)2UYcUP88umBQ?0i&f~6D*!b1zA=P< z%IIPP=s3xt=U=k36$==vFz?wPasB8?P^_0bUr7g@M_F(Vop$_KJUuvz1VYb1>Anz@ zK&wxkzV)n&({?ufmQwt(<p~i0mgq(+vV`Kwa%xk=4B3JUn zSBlvWqNjxjZ(uzrglNWhRy#7p7%${`N`u={UKQcSj{I&u?efkUN?b>^yzFJ)a4_=A zO&PeoO4&5?csLvQ0{dz+=(Me;;|aUzc@5s7O6hVQ-b|zqo?7=*Ij{~)I8#2vAlj~o ze?d4}XMvvr2L^v(P-D(E&Dr<9Bzw+ee8@<2)y-nx&?@_U)Qf){cQQ2Vdhacf#>uU8y>SyG=$y$MIeM>|M?r3> z4@}&-IIo#%302W)ex2K&js#4|sscT!UDye8n+$=B@setYSqhQM=ayRk~8z=thW z z1?UU9eb{@u($t=QOR+3XC`_u2?9t4Z@yzguCk_}XZi@}4XYJguY4pPbMM~(%!wAs_ zlAGO%G&-eC)D7>q*6Ph_R69*efm~h1Ag$MJRbc1^|EvFd{qxTq-4FGEk;=Y7fV1oIgkfpue!FZFrJ$)yY zr5biTWA<$zt6juVci1<92P|1#-;i7_{G!Hr&JlUVw-w$$P}y9{l2ZeH^-Yb zx1>p#R(m}8UFz{^A>>Y)JFn>0{U1S~yccb6<=vgPZ4@&WGY;wN3+yCBUsu^ZNc@gv zU?qQ7tOtR$FAgrXFOqCFS+v*rhIAoCUC%e#)>5%Z5K@p!(&TJ+Wy@b`6Wuc~P;G?> z3|lOh=y6AWa;C9XYkkbCzOKaf0hnDW%W)l<(o0pUtTLDoSEwLueGtimFs6~?@aIwf zQWXTN?D9xPDZYq}8|i&Vzdd96<4`P(?a)Z}SMzV-3Bk?Y=vA?AQD5)_yeJ1@S6k)Y z&8d9Gq%LI#9@`_6YwOa9X`@w(<_4%ba*wZG*90}P-u^5NFa{K2BL~?igetb^^je7D z`{s|dWJWEH57X&LKY14B;(oE&)GT? zhMVF2POi+ZKMKHMb+NtwhJfi&?QF@!Q|v54Bj9{6>9I=E>Vrgg8}pU~ zbWz(aBk!u*&EgMVY#U;ySb0lqjl2yQj(oP2(?{a{ZltEakP$t>aj>=r2NDg$v0qOqt+6o^t50x+2z|HOEWYycWv=S3)j!z!yCLj16Lxx!sp@CmN z-Um8S4?GBOV01gsOA{n}1s_MBh33@x&FXSOUr-Nbu*FiqW;+dSAQxctqFjGvn``>BleD3BNF%(6HO8O3UMsnLo-&UZLNA4@&ww zwEi`w!x-sk;_)XXCN!Vir?2zOdCz7dJ4k{}c%zi289o^{(F0XpKQq%!QRd~4g3i~C zWUx(^vbQDO-a#5;<>{ znPJh7>(pK2=~<-Lo#fJ}^ytQoh?uR4}VKdA8^_^E&J*7pWa~ zOmkN zeE#bA#{JJJkXz3cJK5DN%o{>DuJ2y>ii)+A`U$MfXCso@``1%4=Ka{jeLk$tG4ii2 zOP&O4%L23&+jw1vAB);eBHstUbR0>=PV%SC>A6T31jiQD=``K5kX4?Ct!7`ys?Y0r zluw<%56spp{t-8Vx6b%Uy^^~qZ@8)n`Fv)ggPrKtp}Jpq_W9PkkmpqQaeV(PLUsB+ z6e%p|1F2wON&mil>V`~J3IlG}X$RIT->~d{NbqOgXLtW}S4a5n;LG-Gk(a@DY#zmE z)tp+^otFGw?LNXBh#18SwUwn?ztiTaDes}o8nzE;OHH5chu%uDH@I-dv7QdUY4IpD z7hk$aTrENBZ-QLFR2%XD#vdSya)s@U_`OF3_)mc_`*?U;u#Nm|Z-WIV@2Pn=;o+@`>u>l``a?uk)D^nOsUH04AlJm%HA3k`Cxwm(u*jZW$#-6@g~#+Z z&97#OpNq{;3w^u%NcBEmnIO7qmlQ~_j~(!%zh9lNx4O0+e1uZn{K}#kS)V}(de>TS z^xdCS?$7Pxwth+3L}ak{PWGl>sQeQ7pr^_SRvCX9lKRLwvN?mjczd>_B9mtOhhycf zMJ<*8GTndXt!U|0)lnNHBEs{JCe;3yJPRtz-g#VeJH^lCmJ=xdSA$B!dBMH1*-Kz* z`$Ut}S4FqF`>(i~q=a58-u^iZ#Kj$QkIK{z5{b%4)svuNov6XwoO{5)H6wJT=niYFpEfl z-Btiy3Y|M25vB%$-f(8mQg-DL3|vQbAqm@Unr?xpdOFKVdbN`WX4tjGlqRWvT)LV6 zKOyDcZ24~s-Q+1;kI4SN?6%jtBz8VVF3Qzu7EeBryU)1#Oaf8DLcyWkmM|e>&3RLr zDhuF-ywj1&x9?i#ymFn6K4jYgOC6j!+r@VTZBp)_C6$F!)IW>`_D4PfT50F} z1*GG7FHH+ocpf)q(sKds0?8;v(X{vi_@2h!~U zvqp}6truVGbD+Gg@hKR%e*{o$i`ooZIr$9YGiDI{*Wqr}T1`@F7bX3?y^`h~w+tRf zE%Jig)3b(W<-{8VMJ7$~UHW3>5{O&GS@Hg0=DuZ7NVR&S=d#ozHq*tx?acIQFT#!M zBTD;yN*g{dfhcvPM?&jRCJsCS64gu4^A>~0%ihnzJejODq}sDn9lV0+e=u~Ah3TWrcVORIhu2-+0@=bY|;iMc%aD9aQy6rXx4VCN3$jxPC1q?@_PeJddSiEZM9o^JLq;cCRz2FuiVEw5v@(}OON)}KkP?Q!@$G-lHER+F_c;O5Wbd{n=)i5{d0W{7p30V9*!b99L@?R-41VSj=8m32fBftqr(l zcFtmUpFg=0StgpPVfT&c{8I8&#n1!aBiog%?9xq)Z`TuDuBbz`_tSZhv8S6J*~KbJ z5$RgfYCeds|LFZ+Vfg=4VJ%*Z>U-ipQQ2uwhez@MdJq@|`fvY7Ex>=6_}{kvp9#8# zA*u_L7Z&-qYo^pmeeSDI3>n2eXjY&6li8;=F}SKTzjToE`Bss!(rB`y34a3W01+ky zJZ;fp`JoX$;GM0zQgz1TU#x|^&$rx_dBhMi*^4&$?Gkvb>g~*wIZDN>GYo`D-otWs ztWN|iDz76Fce1&63>c@4a*Q}FkT?~Qm{f0Nes%C;(8=j;cbAY!@TZR}Sf-vgWba0Q zM!i{Bd5{55=k)B5jDFdAJ}+;pI$it1UFU41f9XTN65^ZB7 z(kM(Lzc_Jk>4~TqFkTA=xiM+P*A6LiXa#<`c36De>uWGXLaXpX?v`Y zy~(B5)jj^2x@x6fgR-DVnsw$U`{#N4x6}Ol4vHTJ6L>oMB6nobT*+UC;_gx_$4TT7 zltc1NlAJwV<3Mh;G0C1mnMNxMJ7R;BzZn>u{P z@)=_asr@VCDN#27zZH2ZxBqJ~){6SzS!i*~bE7_Mfl*5Ahc7>xWn=4kt3|%W=Md$Fj?RR?w-m+#B8=Q1&lL}NH*d08WZo90Va z*5a#;x`aG>tGohhS2-g6`#3`S!w~izkaGGU#V<$SQF8Mz21+7qu`1rzJXsC;I zw7i9_(?g8@fpWn}r}N@!{Bnk*jm$gIW=mya1sSsP3<@0aGfGLiO=_|Lcx$(uXN&N85 z5{VI8a1_HSgX^%tBx_>3vN00NxR*xp6|rR$fohk{{2QSenJOdXU3R*H&n{TWBX#z3 zu8WLXi@WaFe$b|SfPy8*NFgLf*%L*hQOiFLVL12iodew5(xv{-pL7Qc$Z$Ik9vUWH;3#q&Bz#0c{ZP1`CW>*LDbQm6o*QTG}QUz&*+~?b@9%Fb7 zjlY&7`tJKm=vVdU+{h3KY7mSS)Q6qzURsS1uPm|_QGuyjdd0L=D++^Ob?`#InNRU? z&MRC`O|Y>k(1AzgkKEvP4GppY0SG%j7QfLdJh#I{Ou-K(@f8k*_PyQKA)k7m0l0xzH)y@ZAp6<^G!R-07s(yBNLgY@{?mvYEG8-$;P$r06 z6OY7c55C~4M=6Eqq#@R~pS3fojWu@xu*(2KqIN?_@6g?vu-z+$Fx?(a0l z+78XL!>Koht3Tid#X4_J!-ovUd^bvZB!9&J(2@LhzZ4(g=5Zt1nuNIuL+r?6@$|K^ zN@~hLM(kzD?b8>}tV2tL`AaediI0|o&_=nmP}?W|iFn4vpk@qlpEL1`X!3W?{&~XiH}Vfp zhQNp)tC_)eH7#hr;E{E-K`9%X>ku0D>ff;YkAz^)&L9M(CGgp=F!jokelz186zS=Z zK|*gz;y;G{igfwkT6gB3<;#wcz!F`B6LNAW2JAQ)6&_LBZB-U9iI)xKA)7Ye-Uu=e ziC)LamE1FY#hd6;Co>u5aHqD;&owq#9oTKuWd!I+`pe7jZs)?qUVDn}+e2 zB@ao9YTUVl`S)`EpIw@NNBaM{(9L^e&m7;1&z|SB z1aecM=i#TtI^7YBqc?c%btUC;6dE`&2LRu=(76kRxvF!Q)6`JSc;w3VTRa9!@k#A2 z?VPR6X>vzBLdW;V<=eGC(9o_rGk+ip$)~hC_OjQ43~5NFxyH3ngjZfD10f7F>JJrY zyYOSvKCk%A6P^Yj@rQZETvS6T_2DI9(bku}&9<5WcKrJG_BFPxo3>_|!x1N&C=!?i zX2rh$ThTge*o*Z(2VajW&3HS7V|?o-Digo2MleQARUh@0c&20j$a-rm!%NXye+eSr zzh|#)hE9T*Os07v!a2QRXKAfhmG9iXIwkBiXnX%-)En02xzv_TaFcXuZHGuv z#iHaaUt@0=tmL1>eZu_zx&KltM)J2&<){=OUE(lmL4megjeY7OyG4t(Rb0DB>XtU8 zy`Ced^v!rlSp3}zg~mfQOm|xL9GWd${4blt3s6>>mDot}@tcYL%vTeewD@U-#}p;% zw^-3=MChCtWLc25?v1GA5Se5kR!<=2y?Kk$OcdNVL)Q5C7ZWu>-TQoFGhawU z-|6(2{ISuF-|M!`o8tX$KO(El>S3uXQ$rGfn-p$_#sf>IwVBfiuJD|RaMgni6&6P? zV1@7BB6{ojkR*HCb@5kRs;WfR>Jk6m=JVo&5$>AJoHqM4-;02h;P#C76#EqGXwafB zVwtNhkZfGq-l+9tQ#SaI3b3UGj1$CG$@-lknGNTo?Sd(%VNKn)EA4-;ot%lS(UkQw zx{r#}F(rGyZIu3#+au)~B_+3CZ~D3^Zz^_wsEj@5dTf-apq{~&QfLE<@at*WNX_oD{eTYyZQ4K#ZbCX4^sDI4=&IX2 zNeftvqaPA8h0xmg=-??IIa*ui?cPEfmSfmRV9sSb!3_4Im-bc0bVrxopbaQM=IUKX z3keMTp_C?qp0SNon+X|G=}@XGEWKp$e*cd5{F*Ygz;}zz@UztSbE83i?5zUfC>k5< zj-{>|)#JP61ggUM+f-2D(loyH;^o zLw{zRk2!l*Ltf9{t6+Z4Cm5Y{*=z1NZclNEtL(5+&1s`dmHO7KULW{E(zvbX4X0RQ z-w&Z#S*b+IU$~fqcgZ)xP8To1;DP z!5YusQDCKAm+P`?Y?n0I0d`%(+K|F$EuXSexUNf0-4Yhv{ynfk*QAP!%JbB}dg0;utht~R1e^HQoL~R+_VCtxM@7Ksph3EE>`9(1R zMphmRlF`KRL|b~z<#{v*rZdEqZIh+?l+)24ZiyrWae3io@r8#J$G^2{OEo#a8Tz&5 z1;J;UO{%nC{ASBh%$(7S+m(QSKFi7R_Wn{{L+>3C76 zwfydK2%hWIxJFJLDo;AAnJ`SYWG+X2_ZPq3yHs5%H8mKHX8h)F6f7PM6>862D2Figt*^ za7tU7Cbm43Bm)6JrFD$*k2qTYE_7%4mj8b7_HMX>dgKwIsdJrS8q~re;mPkE9&ys# zxOO=EwO_$K0`}h9$QZMyD|p%5%6i73L`Gv8LxamyAWshPkzgythJhu8c7m8oMlTgV zF%xsoeJLJJ%*Xo%;UU5EbeyNwcGP)hWDc8y;Sy80GhJ#c z2lBRD4;K~xGQ{b>cezA&PktU5ii>9Helygng+j{$%Bh9to;1eN4HJAPeacPLEyVWTA=?bcl0qT? zImk3C%FvWZmg%Z*K@AhUsyIr3vV@QVM%>;0PGhSZ55Vx!fX&jj0}BwJrOJREt+f4u z%RtAMjH3-M_kg_Chn9qczSxR6sO?N{r99ZmpqkY-*Q7UQdUVc;cy1|F{xcq`6WB>! z0ZBrnBquQ9y?)*`CU{49(gHfj$FK!CY2vy7b-!2ioxRx~q zWH$|7Uvs$;gxd78abXbFt|}uXa%=!S zzPoPT#URzL@w>cd?8y!1EPMF(tRhN5MZb0d+&0j5AR?G=WD8yu>~~z!Hla1@x#RF> z`W3dng|_?L=6G`cLAks@`uk`vUNY(rRM2(T?VwlELngXa`RD#79)(d$*f8lnw;9fW zS<0r;^iL+aN$d-j>wm5>-`$E%5%6ZHrI$Bj13Rnna?cyR+k_R6oz^0tQr&#PU9V~p z{Aq9ynZ0PpZ9uw_ssAYwd%ma>!Yny#N%Dz!`3H;~t5Bg=@DoKnnW>0m6#kZ5Yc)%J z^1b_)q+I5G$+Yy3BRUUWZ;4>mB?>By1Pzo4y&GGfQ|AbhcdPU|_zZ6HO7m<^^EZez zNVfC5O>S6y3^Mr6Xe1D6G_uam7o}RM!eCn^c+WW;Y}K<>u5vz zr^GMfAdEf5OTjx98w*v+pn=lw@SR7M;T{MX@=^bJ+e5VgJ(tGmVvE0wHpvyUg^xSq=xYf4nF2$eB6{u>27f zXb|J^^M_SHn!;*UjFHNU$rnPMjOMcFhoueYqRJ&^plAHi0E-)85Y&5By&yz~y^z^a zI(5C=X@IT@SR7Ga&iZz2sgnd!&U^wwA+#G3K0h0LuI)%aTv>G=mX__KvrBJgxoi9? z3n25np~BCg@@}1mv7@X#|MY-4K#$^_l{jqtc9=h|x}+JH3;9T>o7XejjuC=MPA}iy zR?LECfZnL}40YkHz*D*5!LFWl=!9q`n+Uc+_+f7ewn2Qas(honCn$o+?AES)rwm6`+{feR5m4SE3wX@-na{Y-ffFU z;)Lfu3SKT_l54RO6eoH(b5-LQjl+*FoK07fI=H(XnSpke*CBtMS@yugdY`a_f}QZa z8|@d}KKifW-160q_4p^5N(e!>p{>O(Wi_ZrR0N2jke7s4`j=ZNYnY@N>%){2cMm2O zx?2Y?;?>n{z2c!&nlyHq>y9(2iRxC}1n{1Mcg}FpZ{0%j@K7tW-KvsVu3F0xt10hwLoC*hjJJQb#g z!fz|MJVu#sGFFoFTzL4~0|SM^_F{_EE1KTKPnrgh$c1l~*ZdsHw{D`gaI3n{uaw2f zEYzNm77KQ+y&Ipun(^d;ryx1<*tT=s_m6Vi!<3d)(o>3DHZ9BmuacexA0BRiiDo7gSkxB z$MG2hNHb{%q;Hk{v7!O|Qc!v*on$d`)cmA}t9gxm`FHb<$>lgQ5y5B|v(vtKlP0Fz zhm)=8q`;>+xS+9W3eCHZlP&_Q%$|Si9(c?yxa-cG^vvq^0T0`~20xx2gZr{Cnz1mw zCu@yXY5@U#DZyeM(wcH_ivb|Lc-oitFA-G-sz-NF&}{S=i|paWimjjxLv6xIK-}*& zxmq(2d!qgb1ND}EnnJ#8W_@ME9mUl^HRzM9R! z^}Tlf)TQXQ(5Zpdt3Cme|4$=%8`9x;1xwI*OK+fk+s8=YC)U7|$J`~Ne5${lrh zAom%;^_Y^&1?dYhVmCcyR`6qo$=)sQo()^v7lcd0zG^bz8XIA(xD(vQJ!i|4zk=}b zW?qDgwVc&dkSeU)vK&~iJT3YVQp=3gmU+XhD<0J;jifSD#30KpN*SiKY-{xpZxD8$ z|K2IRC{y>%C_%`X18s0XfCjqUth-yfWfxohypIR{5uT1;fAXXMw`gHy>-*wdrRmBw z{CU%aVH&(j+5h$xEb?dP$dJC5sKpJ1cpb=P0;Cmd9ZxyvdU<}iz#+z7EvNDTy;BqJ zEY@^Qh}mVx))v#ihCtHaerhE|K`_qz1BhW}YaH;aH9y1v)P_Uu`@tOZhGtrkjT2v7d!t|anY&svI+B;cz?Vo++yWP zOLXV?>J#tR*E!kA+4yBqTK<}NEF?ir=R%JSyK!VAq;#y;RoHj{3^0g`<`JFj>tL-( zl)Obn9Qh1hbYc?sb@sp{%6LY9D;|T*DLa_T&7N-r! z=Ugo=8h)*d1((Rc8=Rz+>M+@Qs~BhG)`IxVqS&7Hho3YPk2mU4t0_LmY#8ykTIeNK zuLbOWAbB9Ah$W6_SA}7uur21E>U8d#&tSi#h}Xq6u@=@ziq_3(K}cNWsw6cApk0leyZiz66#?WP(oa$)3H12FKx8 zS!vLy`o%PyOty1z*P*)p(P)mz?W_W1$bPUiI3z?)eUAxeu{7Tg~1PE{QIYUSD$&PahzJ#hnlRy?1^WPWAUKqkav;`RMF&Fm?2i71NPbvd? zW!$aJdI!saHEFX#1jSXMa@kdHpi6;VAOm9mQI&B4aQj@dsns{g4R6LR(`PSwHdoo4 z=+)0=aO~fD#ul<$4nWGxxKzmoojIUoQ9LH5!Eg@6h@KAXsvdnyck-$)&_}l?)RVSO z*{ZNL!EIBvBYy5q7fRA2neMznPs~>)jpR0JW64^#6bS0f(ha+0R zhMboIbHOW&zSw^GrK^6pBZftbzb$jw9n6NV--NdoHWG#Dtgh$=?a>D?E!E+_j??+R z8DC@qTfM3|S#~?TK0omKIHSV}@JGx#Jdx|k$!1t`*=cn;@wxWBS0!PG6r);n8Ff)< z#m`W(P3TT6*;#7RN(bKo=}r`k38*!M4VCX9qj80zG{$-vl$v zk|l39laO4y|Ic>;F!9VlQa4WNEPIb|_ zW$`$nXa|+O+)5#-ia3%IgXlZDn`M)I$;jBd7-F2he%YB+FZEZ!8tBG#-vG3@TA%CQ zFSsuHgRO$6*w2c>%y;VF5&^6|<}2nZ ze9~`q<9};Dc~JV0Al}QufW+DMB;kMF0A}*Ry;NAIa8RSpnS9%(z>VqoY3U)ezh5Z; z$g7S#^g92ITIbr$Cd;Oi;c0XLo%CuShU}`70URUf+GS9E{cV zei0jYo-?}J5xS1&p*Fa^w{^GFx{f0N=`D-TiD8<>MF7KvtFBsPpAq}^wHntk7bcXJ ziRmVh2EM7#7ZVO|r>h|G`8u(B(FM}enS)%??SXELpz3a%Uwv(Y= 0; i--) { + _b.destructors[i](); + } + }; +}; diff --git a/misc/Legacy Worlds.wdgt/lib/Base/Browser.js b/misc/Legacy Worlds.wdgt/lib/Base/Browser.js new file mode 100644 index 0000000..ec3b358 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/Base/Browser.js @@ -0,0 +1,94 @@ +/** The Base.Browser class contains the code that detects the user's browser and keeps + * track of the current window's size. + */ +Base.Browser = Base.Comp.inherits({ + + /** The constructor detects the browser type, then initialises the window size + * watching code. + */ + constructor: function () { + if (Base.Browser.singleton) { + throw "SingletonException"; + } + + this.base(); + + // Get the document body object + this.docBody = (document.compatMode && document.compatMode != 'BackCompat') ? document.documentElement : (document.body ? document.body : null); + + // Detect the browser's type + var nav = navigator.userAgent.toLowerCase(), ver = navigator.appVersion; + this.opera = window.opera && document.getElementById; + this.ie = nav.indexOf("msie") != -1 && document.all && this.docBody && !this.opera; + this.opera6 = this.opera && !document.defaultView; + this.operaOther = this.opera && !this.opera6; + this.ie7 = this.ie && parseFloat(ver.substring(ver.indexOf("MSIE")+5)) >= 7; + this.ie6 = this.ie && !this.ie7 && parseFloat(ver.substring(ver.indexOf("MSIE")+5)) >= 5.5; + var ns = !this.opera && document.defaultView && (typeof document.defaultView.getComputedStyle != "undefined"); + this.konqueror = ns && nav.indexOf('konqueror') != -1; + this.safari = ns && nav.indexOf('applewebkit') != -1; + this.ns6 = ns && !this.konqueror && !this.safari; + this.supported = this.docBody && (this.ie6 || this.ie7 || this.opera || this.ns6 || this.konqueror || this.safari); + + // Prepare the window size watching code + this.addEvent('SizeChanged'); + var _cid = this._cid; + window.onresize = function () { + Base.Comp.get(_cid).handleResize(); + }; + if (this.konqueror) { + this.addSlot('resizeTimer'); + this.timer = new Base.Timer(250, false); + this.timer.bindEvent('Tick', 'resizeTimer', this); + } + this.readSize(); + }, + + /** This method reads the current window's size. + */ + readSize: function () { + this.width = (this.ie ? this.docBody.offsetWidth : window.innerWidth); + this.height = (this.ie ? this.docBody.offsetHeight : window.innerHeight); + }, + + /** This method is called by the resize handler or, if the user is running Konqueror, + * by the timer that keeps track of resizing. It reads the current size, and if that + * size is different from the old size, it sends a SizeChanged(width,height) event. + */ + resizeTimer: function () { + var ow = this.width, oh = this.height; + this.readSize(); + if (ow != this.width || oh != this.height) { + this.onSizeChanged(this.width, this.height); + } + }, + + /** This method either forces the object to re-check the window's size or, if the + * user is running Konqueror, starts or restarts the update timer to prevent + * receiving multiple window update codes. + */ + handleResize: function () { + if (this.konqueror) { + this.timer.restart(); + return; + } + this.resizeTimer(); + } + +}, { + + /** This class property contains the single Base.Browser instance. + */ + singleton: null, + + /** This class method returns the Base.Browser instance after creating it + * if it didn't exist. + */ + get: function () { + if (!Base.Browser.singleton) { + Base.Browser.singleton = new Base.Browser(); + } + return Base.Browser.singleton; + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/Base/Comp.js b/misc/Legacy Worlds.wdgt/lib/Base/Comp.js new file mode 100644 index 0000000..176aafc --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/Base/Comp.js @@ -0,0 +1,217 @@ +/** The Base.Comp class is the base class for all of the components. It provides the + * signal/slot framework that the components use, and allows access to components using + * their unique identifier. + */ +Base.Comp = Base.inherits({ + + /** The constructor initialises the component by assigning its identifier, creating the + * tables listing the slots, events and children, and creating the low-level slots and + * events common to all components. + */ + constructor: function () { + this._cid = ++ Base.Comp.lastId; + this._slots = new Base.Util.Hashtable(); + this._evts = new Base.Util.Hashtable(); + this._chld = new Base.Util.Hashtable(); + this._cnt = null; + + Base.Comp.list.put(this._cid, this); + Base.Comp.count ++; + + this.destroyChildren = false; + + this.addEvent('Destroy'); + this.addSlot('addChild'); + this.addSlot('removeChild'); + }, + + /** This method is responsible for the destruction of a component. It + * starts by launching the onDestroy() event; it then detaches the + * component's children (or destroy them if the destroyChildren member + * is set to true), then detaches all of the component's slots and + * events. Finally, it removes the reference to the component in the + * global components list. + */ + destroy: function () { + this.onDestroy(this._cid); + + var k = this._chld.keys(); + for (var i in k) { + var c = this._chld.get(k[i]); + if (this.destroyChildren) { + c.destroy(); + } else { + this.removeChild(c); + } + } + + k = this._slots.keys(); + for (var i in k) { + this._slots.get(k[i]).destroy(); + } + this._slots.clear(); + + k = this._evts.keys(); + for (var i in k) { + this._evts.get(k[i]).destroy(); + } + this._evts.clear(); + + Base.Comp.list.remove(this._cid); + Base.Comp.count --; + + for (var i in this) { + this[i] = null; + } + }, + + + /** This method declares a new slot for the current component. + * @param name the name of the slot to be declared + */ + addSlot: function (name) { + if (this._slots.containsKey(name) || !this[name]) { + return; + } + this._slots.put(name, new Base.Comp.Slot(name, this)); + }, + + /** This method generates a new event handler for the current component. + * @param name the name of the event to create + * @param propagate a boolean indicating the event propagates to the component's children + */ + addEvent: function (name, propagate) { + if (this._evts.containsKey(name) || this["on" + name]) { + return; + } + + this._evts.put(name, new Base.Comp.Evt(name, propagate)); + this["on" + name] = function() { + this.triggerEvent(name, arguments); + }; + this.addSlot('on' + name); + }, + + + /** This method binds an event from the current component to a slot on either the current + * component or, if the last parameter is present, another component. + * @param eName the name of the event to bind + * @param sName the name of the slots to bind an event to + * @param cmp the component on which to bind (if this parameter isn't passed, the current component is used) + */ + bindEvent: function (eName, sName, cmp) { + var e = this._evts.get(eName), c = (cmp ? cmp : this), s = c._slots.get(sName); + if (!(e && s)) { + return; + } + e.bind(s); + }, + + /** This method detaches an event from the current component from a slot to which it had been bound. + * @param eName the name of the event to detach + * @param sName the name of the slot to detach + * @param cmp the component on which the slot to detach is located + */ + detachEvent: function (eName, sName, cmp) { + var e = this._evts.get(eName), c = (cmp ? cmp : this), s = c._slots.get(sName); + if (!(e && s)) { + return; + } + e.detach(s); + }, + + + /** This method triggers the execution of an event on the current component. + * @param eName the name of the event to trigger + * @param args an array containing the arguments to be passed to the event's handlers + */ + triggerEvent: function (eName, args) { + var e = this._evts.get(eName); + if (!e) { + return; + } + + e.trigger.apply(e, args); + + if (!e.propagate) { + return; + } + + var k = this._chld.keys(); + for (i=0;i 0) ? x.substr(0, m) : ''), z; + for (z=0;z 0) { + this.preferences.put(keys[i], p); + } else { + this.preferences.put(keys[i], defs[i]); + } + } + } else { + // We're not inside Dashboard. Use defaults. + for (var i in keys) { + this.preferences.put(keys[i], defs[i]); + } + } + }, + + + getPreference: function (key) { + return (this.preferences.containsKey(key) ? this.preferences.get(key) : null); + }, + + + setPreference: function (key, value) { + if (this.preferences.containsKey(key) && typeof value == 'string') { + Base.Log.write("Setting preference " + key + " to '" + value + "'"); + this.preferences.put(key, value); + if (window.widget) { + widget.setPreferenceForKey(key, value); + } + } + }, + + showDebug: function () { + if (!this.debugPanel) { + this.debugPanel = new LWWidget.Debug(); + } + this.debugPanel.show(); + } + + +}, { + + // Base URL for the LegacyWorlds website. + base: 'http://www.legacyworlds.com', + + // Enable/disable debugging + debug: true, + + // This widget's version number + version: 1, + + // Main widget instance + main: null, + + // Password storage + password: '', + + // When the widget page is loaded, initialise the component + onLoad: function () { + new LWWidget(); + } + +}, "LWWidget"); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5.js new file mode 100644 index 0000000..2a4e2f0 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5.js @@ -0,0 +1,77 @@ +LWWidget.Beta5 = LWWidget.Game.inherits({ + + constructor: function (path) { + this.base(path); + this.pageElement = 'b5Display'; + + this.pages[0] = new LWWidget.Beta5.Player(this); + this.pages[1] = new LWWidget.Beta5.Planets(this); + this.pages[2] = new LWWidget.Beta5.Fleets(this); + this.pages[3] = new LWWidget.Beta5.Cash(this); + this.pages[4] = new LWWidget.Beta5.Techs(this); + this.pages[5] = new LWWidget.Beta5.Msg(this); + this.pages[6] = new LWWidget.Beta5.Forums(this); + this.pages[7] = new LWWidget.Beta5.Ticks(this); + + this.setPage('player'); + + this.addSlot('updateServerTime'); + this.stUpdate = new Base.Timer(1000, true); + this.stUpdate.bindEvent('Tick', 'updateServerTime', this); + this.stUpdate.start(); + }, + + destroy: function () { + this.stUpdate.destroy(); + this.base(); + }, + + + setData: function (data) { + this.base(data); + this.serverTime = parseInt(data.getAttribute('serverTime'), 10); + this.drawServerTime(); + }, + + + draw: function () { + var _e = document.getElementById('gDisplay'); + if (!_e) { + return; + } + + var _e2 = document.getElementById('b5Display'); + if (!_e2) { + _e.style.overflow = 'visible'; + _e.innerHTML = '' + + '' + + '' + + '
 
 
'; + } + this.drawServerTime(); + + this.base(); + }, + + + updateServerTime: function () { + if (!this.serverTime) { + return; + } + this.serverTime ++; + this.drawServerTime(); + }, + + drawServerTime: function (u) { + var _e = document.getElementById('serverTime'); + if (!_e || !this.serverTime) { + return; + } + + _e.innerHTML = LWWidget.Game.formatTime(this.serverTime); + } + +}); + +// Register the version +LWWidget.Game.versions().put('beta5', LWWidget.Beta5); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Cash.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Cash.js new file mode 100644 index 0000000..bcde32d --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Cash.js @@ -0,0 +1,17 @@ +LWWidget.Beta5.Cash = LWWidget.Game.Page.inherits({ + + setData: function (data) { + var _d = data.getChildren('Empire')[0].getChildren('Budget')[0]; + + this.income = parseInt(_d.getAttribute('income'), 10); + this.upkeep = parseInt(_d.getAttribute('upkeep'), 10); + this.profit = parseInt(_d.getAttribute('profit'), 10); + }, + + draw: function (_e) { + _e.innerHTML = '



Planetary income: €' + Base.Util.formatNumber(this.income) + + '

Fleet upkeep: €' + Base.Util.formatNumber(this.upkeep) + + '

Daily profit: €' + Base.Util.formatNumber(this.profit) + '

'; + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Fleets.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Fleets.js new file mode 100644 index 0000000..a405286 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Fleets.js @@ -0,0 +1,55 @@ +LWWidget.Beta5.Fleets = LWWidget.Game.Page.inherits({ + + setData: function (data) { + var _d = data.getChildren('Empire')[0].getChildren('Fleets')[0]; + + this.fleets = parseInt(_d.getAttribute('count'), 10); + if (this.fleets) { + // Fleets in battle + this.inBattle = parseInt(_d.getAttribute('inBattle'), 10); + // Total fleet power + this.power = parseInt(_d.getAttribute('power'), 10); + // Ships types + this.gaships = parseInt(_d.getAttribute('gaships'), 10); + this.fighters = parseInt(_d.getAttribute('fighters'), 10); + this.cruisers = parseInt(_d.getAttribute('cruisers'), 10); + this.bcruisers = parseInt(_d.getAttribute('bcruisers'), 10); + // Upkeep + _d = data.getChildren('Empire')[0].getChildren('Budget')[0]; + this.upkeep = parseInt(_d.getAttribute('upkeep'), 10); + } + }, + + draw: function (_e) { + if (this.fleets == 0) { + _e.innerHTML = '

No fleets

'; + return; + } + + var _s = '

' + Base.Util.formatNumber(this.fleets) + + ' fleet' + (this.fleets > 1 ? 's' : ''); + if (this.inBattle) { + _s += ' (' + Base.Util.formatNumber(this.inBattle) + ' engaged in battle!)'; + } + _s += '

Total fleet power: ' + Base.Util.formatNumber(this.power) + + '
Upkeep: €' + Base.Util.formatNumber(this.upkeep) + + '

Ship types
'; + + if (this.gaships) { + _s += 'G.A. ships: ' + Base.Util.formatNumber(this.gaships) + '
'; + } + if (this.fighters) { + _s += 'Fighters: ' + Base.Util.formatNumber(this.fighters) + '
'; + } + if (this.cruisers) { + _s += 'Cruisers: ' + Base.Util.formatNumber(this.cruisers) + '
'; + } + if (this.bcruisers) { + _s += 'Battle Cruisers: ' + Base.Util.formatNumber(this.bcruisers) + '
'; + } + _s += '

'; + + _e.innerHTML = _s; + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Forums.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Forums.js new file mode 100644 index 0000000..4e4d3e2 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Forums.js @@ -0,0 +1,125 @@ +LWWidget.Beta5.Forums = LWWidget.Game.Page.inherits({ + + switchMode: function () { + this.details = !this.details; + this.game.draw(); + }, + + setData: function (data) { + var _f = data.getChildren('Communications')[0].getChildren('Forums')[0]; + + // Read general forums + var gf = _f.getChildren('GeneralForums'), gcats = new Base.Util.Hashtable(); + for (var i in gf) { + var c = gf[i], desc = c.getChildren('Description')[0].getText(), + cf = c.getChildren('Forum'), forums = new Array(); + for (var j in cf) { + forums.push([cf[j].getText(), parseInt(cf[j].getAttribute('topics'), 10), parseInt(cf[j].getAttribute('unread'), 10)]); + } + gcats.put(desc, forums); + } + + // Read alliance forums + var af = _f.getChildren('AllianceForums'), aForums = new Array(); + if (af.length) { + af = af[0].getChildren('Forum'); + for (var i in af) { + aForums.push([af[i].getText(), parseInt(af[i].getAttribute('topics'), 10), parseInt(af[i].getAttribute('unread'), 10)]); + } + } + + // Store data + this.general = gcats; + this.alliance = aForums; + }, + + + show: function () { + this.details = false; + }, + + draw: function (_e) { + if (this.details) { + _e.innerHTML = this.getDetailedHTML(); + } else { + _e.innerHTML = this.getShortHTML(); + } + }, + + + getShortHTML: function () { + var text = new Array(), gk = this.general.keys(); + for (var i in gk) { + text.push(this.catSummary(gk[i], this.general.get(gk[i]))); + } + + if (this.alliance.length) { + text.push(this.catSummary('Alliance forums', this.alliance)); + } + + return '

' + text.join('
') + '

Show details'; + }, + + catSummary: function (name, list) { + var s = '' + name + ': '; + + var unread = this.countUnread(list); + if (unread > 0) { + s += '' + Base.Util.formatNumber(unread) + ' unread topic' + (unread > 1 ? 's' : ''); + } else { + s += 'no unread topics'; + } + return s; + }, + + countUnread: function (forums) { + var s = 0; + for (var i in forums) { + s += forums[i][2]; + } + return s; + }, + + + getDetailedHTML: function () { + var text = new Array(), gk = this.general.keys(); + + for (var i in gk) { + text.push(this.catDetailed(gk[i], this.general.get(gk[i]))); + } + + if (this.alliance.length) { + text.push(this.catDetailed('Alliance forums', this.alliance)); + } + + return '

Hide details

' + text.join('

'); + }, + + catDetailed: function (name, forums) { + if (!forums.length) { + return ""; + } + + var text = new Array(); + + text.push('' + name + ''); + for (var i in forums) { + var f = forums[i], s = '' + f[0] + ': '; + + if (f[1]) { + s += '' + Base.Util.formatNumber(f[1]) + ' topic' + (f[1] > 1 ? 's' : ''); + if (f[2]) { + s += ' (' + Base.Util.formatNumber(f[2]) + ' unread)'; + } + } else { + s += 'empty forum'; + } + text.push(s); + } + + return text.join('
'); + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Msg.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Msg.js new file mode 100644 index 0000000..682b04f --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Msg.js @@ -0,0 +1,71 @@ +LWWidget.Beta5.Msg = LWWidget.Game.Page.inherits({ + + setData: function (data) { + var _d = data.getChildren('Communications')[0].getChildren('Messages')[0]; + + // Get default folders + var _f = _d.getChildren('DefaultFolder'); + for (var i in _f) { + var _a = [parseInt(_f[i].getAttribute('all'), 10), parseInt(_f[i].getAttribute('new'), 10)]; + switch (_f[i].getAttribute('id')) { + case 'IN': this.inbox = _a; break; + case 'INT': this.internal = _a; break; + case 'OUT': this.outbox = _a; break; + } + } + + // Get custom folders + this.custom = new Base.Util.Hashtable(); + _f = _d.getChildren('CustomFolder'); + for (var i in _f) { + this.custom.put(_f[i].getAttribute('id'), [parseInt(_f[i].getAttribute('all'), 10), parseInt(_f[i].getAttribute('new'), 10), _f[i].getText()]); + } + }, + + show: function () { + this.dCustom = false; + }, + + draw: function (_e) { + var _s = '

'; + _s += this.folderCode('Inbox', this.inbox[0], this.inbox[1]) + '
'; + _s += this.folderCode('Transmissions', this.internal[0], this.internal[1]) + '
' + _s += this.folderCode('Sent messages', this.outbox[0], this.outbox[1]) + '

'; + + if (this.custom.isEmpty()) { + _s += 'No custom folders'; + } else if (this.dCustom) { + _s += 'Hide custom folders
'; + var cf = this.custom.values(); + for (var i in cf) { + _s += this.folderCode(cf[i][2], cf[i][0], cf[i][1]) + '
'; + } + } else { + _s += 'Display custom folders'; + } + + _s += '

'; + _e.innerHTML = _s; + }, + + folderCode: function (name, a, n) { + var _s = '' + name + ': '; + if (a) { + if (n) { + _s += '' + Base.Util.formatNumber(n) + ' new message' + (n > 1 ? 's' : ''); + } else { + _s += 'no new messages'; + } + _s += ' (' + Base.Util.formatNumber(a) + ' total)'; + } else { + _s += 'no messages'; + } + return _s; + }, + + switchCustom: function () { + this.dCustom = !this.dCustom; + this.game.draw(); + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Planets.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Planets.js new file mode 100644 index 0000000..1cdaaa3 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Planets.js @@ -0,0 +1,86 @@ +LWWidget.Beta5.Planets = LWWidget.Game.Page.inherits({ + + setData: function (data) { + var _d = data.getChildren('Empire')[0].getChildren('Planets')[0]; + + this.planets = parseInt(_d.getAttribute('count'), 10); + this.pList = new Array(); + if (this.planets) { + // Planets under attack + this.underAttack = parseInt(_d.getAttribute('siege'), 10); + // Happiness / Corruption + this.avgHappiness = parseInt(_d.getAttribute('avgHap'), 10); + this.avgCorruption = parseInt(_d.getAttribute('avgCor'), 10); + // Population + this.totPopulation = parseInt(_d.getAttribute('totPop'), 10); + this.avgPopulation = parseInt(_d.getAttribute('avgPop'), 10); + // Factories + this.totFactories = parseInt(_d.getAttribute('totFac'), 10); + this.avgFactories = parseInt(_d.getAttribute('avgFac'), 10); + // Factories + this.totTurrets = parseInt(_d.getAttribute('totTur'), 10); + this.avgTurrets = parseInt(_d.getAttribute('avgTur'), 10); + + // Planet list + var _l = _d.getChildren('List')[0].getChildren('Planet'); + for (var i in _l) { + this.pList.push(_l[i].getText()); + } + } + }, + + show: function () { + this.mode = 0; + }, + + draw: function (_e) { + _e.innerHTML = (this.mode == 0 || !this.planets) ? this.getStats() : this.getList(); + }, + + setMode: function (m) { + this.mode = m; + if (this.game) { + this.game.draw(); + } + }, + + + getStats: function () { + if (this.planets == 0) { + return '

No planets.

'; + } + + var _s = '

' + Base.Util.formatNumber(this.planets) + ' planet' + + (this.planets > 1 ? 's' : '') + '
'; + if (this.underAttack) { + _s += '' + Base.Util.formatNumber(this.underAttack) + ' planet' + + (this.underAttack > 1 ? 's' : '') + ' under attack!'; + } else { + _s += 'No planets under attack'; + } + + _s += '

Average happiness: ' + this.avgHappiness + '%
Average corruption: ' + + this.avgCorruption + '%
Population: ' + Base.Util.formatNumber(this.totPopulation) + ''; + if (this.planets > 1) { + _s += ' (' + Base.Util.formatNumber(this.avgPopulation) + ' on average)'; + } + _s += '
Factories: ' + Base.Util.formatNumber(this.totFactories) + ''; + if (this.planets > 1) { + _s += ' (' + Base.Util.formatNumber(this.avgFactories) + ' on average)'; + } + _s += '
Turrets: ' + Base.Util.formatNumber(this.totTurrets) + ''; + if (this.planets > 1) { + _s += ' (' + Base.Util.formatNumber(this.avgTurrets) + ' on average)'; + } + _s += '

View list

'; + + return _s; + }, + + getList: function () { + return '

<<- Back

' + + this.pList.join('
') + '

'; + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Player.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Player.js new file mode 100644 index 0000000..cec2b9d --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Player.js @@ -0,0 +1,82 @@ +LWWidget.Beta5.Player = LWWidget.Game.Page.inherits({ + + setData: function (data) { + var _d = data.getChildren('Player')[0]; + + // Player name and cash + this.playerName = _d.getAttribute('name'); + this.cash = _d.getAttribute('cash'); + + // Alliance status + var _a = _d.getChildren('Alliance'); + if (_a.length) { + _a = _a[0]; + this.alliance = { joined: (_a.getAttribute('inalliance') == '1'), tag: _a.getAttribute('tag') }; + } else { + this.alliance = null; + } + + // General rankings + _a = _d.getChildren('Rankings')[0]; + var _r = _a.getChildren('Ranking'); + for (var i in _r) { + if (_r[i].getText() != 'General Ranking') { + continue; + } + this.points = _r[i].getAttribute('points'); + this.rank = _r[i].getAttribute('rank'); + break; + } + + // Planets / planets under attack + _d = data.getChildren('Empire')[0].getChildren('Planets')[0]; + this.planets = parseInt(_d.getAttribute('count'), 10); + this.underAttack = parseInt(_d.getAttribute('siege'), 10); + + // Fleets / fleets in battle + _d = data.getChildren('Empire')[0].getChildren('Fleets')[0]; + this.fleets = parseInt(_d.getAttribute('count'), 10); + this.inBattle = parseInt(_d.getAttribute('inBattle'), 10); + }, + + draw: function (_e) { + if (!this.playerName) { + return; + } + + var str = '

Player ' + this.playerName + ''; + if (this.alliance && this.alliance.joined) { + str += ' [' + this.alliance.tag + ']'; + } else if (this.alliance && !this.alliance.joined) { + str += '
Requesting to join alliance [' + this.alliance.tag + ']'; + } + + str += '

Cash: €' + Base.Util.formatNumber(this.cash) + '' + + '
General ranking: #' + Base.Util.formatNumber(this.rank) + ' (' + + Base.Util.formatNumber(this.points) + ' points)

'; + + if (this.planets == 0) { + str += "No planets"; + } else { + str += '' + Base.Util.formatNumber(this.planets) + ' planet' + (this.planets > 1 ? 's' : ''); + if (this.underAttack > 0) { + str += ' (' + Base.Util.formatNumber(this.underAttack) + ' under attack!)'; + } + } + str += '
'; + + if (this.fleets == 0) { + str += "No fleets"; + } else { + str += '' + Base.Util.formatNumber(this.fleets) + ' fleet' + (this.fleets > 1 ? 's' : ''); + if (this.inBattle > 0) { + str += ' (' + Base.Util.formatNumber(this.inBattle) + ' in battle)'; + } + } + + str += '

'; + + _e.innerHTML = str; + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Techs.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Techs.js new file mode 100644 index 0000000..03a12ea --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Techs.js @@ -0,0 +1,37 @@ +LWWidget.Beta5.Techs = LWWidget.Game.Page.inherits({ + + setData: function (data) { + var _d = data.getChildren('Empire')[0].getChildren('Research')[0]; + + this.points = parseInt(_d.getAttribute('points'), 10); + this.newTechs = parseInt(_d.getAttribute('new'), 10); + this.foreseen = parseInt(_d.getAttribute('foreseen'), 10); + + _d = _d.getChildren('RBudget')[0]; + this.fundamental = parseInt(_d.getAttribute('fundamental'), 10); + this.military = parseInt(_d.getAttribute('military'), 10); + this.civilian = parseInt(_d.getAttribute('civilian'), 10); + }, + + draw: function (_e) { + var _s = '

'; + if (this.newTechs == 0) { + _s += 'No new technologies discovered.'; + } else { + _s += '' + this.newTechs + ' new technolog' + (this.newTechs > 1 ? 'ies' : 'y') + ' discovered.'; + } + _s += '
'; + if (this.foreseen == 0) { + _s += 'No breakthroughs foreseen.'; + } else { + _s += '' + this.foreseen + ' breakthrough' + (this.foreseen > 1 ? 's' : '') + ' foreseen.'; + } + _s += '

Research budget

' + Base.Util.formatNumber(this.points) + ' research points per day
' + + this.fundamental + '% allocated to Fundamental research
' + + this.military + '% allocated to Military research
' + + this.civilian + '% allocated to Civilian research

'; + + _e.innerHTML = _s; + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Ticks.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Ticks.js new file mode 100644 index 0000000..30a0626 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Beta5/Ticks.js @@ -0,0 +1,121 @@ +LWWidget.Beta5.Ticks = LWWidget.Game.Page.inherits({ + + constructor: function (game) { + this.base(game); + + this.addSlot('update'); + this.timer = new Base.Timer(1000, true); + this.timer.bindEvent('Tick', 'update', this); + }, + + destroy: function () { + this.timer.destroy(); + this.base(); + }, + + + setData: function (data) { + var tList = data.getChildren('Ticks')[0].getChildren('Tick'), ticks = new Array(); + + for (var i in tList) { + var tick = new Array(); + tick[0] = tList[i].getText(); + tick[1] = parseInt(tList[i].getAttribute('first'), 10); + tick[2] = parseInt(tList[i].getAttribute('interval'), 10); + + var l = tList[i].getAttribute('last'); + if (typeof l == 'string') { + tick[3] = parseInt(l, 10); + } else { + tick[3] = null; + } + + ticks.push(tick); + } + + this.tDiff = (new Date()).getTime() - (parseInt(data.getAttribute('serverTime'), 10) * 1000); + this.ticks = ticks; + }, + + + show: function () { + this.timer.start(); + }, + + hide: function () { + this.timer.stop(); + }, + + + draw: function (_e) { + var text = new Array(); + + for (var i in this.ticks) { + text.push('Next ' + this.ticks[i][0] + ':  '); + } + + _e.innerHTML = '

' + text.join('
') + '

'; + this.update(); + }, + + + update: function () { + this.computeNextTicks(); + + for (var i in this.ticks) { + var t; + + if (typeof this.ticks[i][5] == 'number') { + var d, h, m, s; + + h = this.ticks[i][5] % 86400; d = (this.ticks[i][5] - h) / 86400; + m = h % 3600; h = (h - m) / 3600; + s = m % 60; m = (m - s) / 60; + + if (h < 10) { h = '0' + h; } + if (m < 10) { m = '0' + m; } + if (s < 10) { s = '0' + s; } + + t = '' + h + ':' + m + ':' + s + ''; + if (d > 0) { + t = '' + d + ' day' + (d > 1 ? 's' : '') + ', ' + t; + } + } else { + t = 'never'; + } + + var e = document.getElementById('dtick' + i); + if (!e) { + break; + } + e.innerHTML = t; + } + }, + + computeNextTicks: function () { + var now = Math.round(((new Date()).getTime() - this.tDiff) / 1000); + for (var i in this.ticks) { + if (this.ticks[i][3] && now > this.ticks[i][3]) { + this.ticks[i][4] = null; + continue; + } + + if (now <= this.ticks[i][1]) { + this.ticks[i][4] = this.ticks[i][1]; + continue; + } + + var d = now - this.ticks[i][1], m = (d - (d % this.ticks[i][2])) / this.ticks[i][2]; + this.ticks[i][4] = this.ticks[i][1] + (m + 1) * this.ticks[i][2]; + } + + for (var i in this.ticks) { + if (typeof this.ticks[i][4] != 'number') { + this.ticks[i][5] = null; + } else { + this.ticks[i][5] = this.ticks[i][4] - now; + } + } + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Data.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Data.js new file mode 100644 index 0000000..44cc265 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Data.js @@ -0,0 +1,25 @@ +LWWidget.Data = Base.inherits({ + + constructor: function (name) { + this.base(); + this.name = name; + this.attributes = new Base.Util.Hashtable(); + }, + + setAttribute: function (name, value) { + this.attributes.put(name, value); + }, + + getAttribute: function (name) { + return (this.attributes.containsKey(name) ? this.attributes.get(name) : null); + }, + + getChildren: function (name) { + return new Array(); + }, + + getText: function () { + return ""; + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Data/Leaf.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Data/Leaf.js new file mode 100644 index 0000000..217326a --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Data/Leaf.js @@ -0,0 +1,12 @@ +LWWidget.Data.Leaf = LWWidget.Data.inherits({ + + constructor: function (name, text) { + this.base(name); + this.text = text; + }, + + getText: function () { + return this.text; + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Data/Loader.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Data/Loader.js new file mode 100644 index 0000000..fccddc8 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Data/Loader.js @@ -0,0 +1,130 @@ +LWWidget.Data.Loader = Base.Comp.inherits({ + + constructor: function (login, password) { + this.base(); + + this.addEvent('CommError'); + this.addEvent('FatalError'); + this.addEvent('Kick'); + this.addEvent('Load'); + this.addEvent('LoginFailure'); + this.addEvent('Maintenance'); + + this.addSlot('loaderDestroyed'); + this.addSlot('dataReceived'); + + this.loader = new Base.XMLLoader('', true); + this.loader.bindEvent('Destroy', 'loaderDestroyed', this); + this.loader.bindEvent('NetworkError', 'onCommError', this); + this.loader.bindEvent('ServerError', 'onCommError', this); + this.loader.bindEvent('Timeout', 'onCommError', this); + this.loader.bindEvent('Unsupported', 'onCommError', this); + this.loader.bindEvent('Aborted', 'onCommError', this); + this.loader.bindEvent('Loaded', 'dataReceived', this); + + this.params = new Base.Util.Hashtable(); + this.params.put('__s', '1'); + this.params.put('__l', login); + this.params.put('__p', password); + }, + + destroy: function () { + if (this.loader) { + this.loader.destroy(); + } + this.base(); + }, + + + load: function (from) { + if (this.loader.isLoading) { + return false; + } + this.loader.changeParameters(LWWidget.base + '/index.php/' + from + '.xml'); + this.loader.load(this.params); + }, + + + loaderDestroyed: function () { + this.loader = null; + }, + + + dataReceived: function (data) { + var _xml = Base.Util.parseXML(data); + if (!_xml) { + this.onCommError(); + return; + } + + var _doc = _xml.documentElement; + if (_doc.nodeName == 'Maintenance') { + var _r, _u, _c; + _r = LWWidget.Data.Loader.getXMLText(_doc, 'Reason'); + _u = LWWidget.Data.Loader.getXMLText(_doc, 'Until'); + _c = LWWidget.Data.Loader.getXMLText(_doc, 'Current'); + this.onMaintenance(_r, _u, _c); + } else if (_doc.nodeName == 'FatalError') { + this.onFatalError(_doc.getAttribute('code'), LWWidget.Data.Loader.getXMLText(_doc, 'Text')); + } else if (_doc.nodeName == 'Kicked') { + this.onKick(LWWidget.Data.Loader.getXMLText(_doc, 'Reason')); + } else if (_doc.nodeName == 'Failed') { + this.onLoginFailure(_doc.getAttribute('code')); + } else { + var _data = LWWidget.Data.Loader.parse(_doc); + if (_data) { + this.onLoad(_data); + } else { + this.onCommError(); + } + } + } + +}, { + + getXMLText: function (node, name) { + var _t = ''; + for (var i=0;iFlush log - Hide' + + '
 
'; + document.getElementById('debugDiv').innerHTML = _str; + this.drawLog(); + }, + + clear: function () { + Base.Log.flush(); + this.drawLog(); + }, + + drawLog: function () { + var _e = document.getElementById('dbgContents'); + if (!_e) { + return; + } + + var _l = Base.Log.get(); + if (_l.length == this.lastLength) { + return; + } + this.lastLength = _l.length; + + var _s; + if (_l.length == 0) { + _s = '

The log is empty

'; + } else { + _s = '

'; + for (var i=_l.length;i>0;i--) { + _s += '[' + i + '] ' + _l[i-1] + '
'; + } + _s += '

'; + } + _e.innerHTML = _s; + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Game.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Game.js new file mode 100644 index 0000000..289e4c7 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Game.js @@ -0,0 +1,168 @@ +LWWidget.Game = Base.Comp.inherits({ + + constructor: function (path) { + this.base(); + this.path = path; + + this.addEvent('RequestUpdate'); + this.addSlot('startDataUpdate'); + + this.pageElement = 'gDisplay'; + + this.autoUpdate = new Base.Timer(30000, true); + this.autoUpdate.bindEvent('Tick', 'startDataUpdate', this); + this.autoUpdate.start(); + + this.pages = [null, null, null, null, null, null, null, null]; + this.page = null; + this.data = null; + }, + + destroy: function () { + this.autoUpdate.destroy(); + if (this.page) { + this.page.hide(); + this.page = null; + } + for (var i in this.pages) { + if (this.pages[i]) { + var _p = this.pages[i]; + this.pages[i] = null; + _p.destroy(); + } + } + this.base(); + }, + + + startDataUpdate: function () { + this.onRequestUpdate(this.path); + }, + + setData: function (data) { + this.data = data; + for (var i in this.pages) { + if (this.pages[i]) { + this.pages[i].setData(data); + } + } + this.draw(); + }, + + + setPage: function (pName) { + // Check for a valid page name + if (!LWWidget.Game.pageMap().containsKey(pName)) { + return; + } + + // Check if something was changed + var _p = this.pages[LWWidget.Game.pageMap().get(pName)]; + if (_p && this.page && _p._cid == this.page._cid || !(_p || this.page)) { + return; + } + + // Hide current page and show new page + if (this.page) { + this.page.hide(); + } + this.page = _p; + if (_p) { + this.page.show(); + } + this.draw(); + }, + + draw: function () { + var _e = document.getElementById(this.pageElement); + if (_e) { + if (this.page) { + this.page.draw(_e); + } else { + _e.innerHTML = '

Page not implemented

'; + } + } + }, + + show: function () { + this.startDataUpdate(); + this.autoUpdate.start(); + if (this.page) { + this.page.show(); + } + }, + + hide: function () { + this.autoUpdate.stop(); + if (this.page) { + this.page.hide(); + } + } + +}, { + + pageMapInst: null, + + versionsInst: null, + + pageMap: function () { + if (!LWWidget.Game.pageMapInst) { + var _h = new Base.Util.Hashtable(); + _h.put('player', 0); + _h.put('planets', 1); + _h.put('fleets', 2); + _h.put('budget', 3); + _h.put('tech', 4); + _h.put('msg', 5); + _h.put('forums', 6); + _h.put('ticks', 7); + LWWidget.Game.pageMapInst = _h; + } + return LWWidget.Game.pageMapInst; + }, + + versions: function () { + if (!LWWidget.Game.versionsInst) { + LWWidget.Game.versionsInst = new Base.Util.Hashtable(); + } + return LWWidget.Game.versionsInst; + }, + + formatTime: function (ts) { + var d = new Date(ts * 1000); + var str, s2; + + s2 = d.getUTCHours().toString(); + if (s2.length == 1) { + s2 = "0" + s2; + } + str = s2 + ':'; + + s2 = d.getUTCMinutes().toString(); + if (s2.length == 1) { + s2 = "0" + s2; + } + str += s2 + ':'; + + s2 = d.getUTCSeconds().toString(); + if (s2.length == 1) { + s2 = "0" + s2; + } + str += s2 + ' on '; + + s2 = d.getUTCDate().toString(); + if (s2.length == 1) { + s2 = "0" + s2; + } + str += s2 + '/'; + + s2 = (d.getUTCMonth() + 1).toString(); + if (s2.length == 1) { + s2 = "0" + s2; + } + str += s2 + '/' + d.getUTCFullYear(); + + return str; + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Game/Page.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Game/Page.js new file mode 100644 index 0000000..18c1a0d --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Game/Page.js @@ -0,0 +1,34 @@ +LWWidget.Game.Page = Base.Comp.inherits({ + + constructor: function (game) { + this.base(); + + this.addSlot('gameDestroyed'); + this.game = game; + this.game.bindEvent('Destroy', 'gameDestroyed', this); + }, + + writeContents: function (contents) { + var e = document.getElementById('gDisplay'); + if (e) { + e.innerHTML = contents; + } + }, + + gameDestroyed: function () { + this.game = null; + }, + + setData: function (data) { + }, + + draw: function (_e) { + }, + + show: function () { + }, + + hide: function () { + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/GameDisplay.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/GameDisplay.js new file mode 100644 index 0000000..10fe24e --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/GameDisplay.js @@ -0,0 +1,73 @@ +LWWidget.GameDisplay = LWWidget.Page.inherits({ + + constructor: function () { + this.base(); + + this.addSlot('gameDestroyed'); + this.addEvent('Refresh'); + + this.gPath = ''; + this.game = null; + }, + + destroy: function () { + if (this.game) { + this.game.destroy(); + } + this.base(); + }, + + + setGame: function (gClass, gPath) { + if (this.game) { + this.game.destroy(); + } + this.game = new gClass(gPath); + this.game.bindEvent('Destroy', 'gameDestroyed', this); + this.game.bindEvent('RequestUpdate', 'onRefresh', this); + }, + + gameDestroyed: function () { + this.game = null; + }, + + + handleData: function (data) { + if (this.game) { + this.game.setData(data); + } + }, + + + hide: function () { + if (this.game) { + this.game.hide(); + } + }, + + + show: function () { + if (this.game) { + this.game.show(); + } + this.draw(); + }, + + + setPage: function (pName) { + if (this.game) { + this.game.setPage(pName); + } + }, + + + draw: function () { + if (!document.getElementById('gDisplay')) { + this.writeContents('
 
'); + } + if (this.game) { + this.game.draw(); + } + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/GameSelector.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/GameSelector.js new file mode 100644 index 0000000..8c4ce54 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/GameSelector.js @@ -0,0 +1,65 @@ +LWWidget.GameSelector = LWWidget.Page.inherits({ + + constructor: function () { + this.base(); + this.games = []; + + this.addEvent('Select'); + }, + + + setGames: function (games) { + if (games instanceof Array) { + this.games = games; + this.draw(); + } + }, + + + draw: function () { + var cc = '"return Base.Comp.get(' + this._cid + ').select()"', + st = ' style="color:white;font-size:13px;background-color:#3f7fff;width:100%" ', + st2 = ' style="color:white;font-size:13px;background-color:#3f7fff" ', + str = '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
Game selection
 
' + + '
 
'; + this.writeContents(str); + + var _cb = document.getElementById('grem'); + if (_cb) { + _cb.checked = (LWWidget.main.getPreference('lwRemGame') == '1'); + } + }, + + select: function () { + var _s = document.getElementById('gsel'); + if (!_s) { + return false; + } + + var sv = parseInt(_s.options[_s.selectedIndex].value, 10), rem = document.getElementById('grem').checked; + LWWidget.main.setPreference('lwRemGame', rem ? '1' : '0'); + if (rem) { + LWWidget.main.setPreference('lwGame', this.games[sv].path); + } + + this.onSelect(this.games[sv]); + + return false; + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Login.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Login.js new file mode 100644 index 0000000..5355abb --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Login.js @@ -0,0 +1,77 @@ +LWWidget.Login = LWWidget.Page.inherits({ + + constructor: function () { + this.base(); + this.addEvent('Login'); + this.error = ""; + }, + + draw: function () { + var cc = '"return Base.Comp.get(' + this._cid + ').login()"', + st = ' style="color:white;font-size:13px;background-color:#3f7fff;width:100%" ', + st2 = ' style="color:white;font-size:13px;background-color:#3f7fff" ', + str = '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '
' + + '
' + + '
 
 
 
'; + this.writeContents(str); + this.fillForm(); + }, + + fillForm: function () { + var l = LWWidget.main.getPreference('lwLogin'), p = LWWidget.main.getPreference('lwPassword'), r = LWWidget.main.getPreference('lwRemAuth'); + if (!document.getElementById('lname')) { + return; + } + document.getElementById('lname').value = l; + document.getElementById('lpass').value = p; + document.getElementById('lrem').checked = (r == '1'); + document.getElementById("lerror").innerHTML = (this.error == "") ? ' ' : this.error; + }, + + + setError: function (error) { + this.error = error; + var e = document.getElementById("lerror"); + if (e) { + e.innerHTML = (error == "") ? ' ' : error; + } + }, + + + login: function () { + if (!document.getElementById('lname')) { + return false; + } + + var l = document.getElementById('lname').value, p = document.getElementById('lpass').value, + r = document.getElementById('lrem').checked ? '1' : '0'; + + if (l == '') { + this.setError("Please type in your username"); + } else if (p == '') { + this.setError("Please type in your password"); + } else { + // Save the login as a preference and store the rest until we're sure + LWWidget.main.setPreference('lwLogin', l); + LWWidget.main.setPreference('lwRemAuth', r); + LWWidget.main.password = p; + + this.onLogin(); + } + + return false; + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Main.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Main.js new file mode 100644 index 0000000..c7eda09 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Main.js @@ -0,0 +1,392 @@ +LWWidget.Main = Base.Comp.inherits({ + + constructor: function () { + this.base(); + + this.addEvent('Show'); + this.addEvent('Hide'); + this.addSlot('draw'); + this.bindEvent('Show', 'draw'); + + // Game-related slots + this.addSlot('login'); + this.addSlot('loadGame'); + this.addSlot('loadData'); + + // Version management slots + this.addSlot('versionError'); + this.addSlot('versionNewAvailable'); + this.addSlot('versionNewRequired'); + this.addSlot('versionOk'); + + // Generate the menu + this.makeMenu(); + + // Version checker + this.versionCheck = new LWWidget.VersionCheck(); + this.versionPage = new LWWidget.NewVersion(); + this.versionCheck.bindEvent('CommError', 'versionError', this); + this.versionCheck.bindEvent('NewVersion', 'versionNewAvailable', this); + this.versionCheck.bindEvent('RequiredVersion', 'versionNewRequired', this); + this.versionCheck.bindEvent('VersionOk', 'versionOk', this); + this.versionChecked = false; + + // Login page + this.loginPage = new LWWidget.Login(); + this.loginPage.bindEvent('Login', 'login', this); + + // Game selection page + this.gameSelector = new LWWidget.GameSelector(); + this.gameSelector.bindEvent('Select', 'loadGame', this); + + // Game display + this.gameDisplay = new LWWidget.GameDisplay(this); + this.gameDisplay.bindEvent('Refresh', 'loadData', this); + + // Generic text display + this.textPage = new LWWidget.TextPage(); + + // FIXME: Set Dashboard-specific event handlers + // this.initDashboard(); + + // FIXME: should use saved password if possible + this.page = this.loginPage; + this.menu.setMode('out'); + this.state = 0; + + this.onShow(); // FIXME: use Dashboard events if possible + }, + + + initDashboard: function () { + if (window.widget) { + var _cid = this._cid; + widget.onshow = function () { + if (Base) { + Base.Comp.get(_cid).onShow(); + } + }; + widget.onhide = function () { + if (Base) { + Base.Comp.get(_cid).onHide(); + } + }; + } + }, + + + makeMenu: function () { + var _m; + this.menu = new LWWidget.Menu(); + this.addSlot('menuCommand'); + this.menu.bindEvent('Click', 'menuCommand', this); + + // Waiting + _m = this.menu.newMode('wait'); + _m.addItem(new LWWidget.Menu.Text('Loading ... Please wait ...')); + + // New version available + _m = this.menu.newMode('nvAvail'); + _m.addItem(new LWWidget.Menu.Text('New version!     ')); + _m.addItem(new LWWidget.Menu.Command('continue', 'Continue')); + _m.addItem(new LWWidget.Menu.Command('logout', 'Log out')); + + // New version required + _m = this.menu.newMode('nvReq'); + _m.addItem(new LWWidget.Menu.Text('Update required     ')); + _m.addItem(new LWWidget.Menu.Command('logout', 'Log out')); + + // Logged out user + _m = this.menu.newMode('out'); + _m.addItem(new LWWidget.Menu.Text('Please log in')); + + // Game selection + _m = this.menu.newMode('game'); + _m.addItem(new LWWidget.Menu.Entry('gamesel', 'Game selection')); + _m.addItem(new LWWidget.Menu.Command('logout', 'Log out')); + + // Error + _m = this.menu.newMode('error'); + _m.addItem(new LWWidget.Menu.Text('Error! - ')); + _m.addItem(new LWWidget.Menu.Command('logout', 'Log out')); + + // Standard + _m = this.menu.newMode('std'); + _m.addItem(new LWWidget.Menu.Command('gamesel', 'Game selection')); + _m.addItem(new LWWidget.Menu.Entry('player', 'Player')); + _m.addItem(new LWWidget.Menu.Entry('planets', 'Planets')); + _m.addItem(new LWWidget.Menu.Entry('fleets', 'Fleets')); + _m.addItem(new LWWidget.Menu.Entry('budget', 'Budget')); + _m.addItem(new LWWidget.Menu.Entry('tech', 'Research')); + _m.addItem(new LWWidget.Menu.Entry('msg', 'Messages')); + _m.addItem(new LWWidget.Menu.Entry('forums', 'Forums')); + _m.addItem(new LWWidget.Menu.Entry('ticks', 'Ticks')); + _m.addItem(new LWWidget.Menu.Command('logout', 'Log out')); + }, + + + initLoader: function (login, password) { + this.loader = new LWWidget.Data.Loader(LWWidget.main.getPreference('lwLogin'), LWWidget.main.password); + + var events = ['CommError', 'FatalError', 'Kick', 'Load', 'LoginFailure', 'Maintenance', 'Destroy']; + var slots = ['commError', 'serverError', 'playerKicked', 'dataLoaded', 'loginFailed', 'serverMaintenance', 'loaderDestroyed']; + for (var i=0;i' + + '
' + + '' + + '' + + '' + + '
Legacy Worlds
 
 
'; + if (LWWidget.debug) { + str += '
 
'; + } + Base.Browser.get().docBody.innerHTML = str; + + this.menu.draw(); + this.page.draw(); + }, + + + setTextPage: function (mMode, text) { + this.menu.setMode(mMode); + this.page = this.textPage; + this.page.setText(text); + }, + + menuCommand: function (command) { + if (command == 'logout') { + if (this.loader) { + this.loader.destroy(); + } + if (this.versionCheck) { + this.versionCheck.stop(); + } + if (this.state == 3) { + this.gameDisplay.hide(); + } + this.state = 0; + this.versionChecked = false; + this.loginPage.setError(''); + this.page = this.loginPage; + this.menu.setMode('out'); + this.page.draw(); + } else if (command == 'continue') { + this.login(); + } else if (command == 'gamesel') { + if (this.state == 3) { + this.page.hide(); + } + this.state = 1; + this.setTextPage('wait', 'Connecting to the Legacy Worlds server

Please wait'); + this.loader.load('main/index'); + } else if (this.state == 3) { + this.gameDisplay.setPage(command); + } + }, + + + versionError: function () { + // Ignore the error if the version has already been checked + if (!this.versionChecked) { + this.commError(); + } + }, + + versionNewAvailable: function (url) { + // Ignore the new version unless it is the first time we check + if (this.versionChecked) { + return; + } + this.versionChecked = true; + + this.versionPage.setData(url, false); + this.page = this.versionPage; + this.menu.setMode('nvAvail'); + this.page.draw(); + }, + + versionNewRequired: function (url) { + if (this.state == 3) { + this.gameDisplay.hide(); + } + this.versionCheck.stop(); + this.versionPage.setData(url, true); + this.page = this.versionPage; + this.menu.setMode('nvReq'); + this.page.draw(); + }, + + versionOk: function () { + if (!this.versionChecked) { + // Continue login + this.versionChecked = true; + this.login(); + } + }, + + + login: function () { + this.setTextPage('wait', 'Connecting to the Legacy Worlds server

Please wait'); + + // If the version hasn't been checked, well, check it + if (!this.versionChecked) { + this.versionCheck.start(); + return; + } + + // Initialise the loader and get the game list + this.initLoader(); + this.loader.load('main/index'); + }, + + + handleGameList: function (data, force) { + this.state = 1; + + var games = new Array(), auto = null; + + if (!force && LWWidget.main.getPreference('lwRemGame') == '1') { + auto = LWWidget.main.getPreference('lwGame'); + } + + for (var i in data.contents) { + // Check if version is supported + var _v = data.contents[i].getAttribute('version'); + if (!LWWidget.Game.versions().containsKey(_v)) { + continue; + } + + var _g = { + name: data.contents[i].getText(), + path: data.contents[i].getAttribute('path'), + version: LWWidget.Game.versions().get(_v) + }; + if (auto && auto == _g.path) { + this.loadGame(_g); + return; + } + games.push(_g); + } + + if (games.length == 0) { + this.setTextPage('error', 'It appears that you are not registered to any Legacy Worlds game.'); + } else if (!force && games.length == 1) { + this.loadGame(games[0]); + } else { + this.page = this.gameSelector; + this.menu.setMode('game'); + this.gameSelector.setGames(games); + } + }, + + + loadGame: function (game) { + this.state = 2; + this.currentGame = game; + this.loader.load(game.path + '/play'); + }, + + loadData: function (path) { + if (this.state == 3 && this.loader) { + this.loader.load(path + '/play'); + } + }, + + + + commError: function () { + this.setTextPage('error', 'Communications error

The server could not be reached or it sent an invalid reply.'); + }, + + serverError: function (code, text) { + var str = 'Server error

The server encountered an internal error:
' + + text + '
(code: ' + code + ')'; + this.setTextPage('error', str); + }, + + playerKicked: function (reason) { + var str = (reason == '' ? 'No reason was specified' : ('Reason: ' + reason)); + if (this.state == 3) { + this.gameDisplay.hide(); + } + this.setTextPage('error', 'You have been kicked from the game!

' + str); + }, + + loginFailed: function (code) { + switch (code) { + case '0': + if (this.state == 0) { + this.loader.destroy(); + LWWidget.main.password = ''; + this.loginPage.setError('Invalid username or password'); + this.page = this.loginPage; + this.page.draw(); + } else { + this.setTextPage('error', 'Your password has been changed.'); + } + break; + case '1': + this.setTextPage('error', 'The server encountered an internal error while authenticating your account.'); + break; + case '2': + this.setTextPage('error', 'Your account has been closed!'); + break; + case '3': + this.setTextPage('error', 'Your account has not been validated yet, please check your e-mail.'); + break; + case '4': + if (this.state < 2) { + this.setTextPage('error', 'The server just did something it\'s not supposed to do. Ever.'); + } else { + this.gameDisplay.hide(); + this.setTextPage('errsel', 'You are no longer registered to this game!'); + } + break; + } + }, + + serverMaintenance: function (reason, until, now) { + var str = 'The server is undergoing maintenance.

Reason: ' + reason + + '

It will be down until ' + until + '.
Current server time: ' + now; + this.setTextPage('error', str); + }, + + loaderDestroyed: function () { + this.loader = null; + }, + + dataLoaded: function (data) { + if (this.state == 0) { + // We were not loging in, save password if needed + if (LWWidget.main.getPreference('lwRemAuth') == '1') { + LWWidget.main.setPreference('lwPassword', LWWidget.main.password); + } + + this.handleGameList(data); + } else if (this.state == 1) { + // We were logged in but asked to select a game + this.handleGameList(data, true); + } else if (this.state == 2) { + // We just received game data for the first time + this.gameDisplay.setGame(this.currentGame.version, this.currentGame.path); + this.gameDisplay.handleData(data); + this.page = this.gameDisplay; + this.menu.setMode('std'); + this.menu.setSelected('player'); + this.page.draw(); + this.state = 3; + } else { + // Update game contents + this.gameDisplay.handleData(data); + } + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Menu.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Menu.js new file mode 100644 index 0000000..23abe8c --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Menu.js @@ -0,0 +1,81 @@ +LWWidget.Menu = Base.Comp.inherits({ + + constructor: function () { + this.base(); + + this.currentMode = null; + this.modes = new Base.Util.Hashtable(); + + this.addSlot('modeDestroyed'); + this.addSlot('draw'); + this.addEvent('Click'); + this.bindEvent('Click', 'draw'); + }, + + destroy: function () { + var modes = this.modes.values(); + for (var i in modes) { + modes[i].destroy(); + } + this.base(); + }, + + + newMode: function (name) { + if (this.modes.containsKey(name)) { + return null; + } + + var mode = new LWWidget.Menu.Mode(name); + this.modes.put(name, mode); + if (!this.currentMode) { + this.currentMode = mode; + } + + mode.bindEvent('Destroy', 'modeDestroyed', this); + mode.bindEvent('Click', 'onClick', this); + + return mode; + }, + + setMode: function (name) { + if (!this.modes.containsKey(name) || this.currentMode && this.currentMode.name == name) { + return; + } + this.currentMode = this.modes.get(name); + this.draw(); + }, + + setSelected: function (name) { + if (this.currentMode) { + this.currentMode.setSelected(name); + this.draw(); + } + }, + + modeDestroyed: function (mode) { + if (!(mode && mode._cid && mode.name && this.modes.containsKey(mode.name))) { + return; + } + + this.modes.remove(mode.name); + if (this.currentMode && this.currentMode._cid == mode._cid) { + var _v = this.modes.values(); + if (_v.length) { + this.currentMode = _v[0]; + } else { + this.currentMode = null; + } + } + }, + + + draw: function () { + var e = document.getElementById('mtMenu'); + if (!(e && this.currentMode)) { + return; + } + e.innerHTML = this.currentMode.draw(); + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Menu/Command.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Menu/Command.js new file mode 100644 index 0000000..d4af539 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Menu/Command.js @@ -0,0 +1,7 @@ +LWWidget.Menu.Command = LWWidget.Menu.Item.inherits({ + + constructor: function (name, text) { + this.base(name, text, true, false, ' - '); + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Menu/Entry.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Menu/Entry.js new file mode 100644 index 0000000..aad8595 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Menu/Entry.js @@ -0,0 +1,7 @@ +LWWidget.Menu.Entry = LWWidget.Menu.Item.inherits({ + + constructor: function (name, text) { + this.base(name, text, true, true, ' - '); + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Menu/Item.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Menu/Item.js new file mode 100644 index 0000000..03e3526 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Menu/Item.js @@ -0,0 +1,38 @@ +LWWidget.Menu.Item = Base.Comp.inherits({ + + constructor: function (name, text, clickable, selectable, separator) { + this.base(); + + this.name = name; + this.text = text; + this.clickable = !!clickable; + this.selectable = this.clickable && !!selectable; + this.separator = (typeof separator == 'string') ? separator : ''; + this.selected = false; + + this.addEvent('Click'); + }, + + draw: function () { + var pfx, sfx; + if (this.clickable && !this.selected) { + pfx = ''; + sfx = ''; + } else if (this.selectable && this.selected) { + pfx = ''; + sfx = ''; + } else { + pfx = sfx = ''; + } + + return pfx + this.text + sfx; + }, + + click: function () { + if (this.selectable) { + this.selected = true; + } + this.onClick(this); + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Menu/Mode.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Menu/Mode.js new file mode 100644 index 0000000..8d92b0a --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Menu/Mode.js @@ -0,0 +1,109 @@ +LWWidget.Menu.Mode = Base.Comp.inherits({ + + constructor: function (name) { + this.base(); + + this.name = name; + this.items = new Base.Util.Hashtable(); + this.selected = null; + + this.addSlot('itemDestroyed'); + this.addSlot('itemClicked'); + this.addEvent('Click'); + }, + + destroy: function () { + var items = this.items.values(); + for (var i in items) { + var _i = items[i]; + this.items.remove(items[i]._cid); + items[i].destroy(); + } + + this.base(); + }, + + draw: function () { + var str = '', items = this.items.values(); + + for (var i=0;iDownload the new version'; + + this.writeContents(_s); + }, + + + startDownload: function () { + if (window.widget) { + widget.openURL(this.url); + } else { + window.open(this.url); + } + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Page.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Page.js new file mode 100644 index 0000000..9f993a8 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Page.js @@ -0,0 +1,17 @@ +LWWidget.Page = Base.Comp.inherits({ + + constructor: function () { + this.base(); + + this.addEvent('Show'); + this.addEvent('Hide'); + }, + + writeContents: function (contents) { + var e = document.getElementById('mtContents'); + if (e) { + e.innerHTML = contents; + } + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/TextPage.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/TextPage.js new file mode 100644 index 0000000..152482b --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/TextPage.js @@ -0,0 +1,18 @@ +LWWidget.TextPage = LWWidget.Page.inherits({ + + constructor: function () { + this.text = ""; + }, + + setText: function (text) { + if (this.text != text) { + this.text = text; + } + this.draw(); + }, + + draw: function () { + this.writeContents(this.text == "" ? ' ' : this.text); + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/VersionCheck.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/VersionCheck.js new file mode 100644 index 0000000..7918974 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/VersionCheck.js @@ -0,0 +1,101 @@ +LWWidget.VersionCheck = Base.Comp.inherits({ + + constructor: function () { + this.base(); + + this.addEvent('CommError'); + this.addEvent('NewVersion'); + this.addEvent('RequiredVersion'); + this.addEvent('VersionOk'); + + this.addSlot('dataReceived'); + this.addSlot('loaderDestroyed'); + this.addSlot('timerDestroyed'); + + this.loader = new Base.XMLLoader(LWWidget.base + '/index.php/main/macwidget.xml', false); + this.loader.bindEvent('Destroy', 'loaderDestroyed', this); + this.loader.bindEvent('NetworkError', 'onCommError', this); + this.loader.bindEvent('ServerError', 'onCommError', this); + this.loader.bindEvent('Timeout', 'onCommError', this); + this.loader.bindEvent('Unsupported', 'onCommError', this); + this.loader.bindEvent('Aborted', 'onCommError', this); + this.loader.bindEvent('Loaded', 'dataReceived', this); + + this.timer = new Base.Timer(300000, true); + this.timer.bindEvent('Tick', 'load', this.loader); + this.timer.bindEvent('Destroy', 'timerDestroyed', this); + + this.knownVersion = LWWidget.version; + }, + + + destroy: function () { + if (this.loader) { + this.loader.destroy(); + } + if (this.timer) { + this.timer.destroy(); + } + this.base(); + }, + + + timerDestroyed: function () { + this.timer = null; + }, + + loaderDestroyed: function () { + this.loader = null; + }, + + + start: function () { + if (typeof this.requiredVersion != 'undefined' && LWWidget.version < this.requiredVersion) { + return; + } + this.timer.start(); + this.loader.load(); + }, + + stop: function () { + this.timer.stop(); + this.knownVersion = LWWidget.version; + delete this.latestVersion; + delete this.requiredVersion; + }, + + + dataReceived: function (data) { + var _xml = Base.Util.parseXML(data); + if (!_xml) { + this.onCommError(); + return; + } + + var _doc = _xml.documentElement; + this.latestVersion = parseInt(_doc.getAttribute('latest'), 10); + this.requiredVersion = parseInt(_doc.getAttribute('oldestOk'), 10); + this.url = _doc.childNodes[0].nodeValue; + + if (this.knownVersion == this.latestVersion && this.requiredVersion <= LWWidget.version) { + this.onVersionOk(); + return; + } + + Base.Log.write('Version check: current version = ' + LWWidget.version); + Base.Log.write('Version check: latest received version = ' + this.knownVersion); + Base.Log.write('Version check: latest version = ' + this.latestVersion); + Base.Log.write('Version check: oldest accepted version = ' + this.requiredVersion); + + this.knownVersion = this.latestVersion; + if (LWWidget.version < this.requiredVersion) { + this.onRequiredVersion(this.url); + this.timer.stop(); + } else if (LWWidget.version < this.latestVersion) { + this.onNewVersion(this.url); + } else { + this.onVersionOk(); + } + } + +}); diff --git a/misc/Legacy Worlds.wdgt/lib/LWWidget/Versions.js b/misc/Legacy Worlds.wdgt/lib/LWWidget/Versions.js new file mode 100644 index 0000000..ae403de --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/LWWidget/Versions.js @@ -0,0 +1,2 @@ +LWWidget.Versions = new Base.Util.Hashtable(); +LWWidget.Versions.put('beta5', LWWidget.Beta5); diff --git a/misc/forums-branch.patch b/misc/forums-branch.patch new file mode 100644 index 0000000..d43fa31 --- /dev/null +++ b/misc/forums-branch.patch @@ -0,0 +1,11688 @@ +diff -Naur beta5//scripts/config.inc forums//scripts/config.inc +--- beta5//scripts/config.inc 2011-02-05 10:09:57.904335002 +0100 ++++ forums//scripts/config.inc 2011-03-12 15:47:09.431300051 +0100 +@@ -39,7 +39,7 @@ + "widgetURL" => "http://www.legacyworlds.com/downloads/LegacyWorlds-Dashboard-latest.zip", + + // Version numbers to make us feel good +- "v_engine" => "0.85a", ++ "v_engine" => "0.86", + "v_game" => "Beta 5", + "v_rev" => "2218", + +diff -Naur beta5//scripts/game/beta5/actions/getForums.inc forums//scripts/game/beta5/actions/getForums.inc +--- beta5//scripts/game/beta5/actions/getForums.inc 1970-01-01 01:00:00.000000000 +0100 ++++ forums//scripts/game/beta5/actions/getForums.inc 2011-02-05 10:10:03.244335002 +0100 +@@ -0,0 +1,84 @@ ++ array( ++ 'en' => array('General forums', "LegacyWorlds' public forums"), ++ 'fr' => array('Forums généraux', "Les forums publics de LegacyWorlds") ++ ), ++ 'U' => array( ++ 'en' => array('Player forums', "Forums belonging to other LegacyWorlds players"), ++ 'fr' => array('Forums des joueurs', "Forums appartenant à d'autres joueurs de LegacyWorlds") ++ ) ++ ); ++ ++ public function __construct($game) { ++ $this->game = $game; ++ $this->db = $this->game->getDBAccess(); ++ ++ // Load the forums' access libraries ++ $this->gForums = $this->game->getLib('main/gforums'); ++ $this->uForums = $this->game->getLib('main/uforums'); ++ $this->aForums = $this->game->getLib('beta5/aforums'); ++ ++ // Load the container class ++ loader::needClasses('main/forums', 'fl_container'); ++ } ++ ++ public function run( $player ) { ++ // Get the player's user ID ++ $q = $this->db->query("SELECT userid FROM player WHERE id = $1", (int) $player); ++ if (!( $q && dbCount($q) == 1)) { ++ return null; ++ } ++ list($user) = dbFetchArray($q); ++ ++ // Get the language string for the user ++ // FIXME: that thing would not work inside anything *but* a web session ++ $lang = getLanguage(); ++ ++ // Get the list of general, user-specific and alliance forums ++ $gForums = $this->gForums->call('getStructure', $user); ++ $aForums = $this->aForums->call('getStructure', $user); ++ $uForums = $this->uForums->call('getStructure', $user); ++ ++ // Generate the container for the whole thing ++ $forums = new fl_container('/', '', ''); ++ ++ // Generate the container for general forums ++ $gContainer = new fl_container('G', self::$names['G'][$lang][0], self::$names['G'][$lang][1]); ++ foreach ($gForums as $gc) { ++ $gContainer->addCategory($gc); ++ } ++ $forums->addCategory($gContainer); ++ ++ // Add the alliance forums ++ if (count($aForums) == 1) { ++ $forums->addCategory($aForums[0]); ++ } ++ ++ // Add the player's own forums ++ $forums->addCategory(array_shift($uForums)); ++ ++ // Add the other players' forums ++ if (count($uForums)) { ++ $uContainer = new fl_container('U', self::$names['U'][$lang][0], self::$names['U'][$lang][1]); ++ foreach ($uForums as $uc) { ++ $uContainer->addCategory($uc); ++ } ++ $forums->addCategory($uContainer); ++ } ++ ++ return $forums; ++ } ++} ++ ++?> +diff -Naur beta5//scripts/game/beta5/actions.inc forums//scripts/game/beta5/actions.inc +--- beta5//scripts/game/beta5/actions.inc 2011-02-05 10:09:57.784335002 +0100 ++++ forums//scripts/game/beta5/actions.inc 2011-03-12 14:56:24.591300053 +0100 +@@ -29,6 +29,10 @@ + var $fleetDepartures = array(); + var $ePower = array(); + ++ var $index = array( ++ 'getForums' ++ ); ++ + + function actions_beta5($game) { + $this->game = $game; +diff -Naur beta5//scripts/game/beta5/aforums/library.inc forums//scripts/game/beta5/aforums/library.inc +--- beta5//scripts/game/beta5/aforums/library.inc 1970-01-01 01:00:00.000000000 +0100 ++++ forums//scripts/game/beta5/aforums/library.inc 2011-02-05 10:10:03.334335002 +0100 +@@ -0,0 +1,256 @@ ++lib = $lib; ++ $this->game = $this->lib->game; ++ $this->version = $this->game->version; ++ $this->db = $this->game->db; ++ ++ $this->fLib = $this->game->getLib('main/forums'); ++ loader::needClasses('main/forums', array('fl_category', 'fl_forum')); ++ ++ $this->getPlayerQ = $this->db->prepare( ++ "SELECT id FROM player WHERE userid = $1 AND (quit IS NULL OR quit > UNIX_TIMESTAMP(NOW()))", ++ array("user")); ++ $this->getAllianceCatQ = $this->db->prepare( ++ "SELECT a.f_category FROM player p, alliance a " ++ . "WHERE a.id = p.alliance AND p.alliance IS NOT NULL AND p.a_status='IN ' AND p.id = $1", ++ array("player") ); ++ $this->getAllianceQ = $this->db->prepare( ++ "SELECT a.tag, a.name FROM alliance a WHERE a.f_category = $1", ++ array("category") ); ++ $this->getCatForumsQ = $this->db->prepare( ++ "SELECT id FROM forums.t_forum WHERE category = $1 ORDER BY f_order", array("category") ); ++ $this->getForumPrivQ = $this->db->prepare( ++ "SELECT * FROM get_aforums_privs( $1, $2 )", array("player", "forum") ); ++ $this->getForumQ = $this->db->prepare( ++ "SELECT * FROM alliance_forum WHERE forum = $1", array("forum") ); ++ $this->getForumRanksQ = $this->db->prepare( ++ "SELECT rank, is_mod FROM al_rank_forum WHERE forum = $1", array("forum") ); ++ $this->getRankNameQ = $this->db->prepare( ++ "SELECT CASE name IS NULL WHEN TRUE THEN '-' ELSE name END FROM alliance_grade " ++ . "WHERE id = $1", ++ array("rank") ); ++ } ++ ++ public function getStructure( $user ) { ++ $cats = array(); ++ ++ $q = $this->getPlayerQ->execute($user); ++ if ($q && dbCount($q) == 1) { ++ list($player) = dbFetchArray($q); ++ ++ $q = $this->getAllianceCatQ->execute($player); ++ if ($q && dbCount($q)) { ++ list($catId) = dbFetchArray($q); ++ $cat = $this->fLib->call('getCategory', $catId, $user); ++ if (! is_null($cat)) { ++ array_push($cats, $cat); ++ } ++ } ++ } ++ ++ return $cats; ++ } ++ ++ public function getCategory( $catId , $user ) { ++ $q = $this->getAllianceQ->execute($catId); ++ if (! ($q && dbCount($q) == 1)) { ++ return null; ++ } ++ list($tag, $name) = dbFetchArray($q); ++ ++ $title = "[$tag] $name"; ++ $description = "These are the forums for the alliance you are a member of, $name."; ++ ++ $cat = new fl_category( $this->game, $user, $catId, $title, $description ); ++ ++ $q = $this->getCatForumsQ->execute($catId); ++ while ($r = dbFetchArray($q)) { ++ $forum = $this->fLib->call('getForum', $r[0], $user); ++ if ($forum->canView() && ($forum->isAdmin() || ! $forum->isDeleted())) { ++ $cat->addForum( $forum ); ++ } ++ } ++ ++ return $cat; ++ } ++ ++ ++ public function getForum( $forumId, $user ) { ++ $q = $this->getPlayerQ->execute($user); ++ if (!($q && dbCount($q) == 1)) { ++ return null; ++ } ++ list($player) = dbFetchArray($q); ++ ++ $q = $this->getForumPrivQ->execute($player, $forumId); ++ if (! ($q && dbCount($q) == 1)) { ++ return null; ++ } ++ $privs = dbFetchHash($q); ++ ++ try { ++ $forum = new fl_forum( $this->game, $forumId, $user ); ++ } catch (Exception $e) { ++ return null; ++ } ++ ++ $forum->setAdmin( $privs['is_admin'] == 't' ); ++ $forum->setMod( $privs['is_mod'] == 't' ); ++ $forum->setCreateTopic( $privs['can_create'] == 't' ); ++ $forum->setCreatePoll( $privs['can_poll'] == 't' ); ++ $forum->setPost( $privs['can_post'] == 't' ); ++ $forum->setView( $privs['can_view'] == 't' ); ++ ++ return $forum; ++ } ++ ++ public function getAdmins($forum) { ++ $q = $this->getForumQ->execute($forum); ++ if (!($q && dbCount($q) == 1)) { ++ return null; ++ } ++ $r = dbFetchHash($q); ++ $alliance = $r['alliance']; ++ ++ if (! is_null(self::$allianceAdmins[$alliance])) { ++ return self::$allianceAdmins[$alliance]; ++ } ++ ++ if (is_null($this->alliance)) { ++ $this->alliance = $this->game->getLib('beta5/alliance'); ++ } ++ ++ $l = array_keys($this->alliance->call('getRanks', $alliance)); ++ $admin = array(); ++ foreach ($l as $rId) { ++ $pr = $this->alliance->call('getRankPrivileges', $rId); ++ if (! $pr['forum_admin']) { ++ continue; ++ } ++ array_push($admin, $rId); ++ } ++ ++ return (self::$allianceAdmins[$alliance] = $admin); ++ } ++ ++ public function getModerators($forum) { ++ $rl = $this->getRankAccess($forum); ++ $rv = array(); ++ foreach ($rl as $rId => $isMod) { ++ if ($isMod) { ++ array_push($rv, $rId); ++ } ++ } ++ return $rv; ++ } ++ ++ public function getUsers($forum) { ++ $rl = $this->getRankAccess($forum); ++ $rv = array(); ++ foreach ($rl as $rId => $isMod) { ++ if (! $isMod) { ++ array_push($rv, $rId); ++ } ++ } ++ return $rv; ++ } ++ ++ ++ private function getRankAccess($forum) { ++ if (! is_null(self::$rAccess[$forum])) { ++ return self::$rAccess[$forum]; ++ } ++ ++ $rv = array(); ++ $q = $this->getForumRanksQ->execute($forum); ++ ++ if ($q && dbCount($q)) { ++ while ($r = dbFetchHash($q)) { ++ $rv[$r['rank']] = ($r['is_mod'] == 't'); ++ } ++ } ++ ++ return (self::$rAccess[$forum] = $rv); ++ } ++ ++ ++ public function aclIdToName($id) { ++ $q = $this->getRankNameQ->execute($id); ++ if (!($q && dbCount($q) == 1)) { ++ return null; ++ } ++ list($rv) = dbFetchArray($q); ++ return $rv; ++ } ++ ++ ++ ++ public function getUserPrivileges($forum) { ++ $q = $this->getForumQ->execute($forum); ++ if (!($q && dbCount($q) == 1)) { ++ return null; ++ } ++ $r = dbFetchHash($q); ++ return $r['access_mode']; ++ } ++ ++ ++ public function create($player, $user, $alliance, $order, $name, $description, $accessMode) { ++ $q = $this->db->query("SELECT create_alliance_forum($1, $2, $3, $4, $5, $6)", ++ $player, $alliance, $order, $name, $description, $accessMode); ++ if (!($q && dbCount($q))) { ++ return -6; ++ } ++ list($rv) = dbFetchArray($q); ++ ++ if ($rv > 0) { ++ $forum = $this->fLib->call('getForum', $rv, $user); ++ if (is_null($forum)) { ++ return -6; ++ } ++ $forum->getCategory()->insertNewForum($forum); ++ $rv = $forum; ++ } else { ++ $rv = -$rv; ++ } ++ ++ return $rv; ++ } ++ ++ public function modifyForum($forum, $player, $name, $description, $accessMode) { ++ $this->db->query("SELECT modify_alliance_forum($1,$2,$3,$4,$5)", ++ $player, $forum, $name, $description, $accessMode); ++ if (!($q && dbCount($q))) { ++ return -5; ++ } ++ list($rv) = dbFetchArray($q); ++ return $rv; ++ } ++ ++ public function clearForumACL($forum) { ++ $this->db->query("DELETE FROM al_rank_forum WHERE forum = $1", $forum); ++ } ++ ++ public function addForumModerator( $forum, $id ) { ++ $this->db->query("INSERT INTO al_rank_forum (rank, forum, is_mod) VALUES ($1, $2, TRUE)", ++ $id, $forum); ++ } ++ ++ public function addForumUser( $forum, $id ) { ++ $this->db->query("INSERT INTO al_rank_forum (rank, forum, is_mod) VALUES ($1, $2, FALSE)", ++ $id, $forum); ++ } ++} ++ ++?> +diff -Naur beta5//scripts/game/beta5/forums/topic/library.inc forums//scripts/game/beta5/forums/topic/library.inc +--- beta5//scripts/game/beta5/forums/topic/library.inc 1970-01-01 01:00:00.000000000 +0100 ++++ forums//scripts/game/beta5/forums/topic/library.inc 2011-02-05 10:10:03.334335002 +0100 +@@ -0,0 +1,330 @@ ++lib = $lib; ++ $this->game = $this->lib->game; ++ } ++ ++ public function initialize($pageHandler) { ++ $this->pHandler = $pageHandler; ++ } ++ ++ public function forumCommand($player, $commandArgs) { ++ ++ // Read the arguments ++ if (strstr($commandArgs, "#") !== FALSE) { ++ list( $topicIdS, $hexId ) = explode('#', $commandArgs); ++ if (strlen($hexId) != 32 || preg_match('/[^A-Z0-9]/i', $hexId)) { ++ $hexId = md5(uniqid(rand())); ++ } ++ ++ $topicId = (int) $topicIdS; ++ $data = $this->pHandler->getSessionData('topic_' . $hexId); ++ if (is_null($data) || $topicId != $data['topic']) { ++ $hexId = md5(uniqid(rand())); ++ $firstTime = true; ++ } else { ++ $firstTime = false; ++ } ++ } else { ++ $topicId = (int) $commandArgs; ++ $hexId = md5(uniqid(rand())); ++ $firstTime = true; ++ } ++ ++ // Get the player's user ID ++ $pinf = $this->game->getLib('beta5/player')->call('get', $player); ++ $user = $pinf['uid']; ++ ++ if ($firstTime) { ++ $this->game->action('getForums', $player); ++ $data = $this->initTopicData($user, $topicId); ++ $this->makeDataString($data, $hexId); ++ } else { ++ $this->updateTopicData($topicId, $data); ++ } ++ $this->pHandler->setSessionData('topic_' . $hexId, $data); ++ $this->data = $data; ++ ++ if ($data['exists']) { ++ $rv = array("V#F#{$data['forum']}", "vTopic", "$topicId#$hexId"); ++ } else { ++ $rv = array("", "vTopic", "$topicId#$hexId"); ++ } ++ return $rv; ++ } ++ ++ ++ /* This method initialises the stored session data for the specified topic ID. ++ */ ++ private function initTopicData($user, $topicId) { ++ $data = array( ++ "viewTime" => time(), ++ "topic" => $topicId, ++ "user" => $user ++ ); ++ ++ // Check whether the topic actually exists ++ $topic = $this->game->getLib('main/forums')->call('getTopic', $topicId, $user); ++ if (is_null($topic)) { ++ $data['exists'] = false; ++ return $data; ++ } ++ ++ // Check whether the topic is available ++ $forum = $this->forum = $topic->getForum(); ++ if (is_null($forum) || ! $forum->canView()) { ++ $data['exists'] = false; ++ return $data; ++ } ++ ++ // The topic exists ++ $data['exists'] = true; ++ $data['forum'] = $forum->getId(); ++ $data['title'] = $topic->getTitle(); ++ ++ // Has it been deleted? ++ if ($topic->isDeleted()) { ++ $data['deleted'] = true; ++ $data['deletedAt'] = $topic->getDeletionTime(); ++ $data['deletedBy'] = $topic->getDeletionMod(); ++ return $data; ++ } ++ ++ // Get additional data ++ $users = array(); ++ $data['lastChange'] = $topic->getLastChange(); ++ $data['lastRead'] = $topic->getLastRead(); ++ $data['isLocked'] = $topic->isLocked(); ++ $data['isMod'] = $forum->isMod(); ++ $data['canPost'] = $forum->canPost(); ++ ++ // Get the topic's posts ++ $data['postOrder'] = array(); ++ ++ // -> linear, newest first ++ $list = $topic->getPostList(false, false); ++ $data['postOrder']['ln'] = array(); ++ foreach ($list as $p) { ++ array_push($data['postOrder']['ln'], $p->getId()); ++ } ++ ++ // -> linear, oldest first ++ $list = $topic->getPostList(false, true); ++ $data['postOrder']['lo'] = array(); ++ foreach ($list as $p) { ++ array_push($data['postOrder']['lo'], $p->getId()); ++ } ++ ++ // -> threaded, newest first ++ $list = $topic->getPostList(true, false); ++ $data['postOrder']['tn'] = array(); ++ foreach ($list as $p) { ++ array_push($data['postOrder']['tn'], $p->getId()); ++ } ++ ++ // -> threaded, oldest first ++ $list = $topic->getPostList(true, true); ++ $data['postOrder']['to'] = array(); ++ foreach ($list as $p) { ++ array_push($data['postOrder']['to'], $p->getId()); ++ } ++ ++ // -> post data ++ $data['posts'] = array(); ++ foreach ($list as $p) { ++ // post_id # depth # author_id # posted_at # is_unread # lc_time # lc_author ++ // post_title ++ $pStr = $p->getId() . "#" . $p->getDepth() . "#" . $p->getPostedBy() ++ . "#" . $p->getPostedAt() . "#" . ($p->isUnread() ? 1 : 0) ++ . "#" . $p->getLastChange() . "#" . $p->getLastChangeAuthor() ++ . "\n" . utf8entities($p->getTitle()); ++ array_push($data['posts'], $pStr); ++ if (!in_array($p->getPostedBy(), $users)) { ++ array_push($users, $p->getPostedBy()); ++ } ++ if (!(is_null($p->getLastChangeAuthor()) || in_array($p->getLastChangeAuthor(), $users))) { ++ array_push($users, $p->getLastChangeAuthor()); ++ } ++ } ++ ++ // Get poll data ++ $poll = $topic->getPoll(); ++ $data['hasPoll'] = ! is_null($poll); ++ if ($data['hasPoll']) { ++ // FIXME: get poll data ++ } ++ ++ // Get user names ++ $data['users'] = $this->dumpNames($users); ++ ++ return $data; ++ } ++ ++ private function dumpNames($ids) { ++ $accLib = $this->game->getLib('main/account'); ++ ++ $output = array(); ++ array_push($output, count($ids)); ++ foreach ($ids as $id) { ++ array_push($output, "$id#" . utf8entities($accLib->call('getUserName', $id))); ++ } ++ return $output; ++ } ++ ++ /* This method generates the data string to be sent the first time a topic ++ * page is loaded. ++ */ ++ private function makeDataString(&$data, $hexId) { ++ $result = array( ++ "{$data['topic']}#$hexId" ++ ); ++ if ($data['exists']) { ++ // Fetch the path to the topic ++ $parents = array(); ++ $obj = $this->forum; ++ do { ++ array_push($parents, array( ++ $obj->getId(), utf8entities($obj->getTitle()) ++ )); ++ $obj = $obj->getParent(); ++ } while (! is_null($obj)); ++ ++ if ($data['deleted']) { ++ // If the topic has been deleted ++ array_push($result, "DELETED#{$data['deletedAt']}#" . count($parents)); ++ array_push($result, utf8entities($data['title'])); ++ // FIXME: fetch moderator's name ++// array_push($result, $this->game->getLib(' ++ ++ // Dump the path ++ foreach (array_reverse($parents) as $p) { ++ array_push($result, join('#', $p)); ++ } ++ ++ } else { ++ // If the topic is available ++ array_push($result, "TOPIC#" . count($parents)); ++ array_push($result, utf8entities($data['title'])); ++ array_push($result, "{$data['user']}#" . ($data['isMod'] ? 1 : 0) ++ . "#" . ($data['canPost'] ? 1 : 0) . "#" . ($data['hasPoll'] ? 1 : 0)); ++ ++ // Dump the path ++ foreach (array_reverse($parents) as $p) { ++ array_push($result, join('#', $p)); ++ } ++ ++ // Post orders ++ array_push($result, join('#', $data['postOrder']['ln'])); ++ array_push($result, join('#', $data['postOrder']['lo'])); ++ array_push($result, join('#', $data['postOrder']['tn'])); ++ array_push($result, join('#', $data['postOrder']['to'])); ++ ++ // Posts ++ foreach ($data['posts'] as $post) { ++ array_push($result, $post); ++ } ++ ++ // Users ++ foreach ($data['users'] as $user) { ++ array_push($result, $user); ++ } ++ ++ // Get the viewing options ++ array_push($result, join('#', $this->getOptions($data['topic']))); ++ } ++ } else { ++ array_push($result, "MEH"); ++ } ++ ++ $data['initString'] = join("\n", $result); ++ } ++ ++ /** This method checks for updates on the topic */ ++ private function updateTopicData($topicId, &$data) { ++ $this->topic = $topic = $this->game->getLib('main/forums')->call('getTopic', $topicId, $user); ++ if (is_null($topic)) { ++ $data['stillExists'] = false; ++ return $data; ++ } ++ ++ // Check whether the topic is available ++ $forum = $this->forum = $topic->getForum(); ++ if (is_null($forum) || ! $forum->canView()) { ++ $data['stillExists'] = false; ++ return $data; ++ } ++ ++ // The topic exists ++ $data['stillExists'] = true; ++ ++ // Has it been deleted? ++ if ($topic->isDeleted()) { ++ $data['nowDeleted'] = true; ++ $data['deletedAt'] = $topic->getDeletionTime(); ++ $data['deletedBy'] = $topic->getDeletionMod(); ++ return $data; ++ } ++ ++ // Topic hasn't been deleted, check for updates ++ $data['lastChange'] = $topic->getLastChange(); ++ } ++ ++ public function getData() { ++ if (is_null($this->data['updateString'])) { ++ return $this->data['initString']; ++ } ++ } ++ ++ ++ private function getOptions( $id ) { ++ if (! is_null($this->forum)) { ++ $isMod = $this->forum->isMod(); ++ } else { ++ $isMod = false; ++ } ++ ++ // perPage = posts / page, default 50, possible values 25 50 75 100 ++ $perPage = prefs::get("main/T#PP#$id", prefs::get("main/T#PP", 50)); ++ // vDeleted = view deleted posts, default "no" ++ $vDeleted = $isMod ? prefs::get("main/T#VD#$id", prefs::get("main/T#VD", 0)) : 0; ++ // threaded = enable threaded mode, default "yes" ++ $threaded = prefs::get("main/T#TV#$id", prefs::get("main/T#TV", 1)); ++ // order = show oldest posts first, default "no" ++ $order = prefs::get("main/T#PO#$id", prefs::get("main/T#PO", 0)); ++ // openPosts = posts open by default, default 1, possible values 0: none, 1: new only, 2: all ++ $openPosts = prefs::get("main/T#OP#$id", prefs::get("main/T#OP", 1)); ++ ++ return array($perPage, $vDeleted, $threaded, $order, $openPosts); ++ } ++ ++ ++ public function getPostContents($postId) { ++ $postId = (int) $postId; ++ if (! ($this->data['exists'] && $this->data['stillExists'])) { ++ return "-#$postId"; ++ } ++ ++ $post = $this->topic->getPostById($postId); ++ if (is_null($post)) { ++ return "-#$postId"; ++ } ++ ++ $contents = $this->game->getLib('main/forums')->call('substitute', ++ $post->getContents($this->data['viewTime']), ++ $post->codeEnabled($this->data['viewTime']) ++ ); ++ ++ return "+#$postId\n$contents"; ++ } ++} ++ ++?> +diff -Naur beta5//scripts/game/beta5/forums/view/library.inc forums//scripts/game/beta5/forums/view/library.inc +--- beta5//scripts/game/beta5/forums/view/library.inc 1970-01-01 01:00:00.000000000 +0100 ++++ forums//scripts/game/beta5/forums/view/library.inc 2011-02-05 10:10:03.334335002 +0100 +@@ -0,0 +1,405 @@ ++lib = $lib; ++ $this->game = $this->lib->game; ++ } ++ ++ public function initialize($pageHandler) { ++ $this->pHandler = $pageHandler; ++ } ++ ++ public function forumCommand($player, $commandArgs) { ++ // Examine the arguments ++ $args = explode('#', $commandArgs); ++ if (count($args) != 2 || ! in_array($args[0], array('F', 'C'))) { ++ $args = array('C', '/'); ++ } ++ ++ // Try to find the requested object ++ $this->fStructure = $this->game->action('getForums', $player); ++ if ($args[0] == 'C') { ++ $this->displayObject = $this->fStructure->findCategory($args[1]); ++ } else { ++ $this->displayObject = $this->fStructure->findForum($args[1]); ++ if (! is_null($this->displayObject) && ( ++ ($this->displayObject->isDeleted() && ! $this->displayObject->isAdmin()) ++ || ! $this->displayObject->canView()) ) { ++ $this->displayObject = null; ++ } ++ } ++ if (is_null($this->displayObject)) { ++ $this->displayObject = $this->fStructure; ++ $args = array('C', '/'); ++ } ++ ++ $view = join('#', $args); ++ return array("V#$view", $args[0] == 'C' ? 'vCat' : 'vForum', $view); ++ } ++ ++ public function getData($oldMD5 = null) { ++ if (is_null($this->displayObject)) { ++ $r = null; ++ } else { ++ if ($this->displayObject instanceof fl_forum) { ++ $r = $this->getForumData(); ++ } else { ++ $r = $this->getCategoryData(); ++ } ++ $md5 = md5(serialize($r)); ++ if ($md5 != $oldMD5) { ++ array_unshift($r, $md5); ++ $r = join("\n", $r); ++ } else { ++ $r = '-'; ++ } ++ } ++ return $r; ++ } ++ ++ public function categoryRead($category) { ++ if (is_null($this->displayObject)) { ++ return; ++ } ++ ++ $cat = $this->displayObject->findCategory((int) $category); ++ if (is_null($cat) || ! ($cat instanceof fl_category)) { ++ return; ++ } ++ $cat->markRead(); ++ } ++ ++ public function forumRead() { ++ if (is_null($this->displayObject) || ! $this->displayObject instanceof fl_forum ) { ++ return; ++ } ++ ++ $this->displayObject->markRead(); ++ } ++ ++ public function restoreTopics($topicList) { ++ if (is_null($this->displayObject) || ! $this->displayObject instanceof fl_forum ++ || ! $this->displayObject->isMod() ) { ++ return; ++ } ++ ++ foreach ($topicList as $topicId) { ++ $topic = $this->displayObject->findTopic($topicId); ++ if (! is_null($topic) && $topic->isInForum($this->displayObject)) { ++ $topic->restore(); ++ } ++ } ++ $this->displayObject->refresh(); ++ } ++ ++ public function deleteTopics($topicList) { ++ if (is_null($this->displayObject) || ! $this->displayObject instanceof fl_forum ++ || ! $this->displayObject->isMod() ) { ++ return; ++ } ++ ++ foreach ($topicList as $topicId) { ++ $topic = $this->displayObject->findTopic($topicId); ++ if (!is_null($topic) && $topic->isInForum($this->displayObject)) { ++ $topic->delete(); ++ } ++ } ++ $this->displayObject->refresh(); ++ } ++ ++ public function changeTopicsLevel($topicList, $change) { ++ $change = (int) $change; ++ if (is_null($this->displayObject) || ! $this->displayObject instanceof fl_forum ++ || ! $this->displayObject->isMod() || $change != -1 && $change != 1 ) { ++ return; ++ } ++ ++ foreach ($topicList as $topicId) { ++ $topic = $this->displayObject->findTopic($topicId); ++ if (!is_null($topic) && $topic->isInForum($this->displayObject)) { ++ $topic->setStickyLevel($topic->getStickyLevel() + $change); ++ } ++ } ++ $this->displayObject->refresh(); ++ } ++ ++ public function setTopicsLevel($topicList, $level) { ++ $level = (int) $level; ++ if (is_null($this->displayObject) || ! $this->displayObject instanceof fl_forum ++ || ! $this->displayObject->isMod() || $level < 0 || $level > 10) { ++ return; ++ } ++ ++ foreach ($topicList as $topicId) { ++ $topic = $this->displayObject->findTopic($topicId); ++ if (!is_null($topic) && $topic->isInForum($this->displayObject)) { ++ $topic->setStickyLevel($level); ++ } ++ } ++ $this->displayObject->refresh(); ++ } ++ ++ public function setTopicsLock($topicList, $lock) { ++ if (is_null($this->displayObject) || ! $this->displayObject instanceof fl_forum ++ || ! $this->displayObject->isMod()) { ++ return; ++ } ++ ++ foreach ($topicList as $topicId) { ++ $topic = $this->displayObject->findTopic($topicId); ++ if (!is_null($topic) && $topic->isInForum($this->displayObject)) { ++ $topic->setLock($lock); ++ } ++ } ++ } ++ ++ public function moveTopics($topicList, $destId) { ++ if (is_null($this->displayObject) || ! $this->displayObject instanceof fl_forum ++ || ! $this->displayObject->isMod() || $destId == $this->displayObject->getId()) { ++ return; ++ } ++ ++ $destination = $this->fStructure->findForum((int) $destId); ++ if (is_null($destination) || ! $destination->isMod()) { ++ return; ++ } ++ ++ foreach ($topicList as $topicId) { ++ $topic = $this->displayObject->findTopic($topicId); ++ if (! is_null($topic) && $topic->isInForum($this->displayObject)) { ++ $topic->moveTo($destination); ++ } ++ } ++ $this->displayObject->refresh(); ++ $destination->refresh(); ++ } ++ ++ ++ private function dumpNames(&$output, $ids) { ++ $accLib = $this->game->getLib('main/account'); ++ ++ array_push($output, count($ids)); ++ foreach ($ids as $id) { ++ array_push($output, "$id#" . utf8entities($accLib->call('getUserName', $id))); ++ } ++ } ++ ++ private function getCategoryData() { ++ $output = array( ++ "C#" . $this->displayObject->getId() ++ ); ++ $namesNeeded = array(); ++ ++ $this->dumpCategory( &$output, &$namesNeeded, $this->displayObject ); ++ $this->dumpNames( &$output, $namesNeeded ); ++ ++ return $output; ++ } ++ ++ private function dumpCategory( &$output, &$namesNeeded, $object ) { ++ if ( $object instanceof fl_container ) { ++ $contents = $object->getCategories(); ++ $type = 'C'; ++ } else { ++ $contents = array(); ++ foreach ($object->getForums() as $forum) { ++ if ($forum->canView() && ($forum->isAdmin() || ! $forum->isDeleted())) { ++ array_push($contents, $forum); ++ } ++ } ++ $type = 'F'; ++ } ++ ++ $fullDesc = explode("\n", $object->getDescription()); ++ $hasUnread = ($object->getUnread() > 0) ? 1 : 0; ++ array_push( $output, $object->getId() . "#$type#$hasUnread#" ++ . count($contents) . "#" . count($fullDesc) ); ++ if ($type == 'F') { ++ array_push( $output, $object->getTypeName(getLanguage()) ); // FIXME getLanguage() ... ++ } ++ array_push( $output, utf8entities($object->getTitle()) ); ++ foreach ($fullDesc as $dLine ) { ++ array_push( $output, utf8entities($dLine) ); ++ } ++ ++ if ( $object instanceof fl_container ) { ++ foreach ($contents as $cat) { ++ $this->dumpCategory( &$output, &$namesNeeded, $cat ); ++ } ++ } else { ++ foreach ($contents as $forum) { ++ $this->dumpCatForum( &$output, &$namesNeeded, $forum ); ++ } ++ } ++ } ++ ++ private function dumpCatForum( &$output, &$namesNeeded, $forum ) { ++ $fullDesc = explode("\n", $forum->getDescription()); ++ ++ $deleted = $forum->isDeleted() ? '1' : '0'; ++ $deletedAt = $forum->deletedAt(); ++ $deletedBy = $forum->deletedBy(); ++ if (! (is_null($deletedBy) || in_array($deletedBy, $namesNeeded))) { ++ array_push($namesNeeded, $deletedBy); ++ } ++ ++ $topics = $forum->getTopics(); ++ $posts = is_null($lp = $forum->getLastPost()) ? 0 : $forum->getPosts(); ++ $unread = ($forum->getUnread() > 0 ? 1 : 0); ++ ++ array_push( $output, $forum->getId() . "#" . count($fullDesc) ++ . "#$deleted#$deletedAt#$deletedBy#$topics#$posts#$unread" ); ++ ++ if ($posts != 0 && !is_null($lp)) { ++ $moment = $lp->getLastChange(); ++ $author = $lp->getLastChangeAuthor(); ++ if (! in_array($author, $namesNeeded) ) { ++ array_push($namesNeeded, $author); ++ } ++ array_push( $output, "$author#$moment" ); ++ } ++ ++ array_push( $output, utf8entities($forum->getTitle()) ); ++ foreach ($fullDesc as $dLine ) { ++ array_push( $output, utf8entities($dLine) ); ++ } ++ } ++ ++ private function getForumData() { ++ $output = array( ++ "F#" . $this->displayObject->getId() ++ ); ++ $namesNeeded = array(); ++ $fob = $this->displayObject; ++ $id = $fob->getId(); ++ ++ $perPage = prefs::get("main/F#PP#$id", prefs::get("main/F#PP", 10)); ++ $vDeleted = $fob->isMod() ? prefs::get("main/F#VD#$id", prefs::get("main/F#VD", 0)) : 0; ++ ++ $rTopics = $fob->getTopicList(); ++ $topics = array(); ++ foreach ($rTopics as $topic) { ++ if ( ! ($topic->isInForum($fob) || !is_null($topic->getForum()) && $topic->getForum()->canView()) ++ || ($topic->isDeleted() && ! $vDeleted) ) { ++ continue; ++ } ++ array_push($topics, $topic); ++ } ++ ++ $nTopics = count($topics); ++ $hasUnread = ($fob->getUnread() > 0) ? 1 : 0; ++ $isMod = $fob->isMod() ? 1 : 0; ++ $canPost = $fob->canCreateTopic() ? 1 : 0; ++ $fDesc = explode("\n", $fob->getDescription()); ++ $admins = $fob->getAdministrators(true); ++ $mods = $fob->getModerators(true); ++ $users = $fob->getUsers(true); ++ ++ if ($fob->isMod()) { ++ $fMoveTo = array(); ++ $list = $this->fStructure->findModForums(); ++ foreach ($list as $forum) { ++ $fId = $forum->getId(); ++ if ($fId == $id) { ++ continue; ++ } ++ $fName = array($forum->getParent()->getTitle(), $forum->getTitle()); ++ array_push($fMoveTo, "$fId#" . utf8entities(join(' > ', $fName))); ++ } ++ } ++ ++ $parents = array(); ++ $obj = $fob->getParent(); ++ do { ++ array_push($parents, array( ++ $obj->getId(), utf8entities($obj->getTitle()) ++ )); ++ $obj = $obj->getParent(); ++ } while (! is_null($obj)); ++ ++ array_push($output, "$id#$nTopics#$hasUnread#$isMod#$canPost#$perPage#$vDeleted#" . count($fDesc) ++ . "#" . count($parents) . "#" . count($admins) . "#" . count($mods) ++ . "#" . count($users) . ($isMod ? ("#" . count($fMoveTo)) : "")); ++ array_push($output, utf8entities($fob->getTitle())); ++ ++ // Dump description ++ foreach ($fDesc as $dLine) { ++ array_push($output, utf8entities($dLine)); ++ } ++ // Parent categories ++ foreach (array_reverse($parents) as $p) { ++ array_push($output, join('#', $p)); ++ } ++ // Administrators ++ foreach ($admins as $name) { ++ array_push($output, utf8entities($name)); ++ } ++ // Moderators ++ foreach ($mods as $name) { ++ array_push($output, utf8entities($name)); ++ } ++ // Users ++ foreach ($users as $name) { ++ array_push($output, utf8entities($name)); ++ } ++ // List of forums one can move a topic to ++ if ($isMod) { ++ foreach ($fMoveTo as $l) { ++ array_push($output, $l); ++ } ++ } ++ ++ foreach ($topics as $topic) { ++ if ($topic->isDeleted() && ! $vDeleted) { ++ continue; ++ } ++ ++ $tid = $topic->getId(); ++ $movedTo = $topic->isInForum($fob) ? '' : $topic->getForum()->getId(); ++ $unread = ($topic->getLastRead() < $topic->getLastChange()) ? 1 : 0; ++ $sticky = $topic->getStickyLevel(); ++ $tReplies = $topic->getPosts() - 1; ++ ++ $fpTime = $topic->getPosted(); ++ $fpAuth = $topic->getAuthor(); ++ if (! (is_null($fpAuth) || in_array($fpAuth, $namesNeeded))) { ++ array_push($namesNeeded, $fpAuth); ++ } ++ $lcTime = $topic->getLastChange(); ++ $lcAuth = $topic->getLastChangeAuthor(); ++ if (! (is_null($lcAuth) || in_array($lcAuth, $namesNeeded))) { ++ array_push($namesNeeded, $lcAuth); ++ } ++ ++ $isLocked = $topic->isLocked() ? 1 : 0; ++ $hasPoll = is_null($topic->getPoll()) ? 0 : 1; ++ ++ $isDeleted = $topic->isDeleted() ? 1 : 0; ++ $deletedAt = $topic->getDeletionTime(); ++ $deletedBy = $topic->getDeletionMod(); ++ if (! (is_null($deletedBy) || in_array($deletedBy, $namesNeeded))) { ++ array_push($namesNeeded, $deletedBy); ++ } ++ ++ array_push($output, "$tid#$movedTo#$unread#$sticky#$tReplies#$fpTime#$fpAuth#$lcTime#$lcAuth" ++ . "#$isLocked#$hasPoll#$isDeleted#$deletedAt#$deletedBy"); ++ array_push($output, utf8entities($topic->getTitle())); ++ if ($movedTo != '') { ++ array_push($output, utf8entities($topic->getForum()->getTitle())); ++ } ++ } ++ ++ $this->dumpNames( &$output, $namesNeeded ); ++ return $output; ++ } ++} ++ ++?> +diff -Naur beta5//scripts/game/main/forums/fl_category.inc forums//scripts/game/main/forums/fl_category.inc +--- beta5//scripts/game/main/forums/fl_category.inc 1970-01-01 01:00:00.000000000 +0100 ++++ forums//scripts/game/main/forums/fl_category.inc 2011-02-05 10:10:03.434335002 +0100 +@@ -0,0 +1,270 @@ ++game = $game; ++ $this->db = $this->game->getDBAccess(); ++ ++ $this->user = $user; ++ $this->id = $id; ++ $this->title = $title; ++ $this->description = $description; ++ } ++ ++ private static function initQueries() { ++ if (!is_null(self::$getLibQuery)) { ++ return; ++ } ++ ++ $db = config::getMainInterface()->getDBAccess(); ++ self::$getLibQuery = $db->prepare( ++ "SELECT t.id AS id,t.lib_path AS path " ++ . "FROM forums.category_type t, forums.category c " ++ . "WHERE c.id = $1 AND t.id = c.acl_lib", ++ array("id") ); ++ self::$getNameQuery = $db->prepare( ++ "SELECT name FROM forums.cat_type_text " ++ . "WHERE id = $1 AND lang = $2", ++ array("id", "lang") ); ++ } ++ ++ public function findCategory( $catId ) { ++ if ($this->getId() == $catId) { ++ return $this; ++ } ++ return null; ++ } ++ ++ ++ // ---------------------------------------------------------------------- ++ // READING CATEGORY PROPERTIES ++ // ---------------------------------------------------------------------- ++ ++ public function getId() { ++ return $this->id; ++ } ++ ++ public function getTitle() { ++ return $this->title; ++ } ++ ++ public function getDescription() { ++ return $this->description; ++ } ++ ++ public function getLibrary() { ++ if (is_null($this->hdlLib)) { ++ $q = self::$getLibQuery->execute(array("id" => $this->id)); ++ if (! ($q && dbCount($q) == 1)) { ++ return null; ++ } ++ ++ $r = dbFetchHash($q); ++ $r['library'] = $this->game->getLib($r['path']); ++ $r['text'] = array(); ++ $this->hdlLib = $r; ++ } ++ return $this->hdlLib['library']; ++ } ++ ++ public function getTypeName($lang) { ++ if (is_null($this->hdlLib)) { ++ if (is_null($this->getLibrary())) { ++ return null; ++ } ++ } ++ ++ if (! isset($this->hdlLib['text'][$lang]) ) { ++ $q = self::$getNameQuery->execute(array( ++ "id" => $this->hdlLib['id'], ++ "lang" => $lang ++ )); ++ if (! ($q || dbCount($q) == 1)) { ++ return null; ++ } ++ list($this->hdlLib['text'][$lang]) = dbFetchArray($q); ++ } ++ ++ return $this->hdlLib['text'][$lang]; ++ } ++ ++ public function getUnread( ) { ++ if (!is_null($this->unread)) { ++ return $this->unread; ++ } ++ ++ $total = 0; ++ foreach ($this->forums as $f) { ++ if ( $f->canView() && ! $f->isDeleted() ) { ++ $total += $f->getUnread(); ++ } ++ } ++ return ($this->unread = $total); ++ } ++ ++ public function getTopics( ) { ++ if (!is_null($this->topics)) { ++ return $this->topics; ++ } ++ ++ $total = 0; ++ foreach ($this->forums as $f) { ++ if ( $f->canView() && ! $f->isDeleted() ) { ++ $total += $f->getTopics(); ++ } ++ } ++ return ($this->topics = $total); ++ } ++ ++ public function getPosts( ) { ++ if (!is_null($this->posts)) { ++ return $this->posts; ++ } ++ ++ $total = 0; ++ foreach ($this->forums as $f) { ++ if ( $f->canView() && ! $f->isDeleted() ) { ++ $total += $f->getPosts(); ++ } ++ } ++ return ($this->posts = $total); ++ } ++ ++ public function setParent( $category ) { ++ $this->parent = $category; ++ } ++ ++ public function getParent() { ++ return $this->parent; ++ } ++ ++ ++ // ---------------------------------------------------------------------- ++ // FORUM ACCESS ++ // ---------------------------------------------------------------------- ++ ++ public function getForums() { ++ return $this->oForums; ++ } ++ ++ public function findModForums() { ++ $r = array(); ++ foreach ($this->oForums as $f) { ++ if ($f->isMod()) { ++ array_push($r, $f); ++ } ++ } ++ return $r; ++ } ++ ++ public function addForum( $forum ) { ++ if (is_null($forum)) { ++ return; ++ } ++ $this->forums[(string) $forum->getId()] = $forum; ++ array_push($this->oForums, $forum); ++ $forum->setParent($this); ++ } ++ ++ public function insertNewForum($forum) { ++ if (isset($this->forums[$id])) { ++ return; ++ } ++ ++ $this->forums[$forum->getId()] = $forum; ++ $forum->setParent($this); ++ $order = array(); ++ for ($i = 0; $i < count($this->oForums); $i ++) { ++ if ($i == $forum->getOrder()) { ++ array_push($order, $forum); ++ } ++ if ($i >= $forum->getOrder()) { ++ $this->oForums[$i]->increaseOrder(); ++ } ++ array_push($order, $this->oForums[$i]); ++ } ++ if ($i == $forum->getOrder()) { ++ array_push($order, $forum); ++ } ++ $this->oForums = $order; ++ } ++ ++ public function findForum( $id ) { ++ return isset($this->forums[$id]) ? $this->forums[$id] : null; ++ } ++ ++ public function moveForum( $id, $moveUp ) { ++ if (!isset($this->forums[$id])) { ++ return false; ++ } ++ ++ $qs = "SELECT forums.move_" . ($moveUp ? 'up' : 'down') . '($1)'; ++ $q = $this->db->query($qs, $id); ++ if (! ($q && dbCount($q))) { ++ return false; ++ } ++ list($rc) = dbFetchArray($q); ++ ++ if ($rc == 't') { ++ $n = count($this->oForums); ++ for ($i = 0; $i < $n; $i ++) { ++ if ($this->oForums[$i]->getId() == $id) { ++ if ($moveUp) { ++ $rv = $this->oForums[$i - 1]; ++ $this->oForums[$i - 1] = $this->oForums[$i]; ++ $this->oForums[$i] = $rv; ++ } else { ++ $rv = $this->oForums[$i + 1]; ++ $this->oForums[$i + 1] = $this->oForums[$i]; ++ $this->oForums[$i] = $rv; ++ } ++ break; ++ } ++ } ++ } else { ++ $rv = false; ++ } ++ ++ return $rv; ++ } ++ ++ public function markRead() { ++ foreach ($this->oForums as $forum) { ++ $forum->markRead(); ++ } ++ } ++ ++ public function refresh() { ++ $this->unread = $this->topics = $this->posts = null; ++ if ($this->parent) { ++ $this->parent->refresh(); ++ } ++ } ++} ++ ++ ++?> +diff -Naur beta5//scripts/game/main/forums/fl_container.inc forums//scripts/game/main/forums/fl_container.inc +--- beta5//scripts/game/main/forums/fl_container.inc 1970-01-01 01:00:00.000000000 +0100 ++++ forums//scripts/game/main/forums/fl_container.inc 2011-02-05 10:10:03.434335002 +0100 +@@ -0,0 +1,116 @@ ++id = $id; ++ $this->title = $title; ++ $this->description = $description; ++ } ++ ++ public function addCategory( $category ) { ++ if (! is_null($category)) { ++ array_push($this->categories, $category); ++ $category->setParent( $this ); ++ } ++ } ++ ++ public function setParent( $category ) { ++ $this->parent = $category; ++ } ++ ++ public function getParent() { ++ return $this->parent; ++ } ++ ++ public function getCategories() { ++ return $this->categories; ++ } ++ ++ public function getId() { ++ return $this->id; ++ } ++ ++ public function getTitle() { ++ return $this->title; ++ } ++ ++ public function getDescription() { ++ return $this->description; ++ } ++ ++ public function getUnread( ) { ++ if (!is_null($this->unread)) { ++ return $this->unread; ++ } ++ ++ $total = 0; ++ foreach ($this->categories as $c) { ++ $total += $c->getUnread(); ++ } ++ return ($this->unread = $total); ++ } ++ ++ public function getTopics( ) { ++ if (!is_null($this->topics)) { ++ return $this->topics; ++ } ++ ++ $total = 0; ++ foreach ($this->categories as $c) { ++ $total += $c->getTopics(); ++ } ++ return ($this->topics = $total); ++ } ++ ++ public function findCategory( $catId ) { ++ if ($this->getId() == $catId) { ++ return $this; ++ } ++ foreach ($this->categories as $cat) { ++ $x = $cat->findCategory( $catId ); ++ if (! is_null($x)) { ++ return $x; ++ } ++ } ++ return null; ++ } ++ ++ public function findForum( $fId ) { ++ foreach ($this->categories as $cat) { ++ $x = $cat->findForum( $fId ); ++ if (! is_null($x)) { ++ return $x; ++ } ++ } ++ return null; ++ } ++ ++ public function findModForums() { ++ $r = array(); ++ foreach ($this->categories as $cat) { ++ $r = array_merge($r, $cat->findModForums()); ++ } ++ return $r; ++ } ++ ++ public function refresh() { ++ $this->unread = $this->topics = null; ++ if ($this->parent) { ++ $this->parent->refresh(); ++ } ++ } ++} ++ ++?> +diff -Naur beta5//scripts/game/main/forums/fl_forum.inc forums//scripts/game/main/forums/fl_forum.inc +--- beta5//scripts/game/main/forums/fl_forum.inc 1970-01-01 01:00:00.000000000 +0100 ++++ forums//scripts/game/main/forums/fl_forum.inc 2011-02-05 10:10:03.434335002 +0100 +@@ -0,0 +1,533 @@ ++ array(), ++ "mods" => array(), ++ "users" => array() ++ ); ++ ++ public function __construct($game, $id, $user) { ++ self::initQueries(); ++ ++ $this->game = $game; ++ $this->db = $this->game->getDBAccess(); ++ $this->user = $user; ++ ++ $q = self::$queries['getInfo']->execute($id); ++ if (! ($q && dbCount($q) == 1)) { ++ throw new Exception("Unable to read forum record"); ++ } ++ $this->record = dbFetchHash($q); ++ ++ $this->privs = array( ++ 'adm' => false, ++ 'mod' => false, ++ 'ntop' => false, ++ 'poll' => false, ++ 'post' => false, ++ 'view' => false ++ ); ++ } ++ ++ ++ private static function initQueries() { ++ if (is_array(self::$queries)) { ++ return; ++ } ++ ++ $db = config::getMainInterface()->getDBAccess(); ++ ++ self::$queries['getInfo'] = $db->prepare( ++ "SELECT * FROM forums.forum WHERE id = $1", ++ array("id") ); ++ self::$queries['getRead'] = $db->prepare( ++ "SELECT forums.get_read_topics( $1 , $2 )", ++ array("forum", "user") ); ++ self::$queries['getCurrentSig'] = $db->prepare( ++ "SELECT id FROM forums.get_signature( $1, UNIX_TIMESTAMP(NOW()) )", ++ array("user") ); ++ self::$queries['createTopic'] = $db->prepare( ++ "SELECT forums.create_topic($1, $2, $3, $4, $5, $6, $7, $8)", ++ array("forum", "user", "sticky", "title", "contents", "code", "smileys", "sig") ); ++ self::$queries['killTopic'] = $db->prepare( ++ "DELETE FROM forums.t_topic WHERE id = $1", ++ array("topic") ); ++ self::$queries['getTopics'] = $db->prepare( ++ "SELECT * FROM forums.topic WHERE forum = $1 OR moved_from = $1", ++ array("forum") ); ++ } ++ ++ ++ // ---------------------------------------------------------------------- ++ // READING FORUM PROPERTIES ++ // ---------------------------------------------------------------------- ++ ++ public function getId() { ++ return $this->record['id']; ++ } ++ ++ public function getCategory() { ++ if (is_null($this->mainLib)) { ++ $this->mainLib = $this->game->getLib('main/forums'); ++ } ++ return $this->mainLib->call('getCategory', $this->record['category'], $this->user); ++ } ++ ++ public function getOrder() { ++ return $this->record['f_order']; ++ } ++ ++ public function getTitle() { ++ return $this->record['title']; ++ } ++ ++ public function getDescription() { ++ return $this->record['description']; ++ } ++ ++ public function isDeleted() { ++ return !is_null($this->record['deleted']); ++ } ++ ++ public function deletedAt() { ++ return $this->isDeleted() ? $this->record['deleted'] : null; ++ } ++ ++ public function deletedBy() { ++ return $this->isDeleted() ? $this->record['deleted_by'] : null; ++ } ++ ++ public function getTopics() { ++ return $this->record['topics']; ++ } ++ ++ public function getPosts() { ++ return $this->record['posts']; ++ } ++ ++ public function getUnread() { ++ if (is_null($this->unread)) { ++ $q = self::$queries['getRead']->execute($this->record['id'], $this->user); ++ if (! ($q && dbCount($q) == 1)) { ++ return 0; ++ } ++ list($r) = dbFetchArray($q); ++ $this->unread = $this->record['topics'] - $r; ++ } ++ return $this->unread; ++ } ++ ++ public function isAdmin() { ++ return $this->privs['adm']; ++ } ++ ++ public function isMod() { ++ return $this->privs['mod']; ++ } ++ ++ public function canCreateTopic() { ++ return $this->privs['ntop']; ++ } ++ ++ public function canCreatePoll() { ++ return $this->privs['poll']; ++ } ++ ++ public function canPost() { ++ return $this->privs['post']; ++ } ++ ++ public function canView() { ++ return $this->privs['view']; ++ } ++ ++ public function getLastPost() { ++ $q = $this->db->query("SELECT * FROM forums.get_last_post($1)", $this->record['id']); ++ if (! ($q && dbCount($q))) { ++ return null; ++ } ++ ++ if (! $this->mainLib) { ++ $this->mainLib = $this->game->getLib('main/forums'); ++ } ++ return $this->mainLib->call('getPost', dbFetchHash($q), $this->user); ++ } ++ ++ public function setParent( $category ) { ++ $this->parent = $category; ++ } ++ ++ public function getParent() { ++ return $this->parent; ++ } ++ ++ ++ // ---------------------------------------------------------------------- ++ // FORUM'S ACCESS LIST ++ // ---------------------------------------------------------------------- ++ ++ private function getAcl($type, $libFunc, $iWantNames) { ++ // Check if we read that already ++ $aKey = $iWantNames ? 'names' : 'ids'; ++ if (array_key_exists($aKey, $this->acl[$type])) { ++ return $this->acl[$type][$aKey]; ++ } ++ ++ // Get the library and extract the ACL IDs ++ $catLib = $this->getCategory()->getLibrary(); ++ if (! array_key_exists('ids', $this->acl[$type])) { ++ $this->acl[$type]['ids'] = $catLib->call($libFunc, $this->record['id']); ++ } ++ ++ // If we were requesting the IDs, leave ++ if ($aKey == 'ids') { ++ return $this->acl[$type]['ids']; ++ } ++ ++ // If the IDs list is null, names are null as well ++ if (is_null($this->acl[$type]['ids'])) { ++ $this->acl[$type]['names'] = null; ++ return $this->acl[$type]['names']; ++ } ++ ++ // Convert the identifiers to names and return ++ $this->acl[$type]['names'] = array(); ++ foreach ($this->acl[$type]['ids'] as $id) { ++ $this->acl[$type]['names'][$id] = $catLib->call('aclIdToName', $id); ++ } ++ return $this->acl[$type]['names']; ++ } ++ ++ public function getAdministrators($iWantNames = false) { ++ return $this->getAcl("admins", "getAdmins", $iWantNames); ++ } ++ ++ public function getModerators($iWantNames = false) { ++ return $this->getAcl("mods", "getModerators", $iWantNames); ++ } ++ ++ public function getUsers($iWantNames = false) { ++ return $this->getAcl("users", "getUsers", $iWantNames); ++ } ++ ++ public function clearACL() { ++ $this->getCategory()->getLibrary()->call('clearForumACL', $this->record['id']); ++ $this->acl = array( ++ 'users' => array(), ++ 'mods' => array(), ++ 'admins' => array() ++ ); ++ } ++ ++ public function addModerator($id) { ++ $this->getCategory()->getLibrary()->call('addForumModerator', $this->record['id'], $id); ++ $this->acl['mods'] = array(); ++ } ++ ++ public function addUser($id) { ++ $this->getCategory()->getLibrary()->call('addForumUser', $this->record['id'], $id); ++ $this->acl['users'] = array(); ++ } ++ ++ ++ // ---------------------------------------------------------------------- ++ // SETTING FORUM PROPERTIES ++ // ---------------------------------------------------------------------- ++ ++ public function setAdmin($value) { ++ $this->privs['adm'] = $value; ++ } ++ ++ public function setMod($value) { ++ $this->privs['mod'] = $value; ++ } ++ ++ public function setCreateTopic($value) { ++ $this->privs['ntop'] = $value; ++ } ++ ++ public function setCreatePoll($value) { ++ $this->privs['poll'] = $value; ++ } ++ ++ public function setPost($value) { ++ $this->privs['post'] = $value; ++ } ++ ++ public function setView($value) { ++ $this->privs['view'] = $value; ++ } ++ ++ public function increaseOrder() { ++ $this->record['f_order'] ++; ++ } ++ ++ ++ // ---------------------------------------------------------------------- ++ // INTERNAL METHODS FOR CHECKS AND FORMATTING ++ // ---------------------------------------------------------------------- ++ ++ private function checkPostContents($title, $contents) { ++ if (strlen($title) < 2) { ++ $e = 1; ++ } elseif (strlen($title) > 100) { ++ $e = 2; ++ } elseif (strlen($contents) < 3) { ++ $e = 3; ++ } else { ++ $e = 0; ++ } ++ return $e; ++ } ++ ++ ++ private function reformatContents($contents) { ++ // Max. line breaks ++ $maxNL = 500; ++ // Max. characters without break ++ $maxNC = 100; ++ ++ $ot = $contents; ++ $nt = ""; ++ $nl = 0; ++ ++ while ($ot != '' && $nl < $maxNL) { ++ $p = strpos($ot, '\n'); ++ if ($p !== false && $p < $maxNC) { ++ $nt .= substr($ot, 0, $p+1); ++ $ot = substr($ot, $p+1); ++ } else if (strlen($ot) < $maxNC) { ++ $nt .= $ot; ++ $ot = ""; ++ } else { ++ $s = substr($ot, 0, $maxNC); ++ $p = strrpos($s, ' '); ++ $ot = substr($ot, $maxNC); ++ $nt .= $s; ++ if ($p === false) { ++ $nt .= "\n"; ++ } ++ } ++ $nl ++; ++ } ++ ++ if ($nl >= $maxNL) { ++ return null; ++ } ++ return $nt; ++ } ++ ++ ++ ++ // ---------------------------------------------------------------------- ++ // OPERATIONS ++ // ---------------------------------------------------------------------- ++ ++ /* This method creates a new topic in the forum. It returns the ++ * corresponding fl_topic instance or a numeric error code. ++ * ++ * Error codes: ++ * -1 - Permission denied (topic creation) ++ * -2 - Permission denied (poll creation) ++ * -3 - Title too short ++ * -4 - Title too long ++ * -5 - Contents too short ++ * -6 - Contents too long ++ * -7 - Permission denied (sticky) ++ * -8 - Database access error ++ * -9 - Poll title too short ++ * -10 - Poll title too long ++ * -11 - Not enough poll options ++ * -12 - Too many poll options ++ */ ++ public function createTopic( ++ $title, $contents, $stickyLevel, ++ $enableCode, $enableSmileys, $enableSignature, ++ $poll = null ++ ) { ++ // Check posting privileges ++ if ( ! $this->canCreateTopic() ) { ++ return -1; ++ } ++ if ( ! (is_null($poll) || $this->canCreatePoll()) ) { ++ return -2; ++ } ++ ++ // Check the title and contents ++ $title = trim($title); ++ $contents = trim($contents); ++ $pc = $this->checkPostContents($title, $contents); ++ if ($pc) { ++ return - $pc - 2; ++ } ++ ++ // Reformat the post's contents ++ $contents = $this->reformatContents($contents); ++ if (is_null($contents)) { ++ return -6; ++ } ++ ++ // Check the sticky level ++ $stickyLevel = (int) $stickyLevel; ++ if ( $stickyLevel < 0 || $stickyLevel > 10 || ($stickyLevel > 0 && ! $this->isMod()) ) { ++ return -7; ++ } ++ ++ // Check the poll's contents ++ if (! is_null($poll)) { ++ $pc = $poll->checkData(); ++ if ($pc != 0) { ++ return - $pc - 8; ++ } ++ } ++ ++ // Get the user's current signature if needed ++ if ($enableSignature) { ++ $q = self::$queries['getCurrentSig']->execute($this->user); ++ if ( ! ($q && dbCount($q)) ) { ++ return -8; ++ } ++ ++ list($sig) = dbFetchArray($q); ++ } else { ++ $sig = null; ++ } ++ ++ // Add the topic ++ $q = self::$queries['createTopic']->execute(array( ++ "forum" => $this->record['id'], ++ "user" => $this->user, ++ "sticky" => $stickyLevel, ++ "title" => $title, ++ "contents" => $contents, ++ "code" => dbBool($enableCode), ++ "smileys" => dbBool($enableSmileys), ++ "sig" => $sig ++ )); ++ if (! ($q && dbCount($q)) ) { ++ return -8; ++ } ++ list($tid) = dbFetchArray($q); ++ if (is_null($tid)) { ++ return -8; ++ } ++ ++ // Insert poll if needed ++ if (! (is_null($poll) || $poll->insertIntoDB( $this->game, $this->user, $tid ) ) ) { ++ self::$queries['killTopic']->execute($tid); ++ return -8; ++ } ++ ++ if (! $this->mainLib) { ++ $this->mainLib = $this->game->getLib('main/forums'); ++ } ++ $topic = $this->mainLib->call('getTopic', $tid, $this->user); ++ ++ $this->refresh(); ++ return $topic; ++ } ++ ++ ++ public function getTopicList() { ++ if (is_null($this->topicList)) { ++ $q = self::$queries['getTopics']->execute($this->record['id']); ++ if (! $q) { ++ return null; ++ } ++ ++ if (is_null($this->mainLib)) { ++ $this->mainLib = $this->game->getLib('main/forums'); ++ } ++ $this->topicList = array(); ++ while ($r = dbFetchHash($q)) { ++ $t = $this->mainLib->call('getTopic', $r, $this->user); ++ $this->topicList[$t->getId()] = $t; ++ } ++ } ++ ++ return $this->topicList; ++ } ++ ++ public function findTopic( $id ) { ++ if (is_null($this->topicList)) { ++ $this->getTopicList(); ++ } ++ return array_key_exists($id, $this->topicList) ? $this->topicList[$id] : null; ++ } ++ ++ /* Deletes the forum */ ++ function delete() { ++ if ($this->isDeleted()) { ++ return; ++ } ++ ++ $this->db->query("SELECT forums.delete_forum($1, $2)", $this->record['id'], $this->user); ++ ++ $q = self::$queries['getInfo']->execute($this->record['id']); ++ if ($q && dbCount($q) == 1) { ++ $this->record = dbFetchHash($q); ++ } ++ } ++ ++ /* Restore the forum */ ++ function restore() { ++ if (!$this->isDeleted()) { ++ return; ++ } ++ ++ $this->db->query("SELECT forums.restore_forum($1)", $this->record['id']); ++ ++ $q = self::$queries['getInfo']->execute($this->record['id']); ++ if ($q && dbCount($q) == 1) { ++ $this->record = dbFetchHash($q); ++ } ++ } ++ ++ /* Moves the forum either up or down */ ++ function move($moveUp) { ++ $rv = $this->getCategory()->moveForum($this->record['id'], $moveUp); ++ if ($rv !== false) { ++ $this->record['f_order'] += ($moveUp ? -1 : 1); ++ $rv->record['f_order'] += ($moveUp ? 1 : -1); ++ } ++ } ++ ++ /* Mark all topics as read */ ++ function markRead() { ++ $this->db->query("SELECT forums.mark_forum_read($1, $2)", $this->record['id'], $this->user); ++ } ++ ++ /* Forces the object to be re-read from the DB */ ++ function refresh() { ++ $q = self::$queries['getInfo']->execute($this->record['id']); ++ if (! ($q && dbCount($q) == 1)) { ++ return; ++ } ++ $this->record = dbFetchHash($q); ++ $this->unread = $this->topics = $this->topicList = null; ++ ++ if ($this->parent) { ++ $this->parent->refresh(); ++ } ++ } ++} ++ ++?> +diff -Naur beta5//scripts/game/main/forums/fl_poll.inc forums//scripts/game/main/forums/fl_poll.inc +--- beta5//scripts/game/main/forums/fl_poll.inc 1970-01-01 01:00:00.000000000 +0100 ++++ forums//scripts/game/main/forums/fl_poll.inc 2011-02-05 10:10:03.434335002 +0100 +@@ -0,0 +1,373 @@ ++fromDB = false; ++ $this->checked = false; ++ $this->record = array( ++ 'title' => preg_replace('/\s+/', ' ', trim($args[0])) ++ ); ++ } else { ++ // Load from database ++ $this->fromDB = true; ++ $this->checked = true; ++ $this->game = $args[0]; ++ $this->db = $this->game->getDBAccess(); ++ $this->mainLib = $this->game->getLib('main/forums'); ++ $this->user = $args[1]; ++ $id = $args[2]; ++ ++ // Fetch the poll's data ++ $q = $this->db->query("SELECT * FROM forums.poll WHERE topic = $1 FOR UPDATE", $id); ++ if (! ($q && dbCount($q) == 1)) { ++ throw new Exception("No poll found for topic #$id"); ++ } ++ $this->record = dbFetchHash($q); ++ ++ // Fetch the poll options ++ $q = $this->db->query("SELECT * FROM forums.poll_option " ++ . "WHERE poll = $1 ORDER BY po_order " ++ . "FOR UPDATE", $id); ++ if (! ($q && dbCount($q) > 1)) { ++ throw new Exception("Poll options not found for topic #$id"); ++ } ++ while ($r = dbFetchHash($q)) { ++ array_push($this->options, $r); ++ } ++ ++ $this->prepareNbVotesQuery(); ++ } ++ } ++ ++ ++ private function prepareNbVotesQuery() { ++ $this->nbVotesQ = $this->db->prepare( ++ "SELECT COUNT(*) FROM forums.poll_vote WHERE vote = $1", array("option") ); ++ } ++ ++ ++ // ---------------------------------------------------------------------- ++ // READING POLL PROPERTIES ++ // ---------------------------------------------------------------------- ++ ++ public function getTopic() { ++ if (! $this->fromDB) { ++ return null; ++ } ++ return $this->mainLib->call('getTopic', $this->record['topic'] ); ++ } ++ ++ public function getTitle() { ++ return $this->record['title']; ++ } ++ ++ public function isClosed() { ++ return $this->fromDB && ($this->record['closed'] == 't'); ++ } ++ ++ public function isDeleted() { ++ return $this->fromDB && !is_null($this->record['deleted']); ++ } ++ ++ public function getDeletionTime() { ++ return $this->fromDB ? $this->record['deleted'] : null; ++ } ++ ++ public function getDeletionMod() { ++ return $this->fromDB ? $this->record['deleted_by'] : null; ++ } ++ ++ ++ // ---------------------------------------------------------------------- ++ // POLL CREATION ++ // ---------------------------------------------------------------------- ++ ++ /* Checks the poll's data before inserting it in the database. ++ * Returns null if the poll is already inside the database, 0 if the poll ++ * is ok, or one of the following error codes: ++ * 1 - Title too short ++ * 2 - Title too long ++ * 3 - Not enough options ++ * 4 - Too many options ++ */ ++ public function checkData() { ++ if ($this->fromDB || $this->checked) { ++ return null; ++ } ++ ++ $l = strlen($this->record['title']); ++ if ($l < 3) { ++ return 1; ++ } elseif ($l > 64) { ++ return 2; ++ } ++ ++ $c = count($this->options); ++ if ($c < 2) { ++ return 3; ++ } elseif ($c > 20) { ++ return 4; ++ } ++ ++ $this->checked = true; ++ return 0; ++ } ++ ++ /* Insert the poll's data into the database. */ ++ public function insertIntoDB($game, $user, $topicId) { ++ if ($this->fromDB || ! $this->checked) { ++ return false; ++ } ++ $db = $game->getDBAccess(); ++ ++ // Insert the poll ++ $q = $db->query("SELECT * FROM forums.create_poll($1, $2)", $topicId, $this->record['title']); ++ if (! ($q && dbCount($q) == 1)) { ++ return false; ++ } ++ ++ $rec = dbFetchHash($q); ++ if (is_null($rec['topic'])) { ++ return false; ++ } ++ ++ // Insert the options ++ $pollQuery = $db->prepare("SELECT * FROM forums.create_option($1, $2, $3)", ++ array("poll", "order", "title") ); ++ try { ++ $nOpts = array(); ++ for ($i = 0; $i < count($this->options); $i ++) { ++ $q = $pollQuery->execute(array( ++ "poll" => $topicId, ++ "order" => $i, ++ "title" => $this->options[$i]['title'] ++ )); ++ if (! ($q && dbCount($q) == 1)) { ++ throw new Exception('abort'); ++ } ++ ++ $orec = dbFetchHash($q); ++ if (is_null($orec['id'])) { ++ throw new Exception('abort'); ++ } ++ ++ array_push($nOpts, $orec); ++ } ++ $pollQuery->destroy(); ++ } catch (Exception $e) { ++ $pollQuery->destroy(); ++ $db->query("DELETE FROM forums.poll WHERE topic = $1", $topicId); ++ return false; ++ } ++ ++ // Finalize ++ $this->record = $rec; ++ $this->options = $nOpts; ++ $this->game = $game; ++ $this->db = $db; ++ $this->user = $user; ++ $this->mainLib = $this->game->getLib('main/forums'); ++ $this->prepareNbVotesQuery(); ++ $this->fromDB = true; ++ return true; ++ } ++ ++ ++ // ---------------------------------------------------------------------- ++ // OPTIONS ++ // ---------------------------------------------------------------------- ++ ++ /* Adds a new option at the end of the list. ++ * Returns 0 on success or one of the following error codes: ++ * -1 - Title too short ++ * -2 - Title too long ++ * -3 - Too many options ++ * -4 - Database error ++ * -5 - Adding options after the poll has been checked is not permitted ++ */ ++ public function appendOption($title) { ++ $title = preg_replace('/\s+/', ' ', trim($title)); ++ $l = strlen($title); ++ if ($l < 2) { ++ return -1; ++ } elseif ($l > 64) { ++ return -2; ++ } ++ ++ if ($this->fromDB) { ++ $c = count($this->options); ++ if ($c == 20) { ++ return -3; ++ } ++ ++ $q = $this->db->query("SELECT * FROM forums.create_option($1, $2, $3)", ++ $this->record['topic'], $c, $title); ++ if (! ($q && dbCount($q) == 1)) { ++ return -4; ++ } ++ array_push($this->options, dbFetchHash($q)); ++ } else if (!$this->checked) { ++ array_push($this->options, array( ++ 'title' => $title ++ )); ++ } else { ++ return -5; ++ } ++ return 0; ++ } ++ ++ /* Removes the poll option with the specified index. */ ++ public function removeOption( $order ) { ++ $order = (int) $order; ++ if ($order < 0 || $order >= count($this->options)) { ++ return false; ++ } ++ ++ if ($this->fromDB) { ++ $q = $this->db->query("SELECT forums.delete_option( $1, $2 )", $this->record['topic'], $order); ++ if (! ($q && dbCount($q) == 1)) { ++ return false; ++ } ++ list($ok) = dbFetchArray($q); ++ if ($ok != 't') { ++ return false; ++ } ++ ++ for ($i = $order + 1; $i < count($this->options); $i ++) { ++ $this->options[$i]['po_order'] --; ++ } ++ } ++ array_splice($this->options, $order, 1); ++ return true; ++ } ++ ++ /* Get the options */ ++ public function getOptions() { ++ $opts = $this->options; ++ if (! $this->fromDB) { ++ for ($i = 0; $i < count($opts); $i++) { ++ $opts[$i]['po_order'] = $i; ++ } ++ } ++ return $opts; ++ } ++ ++ /* Move an option up in the list of options */ ++ public function moveUp( $order ) { ++ if ($order <= 0 || $order >= count($this->options)) { ++ return false; ++ } ++ ++ if ($this->fromDB) { ++ $q = $this->db->query("SELECT forums.move_opt_up( $1, $2 )", $this->record['topic'], $order); ++ if (! ($q && dbCount($q) == 1)) { ++ return false; ++ } ++ list($ok) = dbFetchArray($q); ++ if ($ok != 't') { ++ return false; ++ } ++ ++ $this->options[$order]['po_order'] --; ++ $this->options[$order - 1]['po_order'] ++; ++ } ++ ++ $tmp = $this->options[$order]; ++ $this->options[$order] = $this->options[$order - 1]; ++ $this->options[$order - 1] = $tmp; ++ return true; ++ } ++ ++ /* Move an option down in the list of options */ ++ public function moveDown( $order ) { ++ if ($order < 0 || $order >= count($this->options) - 1) { ++ return false; ++ } ++ ++ if ($this->fromDB) { ++ $q = $this->db->query("SELECT forums.move_opt_down($1,$2)", $this->record['topic'], $order); ++ if (! ($q && dbCount($q) == 1)) { ++ return false; ++ } ++ list($ok) = dbFetchArray($q); ++ if ($ok != 't') { ++ return false; ++ } ++ ++ $this->options[$order]['po_order'] ++; ++ $this->options[$order + 1]['po_order'] --; ++ } ++ ++ $tmp = $this->options[$order]; ++ $this->options[$order] = $this->options[$order + 1]; ++ $this->options[$order + 1] = $tmp; ++ return true; ++ } ++ ++ /* Vote */ ++ public function setVote( $order ) { ++ if (! $this->fromDB || $order < 0 || $order >= count($this->options)) { ++ return false; ++ } ++ ++ $q = $this->db->query("SELECT forums.set_vote($1,$2,$3)", $this->user, $this->record['topic'], ++ $this->options[$order]['id']); ++ if (! $q) { ++ return false; ++ } ++ return true; ++ } ++ ++ /* Returns the order of the option the user is voting for */ ++ public function getVote() { ++ if (! $this->fromDB) { ++ return null; ++ } ++ ++ $q = $this->db->query("SELECT o.po_order FROM forums.poll_option o, forums.poll_vote v " ++ . " WHERE o.id = v.vote AND o.poll = $1 AND v.account = $2", ++ $this->record['topic'], $this->user); ++ if (!($q && dbCount($q) == 1)) { ++ return null; ++ } ++ list($order) = dbFetchArray($q); ++ return $order; ++ } ++ ++ /* Returns the amount of votes for an option */ ++ public function getNbVotes( $order ) { ++ if (! $this->fromDB || $order < 0 || $order >= count($this->options)) { ++ return 0; ++ } ++ ++ $q = $this->nbVotesQ->execute($this->options[$order]['id']); ++ if (!($q && dbCount($q) == 1)) { ++ return 0; ++ } ++ ++ list($votes) = dbFetchArray($q); ++ return $votes; ++ } ++} ++ ++ ++?> +diff -Naur beta5//scripts/game/main/forums/fl_post.inc forums//scripts/game/main/forums/fl_post.inc +--- beta5//scripts/game/main/forums/fl_post.inc 1970-01-01 01:00:00.000000000 +0100 ++++ forums//scripts/game/main/forums/fl_post.inc 2011-02-05 10:10:03.434335002 +0100 +@@ -0,0 +1,219 @@ ++game = $game; ++ $this->user = $user; ++ $this->db = $this->game->getDBAccess(); ++ $this->mainLib = $this->game->getLib('main/forums'); ++ ++ if (is_array($data)) { ++ $this->record = $data; ++ } else { ++ $data = (int) $data; ++ $row = self::$getQ->execute($data); ++ if (! ($row && dbCount($row) == 1)) { ++ throw new Exception("Post #$data not found"); ++ } ++ $this->record = dbFetchHash($row); ++ } ++ } ++ ++ ++ static private function initQueries() { ++ if (! is_null(self::$getQ)) { ++ return; ++ } ++ ++ $db = config::getMainInterface()->getDBAccess(); ++ self::$getQ = $db->prepare( "SELECT * FROM forums.post WHERE id = $1", array("id") ); ++ self::$historyQ = $db->prepare( "SELECT * FROM forums.post_text WHERE post = $1 ORDER BY moment", ++ array( "post" )); ++ self::$repliesQ = $db->prepare( "SELECT id FROM forums.post WHERE reply_to = $1 ORDER BY post_moment", ++ array( "post" )); ++ } ++ ++ ++ // ---------------------------------------------------------------------- ++ // READING POST PROPERTIES ++ // ---------------------------------------------------------------------- ++ ++ public function getId() { ++ return $this->record['id']; ++ } ++ ++ ++ public function getForum() { ++ return $this->mainLib->call('getForum', $this->record['forum'], $this->user); ++ } ++ ++ public function getTopic() { ++ return $this->mainLib->call('getTopic', $this->record['topic'], $this->user); ++ } ++ ++ ++ public function getParent() { ++ return is_null($this->record['reply_to']) ? null ++ : $this->mainLib->call('getPost', $this->record['reply_to'], $this->user); ++ } ++ ++ public function getDepth() { ++ return $this->record['depth']; ++ } ++ ++ ++ public function getPostedAt() { ++ return $this->record['post_moment']; ++ } ++ ++ public function getPostedBy() { ++ return $this->record['author']; ++ } ++ ++ public function getLastChange() { ++ return $this->record['last_change']; ++ } ++ ++ public function getLastChangeAuthor() { ++ return $this->record['last_author']; ++ } ++ ++ public function isUnread() { ++ $topic = $this->getTopic(); ++ if (is_null($topic)) { ++ return false; ++ } ++ ++ return $topic->getLastRead() < $this->record['last_change']; ++ } ++ ++ ++ public function isDeleted() { ++ return ! is_null($this->record['deleted']); ++ } ++ ++ public function getDeletionTime() { ++ return $this->record['deleted']; ++ } ++ ++ public function getDeletionMod() { ++ return $this->record['deleted_by']; ++ } ++ ++ ++ public function getTitle() { ++ return $this->record['title']; ++ } ++ ++ public function getContents($time = null) { ++ if (is_null($time)) { ++ $v = $this->record['contents']; ++ } else { ++ $hist = $this->getFullHistory(); ++ $v = ''; ++ foreach ($hist as $he) { ++ if ($he['moment'] > $time) { ++ break; ++ } ++ $v = $he['contents']; ++ } ++ } ++ return $v; ++ } ++ ++ public function codeEnabled($time = null) { ++ if (is_null($time)) { ++ $v = $this->record['enable_code']; ++ } else { ++ $hist = $this->getFullHistory(); ++ $v = false; ++ foreach ($hist as $he) { ++ if ($he['moment'] > $time) { ++ break; ++ } ++ $v = $he['enable_code']; ++ } ++ } ++ return ($v == 't'); ++ } ++ ++ public function smileysEnabled($time = null) { ++ if (is_null($time)) { ++ $v = $this->record['enable_smileys']; ++ } else { ++ $hist = $this->getFullHistory(); ++ $v = false; ++ foreach ($hist as $he) { ++ if ($he['moment'] > $time) { ++ break; ++ } ++ $v = $he['enable_smileys']; ++ } ++ } ++ return ($v == 't'); ++ } ++ ++ ++ public function getSignature() { ++ throw new Exception("FIXME: getSignature() is not implemented"); ++ } ++ ++ /* This function returns a post's full history */ ++ public function getFullHistory() { ++ if (is_null( $this->history )) { ++ $result = self::$historyQ->execute($this->record['id']); ++ if (! ($result && dbCount($result))) { ++ return null; ++ } ++ $history = array(); ++ while ($row = dbFetchHash($result)) { ++ array_push($history, $row); ++ } ++ $this->history = $history; ++ } ++ return $this->history; ++ } ++ ++ /* This function returns the replies to a post */ ++ public function getReplies() { ++ if (is_null( $this->replies )) { ++ $result = self::$repliesQ->execute($this->record['id']); ++ if (! $result) { ++ return null; ++ } ++ $this->replies = array(); ++ while ($row = dbFetchArray($result)) { ++ $reply = $this->mainLib->call('getPost', $row[0], $this->user); ++ if (! is_null($reply)) { ++ array_push($this->replies, $reply); ++ } ++ } ++ } ++ return $this->replies; ++ } ++ ++ /* This function deletes a post if it is not the top-level post */ ++ public function delete() { ++ } ++} ++ ++ ++?> +diff -Naur beta5//scripts/game/main/forums/fl_topic.inc forums//scripts/game/main/forums/fl_topic.inc +--- beta5//scripts/game/main/forums/fl_topic.inc 1970-01-01 01:00:00.000000000 +0100 ++++ forums//scripts/game/main/forums/fl_topic.inc 2011-02-05 10:10:03.434335002 +0100 +@@ -0,0 +1,339 @@ ++game = $game; ++ $this->db = $this->game->getDBAccess(); ++ $this->user = $user; ++ ++ if (is_array($data)) { ++ $this->record = $data; ++ } else { ++ $data = (int) $data; ++ $q = self::$getQuery->execute($data); ++ if (! ($q && dbCount($q) == 1)) { ++ throw new Exception("Topic #$data not found"); ++ } ++ $this->record = dbFetchHash($q); ++ } ++ } ++ ++ static private function initQueries() { ++ if (is_null(self::$getQuery)) { ++ $db = config::getMainInterface()->getDBAccess(); ++ self::$getQuery = $db->prepare( ++ "SELECT * FROM forums.topic WHERE id = $1", ++ array("id") ); ++ self::$restoreQuery = $db->prepare( "SELECT forums.restore_post( $1 )", array("id") ); ++ self::$deleteQuery = $db->prepare( "SELECT forums.delete_post( $1 , $2 )", ++ array("id", "user") ); ++ self::$stickyLevelQuery = $db->prepare( "UPDATE forums.t_topic SET sticky_level = $2 " ++ . "WHERE id = $1", array("id", "level") ); ++ self::$setLockQuery = $db->prepare( "UPDATE forums.t_topic SET locked = $2 WHERE id = $1", ++ array("id", "locked") ); ++ self::$moveQuery = $db->prepare( "SELECT forums.move_topic( $1, $2, $3)", ++ array("topic", "destination", "user") ); ++ self::$postsQuery = $db->prepare( "SELECT * FROM forums.post WHERE topic = $1", ++ array("topic") ); ++ } ++ } ++ ++ ++ // ---------------------------------------------------------------------- ++ // READING TOPIC PROPERTIES ++ // ---------------------------------------------------------------------- ++ ++ public function getId() { ++ return $this->record['id']; ++ } ++ ++ public function getForum() { ++ if (is_null($this->mainLib)) { ++ $this->mainLib = $this->game->getLib('main/forums'); ++ } ++ return $this->mainLib->call('getForum', $this->record['forum'], $this->user); ++ } ++ ++ public function getMovedFrom() { ++ if (is_null($this->record['moved_from'])) { ++ return null; ++ } ++ ++ if (is_null($this->mainLib)) { ++ $this->mainLib = $this->game->getLib('main/forums'); ++ } ++ return $this->mainLib->call('getForum', $this->record['moved_from'], $this->user); ++ } ++ ++ public function isInForum($forum) { ++ return ($forum->getId() == $this->record['forum']); ++ } ++ ++ public function getPoll() { ++ if (is_null($this->mainLib)) { ++ $this->mainLib = $this->game->getLib('main/forums'); ++ } ++ return $this->mainLib->call('getPoll', $this->record['id'], $this->user); ++ } ++ ++ public function getStickyLevel() { ++ return $this->record['sticky_level']; ++ } ++ ++ public function getTitle() { ++ return $this->record['title']; ++ } ++ ++ public function getPosted() { ++ return $this->record['fp_moment']; ++ } ++ ++ public function getAuthor() { ++ return $this->record['fp_author']; ++ } ++ ++ public function getLastChange() { ++ return $this->record['lc_moment']; ++ } ++ ++ public function getLastChangeAuthor() { ++ return $this->record['lc_author']; ++ } ++ ++ public function isDeleted() { ++ return ! is_null($this->record['deleted']); ++ } ++ ++ public function getDeletionTime() { ++ return $this->record['deleted']; ++ } ++ ++ public function getDeletionMod() { ++ return $this->record['deleted_by']; ++ } ++ ++ public function isLocked() { ++ return ($this->record['locked'] == 't'); ++ } ++ ++ public function getLastRead() { ++ if (is_null($this->lastRead)) { ++ $q = $this->db->query("SELECT read_at FROM forums.topic_read WHERE topic = $1 AND read_by = $2", ++ $this->record['id'], $this->user); ++ if (!($q && dbCount($q) == 1)) { ++ return 0; ++ } ++ ++ list($this->lastRead) = dbFetchArray($q); ++ } ++ ++ return $this->lastRead; ++ } ++ ++ ++ // ---------------------------------------------------------------------- ++ // MANAGEMENT FUNCTIONS ++ // ---------------------------------------------------------------------- ++ ++ public function restore() { ++ if (! $this->isDeleted() ) { ++ return; ++ } ++ self::$restoreQuery->execute( $this->record['first_post'] ); ++ $q = self::$getQuery->execute( $this->record['id'] ); ++ if (! ($q && dbCount($q))) { ++ return; ++ } ++ $this->record = dbFetchHash($q); ++ } ++ ++ public function delete() { ++ if ($this->isDeleted() ) { ++ return; ++ } ++ self::$deleteQuery->execute( $this->record['first_post'], $this->user ); ++ $q = self::$getQuery->execute( $this->record['id'] ); ++ if (! ($q && dbCount($q))) { ++ return; ++ } ++ $this->record = dbFetchHash($q); ++ } ++ ++ public function setStickyLevel( $level ) { ++ if ($this->isDeleted() ) { ++ return; ++ } ++ if ($level < 0 || $level > 10) { ++ return; ++ } ++ self::$stickyLevelQuery->execute( $this->record['id'], $level ); ++ $this->record['sticky_level'] = $level; ++ } ++ ++ public function setLock( $locked ) { ++ if ($this->isDeleted() ) { ++ return; ++ } ++ self::$setLockQuery->execute( $this->record['id'], dbBool($locked) ); ++ $this->record['locked'] = $locked ? 't' : 'f'; ++ } ++ ++ public function moveTo( $forum ) { ++ if ($this->isDeleted() ) { ++ return; ++ } ++ self::$moveQuery->execute($this->record['id'], $forum->getId(), $this->user); ++ $q = self::$getQuery->execute( $this->record['id'] ); ++ if (! ($q && dbCount($q))) { ++ return; ++ } ++ $this->record = dbFetchHash($q); ++ } ++ ++ ++ // ---------------------------------------------------------------------- ++ // ACCESSING THE POSTS ++ // ---------------------------------------------------------------------- ++ ++ public function getPosts() { ++ return $this->record['posts']; ++ } ++ ++ public function getFirstPost() { ++ if (is_null($this->mainLib)) { ++ $this->mainLib = $this->game->getLib('main/forums'); ++ } ++ return $this->mainLib->call('getPost', $this->record['first_post'], $this->user); ++ } ++ ++ private function loadPosts() { ++ if (is_null($this->allPosts)) { ++ $q = self::$postsQuery->execute($this->record['id']); ++ if (! ($q && dbCount($q))) { ++ return null; ++ } ++ ++ $this->allPosts = array(); ++ while ($r = dbFetchHash($q)) { ++ $this->allPosts[$r['id']] = $this->mainLib->call('getPost', $r, $this->user); ++ } ++ } ++ } ++ ++ public function getPostList($isThreaded, $oldFirst) { ++ $this->loadPosts(); ++ ++ if ($isThreaded) { ++ $result = $this->threadedSort($this->getFirstPost()->getReplies(), $oldFirst); ++ if (count($result)) { ++ array_shift($result); ++ } ++ array_unshift($result, $this->getFirstPost()); ++ } else { ++ // If we are not in threaded mode, generate the list ++ // of reverse post <=> time associations and sort it ++ // in the appropriate direction ++ $timeList = array(); ++ $result = array(); ++ ++ foreach ($this->allPosts as $id => $p) { ++ $time = (int) $p->getPostedAt(); ++ if (! is_array($timeList[$time])) { ++ $timeList[$time] = array(); ++ } ++ array_push($timeList[$time], $p); ++ } ++ ++ $times = array_keys($timeList); ++ if ($oldFirst) { ++ sort($times); ++ } else { ++ rsort($times); ++ } ++ ++ foreach ($times as $t) { ++ foreach ($timeList[$t] as $p) { ++ array_push($result, $p); ++ } ++ } ++ } ++ ++ return $result; ++ } ++ ++ private function threadedSort($posts, $oldFirst) { ++ if (! count($posts)) { ++ return array(); ++ } ++ ++ $timeList = array(); ++ $replyList = array(); ++ ++ foreach ($posts as $post) { ++ $postReplies = $this->threadedSort($post->getReplies(), $oldFirst); ++ ++ if (count($postReplies)) { ++ $time = array_shift($postReplies); ++ $replyList[$post->getId()] = $postReplies; ++ } else { ++ $time = $post->getPostedAt(); ++ $replyList[$post->getId()] = array(); ++ } ++ ++ $time = (int) $time; ++ if (! is_array($timeList[$time])) { ++ $timeList[$time] = array(); ++ } ++ array_push($timeList[$time], $post); ++ } ++ ++ $times = array_keys($timeList); ++ if ($oldFirst) { ++ sort($times); ++ } else { ++ rsort($times); ++ } ++ ++ $result = array($times[0]); ++ foreach ($times as $t) { ++ foreach ($timeList[$t] as $p) { ++ array_push($result, $p); ++ foreach ($replyList[$p->getId()] as $r) { ++ array_push($result, $r); ++ } ++ } ++ } ++ ++ return $result; ++ } ++ ++ public function getPostById($id) { ++ $this->loadPosts(); ++ return array_key_exists($id, $this->allPosts) ? $this->allPosts[$id] : null; ++ } ++} ++ ++ ++?> +diff -Naur beta5//scripts/game/main/forums/library/substitute.inc forums//scripts/game/main/forums/library/substitute.inc +--- beta5//scripts/game/main/forums/library/substitute.inc 2011-02-05 10:09:57.844335002 +0100 ++++ forums//scripts/game/main/forums/library/substitute.inc 2011-02-05 10:10:03.424335002 +0100 +@@ -1,59 +1,73 @@ + '\[b\](.*?)\[\/b\]', ++ "rt" => '$1' ++ ), array( ++ "re" => '\[u\](.*?)\[\/u\]', ++ "rt" => '$1' ++ ), array( ++ "re" => '\[i\](.*?)\[\/i\]', ++ "rt" => '$1' ++ ), array( ++ "re" => '\[sep(arator)?\]', ++ "rt" => '
' ++ ), array( ++ "re" => '\[item\](.*?)\[\/item\]', ++ "rt" => '
  • $1
' ++ ), array( ++ "re" => '\[quote\](.*?)\[\/quote\]', ++ "rt" => '
$1
', ++ "rep" => true ++ ), array( ++ "re" => '\[quote=([^\]]+)\](.*?)\[\/quote\]', ++ "rt" => '
$1 said:
$2
', ++ "rep" => true ++ ), array( ++ "re" => '\[link=(http[^\]]+)\](.+?)\[\/link\]', ++ "rt" => '$2' ++ ), array( ++ "re" => '\[code\](.*?)\[\/code\]', ++ "rt" => '
$1
' ++ ) ++ ); + + function main_forums_substitute($lib) { + $this->lib = $lib; + $this->db = $this->lib->game->db; + } + +- function run($text, $ec, $es) { ++ function run($text, $ec) { + $src = array('/\\n/','/\\r/'); $dst = array("
",''); ++ $rsSrc = array(); $rsDst = array(); + $text = utf8entities($text, ENT_NOQUOTES); +- $ec = ($ec == 't'); +- $es = ($es == 't'); + + if ($ec) { +- if (is_array($this->code)) { +- foreach ($this->code as $s => $d) { +- array_push($src, '/'.$s.'/i'); +- array_push($dst, $d); +- } +- } else { +- $this->codes = array(); +- $q = $this->db->query("SELECT * FROM f_code"); +- while ($r = dbFetchArray($q)) { +- $this->code[$r[0]] = $r[1]; +- array_push($src, '/'.$r[0].'/i'); +- array_push($dst, $r[1]); ++ foreach (self::$code as $c) { ++ if ($c['rep']) { ++ array_push($rsSrc, "/{$c['re']}/i"); ++ array_push($rsDst, $c['rt']); ++ } else { ++ array_push($src, "/{$c['re']}/i"); ++ array_push($dst, $c['rt']); + } + } + } +- if ($es) { +- if (is_array($this->smiley)) { +- foreach ($this->smiley as $s => $d) { +- array_push($src, '/'.$s.'/i'); +- array_push($dst, $d); +- } +- } else { +- $this->smiley = array(); +- $q = $this->db->query("SELECT * FROM f_smiley"); +- while ($r = dbFetchArray($q)) { +- $fn = getStatic("main/pics/smiles/icon_".$r[1].".gif"); +- if (is_null($fn)) { +- continue; +- } +- $code = "[S]"; +- $this->smiley[$r[0]] = $code; +- array_push($src, '/'.$r[0].'/i'); +- array_push($dst, $code); ++ ++ if (count($rsSrc)) { ++ $i = 0; ++ foreach ($rsSrc as $re) { ++ while (preg_match($re, $text)) { ++ $text = preg_replace($re, $rsDst[$i], $text); + } ++ $i ++; + } + } + +- return preg_replace($src, $dst, $text); ++ $rv = preg_replace($src, $dst, $text); ++ return $rv; + } + } + +diff -Naur beta5//scripts/game/main/forums/library.inc forums//scripts/game/main/forums/library.inc +--- beta5//scripts/game/main/forums/library.inc 2011-02-05 10:09:57.844335002 +0100 ++++ forums//scripts/game/main/forums/library.inc 2011-02-05 10:10:03.434335002 +0100 +@@ -1,71 +1,152 @@ + lib = $lib; +- $this->db = $this->lib->game->db; +- } ++ $this->game = $this->lib->game; ++ $this->db = $this->game->getDBAccess(); + +- function getVersionCategory($ver) { +- $q = $this->db->query("SELECT id,description FROM f_category WHERE title='!$ver!'"); +- return dbFetchHash($q); +- } ++ $this->getForumLib = $this->db->prepare( ++ "SELECT l.lib_path FROM forums.t_forum f, forums.category c, forums.category_type l " ++ . "WHERE f.id = $1 AND c.id = f.category AND l.id = c.acl_lib", ++ array("id") ); ++ $this->getCategoryLib = $this->db->prepare( ++ "SELECT l.lib_path FROM forums.category c, forums.category_type l " ++ . "WHERE l.id = c.acl_lib AND c.id = $1", ++ array("id") ); ++ } ++ ++ public function getCategory($id, $user) { ++ $idx = "$id;$user"; ++ ++ if (! isset($this->categories[$idx])) { ++ $q = $this->getCategoryLib->execute(array("id" => $id)); ++ if (! ($q && dbCount($q) == 1)) { ++ return null; ++ } ++ list($lib) = dbFetchArray($q); ++ ++ if (! isset($this->libraries[$lib]) ) { ++ $this->libraries[$lib] = $this->game->getLib($lib); ++ } ++ $cat = $this->libraries[$lib]->call('getCategory', $id, $user); ++ ++ if (is_null($cat)) { ++ return null; ++ } ++ $this->categories[$idx] = $cat; ++ } + +- function isRead($topic, $player) { +- $q = $this->db->query("SELECT * FROM f_read WHERE topic=$topic AND reader=$player"); +- return $q && dbCount($q); ++ return $this->categories[$idx]; + } + +- function markRead($topic, $player) { +- if ($this->isRead($topic,$player)) { +- return false; ++ public function getForum($id, $user) { ++ $idx = "$id;$user"; ++ ++ if (! isset($this->forums[$idx])) { ++ $q = $this->getForumLib->execute(array("id" => $id)); ++ if (! ($q && dbCount($q) == 1)) { ++ return null; ++ } ++ list($lib) = dbFetchArray($q); ++ ++ if (! isset($this->libraries[$lib]) ) { ++ $this->libraries[$lib] = $this->game->getLib($lib); ++ } ++ $f = $this->libraries[$lib]->call('getForum', $id, $user); ++ ++ if (is_null($f)) { ++ return null; ++ } ++ $this->forums[$idx] = $f; + } +- $this->db->query("DELETE FROM f_read WHERE topic=$topic AND reader=$player"); +- $this->db->query("INSERT INTO f_read(topic,reader)VALUES($topic,$player)"); +- return true; ++ ++ return $this->forums[$idx]; + } + +- function markUnread($topic, $player) { +- $this->db->query("DELETE FROM f_read WHERE topic=$topic AND reader<>$player"); ++ public function getTopic($data, $user) { ++ if (is_array($data)) { ++ $id = $data['id']; ++ } else { ++ $id = $data; ++ } ++ $idx = "$id;$user"; ++ ++ if (! isset($this->topics[$idx])) { ++ loader::needClasses('main/forums', 'fl_topic'); ++ try { ++ $topic = new fl_topic($this->game, $user, $data); ++ } catch (Exception $e) { ++ logText("main/forums::getTopic: failed to fetch topic #$id (user #$user)", LOG_WARNING); ++ return null; ++ } ++ $this->topics[$idx] = $topic; ++ } ++ ++ return $this->topics[$idx]; + } + +- // Get the amount of unread topics in a forum +- function getRead($fid, $uid) { +- $q = $this->db->query("SELECT COUNT(*) FROM f_read r,f_topic t WHERE t.id=r.topic AND t.forum=$fid AND r.reader=$uid AND t.deleted IS NULL"); +- list($nr) = dbFetchArray($q); +- return $nr; ++ public function getPoll($id, $user) { ++ $idx = "$id;$user"; ++ ++ if (! isset($this->polls[$idx])) { ++ loader::needClasses('main/forums', 'fl_poll'); ++ try { ++ $poll = new fl_poll($this->game, $user, $id); ++ } catch (Exception $e) { ++ return null; ++ } ++ $this->polls[$idx] = $poll; ++ } ++ ++ return $this->polls[$idx]; + } + +- function switchSticky($forum, $topic) { +- $this->db->query("UPDATE f_topic SET sticky=NOT sticky WHERE id=$topic AND forum=$forum AND deleted IS NULL"); ++ public function getPost($data, $user) { ++ if (is_array($data)) { ++ $id = $data['id']; ++ } else { ++ $id = $data; ++ } ++ $idx = "$id;$user"; ++ ++ if (! isset($this->posts[$idx])) { ++ loader::needClasses('main/forums', 'fl_post'); ++ try { ++ $post = new fl_post($this->game, $user, $data); ++ } catch (Exception $e) { ++ logText("main/forums::getPost: failed to fetch post #$id (user #$user)", LOG_WARN); ++ return null; ++ } ++ $this->posts[$idx] = $post; ++ } ++ ++ return $this->posts[$idx]; + } + +- function markForumRead($fid, $uid) { +- $q = $this->db->query("SELECT id FROM f_topic WHERE forum=$fid AND deleted IS NULL"); +- while ($r = dbFetchArray($q)) { +- $this->markRead($r[0], $uid); ++ public function setOptions($pid, $forum, $perPage, $viewDeleted) { ++ if ($forum == -1) { ++ prefs::remove('main/F#PP#%'); ++ prefs::remove('main/F#VD#%'); ++ prefs::set('main/F#PP', $perPage); ++ prefs::set('main/F#VD', $viewDeleted ? 1 : 0); ++ } else { ++ prefs::set("main/F#PP#$forum", $perPage); ++ prefs::set("main/F#VD#$forum", $viewDeleted ? 1 : 0); + } + } + } +diff -Naur beta5//scripts/game/main/gforums/library.inc forums//scripts/game/main/gforums/library.inc +--- beta5//scripts/game/main/gforums/library.inc 1970-01-01 01:00:00.000000000 +0100 ++++ forums//scripts/game/main/gforums/library.inc 2011-02-05 10:10:03.384335002 +0100 +@@ -0,0 +1,165 @@ ++lib = $lib; ++ $this->game = $this->lib->game; ++ $this->version = $this->game->version; ++ $this->db = $this->game->db; ++ ++ $this->fLib = $this->game->getLib('main/forums'); ++ loader::needClasses('main/forums', array('fl_category', 'fl_forum')); ++ ++ $this->getCatQuery = $this->db->prepare( ++ "SELECT t_string,t_is_game FROM main.gf_category WHERE category = $1", array("id") ); ++ $this->getCatForumsQuery = $this->db->prepare( ++ "SELECT id FROM forums.t_forum WHERE category = $1 ORDER BY f_order", array("category") ); ++ $this->getForumPrivQuery = $this->db->prepare( ++ "SELECT * FROM main.get_gforums_privs( $1, $2 )", array("user", "forum") ); ++ $this->getUserNameQ = $this->db->prepare( "SELECT name FROM account WHERE id = $1", array("user") ); ++ } ++ ++ function getStructure( $user ) { ++ $cats = array(); ++ ++ $q = $this->db->query("SELECT * FROM main.get_gf_categories($1, $2)", ++ $this->version->id, $this->game->name); ++ while ($r = dbFetchArray($q)) { ++ $cat = $this->fLib->call('getCategory', $r[0], $user); ++ if (! is_null($cat)) { ++ array_push($cats, $cat); ++ } ++ } ++ ++ return $cats; ++ } ++ ++ function getCategory( $catId , $user ) { ++ $q = $this->getCatQuery->execute($catId); ++ if (! ($q && dbCount($q) == 1)) { ++ return null; ++ } ++ list($str, $ig) = dbFetchArray($q); ++ ++ if (is_null($str)) { ++ $title = 'Legacy Worlds forums'; ++ $description = 'These forums are common to all versions of Legacy Worlds after Beta 5.'; ++ } else if ($ig == 'f') { ++ $txt = config::$config->versions[$str]->text; ++ $title = "Legacy Worlds {$txt}"; ++ $description = "These forums are common to all games using the Legacy Worlds " ++ . "{$txt} base code."; ++ } else { ++ $txt = config::$config->games[$str]->text; ++ $title = $txt; ++ $description = "These are the forums for the {$txt} game."; ++ } ++ ++ $cat = new fl_category( $this->game, $user, $catId, $title, $description ); ++ ++ $q = $this->getCatForumsQuery->execute($catId); ++ while ($r = dbFetchArray($q)) { ++ $forum = $this->fLib->call('getForum', $r[0], $user); ++ if ($forum->canView() && ($forum->isAdmin() || ! $forum->isDeleted())) { ++ $cat->addForum( $forum ); ++ } ++ } ++ ++ return $cat; ++ } ++ ++ ++ function getForum( $forumId, $user ) { ++ $q = $this->getForumPrivQuery->execute($user, $forumId); ++ if (! ($q && dbCount($q) == 1)) { ++ return null; ++ } ++ $privs = dbFetchHash($q); ++ ++ try { ++ $forum = new fl_forum( $this->game, $forumId, $user ); ++ } catch (Exception $e) { ++ return null; ++ } ++ ++ $forum->setAdmin( $privs['is_admin'] == 't' ); ++ $forum->setMod( $privs['is_mod'] == 't' ); ++ $forum->setCreateTopic( $privs['can_create'] == 't' ); ++ $forum->setCreatePoll( $privs['can_poll'] == 't' ); ++ $forum->setPost( $privs['can_post'] == 't' ); ++ $forum->setView( $privs['can_view'] == 't' ); ++ ++ return $forum; ++ } ++ ++ ++ public function getAdmins($forum) { ++ $q = $this->db->query("SELECT category FROM forums.t_forum WHERE id = $1", $forum); ++ if (! ($q && dbCount($q) == 1)) { ++ return array(); ++ } ++ list($cat) = dbFetchArray($q); ++ ++ $admins = array(); ++ ++ $q = $this->db->query("SELECT account FROM gf_admin WHERE category = $1 OR category IS NULL", $cat); ++ if (!$q) { ++ return $admins; ++ } ++ while ($r = dbFetchArray($q)) { ++ array_push($admins, $r[0]); ++ } ++ return $admins; ++ } ++ ++ ++ public function getModerators($forum) { ++ $q = $this->db->query("SELECT category FROM forums.t_forum WHERE id = $1", $forum); ++ if (! ($q && dbCount($q) == 1)) { ++ return array(); ++ } ++ list($cat) = dbFetchArray($q); ++ ++ $mods = array(); ++ ++ $q = $this->db->query("SELECT account FROM gf_cat_moderator " ++ . "WHERE category = $1 OR category IS NULL", $cat); ++ if (!$q) { ++ return $mods; ++ } ++ while ($r = dbFetchArray($q)) { ++ array_push($mods, $r[0]); ++ } ++ ++ $q = $this->db->query("SELECT account FROM gf_forum_moderator WHERE forum = $1", $forum); ++ if (!$q) { ++ while ($r = dbFetchArray($q)) { ++ array_push($mods, $r[0]); ++ } ++ } ++ ++ return $mods; ++ } ++ ++ ++ public function getUsers($forum) { ++ return array(); ++ } ++ ++ ++ public function aclIdToName($id) { ++ $q = $this->getUserNameQ->execute($id); ++ if (!($q && dbCount($q) == 1)) { ++ return null; ++ } ++ list($rv) = dbFetchArray($q); ++ return $rv; ++ } ++} ++ ++?> +diff -Naur beta5//scripts/game/main/uforums/library.inc forums//scripts/game/main/uforums/library.inc +--- beta5//scripts/game/main/uforums/library.inc 1970-01-01 01:00:00.000000000 +0100 ++++ forums//scripts/game/main/uforums/library.inc 2011-02-05 10:10:03.374335002 +0100 +@@ -0,0 +1,193 @@ ++lib = $lib; ++ $this->game = $this->lib->game; ++ $this->db = $this->game->getDBAccess(); ++ ++ $this->account = $this->game->getLib('main/account'); ++ $this->fLib = $this->game->getLib('main/forums'); ++ loader::needClasses('main/forums', array('fl_category', 'fl_forum')); ++ ++ $this->getOwnCatQ = $this->db->prepare( ++ "SELECT main.uf_get_category($1)", array("user") ); ++ $this->getOtherCatsQ = $this->db->prepare( ++ "SELECT DISTINCT category FROM forums.t_forum WHERE id IN (" ++ . "SELECT forum FROM main.uf_subscription WHERE account = $1)", ++ array("user") ); ++ $this->getCatOwnerQ = $this->db->prepare( ++ "SELECT account FROM main.user_category WHERE category = $1", array("category") ); ++ $this->getOwnForumsQ = $this->db->prepare( ++ "SELECT id FROM forums.t_forum WHERE category = $1 ORDER BY f_order", array("category") ); ++ $this->getOtherForumsQ = $this->db->prepare( ++ "SELECT id FROM forums.t_forum" ++ . " WHERE category = $1 " ++ . " AND id IN (SELECT forum FROM main.uf_subscription WHERE account = $2)" ++ . " ORDER BY f_order", ++ array("category", "user") ); ++ $this->getForumPrivQ = $this->db->prepare( ++ "SELECT main.uf_get_user_access( $2, $1 )", array("forum", "user") ); ++ $this->getUserNameQ = $this->db->prepare( "SELECT name FROM account WHERE id = $1", array("user") ); ++ } ++ ++ function getStructure( $user ) { ++ $cats = array(); ++ ++ $q = $this->getOwnCatQ->execute($user); ++ if ( $q && dbCount($q) == 1) { ++ list($id) = dbFetchArray($q); ++ $cat = $this->fLib->call('getCategory', $id, $user); ++ if (! is_null($cat)) { ++ array_push($cats, $cat); ++ } ++ } ++ ++ $q = $this->getOtherCatsQ->execute($user); ++ while ($r = dbFetchArray($q)) { ++ $cat = $this->fLib->call('getCategory', $r[0], $user); ++ if (! is_null($cat)) { ++ array_push($cats, $cat); ++ } ++ } ++ ++ return $cats; ++ } ++ ++ function getCategory( $id, $user ) { ++ $q = $this->getCatOwnerQ->execute($id); ++ if (! ($q && dbCount($q) == 1) ) { ++ return null; ++ } ++ list($account) = dbFetchArray($q); ++ ++ if ($account == $user) { ++ $title = "My forums"; ++ $description = "These are your own forums; do with them as you please."; ++ } else { ++ $uname = $this->account->call('getUserName', $account); ++ $title = "$uname's forums"; ++ $description = "These are the personnal forums of player $uname."; ++ } ++ ++ $cat = new fl_category( $this->game, $user, $id, $title, $description ); ++ ++ if ($account == $user) { ++ $q = $this->getOwnForumsQ->execute($id); ++ if (! $q ) { ++ return null; ++ } ++ } else { ++ $q = $this->getOtherForumsQ->execute($id, $user); ++ if (! ($q && dbCount($q) ) ) { ++ return null; ++ } ++ } ++ while ($r = dbFetchArray($q)) { ++ $forum = $this->fLib->call('getForum', $r[0], $user); ++ if ($forum->canView() && ($forum->isAdmin() || ! $forum->isDeleted())) { ++ $cat->addForum( $forum ); ++ } ++ } ++ ++ return $cat; ++ } ++ ++ function getForum( $forumId, $user) { ++ $q = $this->getForumPrivQ->execute($forumId, $user); ++ if (! ($q && dbCount($q) == 1)) { ++ return null; ++ } ++ list($accessMode) = dbFetchArray($q); ++ if (is_null($accessMode)) { ++ return null; ++ } ++ ++ try { ++ $forum = new fl_forum( $this->game, $forumId, $user ); ++ } catch (Exception $e) { ++ return null; ++ } ++ ++ $forum->setAdmin( $accessMode == 'A' ); ++ $forum->setMod( in_array($accessMode, array('M', 'A')) ); ++ $forum->setCreatePoll( in_array($accessMode, array('L', 'M', 'A')) ); ++ $forum->setCreateTopic( in_array($accessMode, array('T', 'L', 'M', 'A')) ); ++ $forum->setPost( $accessMode != 'R' ); ++ $forum->setView( true ); ++ ++ return $forum; ++ } ++ ++ ++ public function getAdmins($forum) { ++ $q = $this->db->query("SELECT uc.account FROM user_category uc, forums.t_forum f " ++ . "WHERE f.id = $1 AND uc.category = f.category", $forum); ++ if (!($q && dbCount($q) == 1)) { ++ return array(); ++ } ++ list($admin) = dbFetchArray($q); ++ ++ return array($admin); ++ } ++ ++ ++ public function getModerators($forum) { ++ $q = $this->db->query("SELECT account FROM uf_subscription WHERE access_mode = 'M' AND forum = $1", ++ $forum); ++ if (!$q) { ++ return array(); ++ } ++ $mods = array(); ++ while ($r = dbFetchArray($q)) { ++ array_push($mods, $r[0]); ++ } ++ ++ return $mods; ++ } ++ ++ ++ public function getUsers($forum) { ++ $q = $this->db->query("SELECT uf_get_access_mode($1)", $forum); ++ if (!($q && dbCount($q))) { ++ return array(); ++ } ++ list($mode) = dbFetchArray($q); ++ if ($mode == 'P') { ++ return array(); ++ } ++ ++ $q = $this->db->query("SELECT account FROM uf_subscription WHERE access_mode <> 'M' AND forum = $1", ++ $forum); ++ if (!$q) { ++ return array(); ++ } ++ $users = array(); ++ while ($r = dbFetchArray($q)) { ++ array_push($users, $r[0]); ++ } ++ ++ return $users; ++ } ++ ++ ++ public function aclIdToName($id) { ++ $q = $this->getUserNameQ->execute($id); ++ if (!($q && dbCount($q) == 1)) { ++ return null; ++ } ++ list($rv) = dbFetchArray($q); ++ return $rv; ++ } ++} ++ ++ ++?> +diff -Naur beta5//scripts/lib/classloader.inc forums//scripts/lib/classloader.inc +--- beta5//scripts/lib/classloader.inc 2011-02-05 10:09:57.434335002 +0100 ++++ forums//scripts/lib/classloader.inc 2011-03-12 14:56:22.471300053 +0100 +@@ -20,6 +20,17 @@ + + array_push(loader::$loadedClasses, $className); + } ++ ++ static function needClasses($basePath, $classes) { ++ $path = config::$main['scriptdir'] . "/game/$basePath/"; ++ if (!is_array($classes)) { ++ $classes = array($classes); ++ } ++ ++ foreach ($classes as $cn) { ++ self::load($path . $cn . ".inc", $cn); ++ } ++ } + } + + ?> +diff -Naur beta5//scripts/lib/config.inc forums//scripts/lib/config.inc +--- beta5//scripts/lib/config.inc 2011-02-05 10:09:57.434335002 +0100 ++++ forums//scripts/lib/config.inc 2011-03-12 14:56:24.031300053 +0100 +@@ -123,6 +123,10 @@ + static function getParam($name) { + return config::$config->games['main']->params[$name]; + } ++ ++ static function getMainInterface() { ++ return config::$config->games['main']; ++ } + } + + config::load(); +diff -Naur beta5//scripts/lib/db_accessor.inc forums//scripts/lib/db_accessor.inc +--- beta5//scripts/lib/db_accessor.inc 2011-02-05 10:09:57.434335002 +0100 ++++ forums//scripts/lib/db_accessor.inc 2011-03-12 15:30:19.021300053 +0100 +@@ -32,9 +32,15 @@ + $this->db->disableExceptions(); + } + +- public function query($q) { ++ public function query() { + $this->setNamespace(); +- return $this->db->query($q); ++ $args = func_get_args(); ++ return call_user_func_array(array($this->db, "query"), $args); ++ } ++ ++ public function prepare($q, $params) { ++ $this->setNamespace(); ++ return db_query::getQuery($this, $q, $params); + } + + public function begin() { +@@ -45,6 +51,14 @@ + return $this->db->end($rb); + } + ++ public function getNamespace() { ++ return $this->namespace; ++ } ++ ++ public function getDatabase() { ++ return $this->db; ++ } ++ + public function needsReset() { + return $this->db->needsReset(); + } +diff -Naur beta5//scripts/lib/db_connection.inc forums//scripts/lib/db_connection.inc +--- beta5//scripts/lib/db_connection.inc 2011-02-05 10:09:57.434335002 +0100 ++++ forums//scripts/lib/db_connection.inc 2011-03-12 15:39:08.891300050 +0100 +@@ -229,17 +229,22 @@ + return true; + } + +- public function query($query) { +- if (!$this->inTrans) { +- $this->fail("query executed outside of transaction", $query); +- return null; ++ public function query() { ++ if (func_num_args() == 1) { ++ $query = func_get_arg(0); ++ if (!$this->checkQuery($query)) { ++ return null; ++ } ++ $r = @pg_query($this->conn, $query); ++ } else { ++ $args = func_get_args(); ++ $query = array_shift($args); ++ if (!$this->checkQuery($query)) { ++ return null; ++ } ++ $r = call_user_func("pg_query_params", $query, $args); + } + +- $this->queries ++; +- if ($this->trace) { +- l::trace("EXECUTE: $query"); +- } +- $r = @pg_query($this->conn, $query); + if (!$r) { + $cStat = pg_connection_status($this->conn); + $error = pg_last_error($this->conn); +@@ -264,48 +269,23 @@ + } + } elseif (preg_match('/^\s*insert\s+into ("?\w+"?)/i', $query, $match)) { + pg_free_result($r); +- +- $tn = $match[1]; +- if ($tn{0} == '"') { +- $tn = str_replace('"', '', $tn); +- } else { +- $tn = strtolower($tn); +- } +- +- $r2 = @pg_query("SELECT last_inserted('$tn')"); +- if ($r2) { +- $rv = pg_fetch_row($r2); +- if (is_null($rv[0])) { +- $r = true; +- } else { +- $r = $rv[0]; +- } +- pg_free_result($r2); +- } elseif (preg_match('/deadlock/i', $error)) { +- l::error("SQL: deadlock detected"); +- if ($this->useExceptions) { +- throw new db_deadlock_exception($error); +- } +- $this->error = true; +- return; +- } else { +- $cStat = pg_connection_status($this->conn); +- $error = pg_last_error($this->conn); +- if ($cStat == PGSQL_CONNECTION_BAD) { +- l::notice("SQL: connection is gone while fetching last ID"); +- $this->__needsReset = true; +- if ($this->useExceptions) { +- throw new db_srv_exception($error); +- } +- } else { +- $this->fail("failed to fetch last ID: $error", "SELECT last_inserted('$tn')"); +- } +- $r = null; +- } ++ $r = $this->getLastInserted($match[1]); + } + return $r; + } + ++ private function checkQuery($query) { ++ if (!$this->inTrans) { ++ $this->fail("SQL: query executed outside of transaction", $query); ++ return false; ++ } ++ if ($this->trace) { ++ l::trace("EXECUTE: $query"); ++ } ++ $this->queries ++; ++ return true; ++ } ++ + public function getGameAccess($prefix) { + if (is_null($this->accessors[$prefix])) { + $this->accessors[$prefix] = new db_accessor($this, $prefix); +@@ -313,6 +293,50 @@ + return $this->accessors[$prefix]; + } + ++ public function getConnection() { ++ return $this->conn; ++ } ++ ++ private function getLastInserted($tn) { ++ if ($tn{0} == '"') { ++ $tn = str_replace('"', '', $tn); ++ } else { ++ $tn = strtolower($tn); ++ } ++ ++ $r2 = @pg_query("SELECT last_inserted('$tn')"); ++ if ($r2) { ++ $rv = pg_fetch_row($r2); ++ if (is_null($rv[0])) { ++ $r = true; ++ } else { ++ $r = $rv[0]; ++ } ++ pg_free_result($r2); ++ } elseif (preg_match('/deadlock/i', $error)) { ++ l::error("SQL: deadlock detected"); ++ if ($this->useExceptions) { ++ throw new db_deadlock_exception($error); ++ } ++ $this->error = true; ++ $r = null; ++ } else { ++ $cStat = pg_connection_status($this->conn); ++ $error = pg_last_error($this->conn); ++ if ($cStat == PGSQL_CONNECTION_BAD) { ++ l::notice("SQL: connection is gone while fetching last ID"); ++ $this->__needsReset = true; ++ if ($this->useExceptions) { ++ throw new db_srv_exception($error); ++ } ++ } else { ++ $this->fail("failed to fetch last ID: $error", "SELECT last_inserted('$tn')"); ++ } ++ $r = null; ++ } ++ return $r; ++ } ++ + public function needsReset() { + return $this->__needsReset; + } +diff -Naur beta5//scripts/lib/db_query.inc forums//scripts/lib/db_query.inc +--- beta5//scripts/lib/db_query.inc 1970-01-01 01:00:00.000000000 +0100 ++++ forums//scripts/lib/db_query.inc 2011-02-05 10:10:03.074335002 +0100 +@@ -0,0 +1,132 @@ ++accessor = $accessor; ++ $this->query = $query; ++ $this->parameters = $pNames; ++ $this->name = $name; ++ ++ $this->database = $this->accessor->getDatabase(); ++ $this->connection = $this->database->getConnection(); ++ ++ $this->accessor->useNamespace(); ++ $r = @pg_prepare($this->connection, $this->name, $query); ++ ++ if (! $r) { ++ $this->accessor->getDatabase()->fail( ++ "SQL: Could not prepare query: " . pg_last_error($this->connection), ++ $this->query ); ++ throw new Exception('failed'); ++ } ++ ++ if (preg_match('/^\s*insert\s+into ("?\w+"?)/i', $this->query, $match)) { ++ $this->table = $match[1]; ++ } ++ } ++ ++ ++ public function execute() { ++ if ($this->counter == 0) { ++ $this->database->fail( ++ "SQL: tried to execute de-allocated query", ++ $this->query ); ++ return null; ++ } ++ if (!$this->database->checkQuery($this->query)) { ++ return null; ++ } ++ ++ $args = func_get_args(); ++ if (count($args) == 1 && is_array($args[0])) { ++ $params = $args[0]; ++ $p = array(); ++ for ($i = 0; $i < count($this->parameters); $i ++) { ++ if (!array_key_exists($this->parameters[$i], $params)) { ++ $this->database->fail( ++ "SQL: missing parameter '{$this->parameters[$i]}' for prepared query", ++ $this->query ); ++ return null; ++ } ++ $p[$i] = $params[$this->parameters[$i]]; ++ } ++ } elseif (count($args) == count($this->parameters)) { ++ $p = $args; ++ } else { ++ $this->database->fail( "SQL: invalid parameters for prepared query", $this->query ); ++ return null; ++ } ++ ++ $this->accessor->useNamespace(); ++ $r = @pg_execute($this->connection, $this->name, $p); ++ ++ if (! $r) { ++ $this->database->fail( ++ "SQL: could not execute prepared query: " . pg_last_error($this->connection), ++ $this->query ); ++ return null; ++ } ++ ++ if (!is_null($this->table)) { ++ pg_free_result($r); ++ $r = $this->database->getLastInserted($this->table); ++ } ++ ++ return $r; ++ } ++ ++ public function destroy($force = false) { ++ if ($this->counter == 0) { ++ return; ++ } ++ ++ if ($force) { ++ $this->counter = 0; ++ } else { ++ $this->counter --; ++ } ++ ++ if ($this->counter == 0) { ++ $this->accessor->useNamespace(); ++ $r = $this->database->query("DEALLOCATE {$this->name}"); ++ self::$queries[$this->name] = null; ++ } ++ } ++ ++ ++ public static function getQuery($accessor, $query, $pNames) { ++ $data = array( ++ "n" => $accessor->getNamespace(), ++ "q" => $query, ++ "p" => $pNames ++ ); ++ $name = "q_" . strtolower(md5(serialize($data))); ++ ++ if (is_null(self::$queries[$name])) { ++ try { ++ self::$queries[$name] = new db_query($name, $accessor, $query, $pNames); ++ } catch (Exception $e) { ++ return null; ++ } ++ } else { ++ self::$queries[$name]->counter ++; ++ } ++ return self::$queries[$name]; ++ } ++ ++} ++ ++?> +diff -Naur beta5//scripts/lib/library.inc forums//scripts/lib/library.inc +--- beta5//scripts/lib/library.inc 2011-02-05 10:09:57.434335002 +0100 ++++ forums//scripts/lib/library.inc 2011-03-12 14:56:22.481300053 +0100 +@@ -66,10 +66,28 @@ + $fcn = $this->loadClass($this->functions[$function][1]); + $this->functions[$function][2] = new $fcn($this); + } +- $rv = call_user_func_array(array($this->functions[$function][2], 'run'), $args); ++ try { ++ $rv = call_user_func_array(array($this->functions[$function][2], 'run'), $args); ++ } catch (Exception $e) { ++ fatalError(32, array( ++ "Function call '{$this->name}::$function' on game '{$this->game->name}' " ++ . "failed with exception:", ++ $e->getMessage() ++ )); ++ } + } else { + // Call the function instance's method +- $rv = call_user_func_array(array($this->mainClass, $this->functions[$function][1]), $args); ++ try { ++ $rv = call_user_func_array( ++ array($this->mainClass, $this->functions[$function][1]), $args ++ ); ++ } catch (Exception $e) { ++ fatalError(32, array( ++ "Function call '{$this->name}::$function' on game '{$this->game->name}' " ++ . "failed with exception:", ++ $e->getMessage() ++ )); ++ } + } + + return $rv; +diff -Naur beta5//scripts/lib/prefs.inc forums//scripts/lib/prefs.inc +--- beta5//scripts/lib/prefs.inc 2011-02-05 10:09:57.434335002 +0100 ++++ forums//scripts/lib/prefs.inc 2011-02-05 10:10:03.074335002 +0100 +@@ -107,6 +107,19 @@ + return $v; + } + ++ static function remove($path) { ++ list($version, $name) = explode('/', $path); ++ $qs = "DELETE FROM user_preferences WHERE account={$_SESSION['userid']} AND version = '$version' AND id"; ++ if (strstr($name, '%') === FALSE) { ++ $q = dbQuery("$qs = '$name'"); ++ } else { ++ $q = dbQuery("$qs LIKE '$name'"); ++ } ++ prefs::$prefs = null; ++ prefs::load(); ++ ++ } ++ + } + + ?> +diff -Naur beta5//scripts/site/beta5/handlers/alliance.inc forums//scripts/site/beta5/handlers/alliance.inc +--- beta5//scripts/site/beta5/handlers/alliance.inc 2011-02-05 10:09:57.204335002 +0100 ++++ forums//scripts/site/beta5/handlers/alliance.inc 2011-03-12 15:16:23.151300053 +0100 +@@ -18,8 +18,8 @@ + // Pending requests + "getPending", "acceptRequests", "rejectRequests", + // Forums +- "getForums", "newForum", "changeForum", "delForum", +- "moveForum", "getForumAcl", ++ "getForums", "newForum", "changeForum", ++ "delForum", "restoreForum", "moveForum", + // Ranks + "getRanks", "newRank", "changeRank", "delRank" + ), +@@ -802,190 +802,316 @@ + // ALLIANCE FORUMS MANAGEMENT + //------------------------------------------- + +- function doGetForums($aid) { +- $afl = gameAction('getAllianceForums', $aid); +- $s = ""; +- foreach ($afl as $id => $afd) +- { +- if ($s != "") +- $s .= "\n"; +- $s .= "$id#" . $afd['order'] . "#" . ($afd['user_post'] ? 1 : 0) . "#" . $afd['title']; +- if ($afd['description'] != '') +- { +- $dll = split("\n", $afd['description']); +- foreach ($dll as $dl) +- $s .= "\n+#$dl"; ++ private function forumsGetLibraries() { ++ // Get the libraries ++ $this->player = input::$game->getLib('beta5/player'); ++ $this->alliance = input::$game->getLib('beta5/alliance'); ++ $this->forums = input::$game->getLib('main/forums'); ++ $this->aForums = input::$game->getLib('beta5/aforums'); ++ } ++ ++ private function doGetForums($aid, $uid) { ++ $out = array(); ++ $players = array(); ++ ++ // Get the category and output the amount of forums ++ $cat = array_shift($this->aForums->call('getStructure', $uid)); ++ array_push($out, count($cat->getForums())); ++ $admin = null; ++ ++ foreach ($cat->getForums() as $forum) { ++ // Get general information regarding the forum ++ $fid = $forum->getId(); ++ $userPrivs = $this->aForums->call('getUserPrivileges', $fid); ++ $topics = $forum->getTopics(); ++ $name = $forum->getTitle(); ++ ++ // Get information about the forum's deletion ++ $deleted = $forum->isDeleted() ? '1' : '0'; ++ $deletedAt = $forum->deletedAt(); ++ $deletedBy = $forum->deletedBy(); ++ if (! (is_null($deletedBy) || in_array($deletedBy, $players))) { ++ array_push($players, $deletedBy); ++ } ++ ++ // Output general information ++ array_push($out, "$fid#$deleted#$deletedBy#$deletedAt#$userPrivs#$topics#$name"); ++ ++ // Get the rank-specific access privileges for that forum ++ array_push($out, join('#', $forum->getUsers())); ++ array_push($out, join('#', $forum->getModerators())); ++ $admins = $forum->getAdministrators(); ++ ++ // Get the description ++ $description = $forum->getDescription(); ++ if (is_null($description) || $description == '') { ++ array_push($out, "0"); ++ } else { ++ $dList = explode("\n", $description); ++ array_push($out, count($dList)); ++ foreach ($dList as $dLine) { ++ array_push($out, $dLine); ++ } + } + } +- return $s; ++ ++ // Get the rank names ++ $ranks = $this->alliance->call('getRanks', $aid); ++ array_push($out, count($ranks)); ++ foreach ($ranks as $rId => $rName) { ++ array_push($out, "$rId#" . utf8entities($rName)); ++ } ++ ++ // List the ranks having administrator privileges ++ if (is_null($admins)) { ++ $admins = array(); ++ foreach ($ranks as $rId => $rName) { ++ $rkp = $this->alliance->call('getRankPrivileges', $rId); ++ if ($rkp['forum_admin']) { ++ array_push($admins, $rId); ++ } ++ } ++ } ++ array_push($out, join('#', $admins)); ++ ++ // Get the player names (for forums that have been deleted) ++ array_push($out, count($players)); ++ foreach ($players as $pid) { ++ array_push($out, "$pid#" . utf8entities($this->player->call('getName', $pid, true))); ++ } ++ ++ return join("\n", $out); + } + +- function getForums() { ++ public function getForums() { + $pid = $_SESSION[game::sessName()]['player']; +- $p = gameAction('getPlayerInfo', $pid); ++ $this->forumsGetLibraries(); ++ ++ // Check if the player's in an alliance ++ $p = $this->player->call('get', $pid); + if (is_null($p['aid'])) { + return "ERR#0"; + } ++ ++ // Check if the player is a forums administrator + $aid = $p['aid']; +- $pr = gameAction('getAlliancePrivileges', $pid); ++ $pr = $this->alliance->call('getPrivileges', $pid); + if (!$pr['forum_admin']) { + return "ERR#4"; + } +- $_SESSION[game::sessName()]['alliance_page'] = 'FAdmin'; + +- return $this->doGetForums($aid); ++ $_SESSION[game::sessName()]['alliance_page'] = 'FAdmin'; ++ return $this->doGetForums($aid, $p['uid']); + } + +- function newForum($name, $userPost, $after, $description, $acl) { +- if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { +- return "ERR#200"; ++ private function setForumPrivileges( $forum, $alliance, $acl ) { ++ $rl = $this->alliance->call('getRanks', $alliance); ++ $acla = explode('#', $acl); ++ $forum->clearACL(); ++ ++ foreach ($acla as $as) { ++ list($rank, $level) = explode('!', $as); ++ $level --; ++ if (is_null($rl[$rank]) || ($level != 0 && $level != 1)) { ++ continue; ++ } ++ ++ if ($level) { ++ $forum->addModerator($rank); ++ } else { ++ $forum->addUser($rank); ++ } + } ++ } + ++ ++ public function newForum($name, $accessMode, $after, $description, $acl) { ++ $this->forumsGetLibraries(); + $pid = $_SESSION[game::sessName()]['player']; +- $p = gameAction('getPlayerInfo', $pid); +- if (is_null($p['aid'])) +- return "ERR#0"; +- $aid = $p['aid']; +- $pr = gameAction('getAlliancePrivileges', $pid); +- if (!$pr['forum_admin']) +- return "ERR#4"; ++ $p = $this->player->call('get', $pid); + +- $afl = gameAction('getAllianceForums', $aid); +- if (count($afl) >= 30) { +- return "ERR#5"; ++ if (is_null($p['aid'])) { ++ return "ERR#0\n0"; + } + +- $name = preg_replace('/\s+/', ' ', trim($name)); +- if ($name == "" || strlen($name) < 4) +- return "ERR#1"; +- foreach ($afl as $fid => $fd) +- if ($fd['title'] == $name) +- return "ERR#2"; +- +- if ($after != "-1" && is_null($afl[$after])) +- return "ERR#6"; +- +- $description = trim($description); +- gameAction('newAllianceForum', $aid, $name, ($userPost == 1), $after, $description); +- +- $afl = gameAction('getAllianceForums', $aid); +- $mId = false; +- foreach ($afl as $fid => $fd) +- if ($fd['title'] == $name) +- { +- $mId = $fid; +- break; ++ if ($this->player->call('isOnVacation', $pid)) { ++ $err = 200; ++ } else { ++ $name = preg_replace('/\s+/', ' ', trim($name)); ++ $description = trim($description); ++ $cat = array_shift($this->aForums->call('getStructure', $p['uid'])); ++ $after = (int) $after; ++ ++ if ($name == "" || strlen($name) < 4) { ++ $err = 1; ++ } elseif ($after != -1 && is_null($cat->findForum($after))) { ++ $err = 6; ++ } else { ++ if ($after == -1) { ++ $order = 0; ++ } else { ++ $order = $cat->findForum($after)->getOrder() + 1; ++ } ++ ++ $rv = $this->aForums->call('create', ++ $pid, $p['uid'], $p['aid'], $order, ++ $name, $description, $accessMode ++ ); ++ if (is_object($rv)) { ++ $this->setForumPrivileges( $rv, $p['aid'], $acl ); ++ $err = null; ++ } else { ++ logText("Alliance forum creation returned $rv"); ++ switch ($rv) : ++ case 1: $err = 0; break; ++ case 2: $err = 4; break; ++ case 3: $err = 5; break; ++ case 4: $err = 2; break; ++ default: $err = 7; break; ++ endswitch; ++ } + } +- if (!$mId) { +- return "ERR#7"; + } + +- $rl = gameAction('getAllianceRanks', $aid); +- $fread = $fmod = array(); +- $acla = explode('#', $acl); +- foreach ($acla as $as) +- { +- list($rank,$level) = explode('!', $as); +- $level --; +- if (is_null($rl[$rank]) || ($level != 0 && $level != 1)) +- continue; +- if ($level) +- array_push($fmod, $rank); +- else +- array_push($fread, $rank); ++ $out = $this->doGetForums($p['aid'], $p['uid']); ++ if (!is_null($err)) { ++ $out = "ERR#$err\n$out"; + } +- gameAction('setForumAccess', $mId, $fread, $fmod); +- +- return $this->doGetForums($aid); ++ return $out; + } + +- function changeForum($id, $name, $userPost, $description, $acl) { +- if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { +- return "ERR#200"; +- } +- ++ public function changeForum($id, $name, $accessMode, $description, $acl) { ++ $this->forumsGetLibraries(); + $pid = $_SESSION[game::sessName()]['player']; +- $p = gameAction('getPlayerInfo', $pid); +- if (is_null($p['aid'])) +- return "ERR#0"; +- $aid = $p['aid']; +- $pr = gameAction('getAlliancePrivileges', $pid); +- if (!$pr['forum_admin']) +- return "ERR#1"; +- +- $afl = gameAction('getAllianceForums', $aid); +- if (is_null($afl[$id])) +- return "ERR#3"; ++ $p = $this->player->call('get', $pid); + +- $name = preg_replace('/\s+/', ' ', trim($name)); +- if ($name == "" || strlen($name) < 4) +- return "ERR#1"; +- foreach ($afl as $fid => $fd) +- if ($fid != $id && $fd['name'] == $name) +- return "ERR#2"; +- +- $description = trim($description); +- gameAction('modifyAllianceForum', $id, $name, ($userPost == 1), $description); ++ if (is_null($p['aid'])) { ++ return "ERR#0\n0"; ++ } + +- $rl = gameAction('getAllianceRanks', $aid); +- $fread = $fmod = array(); +- $acla = explode('#', $acl); +- foreach ($acla as $as) +- { +- list($rank,$level) = explode('!', $as); +- $level --; +- if (is_null($rl[$rank]) || ($level != 0 && $level != 1)) +- continue; +- if ($level) +- array_push($fmod, $rank); +- else +- array_push($fread, $rank); ++ if ($this->player->call('isOnVacation', $pid)) { ++ $err = 200; ++ } else { ++ $id = (int) $id; ++ $name = preg_replace('/\s+/', ' ', trim($name)); ++ $description = trim($description); ++ ++ $rv = $this->aForums->call('modifyForum', $id, $pid, $name, $description, $accessMode); ++ if ($rv > 0) { ++ switch ($rv) : ++ case -1: $err = 7; break; ++ case -2: $err = 0; break; ++ case -3: $err = 4; break; ++ case -4: $err = 8; break; ++ case -5: $err = 7; break; ++ endswitch; ++ } else { ++ $err = null; ++ $this->setForumPrivileges( $this->forums->call('getForum', $id, $p['uid']), ++ $p['aid'], $acl ); ++ } + } +- gameAction('setForumAccess', $id, $fread, $fmod); + +- return $this->doGetForums($aid); ++ $out = $this->doGetForums($p['aid'], $p['uid']); ++ if (!is_null($err)) { ++ $out = "ERR#$err\n$out"; ++ } ++ return $out; + } + + function delForum($id) { +- if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { +- return "ERR#200"; ++ $this->forumsGetLibraries(); ++ $pid = $_SESSION[game::sessName()]['player']; ++ $p = $this->player->call('get', $pid); ++ ++ if (is_null($p['aid'])) { ++ $err = 0; ++ $out = "0"; ++ } else { ++ if ($this->player->call('isOnVacation', $pid)) { ++ $err = 200; ++ } else { ++ $f = $this->forums->call('getForum', (int) $id, $p['uid']); ++ if (is_null($f) || $f->isDeleted()) { ++ $err = 8; ++ } elseif (! $f->isAdmin()) { ++ $err = 4; ++ } else { ++ $f->delete(); ++ $err = null; ++ } ++ } ++ $out = $this->doGetForums($p['aid'], $p['uid']); + } + ++ if (!is_null($err)) { ++ $out = "ERR#$err\n$out"; ++ } ++ return $out; ++ } ++ ++ function restoreForum($id) { ++ $this->forumsGetLibraries(); + $pid = $_SESSION[game::sessName()]['player']; +- $p = gameAction('getPlayerInfo', $pid); +- if (is_null($p['aid'])) +- return "ERR#0"; +- $aid = $p['aid']; +- $pr = gameAction('getAlliancePrivileges', $pid); +- if (!$pr['forum_admin']) +- return "ERR#1"; ++ $p = $this->player->call('get', $pid); + +- $afl = gameAction('getAllianceForums', $aid); +- if (is_null($afl[$id])) { +- return "ERR#8"; ++ if (is_null($p['aid'])) { ++ $err = 0; ++ $out = "0"; ++ } else { ++ if ($this->player->call('isOnVacation', $pid)) { ++ $err = 200; ++ } else { ++ $f = $this->forums->call('getForum', (int) $id, $p['uid']); ++ if (is_null($f)) { ++ $err = 9; ++ } elseif (! $f->isAdmin()) { ++ $err = 4; ++ } elseif (! $f->isDeleted()) { ++ $err = 9; ++ } else { ++ $f->restore(); ++ $err = null; ++ } ++ } ++ $out = $this->doGetForums($p['aid'], $p['uid']); + } +- gameAction('deleteAllianceForum', $id); +- return $this->doGetForums($aid); +- } + +- function moveForum($id, $up) { +- if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { +- return "ERR#200"; ++ if (!is_null($err)) { ++ $out = "ERR#$err\n$out"; + } ++ return $out; ++ } + ++ function moveForum($id, $up) { ++ $this->forumsGetLibraries(); + $pid = $_SESSION[game::sessName()]['player']; +- $p = gameAction('getPlayerInfo', $pid); +- if (is_null($p['aid'])) +- return "ERR#0"; +- $aid = $p['aid']; +- $pr = gameAction('getAlliancePrivileges', $pid); +- if (!$pr['forum_admin']) +- return "ERR#1"; ++ $p = $this->player->call('get', $pid); ++ ++ if (is_null($p['aid'])) { ++ $err = 0; ++ $out = "0"; ++ } else { ++ if ($this->player->call('isOnVacation', $pid)) { ++ $err = 200; ++ } else { ++ $f = $this->forums->call('getForum', (int) $id, $p['uid']); ++ if (is_null($f) || $f->isDeleted()) { ++ $err = 8; ++ } elseif (! $f->isAdmin()) { ++ $err = 4; ++ } else { ++ $f->move($up == '1'); ++ $err = null; ++ } ++ } ++ $out = $this->doGetForums($p['aid'], $p['uid']); ++ } + +- $afl = gameAction('getAllianceForums', $aid); +- if (!is_null($afl[$id])) +- gameAction('moveAllianceForum', $id, ($up == "1")); +- return $this->doGetForums($aid); ++ if (!is_null($err)) { ++ $out = "ERR#$err\n$out"; ++ } ++ return $out; + } + + function getForumAcl($id) { +diff -Naur beta5//scripts/site/beta5/handlers/comms.inc forums//scripts/site/beta5/handlers/comms.inc +--- beta5//scripts/site/beta5/handlers/comms.inc 2011-02-05 10:09:57.204335002 +0100 ++++ forums//scripts/site/beta5/handlers/comms.inc 2011-03-12 15:21:17.911300054 +0100 +@@ -8,6 +8,50 @@ + 'init' => "makeCommsTooltips();\ninitPage();" + ); + ++ /** This method dumps containers, categories and forums to the output data. ++ * The dump's format is the following: ++ * ++ * type # id # nElements # topics # unread ++ * name ++ * ++ * With: ++ * "type" being either "C" for categories and containers or "F" for forums ++ * "id" being the object's identifier ++ * "nElements" is the number of sub-elements the object contains ++ * "topics" and "unread" are the amount of topics and unread topics ++ * "name" is the object's title ++ * ++ * \parameter $result a reference to the output data ++ * \parameter $container the object to be dumped ++ */ ++ private function dumpForums( &$result, $container ) { ++ $res = array(); ++ ++ if ( $container instanceof fl_forum ) { ++ array_push($res, 'F'); ++ $contents = array(); ++ } else { ++ array_push($res, 'C'); ++ if ( $container instanceof fl_category ) { ++ $contents = $container->getForums(); ++ } else { ++ $contents = $container->getCategories(); ++ } ++ } ++ ++ array_push( $res, $container->getId() ); ++ array_push( $res, count($contents) ); ++ array_push( $res, $container->getTopics() ); ++ array_push( $res, $container->getUnread() ); ++ ++ array_push( $result, join('#', $res) ); ++ array_push( $result, utf8entities($container->getTitle())); ++ ++ foreach ($contents as $c) { ++ $this->dumpForums( &$result, $c ); ++ } ++ } ++ + public function getCommsData() { + // Get the data + $data = $this->game->action('getCommsOverview', $_SESSION[game::sessName()]['player']); +@@ -16,33 +60,20 @@ + $result = array(); + array_push($result, count($data['folders']['CUS']) . "#" . count($data['forums']['general']) + . "#" . count($data['forums']['alliance'])); +- +- // Messages in default folders +- $dFld = array('IN', 'INT', 'OUT'); +- foreach ($dFld as $f) { +- array_push($result, join('#', $data['folders'][$f])); +- } +- +- // Custom folders + foreach ($data['folders']['CUS'] as $id => $folder) { + $folder[2] = utf8entities($folder[2]); + array_unshift($folder, $id); + array_push($result, join('#', $folder)); + } + ++ + // Forums +- foreach ($data['forums']['general'] as $cat) { +- array_push($result, "{$cat['id']}#{$cat['type']}#" . count($cat['forums']) +- . "#" . utf8entities($cat['title'])); +- +- foreach ($cat['forums'] as $f) { +- $f[3] = utf8entities($f[3]); +- array_push($result, join('#', $f)); +- } +- } +- foreach ($data['forums']['alliance'] as $f) { +- $f[3] = utf8entities($f[3]); +- array_push($result, join('#', $f)); ++ $forums = gameAction('getForums', $pid); ++ if (is_null($forums)) { ++ array_push($result, "C#/#0#0#0"); ++ array_push($result, ""); ++ } else { ++ $this->dumpForums( &$result, $forums ); + } + + return join("\n", $result); +diff -Naur beta5//scripts/site/beta5/handlers/diplomacy.inc forums//scripts/site/beta5/handlers/diplomacy.inc +--- beta5//scripts/site/beta5/handlers/diplomacy.inc 2011-02-05 10:09:57.204335002 +0100 ++++ forums//scripts/site/beta5/handlers/diplomacy.inc 2011-02-05 10:10:02.884335002 +0100 +@@ -1,7 +1,12 @@ + array('getInformation'), +@@ -9,9 +14,6 @@ + ); + + function getAllianceRanking($tag) { +- if (! $this->rkLib) { +- $this->rkLib = input::$game->getLib('main/rankings'); +- } + $rt = $this->rkLib->call('getType', "a_general"); + $r = $this->rkLib->call('get', $rt, $tag); + if (!$r) { +@@ -20,27 +22,33 @@ + return $r; + } + +- function getInformation() +- { ++ function getInformation() { + $out = array(); + $pid = $_SESSION[game::sessName()]['player']; +- $pinf = gameAction('getPlayerInfo', $pid); +- if (!is_null($pinf['arid'])) +- { +- $ainf = gameAction('getAllianceInfo', $pinf['arid']); ++ ++ // Get the libraries ++ $this->rkLib = input::$game->getLib('main/rankings'); ++ $this->player = input::$game->getLib('beta5/player'); ++ $this->alliance = input::$game->getLib('beta5/alliance'); ++ ++ $pinf = $this->player->call('get', $pid); ++ ++ // Player has requested to join an alliance ++ if (!is_null($pinf['arid'])) { ++ $ainf = $this->alliance->call('get', $pinf['arid']); + $s = "1#" . $ainf['nplanets'] . '#' . $ainf['avgx'] . '#' . $ainf['avgy']; + list($points,$ranking) = $this->getAllianceRanking($ainf['tag']); + $s .= "#$ranking#$points"; + array_push($out, $s); + array_push($out, utf8entities($pinf['alliance_req'])); + array_push($out, utf8entities($pinf['aname'])); +- $alinf = gameAction('getPlayerName', $ainf['leader']); ++ $alinf = $this->player->call('getName', $ainf['leader']); + array_push($out, utf8entities($alinf)); +- } +- elseif (!is_null($pinf['aid'])) +- { +- $ainf = gameAction('getAllianceInfo', $pinf['aid']); +- $pr = gameAction('getAlliancePrivileges', $pid); ++ ++ // Player is a member of an alliance ++ } elseif (!is_null($pinf['aid'])) { ++ $ainf = $this->alliance->call('get', $pinf['aid']); ++ $pr = $this->alliance->call('getPrivileges', $pid); + + $s = "2#" . $ainf['nplanets'] . '#' . $ainf['avgx'] . '#' . $ainf['avgy']; + list($points,$ranking) = $this->getAllianceRanking($ainf['tag']); +@@ -49,31 +57,36 @@ + array_push($out, $s); + array_push($out, utf8entities($pinf['alliance'])); + array_push($out, utf8entities($pinf['aname'])); +- if (!$pr['is_leader']) +- { +- array_push($out, utf8entities(gameAction('getPlayerName', $ainf['leader']))); +- if (is_null($pinf['a_grade'])) ++ ++ // Get the player's rank if he isn't the leader ++ if (!$pr['is_leader']) { ++ array_push($out, utf8entities($this->player->call('getName', $ainf['leader']))); ++ if (is_null($pinf['a_grade'])) { + array_push($out, "-"); +- else +- { +- $rkl = gameAction('getAllianceRanks', $pinf['aid']); ++ } else { ++ $rkl = $this->alliance->call('getRanks', $pinf['aid']); + array_push($out, $rkl[$pinf['a_grade']]); + } + } + +- $fl = gameAction('getAllianceForumsComplete', $pinf['aid']); +- foreach ($fl as $fd) +- { +- $fid = $fd['id']; +- if (!(in_array($fid, $pr['f_read']) || in_array($fid, $pr['f_mod']))) ++ // Get the list of alliance forums ++ $aForums = input::$game->getLib('beta5/aforums'); ++ $cat = array_shift($aForums->call('getStructure', $pinf['uid'])); ++ foreach ($cat->getForums() as $forum) { ++ if ($forum->isDeleted()) { + continue; +- $tot = $fd['topics']; +- $unread = $tot - gameAction('getReadTopics', $fid, $pid); +- array_push($out, "$fid#$tot#$unread#".utf8entities($fd['title'])); ++ } ++ $fid = $forum->getId(); ++ $tot = $forum->getTopics(); ++ $unread = $forum->getUnread(); ++ $name = utf8entities($forum->getTitle()); ++ array_push($out, "$fid#$tot#$unread#$name"); + } +- } +- else ++ ++ // Player is not a member of any alliance and has not requested to join one ++ } else { + array_push($out, "0"); ++ } + + $pm = gameAction('getAllMessages', $pid, 'IN'); + $pmn = gameAction('getNewMessages', $pid, 'IN'); +diff -Naur beta5//scripts/site/beta5/handlers/forums.inc forums//scripts/site/beta5/handlers/forums.inc +--- beta5//scripts/site/beta5/handlers/forums.inc 2011-02-05 10:09:57.204335002 +0100 ++++ forums//scripts/site/beta5/handlers/forums.inc 2011-03-12 14:56:16.341300049 +0100 +@@ -1,9 +1,7 @@ + output = "forums"; + } ++*/ ++ /* Timeout in seconds for the forums' session data */ ++ const SESSION_TIMEOUT = 60; ++ ++ /* AJAX engine data */ ++ public $needsAuth = true; ++ public $ajax = array( ++ "init" => "main = new ForumsLayout();", ++ "func" => array( ++ // Menu functions ++ "showMenu", "hideMenu", "getMenu", ++ "menuOpen", "menuClose", ++ // Category / forum view functions ++ "getView", "categoryRead", "forumOptions", "forumRead", ++ "restoreTopics", "deleteTopics", "changeTopicsLevel", ++ "setTopicsLevel", "setTopicsLock", "moveTopics", ++ // Topic view functions ++ "getTopic", "loadPostContents" ++ )); ++ ++ /* Method that loads the required libraries */ ++ private function loadLibraries() { ++ $this->player = input::$game->getLib('beta5/player'); ++ ++ $this->forums = input::$game->getLib('main/forums'); ++ $this->gForums = input::$game->getLib('main/gforums'); ++ $this->uForums = input::$game->getLib('main/uforums'); ++ $this->aForums = input::$game->getLib('beta5/aforums'); ++ } ++ ++ ++/***********************************************************************************************************************/ ++/** SESSION CODE *******************************************************************************************************/ ++/***********************************************************************************************************************/ ++ ++ /* This method reads the menu's status from the session */ ++ private function getMenuSession() { ++ if (! array_key_exists( 'forums_menu', $_SESSION[game::sessName()] )) { ++ $_SESSION[game::sessName()]['forums_menu'] = array(); ++ } ++ return $_SESSION[game::sessName()]['forums_menu']; ++ } ++ ++ /* This method stores the menu's status */ ++ private function storeMenuSession( $value ) { ++ $_SESSION[game::sessName()]['forums_menu'] = $value; ++ } ++ ++ /* This method initialises the main forums session and removes old data */ ++ private function initMainSession() { ++ ++ if (is_null( $_SESSION[game::sessName()]['forums_data'] )) { ++ $_SESSION[game::sessName()]['forums_data'] = array(); ++ return; ++ } ++ ++ // Clean up old data ++ $now = time(); ++ $nSession = array(); ++ foreach ($_SESSION[game::sessName()]['forums_data'] as $key => $data) { ++ if (!is_array( $data )) { ++ continue; ++ } ++ if ($data['ts'] - $now < self::SESSION_TIMEOUT) { ++ $nSession[$key] = $data; ++ } ++ } ++ $_SESSION[game::sessName()]['forums_data'] = $nSession; ++ } ++ ++ /* This method is used by specific handlers to read from the session */ ++ public function getSessionData($key) { ++ $this->initMainSession(); ++ if (is_array($_SESSION[game::sessName()]['forums_data'][$key]) ++ && array_key_exists( 'data', $_SESSION[game::sessName()]['forums_data'][$key])) { ++ $_SESSION[game::sessName()]['forums_data'][$key]['ts'] = time(); ++ return $_SESSION[game::sessName()]['forums_data'][$key]['data']; ++ } ++ return null; ++ } ++ ++ /* This method is used by specific handlers to write into the session */ ++ public function setSessionData($key, $data) { ++ $this->initMainSession(); ++ $_SESSION[game::sessName()]['forums_data'][$key] = array( ++ "ts" => time(), ++ "data" => $data ++ ); ++ } ++ ++ ++/***********************************************************************************************************************/ ++/** MENU HANDLER *******************************************************************************************************/ ++/***********************************************************************************************************************/ ++ ++ /* Returns a boolean indicating whether the menu should be displayed */ ++ private function getMenuVisibility() { ++ if (! array_key_exists( 'display_forums_menu', $_SESSION[game::sessName()] )) { ++ $_SESSION[game::sessName()]['display_forums_menu'] = true; ++ } ++ return $_SESSION[game::sessName()]['display_forums_menu']; ++ } ++ ++ /* Sets the value for the menu's visibility */ ++ private function setMenuVisibility( $value ) { ++ $_SESSION[game::sessName()]['display_forums_menu'] = (boolean) $value; ++ } ++ ++ /* This method generates the output for a menu entry */ ++ private function makeMenuEntry( &$output, $command, $isLeaf, $isOpen, ++ $displayType, $text, $id = '', $unread = 0 ) { ++ ++ // The first line indicates the type of menu entry, as well as the display type, ++ // amount of unread topics and, if the entry is a submenu, whether it's open or not ++ $txt = $isLeaf ? "L" : "N"; ++ $txt .= "#$displayType#$unread"; ++ if (! $isLeaf) { ++ $txt .= "#" . ($isOpen ? "1" : "0") . "#$id"; ++ } ++ array_push( $output, $txt ); ++ ++ // The two next lines contain the command and text / command ID ++ array_push( $output, $command ); ++ array_push( $output, $text ); ++ } ++ ++ /* This method outputs the "separator" indicator */ ++ private function makeMenuSeparator( &$output ) { ++ array_push( $output, "S" ); ++ } ++ ++ /* This method outputs the "end of menu" indicator */ ++ private function makeEndOfMenu( &$output ) { ++ array_push( $output, "E" ); ++ } ++ ++ /* This method generates the menu's data */ ++ private function dumpMenu( &$output, $structure ) { ++ ++ // Handle the root of the tree ++ if ($structure instanceof fl_container && $structure->getId() == '/') { ++ // This is the root node ++ $this->makeMenuEntry( &$output, "V#C#/", false, true, "C", "overview", '/' ); ++ ++ // Add "Latest messages" entry ++ $this->makeMenuEntry( &$output, "L#C#/", true, false, "C", "latest" ); ++ // Add the "Search forums" entry ++ $this->makeMenuEntry( &$output, "SF", true, false, "C", "search" ); ++ ++ // Add the moderation tools submenu ++ // FIXME ++ ++ // Add the submenus ++ $this->makeMenuSeparator( &$output ); ++ foreach ($structure->getCategories() as $cat) { ++ $this->dumpMenu( &$output, $cat, $menuData ); ++ } ++ ++ $this->makeEndOfMenu( &$output ); ++ return; ++ } ++ ++ // Handle containers ++ if ($structure instanceof fl_container) { ++ $id = $structure->getId(); ++ $menuData = $this->getMenuSession(); ++ ++ // Create the node ++ $this->makeMenuEntry( &$output, "V#C#$id", false, $menuData[$id], ++ "T", $structure->getTitle(), $id, $structure->getUnread()); ++ ++ // Return if the menu's closed ++ if (! $menuData[$id]) { ++ return; ++ } ++ ++ // Add "Latest messages" entry ++ $this->makeMenuEntry( &$output, "L#C#$id", true, false, "C", "latest" ); ++ ++ // Special stuff to be added here ++ // FIXME ++ ++ // Add the submenus ++ $catList = $structure->getCategories(); ++ if (count($catList)) { ++ $this->makeMenuSeparator( &$output ); ++ foreach ($catList as $cat) { ++ $this->dumpMenu( &$output, $cat, $menuData ); ++ } ++ } ++ ++ $this->makeEndOfMenu( &$output ); ++ return; ++ } ++ ++ // Standard categories ++ if ($structure instanceof fl_category) { ++ $id = $structure->getId(); ++ $menuData = $this->getMenuSession(); ++ ++ // Create the node ++ $this->makeMenuEntry( &$output, "V#C#$id", false, $menuData[$id], ++ "T", $structure->getTitle(), $id, $structure->getUnread()); ++ ++ // Return if the menu's closed ++ if (! $menuData[$id]) { ++ return; ++ } ++ ++ // Add "Latest messages" entry ++ $this->makeMenuEntry( &$output, "L#C#$id", true, false, "C", "latest" ); ++ ++ // Special stuff to be added here ++ // FIXME ++ ++ // Add the forums ++ $fList = $structure->getForums(); ++ if (count($fList)) { ++ $this->makeMenuSeparator( &$output ); ++ foreach ($fList as $f) { ++ if ( ! $f->canView() || $f->isDeleted() ) { ++ continue; ++ } ++ $this->makeMenuEntry( &$output, "V#F#" . $f->getId(), ++ true, false, "T", $f->getTitle(), '', $f->getUnread() ); ++ } ++ } ++ ++ $this->makeEndOfMenu( &$output ); ++ return; ++ } ++ } ++ ++ /* AJAX method that shows the menu */ ++ public function showMenu() { ++ $this->setMenuVisibility(true); ++ return $this->getMenu(); ++ } ++ ++ /* AJAX method that hides the menu */ ++ public function hideMenu() { ++ $this->setMenuVisibility(false); ++ } ++ ++ /* AJAX method that returns the forums' menu */ ++ public function getMenu($previousMD5 = null) { ++ $pid = $_SESSION[game::sessName()]['player']; ++ $this->loadLibraries(); ++ ++ // Get the forums' structure ++ $structure = input::$game->action('getForums', $pid); ++ ++ // Dump the whole thing ++ $result = array(); ++ $this->dumpMenu( &$result, $structure ); ++ ++ // Check the MD5 sum ++ $md5 = md5(serialize($result)); ++ if ($md5 !== $previousMD5) { ++ array_unshift($result, $md5); ++ return join("\n", $result); ++ } ++ ++ return "-"; ++ } ++ ++ /* AJAX method that opens a path in the menu */ ++ public function menuOpen($toID) { ++ $intID = (int) $toID; ++ if ( in_array($toID, array('G', 'U', 'MT')) || (string) $intID == $toID ) { ++ if ( (string) $intID == $toID ) { ++ $toID = (string) $intID; ++ } ++ $menuData = $this->getMenuSession(); ++ $menuData[$toID] = true; ++ $this->storeMenuSession($menuData); ++ } ++ ++ return $this->getMenu(); ++ } ++ ++ /* AJAX method that closes a path in the menu */ ++ public function menuClose($toID) { ++ $intID = (int) $toID; ++ if ( in_array($toID, array('G', 'U', 'MT')) || (string) $intID == $toID ) { ++ if ( (string) $intID == $toID ) { ++ $toID = (string) $intID; ++ } ++ $menuData = $this->getMenuSession(); ++ $menuData[$toID] = false; ++ $this->storeMenuSession($menuData); ++ } ++ ++ return $this->getMenu(); ++ } ++ ++ ++/***********************************************************************************************************************/ ++/** CATEGORY / FORUM VIEW **********************************************************************************************/ ++/***********************************************************************************************************************/ ++ ++ /* AJAX method used to refresh a view page */ ++ public function getView($whichView, $md5) { ++ $handler = input::$game->getLib('beta5/forums/view'); ++ $pid = $_SESSION[game::sessName()]['player']; ++ $handler->call('forumCommand', $pid, $whichView); ++ return $handler->call('getData', $md5); ++ } ++ ++ /* AJAX method that marks all of a category's forums as read */ ++ public function categoryRead($category, $whichView) { ++ $handler = input::$game->getLib('beta5/forums/view'); ++ $pid = $_SESSION[game::sessName()]['player']; ++ $handler->call('forumCommand', $pid, $whichView); ++ $handler->call('categoryRead', $category); ++ return $handler->call('getData', $md5); ++ } ++ ++ /* AJAX method that set forums options */ ++ public function forumOptions($forum, $toAll, $perPage, $viewDeleted, $md5) { ++ $forum = (int) $forum; ++ $toAll = ($toAll == '1'); ++ $perPage = ($perPage > 0 && $perPage % 10 == 0 && $perPage / 10 < 6) ? (int)$perPage : 20; ++ $viewDeleted = $viewDeleted; ++ $pid = $_SESSION[game::sessName()]['player']; ++ ++ $this->loadLibraries(); ++ $this->forums->call('setOptions', $pid, $toAll ? -1 : $forum, $perPage, $viewDeleted); ++ ++ $handler = input::$game->getLib('beta5/forums/view'); ++ $handler->call('forumCommand', $pid, "F#$forum"); ++ return $handler->call('getData', $md5); ++ } ++ ++ /* AJAX method that set forums options */ ++ public function forumRead($forum) { ++ $pid = $_SESSION[game::sessName()]['player']; ++ $handler = input::$game->getLib('beta5/forums/view'); ++ $handler->call('forumCommand', $pid, "F#$forum"); ++ $handler->call('forumRead'); ++ return $handler->call('getData'); ++ } ++ ++ /* AJAX method that restores a set of topics */ ++ public function restoreTopics($forum, $topics) { ++ $pid = $_SESSION[game::sessName()]['player']; ++ $handler = input::$game->getLib('beta5/forums/view'); ++ $handler->call('forumCommand', $pid, "F#$forum"); ++ $handler->call('restoreTopics', explode('#', $topics)); ++ return $handler->call('getData'); ++ } ++ ++ /* AJAX method that deletes a set of topics */ ++ public function deleteTopics($forum, $topics) { ++ $pid = $_SESSION[game::sessName()]['player']; ++ $handler = input::$game->getLib('beta5/forums/view'); ++ $handler->call('forumCommand', $pid, "F#$forum"); ++ $handler->call('deleteTopics', explode('#', $topics)); ++ return $handler->call('getData'); ++ } ++ ++ /* AJAX method that changes the sticky level for a set of topics */ ++ public function changeTopicsLevel($forum, $topics, $change) { ++ $pid = $_SESSION[game::sessName()]['player']; ++ $handler = input::$game->getLib('beta5/forums/view'); ++ $handler->call('forumCommand', $pid, "F#$forum"); ++ $handler->call('changeTopicsLevel', explode('#', $topics), $change); ++ return $handler->call('getData'); ++ } ++ ++ /* AJAX method that sets the sticky level for a set of topics */ ++ public function setTopicsLevel($forum, $topics, $level) { ++ $pid = $_SESSION[game::sessName()]['player']; ++ $handler = input::$game->getLib('beta5/forums/view'); ++ $handler->call('forumCommand', $pid, "F#$forum"); ++ $handler->call('setTopicsLevel', explode('#', $topics), $level); ++ return $handler->call('getData'); ++ } ++ ++ /* AJAX method that sets the lock for a set of topics */ ++ public function setTopicsLock($forum, $topics, $lock) { ++ $lock = ($lock == '1'); ++ $pid = $_SESSION[game::sessName()]['player']; ++ $handler = input::$game->getLib('beta5/forums/view'); ++ $handler->call('forumCommand', $pid, "F#$forum"); ++ $handler->call('setTopicsLock', explode('#', $topics), $lock); ++ return $handler->call('getData'); ++ } ++ ++ /* AJAX method that moves a set of topics to another forum */ ++ public function moveTopics($forum, $topics, $destination) { ++ $pid = $_SESSION[game::sessName()]['player']; ++ $handler = input::$game->getLib('beta5/forums/view'); ++ $handler->call('forumCommand', $pid, "F#$forum"); ++ $handler->call('moveTopics', explode('#', $topics), $destination); ++ return $handler->call('getData'); ++ } ++ ++ ++/***********************************************************************************************************************/ ++/** TOPIC VIEW *********************************************************************************************************/ ++/***********************************************************************************************************************/ ++ ++ /* AJAX method that returns a post's contents */ ++ public function loadPostContents($topic, $md5, $postId) { ++ $pid = $_SESSION[game::sessName()]['player']; ++ $handler = input::$game->getLib('beta5/forums/topic'); ++ $handler->call('initialize', $this); ++ $handler->call('forumCommand', $pid, "$topic#$md5"); ++ return $handler->call('getPostContents', $postId); ++ } ++ ++ ++/***********************************************************************************************************************/ ++/** PAGE HANDLER *******************************************************************************************************/ ++/***********************************************************************************************************************/ ++ ++ /* The main page handler */ ++ public function handle($input) { ++ ++ // Get the command ++ $cmd = $input['cmd']; ++ if ($cmd == 'o' || $cmd == '') { ++ $cmd = 'V#C#/'; ++ } ++ ++ // Check the command ++ if ($cmd == 'SF') { ++ // Search forums ++ $args = array(); // FIXME ++ } elseif ($cmd == 'xx') { ++ // FIXME: other 'simple' commands ++ } else { ++ $rCmd = $cmd{0}; ++ $args = substr($cmd, 2); ++ if ($rCmd == 'V') { ++ // View command ++ $handler = input::$game->getLib('beta5/forums/view'); ++ } elseif ($rCmd == 'T') { ++ // View topic command ++ $handler = input::$game->getLib('beta5/forums/topic'); ++ } ++ } ++ ++ // Execute the command ++ $handler->call('initialize', $this); ++ list($mCurrent, $pageType, $pageData) = $handler->call( 'forumCommand', ++ $_SESSION[game::sessName()]['player'], $args ); ++ ++ $this->data = array( ++ "needData" => input::$IE, ++ "showMenu" => $this->getMenuVisibility(), ++ "menuCurrent" => $mCurrent, ++ "pageType" => $pageType ++ ); ++ if (input::$IE) { ++ $this->data['menuContents'] = ''; ++ $this->data['pageData'] = $pageData; ++ } else { ++ if ($this->data['showMenu']) { ++ $this->data['menuContents'] = $this->getMenu(); ++ } ++ $this->data['pageData'] = $handler->call('getData'); ++ } ++ ++ $this->output = "forums"; ++ } ++ + } + + ?> +diff -Naur beta5//scripts/site/beta5/output/comms.en.inc forums//scripts/site/beta5/output/comms.en.inc +--- beta5//scripts/site/beta5/output/comms.en.inc 2011-02-05 10:09:57.294335002 +0100 ++++ forums//scripts/site/beta5/output/comms.en.inc 2011-03-12 14:56:19.761300052 +0100 +@@ -15,9 +15,7 @@ +

+ +

Forums

+-

General forums

+-

+-
++
+ + Help + +diff -Naur beta5//scripts/site/beta5/output/forums.en.inc forums//scripts/site/beta5/output/forums.en.inc +--- beta5//scripts/site/beta5/output/forums.en.inc 2011-02-05 10:09:57.294335002 +0100 ++++ forums//scripts/site/beta5/output/forums.en.inc 2011-02-05 10:10:02.974335002 +0100 +@@ -1,5 +1,6 @@ + +@@ -68,5 +69,19 @@ + $sp = $args['sp']; + $args = $args['d']; + include("forums/en/$sp.inc"); ++*/ + + ?> ++
++
##
++
++ ++
++
++ ++
++
 
+diff -Naur beta5//scripts/ticks.php forums//scripts/ticks.php +--- beta5//scripts/ticks.php 2011-02-05 10:09:57.904335002 +0100 ++++ forums//scripts/ticks.php 2011-03-12 15:13:57.671300053 +0100 +@@ -20,8 +20,8 @@ + $__loader = array( + 'log', 'classloader', + 'version', 'game', 'tick', 'config', +- 'db_connection', 'db_accessor', 'db_copy', 'db', +- 'pcheck', 'library', 'tick_manager', ++ 'db_connection', 'db_accessor', 'db_copy' , 'db_query', ++ 'db', 'pcheck' , 'library', 'tick_manager', + ); + require_once("loader.inc"); + +diff -Naur beta5//site/index.php forums//site/index.php +--- beta5//site/index.php 2011-02-05 10:09:57.164335002 +0100 ++++ forums//site/index.php 2011-02-05 10:10:02.844335002 +0100 +@@ -4,8 +4,8 @@ + $__loader = array( + 'log', 'classloader', + 'version', 'game', 'tick', 'config', +- 'db_connection', 'db_accessor', 'db', +- 'library', 'actions', 'data_tree', ++ 'db_connection', 'db_accessor', 'db_query', ++ 'db', 'library', 'actions', 'data_tree', + 'input', 'ajax', 'handler', 'engine', + 'resource', 'tracking', 'session', + 'account', 'prefs', 'output' +diff -Naur beta5//site/static/beta5/js/pg_alliance-en.js forums//site/static/beta5/js/pg_alliance-en.js +--- beta5//site/static/beta5/js/pg_alliance-en.js 2011-02-05 10:09:56.434335002 +0100 ++++ forums//site/static/beta5/js/pg_alliance-en.js 2011-03-12 15:12:38.321300050 +0100 +@@ -1037,54 +1037,105 @@ + // FORUMS ADMINISTRATION PAGE + //-------------------------------------------------- + +-function drawForumList() +-{ ++function drawForumList() { + var str = ''; + str += ''; + str += '

Alliance Forums

 
 
'; + document.getElementById('alpmain').innerHTML = str; +- if (faForums.length < 30) +- drawCreateForumLink(); +- if (faForums.length == 0) ++ ++ if (faForums.length < 30) { ++ document.getElementById('crforum').innerHTML = 'Create a forum'; ++ } ++ ++ if (faForums.length == 0) { + drawTextNoForums(); +- else ++ } else { + drawRealForumList(); ++ } + } + +-function drawCreateForumLink() +-{ +- var str = ''; +- str += 'Create a forum'; +- document.getElementById('crforum').innerHTML = str; +-} +- +-function drawTextNoForums() +-{ ++function drawTextNoForums() { + document.getElementById('falist').innerHTML = '

No forums have been defined for the alliance.

'; + } + +-function drawRealForumList() +-{ +- var i, str = ''; ++function drawRealForumList() { ++ var i, str = '
'; ++ ++ var getFRankText = function (id) { ++ var r = faRanks['r' + id]; ++ return r == '-' ? 'Default member' : r; ++ } ++ ++ str += ''; ++ for (i = 0; i < faForums.length; i++) { ++ str += ''; +- for (i=0;i/g, '>') + ' ('; +- str += 'Edit - '; +- if (i > 0) +- str += 'Move up - '; +- if (i < faForums.length - 1) +- str += 'Move down - '; +- str += 'Delete)'; +- if (faForums[i].description != '') +- str += '

' + faForums[i].description.replace(/&/g, '&').replace(//g, '>').replace(/\n/g,"
") + '

'; + str += '
'; + } + +@@ -1092,85 +1143,96 @@ + document.getElementById('falist').innerHTML = str; + } + +-function cheatAlert() +-{ +- alert('Possible cheating detected.'); +-} +- +-function confirmDeleteForum(name) +-{ +- var str = 'You are about to delete the following forum:\n' + name + '\n'; +- str += 'The forum\'s topics will be lost and you will not be able to recover them.\nPlease confirm.'; ++function confirmDeleteForum(forum) { ++ var str = 'You are about to delete the following forum:\n ' + forum.name + '\n'; ++ if (forum.topics > 0) { ++ str += 'The forum contains ' ++ + (forum.topics > 1 ? (formatNumber(forum.topics.toString()) + ' topics') : 'a single topic') ++ + '.\n'; ++ } ++ str += 'Please confirm.'; + return confirm(str); + } + +-function alertMaximumFCount() +-{ ++function alertMaximumFCount() { + alert('The alliance has reached its maximum possible count of forums.\nYou will be taken back to the list.'); + } + +-function drawForumEditor() +-{ ++function drawForumEditor() { + document.getElementById('falist').innerHTML = ' '; + + var str; +- if (faEditing.id) +- { ++ if (faEditing.id) { + f = forumById(faEditing.id); + str = "'" + f.name.replace(/&/g, '&').replace(//g, '>') + "' forum"; +- } +- else ++ } else { + str = "Create a forum"; ++ } + document.getElementById('fattl').innerHTML = '

' + str + '

'; + +- str = '
Name & descriptionUser access mode
' ++ + faForums[i].name.replace(/&/g, '&').replace(//g, '>') ++ + ' ('; ++ if (faForums[i].isDeleted) { ++ str += 'deleted by ' + faPlayers['p' + faForums[i].deletedBy] + ' at ' ++ + formatDate(faForums[i].deletedAt) + ' - Restore'; ++ } else { ++ str += 'Edit - '; ++ if (i > 0) { ++ str += 'Move up - '; ++ } ++ if (i < faForums.length - 1) { ++ str += 'Move down - '; ++ } ++ str += 'Delete'; ++ } ++ str += ")"; ++ ++ if (faForums[i].description != '' || faForums[i].users.length || faForums[i].mods.length) { ++ ++ var nbr = false; ++ str += '

'; ++ ++ if (faForums[i].description != '') { ++ str += faForums[i].description.replace(/&/g, '&').replace(//g, '>').replace(/\n/g,"
"); ++ nbr = true; ++ } ++ ++ if (faForums[i].users.length) { ++ str += (nbr ? '
' : '') + 'Users: '; ++ for (var ri in faForums[i].users) { ++ str += (ri > 0 ? ' - ' : '') + getFRankText(faForums[i].users[ri]); ++ } ++ nbr = true; ++ } ++ ++ if (faForums[i].mods.length) { ++ str += (nbr ? '
' : '') + 'Moderators: '; ++ for (var ri in faForums[i].mods) { ++ str += (ri > 0 ? ' - ' : '') + getFRankText(faForums[i].mods[ri]); ++ } ++ nbr = true; ++ } ++ ++ str += '

'; ++ } + +- str += '
Name & descriptionNew threads
'; +- if (faForums[i].userPost) +- str += 'Everyone'; +- else +- str += 'Moderators only'; ++ switch (faForums[i].accessMode) { ++ case 'L': ++ str += 'Full access'; ++ break; ++ case 'T': ++ str += 'No polls'; ++ break; ++ case 'P': ++ str += 'Replies only'; ++ break; ++ case 'M': ++ str += 'Read only'; ++ break; ++ } + str += '
'; +- str += ''; ++ } ++ ++ str += '' ++ + '' ++ + '' ++ + '' ++ + '
Forum name:'; ++ str = '' ++ + '' ++ + ''; +- str += ''; +- str += ''; +- str += ''; +- str += ''; +- str += ''; + +- str += '
Forum name:' ++ + '
User access mode:'; ++ ++ var aModes = { ++ M: 'Read only', ++ P: 'Replies only', ++ T: 'Topic creation', ++ L: 'Complete access' ++ }; ++ for (var i in aModes) { ++ str += ' '; ++ } + str += '
New threads:'; +- str += '
Description:
'; +- str += ' 

Forum access

(Loading access list data ...)

'; ++ if (!faEditing.id) { ++ str += '
Initial position: 
Description:
' ++ + ' 

Forum access

 
'; + document.getElementById('falist').innerHTML = str; ++ ++ document.getElementById('fname').value = faEditing.name; ++ document.getElementById('fdesc').value = faEditing.description; + document.getElementById('feok').disabled = true; +- if (!faEditing.id) ++ if (!faEditing.id) { + updateFPosSelector(); ++ } ++ drawFAccessManager(); + } + +-function updateFPosSelector() +-{ +- var i, str = ''; + document.getElementById('faeipos').innerHTML = str; + } + +-function drawFAccessManager() +-{ ++function drawFAccessManager() { + var lnp = new Array(), lmd = new Array(), lrd = new Array(), ml, i, sc=0; + for (i=0;i 0 ? "\n" : "") + lines.shift(); ++ } ++ ++ return this; ++ }; ++ ++ this.copyFrom = function (ori) { ++ this.id = ori.id; ++ this.isDeleted = ori.isDeleted; ++ this.deletedBy = ori.deletedBy; ++ this.deletedAt = ori.deletedAt; ++ this.accessMode = ori.accessMode; ++ this.topics = ori.topics; ++ this.name = ori.name; ++ this.description = ori.description ++ ++ // Users ++ for (var i in ori.users) { ++ this.users.push(ori.users[i]); ++ } ++ ++ // Moderators ++ for (var i in ori.mods) { ++ this.mods.push(ori.users[i]); ++ } ++ ++ return this; ++ }; + } + +-function ForumACL(id,priv,name) +-{ ++ ++function ForumACL(id, priv, name) { + this.id = id; + this.priv = priv; + this.name = name; + this.selected = false; + } + ++ + function forumsReceived(data) { + if (amPage != 'FAdmin') { + return; + } + +- faForums = new Array(); + if (data.indexOf("ERR#") == 0) { +- alertForum(parseInt((data.split('#'))[1], 10)); +- puTimer = setTimeout('x_getForums(forumsReceived)', 180000); +- return; +- } else if (data != "") { +- parseForumList(data); ++ var lines = data.split('\n'); ++ var el = lines.shift(); ++ data = lines.join('\n'); ++ ++ alertForum(parseInt((el.split('#'))[1], 10)); + } ++ parseForumList(data); + + if (!faEditing) { + drawForumList(); +@@ -1292,15 +1354,19 @@ + alertMaximumFCount(); + forumEditCancel(); + } else if (!faEditing.id) { +- if (faNewPos != -1 && !forumById(faNewPos)) ++ if (faNewPos != -1 && !forumById(faNewPos)) { + faNewPos = -1; ++ } + updateFPosSelector(); + } else if (faEditing.id) { +- var f = forumById(faEditing.id); +- if (!f) { ++ var f = forumById(faEditing.id); ++ if (!f || f.isDeleted) { + alertForumDeleted(); + forumEditCancel(); +- } else if (faOriginal.name != f.name || faOriginal.userPost != f.userPost || faOriginal.description != f.description) { ++ } else if (faOriginal.name != f.name ++ || faOriginal.accessMode != f.accessMode ++ || faOriginal.description != f.description) { ++ + alertForumChanged(); + updateFEditor(); + } else { +@@ -1310,111 +1376,119 @@ + puTimer = setTimeout('x_getForums(forumsReceived)', 180000); + } + +-function parseForumList(data) +-{ +- var dl = data.split('\n'); +- var st = 0, i = 0, cf = 0; +- var a; + +- while (i b.order ? 1 : -1)')); ++ i = lines.shift(); ++ faAdmins = (i == '') ? (new Array()) : i.split('#'); ++ ++ var pCount = parseInt(lines.shift(), 10); ++ faPlayers = { }; ++ for (i = 0; i < pCount; i ++) { ++ var rLine = lines.shift().split('#'); ++ faPlayers['p' + rLine.shift()] = rLine.join('#'); ++ } + } + +-function moveForum(id,up) +-{ ++ ++function moveForum(id, up) { + x_moveForum(id, up ? 1 : 0, forumsReceived); + } + +-function forumById(id) +-{ +- var i; +- for (i=0;i b.name.toLowerCase() ? 1 : -1')); ++ faAccess.push(new ForumACL(rId, pr, rn)); + } +- if (faEditing.id) ++ faAccess.sort(new Function('a','b','return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1')); ++ ++ if (faEditing.id) { + faOriACL = makeForumACLString(); +- drawFAccessManager(); +- updateFEditor(); ++ } + } + ++ + function setFAccessLevel(level) + { + var i,cc=0; +@@ -1432,33 +1506,36 @@ + } + } + +-function updateFEditor() +-{ +- var ok, i; ++function updateFEditor() { ++ var ok, i; + ok = (faEditing.name.length >= 4); +- if (faEditing.id) ++ if (faEditing.id) { + ok = ok && ( + faEditing.name != faOriginal.name || faEditing.description != faOriginal.description +- || faEditing.userPost != faOriginal.userPost || faOriACL != makeForumACLString() ++ || faEditing.accessMode != faOriginal.accessMode || faOriACL != makeForumACLString() + ); ++ } + document.getElementById('feok').disabled = !ok; + } + +-function makeForumACLString() +-{ +- var a = new Array(), i; +- for (i=0;i topic' + (tot > 1 ? 's' : ''); +- if (n == 0) +- return str; +- str += ' (' + formatNumber(n) + ' unread)'; +- return str; ++function makeTopicsText(tot, n) { ++ if (tot == 0) { ++ return "empty forum"; ++ } ++ ++ var str = '' + formatNumber(tot.toString()) + ' topic' + (tot > 1 ? 's' : ''); ++ if (n == 0) { ++ return str; ++ } ++ str += ' (' + formatNumber(n.toString()) + ' unread)'; ++ return str; + } +diff -Naur beta5//site/static/beta5/js/pg_comms.js forums//site/static/beta5/js/pg_comms.js +--- beta5//site/static/beta5/js/pg_comms.js 2011-02-05 10:09:56.434335002 +0100 ++++ forums//site/static/beta5/js/pg_comms.js 2011-03-12 15:09:43.961300049 +0100 +@@ -12,20 +12,40 @@ + this.name = name; + } + +-function Category(id, type, name) +-{ +- this.id = id; +- this.type = type; +- this.name = name; +- this.forums = new Array(); +-} + +-function Forum(id, nTopics, nUnread, name) +-{ +- this.id = id; +- this.nTopics = nTopics; +- this.nUnread = nUnread; +- this.name = name; ++ ++function ForumsEntity(inputData) { ++ var iLine = inputData.shift().split('#'); ++ var nElements; ++ ++ this.type = iLine.shift(); ++ this.id = iLine.shift(); ++ nElements = parseInt(iLine.shift(), 10); ++ this.topics = parseInt(iLine.shift(), 10); ++ this.unread = parseInt(iLine.shift(), 10); ++ this.name = inputData.shift(); ++ this.contents = new Array(); ++ this.output = function (depth) { ++ var str = ''; ++ ++ if (this.id != '/') { ++ for (var i = 0; i < depth; i++) { ++ str += '      '; ++ } ++ str += '' + this.name + ': ' ++ + makeTopicsText(this.topics, this.unread); ++ } ++ ++ for (var i = 0; i < this.contents.length; i ++) { ++ str += '
' + this.contents[i].output(depth + 1); ++ } ++ ++ return str; ++ }; ++ ++ for (var i = 0; i < nElements; i ++) { ++ this.contents.push( new ForumsEntity(inputData) ); ++ } + } + + +@@ -35,110 +55,55 @@ + } + + +-function commsDataReceived(data) +-{ +- var i, l = data.split('\n'); +- var a = l.shift().split('#'); +- var nCustom = parseInt(a[0],10), nGenCats = parseInt(a[1],10), nAForums = parseInt(a[2],10); ++function commsDataReceived(data) { ++ var i, a, nCustom; ++ var l = data.split('\n'); + + // Default folders + dFolders = new Array(); +- for (i=0;i<3;i++) +- { ++ for (i=0;i<3;i++) { + a = l.shift().split('#'); + dFolders.push(new Folder('', a[0], a[1], '')); + } + + // Custom folders ++ nCustom = parseInt(l.shift(), 10); + cFolders = new Array(); +- for (i=0;i' + cFolders[i].name + ': ' + makeMessagesText(cFolders[i].tMsg, cFolders[i].nMsg); ++ for (i=0;i' + cFolders[i].name + ': ' ++ + makeMessagesText(cFolders[i].tMsg, cFolders[i].nMsg); + a.push(s); + } + document.getElementById('cflist').innerHTML = a.join('
') + '
'; + } + +- // General forums +- a = new Array(); +- for (i=0;i' + name + ''; +- for (j=0;j' + forums[j].name + ': '; +- s += makeTopicsText(forums[j].nTopics, forums[j].nUnread); +- } +- a.push(s); +- }} +- document.getElementById('gforums').innerHTML = a.join('

'); +- +- // Alliance forums +- if (aForums.length == 0) +- document.getElementById('aforums').innerHTML = ' '; +- else +- { +- a = new Array(); +- for (j=0;j' + aForums[j].name + ': '; +- s += makeTopicsText(aForums[j].nTopics, aForums[j].nUnread); +- a.push(s); +- } +- document.getElementById('aforums').innerHTML = '

' + allianceForums + '

' + a.join('
') + '

'; +- } ++ // Forums ++ document.getElementById('forums').innerHTML = '

' + forums.output(-1) + '

'; + } +diff -Naur beta5//site/static/beta5/js/pg_forums-en.js forums//site/static/beta5/js/pg_forums-en.js +--- beta5//site/static/beta5/js/pg_forums-en.js 2011-02-05 10:09:56.434335002 +0100 ++++ forums//site/static/beta5/js/pg_forums-en.js 2011-02-05 10:10:02.214335002 +0100 +@@ -1,53 +1,98 @@ +-function confirmDelete() +-{ +- var i = countSelected(); +- if (i == 0) +- { +- alert('Please select the topic(s) you want to delete.'); +- return false; +- } +- return confirm('Please confirm you want to delete ' + (i > 1 ? ('these ' + i + ' topics') : 'this topic') + '.'); +-} +- +-function confirmSticky() +-{ +- var i = countSelected(); +- if (i == 0) +- { +- alert('Please select the topic(s) you want to switch to/from sticky.'); +- return false; +- } +- return confirm('Please confirm you want to switch ' + (i > 1 ? ('these ' + i + ' topics') : 'this topic') + ' to/from sticky.'); +-} +- +-function confirmMove() +-{ +- var i = countSelected(); +- if (i == 0) +- { +- alert('Please select the topic(s) you want to move.'); +- return false; +- } +- +- var e = document.getElementById('mdest'); +- if (e.options[e.selectedIndex].value == '') +- { +- alert('Please select the forum to which the topic'+(i>1?'s':'')+' must be moved.'); +- return false; +- } +- +- return confirm( +- 'Please confirm you want to move the selected topic' + (i>1?'s':'') + '\nto the "' +- + e.options[e.selectedIndex].text + '" forum.' +- ); +-} +- +-function confirmDTopic() +-{ +- return confirm('Deleting this post will delete the whole topic. Please confirm.'); +-} +- +-function confirmDPost() +-{ +- return confirm('Please confirm you want to delete this post.'); +-} ++/* Text for menu commands */ ++MenuItem.commandText = { ++ overview: "Overview", ++ latest: "Latest messages", ++ search: "Search forums" ++}; ++ ++/* Overview pseudo-category title & description */ ++CategoryView.ovTitle = "Forums overview"; ++CategoryView.ovDescription = "This page gives you a global view of all of the forums you have access to."; ++ ++/* Text for layout-level commands */ ++ForumsLayout.menuText = ['S','h','o','w',' ','m','e','n','u']; ++ForumsLayout.hideMenu = "Hide"; ++ForumsLayout.menuTitle = "Forums"; ++ ++/* Text for the category view */ ++CategoryView.empty = 'There are no forums in this category.'; ++CategoryView.headers = { ++ name: 'Forum name', ++ topics: 'Topics', ++ posts: 'Posts', ++ lastPost: 'Last modification' ++}; ++CategoryView.forumIcon = { ++ read: "This forums's topics have been read", ++ unread: "Some topics in this forum haven't been read" ++}; ++CategoryView.deletedAt = "Forum deleted at "; ++CategoryView.by = " by "; ++CategoryView.noPosts = 'Empty forum'; ++CategoryView.markRead = 'Mark forums as read'; ++ ++/* Text for the forum view */ ++ForumView.markRead = 'Mark topics as read'; ++ForumView.adminsHdr = 'Administrators'; ++ForumView.modsHdr = 'Moderators'; ++ForumView.usersHdr = 'Users'; ++ForumView.empty = 'There are no topics in this forum.'; ++ForumView.details = ' More details ... '; ++ForumView.showDetails = 'Show details'; ++ForumView.hideDetails = 'Hide details'; ++ForumView.displayOptions = 'Display options'; ++ForumView.newTopic = 'New topic'; ++ForumView.modTools = 'Moderation tools'; ++ForumView.previousPage = 'Previous page'; ++ForumView.nextPage = 'Next page'; ++ForumView.pageSelHdr = 'Jump to page '; ++ForumView.headers = { ++ topic: 'Topic', ++ replies: 'Replies', ++ fPost: 'First post', ++ lPost: 'Last update' ++}; ++ForumView.movedTo = 'Moved to '; ++ForumView.applyTo = 'Apply to '; ++ForumView.thisForum = 'this forum'; ++ForumView.allForums = 'all forums'; ++ForumView.perPage = 'Topics / page:'; ++ForumView.ok = 'Ok'; ++ForumView.cancel = 'Cancel'; ++ForumView.displayDeleted = 'Display deleted topics:'; ++ForumView.deletedAt = 'Topic deleted at '; ++ForumView.hideModTools = 'Hide moderation tools'; ++ForumView.pleaseSelect = 'Please select at least one topic.'; ++ForumView.deletedTopics = 'Deleted topics: '; ++ForumView.restoreTopics = 'restore'; ++ForumView.selectedTopics = 'Selected topics: '; ++ForumView.deleteTopics = 'delete'; ++ForumView.stickyLevel = 'sticky level: '; ++ForumView.decreaseStickyLevel = 'decrease'; ++ForumView.increaseStickyLevel = 'increase'; ++ForumView.setTo = 'set to '; ++ForumView.normalPost = 'not sticky'; ++ForumView.lock = 'lock'; ++ForumView.unlock = 'unlock'; ++ForumView.moveTo = 'Move topics to '; ++ ++ ++/* Topic view: topic not found */ ++TopicView.notFound = { ++ title: "Topic not found", ++ text: "The topic you were looking for is unavailable, either because it doesn't exist anymore or " ++ + "because you don't have access to the forum it is in." ++}; ++/* Topic view: deleted topic */ ++TopicView.deleted = { ++ header: "This topic was deleted at ", ++ by: " by " ++}; ++/* Topic view: show / hide post contents */ ++TopicView.close = "Hide post contents"; ++TopicView.open = "Show post contents"; ++/* Topic view, misc text */ ++TopicView.posted = "Posted "; ++TopicView.loading = "Loading, please wait ..."; ++TopicView.loadError = "An error occurred while loading this post :-("; ++TopicView.edited = "Edited at "; +diff -Naur beta5//site/static/beta5/js/pg_forums.js forums//site/static/beta5/js/pg_forums.js +--- beta5//site/static/beta5/js/pg_forums.js 2011-02-05 10:09:56.434335002 +0100 ++++ forums//site/static/beta5/js/pg_forums.js 2011-02-05 10:10:02.204335002 +0100 +@@ -1,10 +1,1607 @@ +-function countSelected() +-{ +- var n = 0, i = 0, e; +- while (e = document.getElementById('msel' + i)) +- { +- n += e.checked ? 1 : 0; +- i++; ++var pageContents; ++var useGIFs; ++ ++ ++MenuItem = function (lines) { ++ ++ // Parse the data ++ var fields = lines.shift().split('#'); ++ ++ this.isNode = (fields.shift() == 'N'); ++ this.isCommand = (fields.shift() == 'C'); ++ this.unread = parseInt(fields.shift(), 10); ++ this.isOpen = this.isNode ? (fields.shift() == '1') : false; ++ this.id = this.isNode ? fields.shift() : null; ++ this.cmdLink = lines.shift(); ++ this.text = lines.shift(); ++ this.entries = (this.isNode && this.isOpen) ? (new Array()) : null; ++ ++ if (this.isCommand) { ++ this.text = MenuItem.commandText[this.text]; + } +- return n; +-} ++ ++ // Add menu entries ++ while (this.isNode && this.isOpen && lines[0] != 'E') { ++ if (lines[0] == 'S') { ++ // Separators ++ this.entries.push(null); ++ lines.shift(); ++ } else { ++ // Entries ++ this.entries.push(new MenuItem(lines)); ++ } ++ } ++ if (this.isNode && this.isOpen) { ++ lines.shift(); ++ } ++ ++ this.draw = function (output, depth) { ++ var rd = (typeof depth == 'undefined') ? -1 : depth; ++ ++ var cst = ' style="margin: 0px; padding: 0px; border-width: 0px; vertical-align: middle"'; ++ var tst = ' style="width: 100%; margin: 0px; padding: 0px; border-width: 0px"'; ++ var smallCell = ''; ++ var str = ''; ++ ++ if (depth > 0) { ++ for (var i = 0; i < depth; i ++) { ++ str += smallCell + ' '; ++ } ++ } ++ ++ // 'Open'/'Close' command for submenus or empty cell ++ str += smallCell; ++ if (depth > -1 && this.isNode) { ++ if (this.isOpen) { ++ str += '-'; ++ } else { ++ str += '+'; ++ } ++ } else { ++ str += ' '; ++ } ++ str += ''; ++ ++ // Menu entry's text ++ str += '' ++ + (MenuItem.current == this.cmdLink ? '' : '') ++ + ((this.unread > 0 && ! (this.isNode && this.isOpen)) ? '' : '') ++ + this.text ++ + ((this.unread > 0 && ! (this.isNode && this.isOpen)) ? (' (' + this.unread + ')') : '') ++ + (MenuItem.current == this.cmdLink ? '' : '') ++ + ''; ++ ++ // Output this line ++ output.push(str); ++ ++ // Submenus ++ var sepPrev = false; ++ for (var i in this.entries) { ++ var e = this.entries[i]; ++ if (e) { ++ if (e.isNode && e.isOpen && !sepPrev) { ++ output.push(' '); ++ } ++ e.draw(output, rd + 1); ++ if (e.isNode && e.isOpen) { ++ output.push(' '); ++ sepPrev = true; ++ } else { ++ sepPrev = false; ++ } ++ } else if (!sepPrev) { ++ output.push(' '); ++ sepPrev = true; ++ } ++ } ++ }; ++}; ++ ++ ++function parseNames( lines ) { ++ var line, cnt; ++ var res = { }; ++ ++ cnt = parseInt( lines.shift(), 10 ); ++ for (var i = 0; i < cnt; i ++) { ++ line = lines.shift().split('#'); ++ res[line[0]] = line[1]; ++ } ++ ++ return res; ++}; ++ ++ ++TopicView = function () { ++ ++ var me = this; ++ var tst = ' style="width: 100%; margin: 0px; padding: 0px; border-width: 0px"'; ++ var cst = ' style="margin: 0px; padding: 0px; border-width: 0px; vertical-align: middle'; ++ ++ var pageUpdater = null; ++ var pageMD5 = null; ++ var topicId = null; ++ var locked = false; ++ var parents = []; ++ var posts = []; ++ var pOrder = { ++ ln: null, ++ lo: null, ++ tn: null, ++ to: null ++ }; ++ var opts = { ++ perPage: 50, ++ vDeleted: false, ++ threaded: false, ++ order: false, ++ openPosts: 1 ++ }; ++ var title; ++ var topicStatus; ++ var currentUser; ++ var isMod; ++ var canPost; ++ var hasPoll; ++ var users; ++ var nPages; ++ var cPage; ++ var queue; ++ ++ var parseInitialContents, displayNotFound, displayDeleted, displayTopicLayout, drawPageChanger, ++ displayPage, initLoaderQueue, loadPost, postLoaded; ++ ++ parseInitialContents = function (data) { ++ var line = data.shift(); ++ ++ if (line == 'MEH') { ++ displayNotFound(); ++ return false; ++ } ++ ++ if (line.indexOf('DELETED#') == 0) { ++ var deletedAt, nParents; ++ line = line.split('#'); ++ deletedAt = line[1]; ++ nParents = parseInt(line[2], 10); ++ displayDeleted(data, deletedAt, nParents); ++ return false; ++ } ++ ++ line = line.split('#'); ++ var nParents = parseInt(line[1], 10); ++ ++ title = data.shift(); ++ line = data.shift().split('#'); ++ currentUser = line.shift(); ++ isMod = (line.shift() == 1); ++ canPost = (line.shift() == 1); ++ hasPoll = (line.shift() == 1); ++ ++ for (var i = 0; i < nParents; i ++) { ++ line = data.shift().split('#'); ++ parents.push({ ++ id: line.shift(), ++ name: line.join('') ++ }); ++ } ++ ++ pOrder.ln = data.shift().split('#'); ++ pOrder.lo = data.shift().split('#'); ++ pOrder.tn = data.shift().split('#'); ++ pOrder.to = data.shift().split('#'); ++ ++ for (var i = 0; i < pOrder.ln.length; i ++) { ++ var p = { }; ++ line = data.shift().split('#'); ++ p.id = line.shift(); ++ p.depth = parseInt(line.shift(), 10); ++ p.author = line.shift(); ++ p.postedAt = line.shift(); ++ p.unread = (line.shift() == 1); ++ p.lcTime = line.shift(); ++ p.lcAuthor = line.shift(); ++ p.title = data.shift(); ++ p.loading = false; ++ posts[p.id] = p; ++ } ++ ++ users = parseNames( data ); ++ ++ line = data.shift().split('#'); ++ opts.perPage = parseInt(line.shift(), 10); ++ opts.vDeleted = (line.shift() == 1); ++ opts.threaded = (line.shift() == 1); ++ opts.order = (line.shift() == 1); ++ opts.openPosts = parseInt(line.shift(), 10); ++ ++ for (var i in posts) { ++ switch (opts.openPosts) { ++ case 0: posts[i].open = false; ++ break; ++ case 1: posts[i].open = posts[i].unread; ++ break; ++ case 2: posts[i].open = true; ++ break; ++ } ++ } ++ ++ initLoaderQueue(); ++ ++ var m = pOrder.ln.length % opts.perPage; ++ nPages = (pOrder.ln.length - m) / opts.perPage + (m ? 1 : 0); ++ cPage = 0; ++ displayTopicLayout(); ++ }; ++ ++ displayNotFound = function () { ++ document.getElementById('f-page').innerHTML = '

' + TopicView.notFound.title + '

' ++ + TopicView.notFound.text + '

'; ++ }; ++ ++ displayDeleted = function (data, deletedAt, nParents) { ++ var str = '
' ++ + '

' + data.shift() + '

'; ++ for (var i = 0; i < nParents; i ++) { ++ var l = data.shift().split('#'); ++ var pid = l.shift(); ++ ++ str += (i > 0 ? ' > ' : '') + '' + (pid == '/' ? ForumsLayout.menuTitle : l.join('')) + ''; ++ } ++ str += '

' + TopicView.deleted.header + '' + formatDate(deletedAt) ++ + '' + TopicView.deleted.by + '' ++ + 'some guy (FIXME)' + '.

'; ++ document.getElementById('f-page').innerHTML = str; ++ }; ++ ++ displayTopicLayout = function () { ++ var str = '

' ++ + title + '

'; ++ for (var i in parents) { ++ str += (i > 0 ? ' > ' : '') + '' ++ + (parents[i].id == '/' ? ForumsLayout.menuTitle : parents[i].name) + ''; ++ } ++ ++ str += ''; ++ str += drawPageChanger(); ++ ++ // FIXME: display tools ++ ++ if (hasPoll) { ++ // FIXME: display poll ++ } ++ ++ str += ''; ++ str += drawPageChanger(); ++ str += '
 
'; ++ ++ document.getElementById('f-page').innerHTML = str; ++ displayPage(); ++ }; ++ ++ drawPageChanger = function () { ++ if (nPages > 1) { ++ var st = 'border-width: 0px; margin: 0px; vertical-align: middle; '; ++ var str = '' ++ + (cPage > 0 ? '' : '') ++ + '<-- ' + ForumView.previousPage + (cPage > 0 ? '' : '') ++ + '' ++ + '
' + ForumView.pageSelHdr ++ + '
' ++ + ((cPage' : '') ++ + ForumView.nextPage + '-->' + ((cPage' : '') + ''; ++ return str; ++ } ++ return ''; ++ }; ++ ++ displayPage = function () { ++ var low = cPage * opts.perPage; ++ var high = Math.min(low + opts.perPage, pOrder.ln.length); ++ var str = '\n\n'; ++ var order; ++ ++ if (opts.threaded) { ++ order = opts.order ? pOrder.to : pOrder.tn; ++ } else { ++ order = opts.order ? pOrder.lo : pOrder.ln; ++ } ++ ++ for (var i = low; i < high; i ++) { ++ var post = posts[order[i]]; ++ str += '\n'; ++ if (opts.threaded && post.depth > 0) { ++ var sz = post.depth * 10; ++ str += ' '; ++ } ++ str += ' '; ++ } ++ document.getElementById('topics-display').innerHTML = str + '\n'; ++ ++ displayPosts(); ++ }; ++ ++ displayPosts = function () { ++ for (var i in posts) { ++ var post = posts[i]; ++ var el = document.getElementById("tp-post-" + post.id); ++ if (el) { ++ el.innerHTML = displayPost(post); ++ } ++ } ++ }; ++ ++ displayPost = function (post) { ++ var color = post.unread ? '#FFFFFF' : '#5F5F5F'; ++ var margin = post.open ? (post.depth == 0 ? '0px 0px 10px 0px' : '10px 0px') : '0px'; ++ var onClick = ' onClick="pageContents.togglePost(' + post.id + ')"'; ++ var str = '' ++ + ''; ++ ++ if (post.open) { ++ str += ''; ++ if (post.lcTime != post.postedAt) { ++ str += ''; ++ } ++ } ++ ++ return str + '
' + (post.open ? TopicView.close : TopicView.open) + '' ++ + post.title + '
' + TopicView.posted + '' + formatDate(post.postedAt) + '' ++ + CategoryView.by + '' + users[post.author] + '
' ++ + (post.loaded ? post.contents : ++ ('

' + TopicView.loading + '

')) ++ + '
' + TopicView.edited ++ + formatDate(post.lcTime) + CategoryView.by + '' ++ + users[post.lcAuthor] + '
'; ++ }; ++ ++ initLoaderQueue = function () { ++ var order; ++ ++ if (opts.threaded) { ++ order = opts.order ? pOrder.to : pOrder.tn; ++ } else { ++ order = opts.order ? pOrder.lo : pOrder.ln; ++ } ++ ++ var openPosts = [], fpPosts = [], rest = []; ++ for (var i in order) { ++ var post = posts[order[i]]; ++ if (post.open) { ++ openPosts.push(post); ++ } else if (i < opts.perPage) { ++ fpPosts.push(post); ++ } else { ++ rest.push(post); ++ } ++ } ++ ++ queue = openPosts.concat(fpPosts.concat(rest)); ++ loadPost(); ++ }; ++ ++ postLoaded = function (data) { ++ if (queue.length == 0) { ++ return; ++ } ++ ++ if (data.indexOf('-#') == 0) { ++ data = data.split('#'); ++ if (queue[0].id != data[1]) { ++ loadPost(); ++ return; ++ } ++ queue[0].loading = false; ++ queue[0].contents = '

' + TopicView.loadError + '

'; ++ } else { ++ data = data.split('\n'); ++ var l = data.shift().split('#'); ++ if (queue[0].id != l[1]) { ++ loadPost(); ++ return; ++ } ++ queue[0].loading = false; ++ queue[0].contents = data.join('\n'); ++ } ++ queue[0].loaded = true; ++ ++ var el = document.getElementById("tp-post-" + queue[0].id); ++ if (el) { ++ el.innerHTML = displayPost(queue[0]); ++ } ++ ++ queue.shift(); ++ loadPost(); ++ }; ++ ++ loadPost = function () { ++ if (queue.length == 0 || queue[0].loading) { ++ return; ++ } ++ ++ queue[0].loading = true; ++ x_loadPostContents(topicId, pageMD5, queue[0].id, function (data) { ++ postLoaded(data); ++ }); ++ }; ++ ++ this.parse = function (data) { ++ if (data.indexOf('\n') == -1) { ++ // IE, first update ++ data = data.split('#'); ++ topicId = data.shift(); ++ pageMD5 = data.shift(); ++ this.update(); ++ return; ++ } ++ ++ data = data.split('\n'); ++ var l = data.shift().split('#'); ++ topicId = l.shift(); ++ pageMD5 = l.shift(); ++ if (parseInitialContents(data)) { ++ pageUpdater = window.setTimeout("pageContents.update()", 10000); ++ } ++ locked = false; ++ }; ++ ++ this.update = function () { ++ if (locked) { ++ return; ++ } ++ pageUpdater = null; ++ locked = true; ++ x_getTopic(topicId, pageMD5, function (data) { pageContents.parse(data); }); ++ }; ++ ++ this.togglePost = function (postId) { ++ var post = posts[postId]; ++ post.open = ! post.open; ++ var el = document.getElementById("tp-post-" + post.id); ++ if (el) { ++ el.innerHTML = displayPost(post); ++ } ++ }; ++ ++ this.setFieldValues = function () { }; ++}; ++ ++ ++ForumView = function () { ++ var me = this; ++ ++ var pageUpdater = null; ++ var locked = false; ++ var pageMD5 = null; ++ var viewId = null; ++ var players = []; ++ ++ var nPages = 0; ++ var cPage = -1; ++ var perPage = 10; ++ ++ var topics = []; ++ var isMod = false; ++ var canPost = false; ++ var vDeleted = false; ++ var unread = false; ++ var admins = null; ++ var mods = null; ++ var users = null; ++ var hasMoveTargets = false; ++ var moveTargets = {}; ++ var description = ''; ++ var title = ''; ++ var parents = null; ++ ++ var showDetails = false; ++ var showModTools = false; ++ var modSelAll = false; ++ var showOptions = false; ++ var options = { }; ++ var modTools = { }; ++ var selTopicsStr = ''; ++ ++ var mainParser, parseTopic, displayContents, drawControls, drawModTools, setFieldValues, getSelectedTopics; ++ ++ mainParser = function (lines) { ++ var line = lines.shift().split('#'); ++ var nt, ndl, npe, na, nm, nu, nmt; ++ ++ forumId = line.shift(); ++ nt = parseInt(line.shift(), 10); ++ unread = (line.shift() == '1'); ++ isMod = (line.shift() == '1'); ++ canPost = (line.shift() == '1'); ++ perPage = parseInt(line.shift(), 10); ++ vDeleted = (line.shift() == '1'); ++ ndl = parseInt(line.shift(), 10); ++ npe = parseInt(line.shift(), 10); ++ na = parseInt(line.shift(), 10); ++ nm = parseInt(line.shift(), 10); ++ nu = parseInt(line.shift(), 10); ++ nmt = isMod ? parseInt(line.shift(), 10) : 0; ++ hasMoveTargets = (nmt > 0); ++ title = lines.shift(); ++ ++ var dc = new Array(); ++ for (var i = 0; i < ndl; i ++) { ++ dc.push(lines.shift()); ++ } ++ description = dc.join('
'); ++ ++ parents = new Array(); ++ for (var i = 0; i < npe; i ++) { ++ line = lines.shift().split('#'); ++ parents.push({ ++ id: line.shift(), ++ name: line.join('#') ++ }); ++ } ++ ++ admins = new Array(); ++ for (var i = 0; i < na; i ++) { ++ admins.push(lines.shift()); ++ } ++ mods = new Array(); ++ for (var i = 0; i < nm; i ++) { ++ mods.push(lines.shift()); ++ } ++ users = new Array(); ++ for (var i = 0; i < nu; i ++) { ++ users.push(lines.shift()); ++ } ++ ++ moveTargets = {}; ++ for (var i = 0; i < nmt; i ++) { ++ line = lines.shift().split('#'); ++ var ln = line.shift(); ++ moveTargets[ln] = line.join('#'); ++ } ++ ++ selTopicsStr = ''; ++ for (var i in topics) { ++ if (topics[i].selected) { ++ selTopicsStr += '#' + topics[i].id + '#'; ++ } ++ } ++ ++ topics = new Array(); ++ for (var i = 0; i < nt; i ++) { ++ topics.push(parseTopic(lines)); ++ } ++ ++ if (nt == 0) { ++ cPage = -1; ++ } else { ++ nPages = Math.ceil(nt / perPage); ++ if (cPage < 0) { ++ cPage = 0; ++ } else if (cPage >= nPages) { ++ cPage = nPages - 1; ++ } ++ } ++ }; ++ ++ parseTopic = function (lines) { ++ var topic = { }; ++ var line = lines.shift().split('#'); ++ ++ topic.id = line.shift(); ++ topic.movedTo = line.shift(); ++ topic.unread = (line.shift() == '1'); ++ topic.sticky = parseInt(line.shift(), 10); ++ topic.nReplies = line.shift(); ++ topic.fpTime = line.shift(); ++ topic.fpAuthor = line.shift(); ++ topic.lcTime = line.shift(); ++ topic.lcAuthor = line.shift(); ++ topic.isLocked = (line.shift() == '1'); ++ topic.hasPoll = (line.shift() == '1'); ++ topic.isDeleted = (line.shift() == '1'); ++ topic.deletedAt = line.shift(); ++ topic.deletedBy = line.shift(); ++ topic.title = lines.shift(); ++ topic.mToForum = (topic.movedTo == '') ? null : lines.shift(); ++ topic.selected = (selTopicsStr.indexOf( '#' + topic.id + '#') != -1); ++ ++ return topic; ++ }; ++ ++ getSelectedTopics = function (deleted) { ++ var rv = new Array(); ++ for (var i = cPage * perPage; i < topics.length && i < (cPage + 1) * perPage; i ++) { ++ if (topics[i].movedTo == '' && topics[i].selected && topics[i].isDeleted == deleted) { ++ rv.push(topics[i].id); ++ } ++ } ++ return rv; ++ }; ++ ++ drawControls = function () { ++ // Main controls ++ var str = '' ++ + '' ++ + ''; ++ ++ // Page control ++ if (topics.length && nPages > 1) { ++ var st = 'border-width: 0px; margin: 0px; vertical-align: middle; '; ++ str += ''; ++ } ++ ++ str += '
' ++ + '
' ++ + '' ++ + ForumView.displayOptions + ''; ++ ++ if (canPost) { ++ str += ' - ' + ForumView.newTopic + ''; ++ } ++ if (isMod) { ++ str += ' - ' ++ + ForumView.modTools + ''; ++ } ++ ++ // Display options ++ str += '
' ++ + '' + ForumView.displayOptions + '
' ++ + '' ++ + ''; ++ if (isMod) { ++ str += ''; ++ } ++ str += '
' + ForumView.applyTo ++ + '' ++ +' ' ++ + ' ' ++ + '' ++ + '' + ForumView.ok ++ + '
' ++ + ForumView.perPage + '' ++ + '' ++ + '' + ForumView.cancel ++ + '
'; ++ ++ // Moderation tools ++ if (isMod) { ++ str += '
' ++ + '' + ForumView.modTools + '' ++ + '
' ++ + '
'; ++ } ++ str += '
' ++ + (cPage > 0 ? '' : '') ++ + '<-- ' + ForumView.previousPage + (cPage > 0 ? '' : '') ++ + '' ++ + '
' + ForumView.pageSelHdr ++ + '
' ++ + ((cPage' : '') ++ + ForumView.nextPage + '-->' + ((cPage' : '') + '
'; ++ return str; ++ }; ++ ++ displayContents = function () { ++ // Draw the header ++ var str = '
'; ++ var hasDetails = (description != '' || admins.length || mods.length || users.length); ++ if (unread || hasDetails) { ++ // "Mark as read" link ++ str += '
'; ++ if (hasDetails) { ++ str += '' ++ + (showDetails ? ForumView.hideDetails : ForumView.showDetails) ++ + '' + (unread ? ' - ' : ''); ++ } ++ if (unread) { ++ str += '' ++ + ForumView.markRead + ''; ++ } ++ str += '
'; ++ } ++ // Title and parent categories ++ str += '

' + title + '

'; ++ for (var i in parents) { ++ str += (i > 0 ? ' > ' : '') + '' ++ + (parents[i].id == '/' ? ForumsLayout.menuTitle : parents[i].name) ++ + ''; ++ } ++ str += '
'; ++ // Details (description & ACL) ++ if (hasDetails) { ++ var needsBR = false; ++ str += '
' + ForumView.details + ''; ++ ++ if (description != '') { ++ str += description; ++ needsBR = true; ++ } ++ ++ if (admins.length) { ++ str += (needsBR ? '
' : '') + '' + ForumView.adminsHdr + ': ' ++ + admins.join(' - '); ++ needsBR = true; ++ } ++ ++ if (mods.length) { ++ str += (needsBR ? '
' : '') + '' + ForumView.modsHdr + ': ' ++ + mods.join(' - '); ++ needsBR = true; ++ } ++ ++ if (users.length) { ++ str += (needsBR ? '
' : '') + '' + ForumView.usersHdr + ': ' ++ + users.join(' - '); ++ } ++ ++ str += '
'; ++ } ++ str += ''; ++ ++ str += drawControls(); ++ if (topics.length) { ++ // Topics list ++ str += ''; ++ } ++ str += '
' ++ + ''; ++ ++ for (var i = cPage * perPage; i < topics.length && i < (cPage + 1) * perPage; i ++) { ++ str += '\n'; ++ if (topics[i].isDeleted) { ++ str += ''; ++ } else { ++ str += '' ++ + '' ++ + ''; ++ } ++ str += ''; ++ } ++ ++ str += '
' ++ + '' ++ + ' ' + ForumView.headers.topic ++ + '' + ForumView.headers.replies ++ + '' + ForumView.headers.fPost ++ + '' + ForumView.headers.lPost + '
' ++ + '' ++ + '' ++ + '' ++ + ((topics[i].isLocked || topics[i].hasPoll) ? '
' : '') ++ + (topics[i].isLocked ? ('') : '') ++ + (topics[i].hasPoll ? ('') : '') ++ + ((topics[i].isLocked || topics[i].hasPoll) ? '
' : '') + '
' ++ + (topics[i].isDeleted ? '' : ('')) ++ + topics[i].title + (topics[i].isDeleted ? '' : '') ++ + (topics[i].movedTo != '' ? ('
 -> ' + ForumView.movedTo ++ + '' ++ + topics[i].mToForum + '') : '') ++ + '
' ++ + ForumView.deletedAt + formatDate(topics[i].deletedAt) + CategoryView.by ++ + '' + players[topics[i].deletedBy] + '' ++ + formatNumber(topics[i].nReplies) + '' ++ + formatDate(topics[i].fpTime) + CategoryView.by + '' ++ + players[topics[i].fpAuthor] + '' ++ + formatDate(topics[i].lcTime) + CategoryView.by + '' ++ + players[topics[i].lcAuthor] + '
'; ++ } else { ++ str += '
' ++ + ForumView.empty + '
'; ++ ++ document.getElementById('f-page').innerHTML = str; ++ setFieldValues(); ++ }; ++ ++ hideOptions = function () { ++ var e = document.getElementById('f-options'); ++ if (!e) { ++ return; ++ } ++ showOptions = false; ++ e.style.display = 'none'; ++ document.getElementById('f-std-ctrl').style.display = 'block'; ++ }; ++ ++ drawModTools = function () { ++ var cnt = 0, sel = 0, dSel = 0, nSel = 0, hasLocked = false, hasUnlocked = false, ++ minStL = 11, maxStL = -1; ++ if (cPage != -1) { ++ for (var i = cPage * perPage; i < topics.length && i < (cPage + 1) * perPage; i ++) { ++ if (topics[i].movedTo != '') { ++ continue; ++ } ++ document.getElementById('mt-sel-' + topics[i].id).checked = topics[i].selected; ++ if (topics[i].selected) { ++ if (topics[i].isDeleted) { ++ dSel ++; ++ } else { ++ nSel ++; ++ hasLocked = hasLocked || topics[i].isLocked; ++ hasUnlocked = hasUnlocked || ! topics[i].isLocked; ++ } ++ minStL = (minStL > topics[i].sticky) ? topics[i].sticky : minStL; ++ maxStL = (maxStL < topics[i].sticky) ? topics[i].sticky : maxStL; ++ sel ++; ++ } ++ cnt ++; ++ } ++ document.getElementById('mt-sel-all').checked = modSelAll = (sel == cnt); ++ } ++ ++ var str = '' + ForumView.hideModTools ++ + '
'; ++ if (!(dSel || nSel)) { ++ str += '
' + ForumView.pleaseSelect; ++ } else { ++ if (nSel) { ++ str += '
' + ForumView.selectedTopics ++ + '' ++ + ForumView.deleteTopics + ' - ' + ForumView.stickyLevel; ++ ++ // Sticky level management ++ if (maxStL < 10) { ++ str += '' ++ + ForumView.increaseStickyLevel + ' / '; ++ } ++ if (minStL > 0) { ++ str += '' ++ + ForumView.decreaseStickyLevel + ' / '; ++ } ++ ++ str += ForumView.setTo + ''; ++ ++ // Lock / Unlock ++ if (hasLocked) { ++ str += ' - ' ++ + ForumView.unlock + ''; ++ } ++ if (hasUnlocked) { ++ str += ' - ' ++ + ForumView.lock + ''; ++ } ++ ++ // Move to ++ if (hasMoveTargets) { ++ str += '
' + ForumView.moveTo + ''; ++ } ++ } ++ if (dSel) { ++ str += '
' + ForumView.deletedTopics ++ + '' ++ + ForumView.restoreTopics + ''; ++ } ++ } ++ ++ document.getElementById('f-mod-tools-contents').innerHTML = str; ++ }; ++ ++ setFieldValues = function () { ++ var e = document.getElementById('f-options'); ++ if (!e) { ++ return; ++ } ++ ++ // Options block ++ document.getElementById('f-apply-to-this').checked = (options.toAll == 0); ++ document.getElementById('f-apply-to-all').checked = (options.toAll == 1); ++ document.getElementById('f-per-page').selectedIndex = (options.pp / 10) - 1; ++ ++ // Moderation tools ++ if (! isMod) { ++ return; ++ } ++ document.getElementById('f-disp-del').checked = (options.dd == 1); ++ ++ if (cPage != -1) { ++ for (var i = cPage * perPage; i < topics.length && i < (cPage + 1) * perPage; i ++) { ++ document.getElementById('mt-sel-' + topics[i].id).style.display = ++ ((showModTools && topics[i].movedTo == '') ? 'block' : 'none'); ++ } ++ document.getElementById('mt-sel-all').style.display = (showModTools ? 'block' : 'none'); ++ } ++ ++ drawModTools(); ++ }; ++ ++ // Page update: parser & updater ++ this.parse = function (data) { ++ if (data != '-') { ++ if (data.indexOf('\n') == -1) { ++ // IE, first update ++ viewId = data; ++ this.update(); ++ return; ++ } ++ ++ var lines = data.split('\n'); ++ pageMD5 = lines.shift(); ++ viewId = lines.shift(); ++ ++ mainParser(lines); ++ players = parseNames(lines); ++ ++ displayContents(); ++ } ++ ++ pageUpdater = window.setTimeout("pageContents.update()", 10000); ++ locked = false; ++ }; ++ this.update = function () { ++ if (locked) { ++ return; ++ } ++ pageUpdater = null; ++ locked = true; ++ x_getView(viewId, pageMD5, function (data) { pageContents.parse(data); }); ++ }; ++ this.setFieldValues = function () { setFieldValues(); }; ++ ++ // Displaying details ++ this.toggleDetails = function () { ++ var e = document.getElementById('f-details'); ++ if (!e) { ++ return; ++ } ++ showDetails = ! showDetails; ++ document.getElementById('f-toggle-details').innerHTML ++ = (showDetails ? ForumView.hideDetails : ForumView.showDetails) ++ e.style.display = showDetails ? 'block' : 'none'; ++ }; ++ ++ // "Display options" box management ++ this.displayOptions = function () { ++ var e = document.getElementById('f-options'); ++ if (!e) { ++ return; ++ } ++ options = { ++ toAll: 0, ++ pp: perPage, ++ dd: (isMod && vDeleted) ? 1 : 0 ++ }; ++ setFieldValues(); ++ showOptions = true; ++ e.style.display = 'block'; ++ document.getElementById('f-std-ctrl').style.display = 'none'; ++ }; ++ this.optionsOk = function () { ++ hideOptions(); ++ if (locked) { ++ window.setTimeout("pageContents.optionsOk()", 250); ++ return; ++ } ++ locked = true; ++ if (pageUpdater) { ++ window.clearTimeout(pageUpdater); ++ pageUpdater = null; ++ } ++ x_forumOptions(forumId, options.toAll, options.pp, options.dd, pageMD5, function (data) { ++ pageContents.parse(data); }); ++ }; ++ this.optionsCancel = function () { hideOptions(); }; ++ this.setOption = function (name, value) { options[name] = value; }; ++ ++ // Moderation tools ++ this.displayModTools = function () { ++ var e = document.getElementById('f-mod-tools'); ++ if (!e) { ++ return; ++ } ++ showModTools = true; ++ setFieldValues(); ++ e.style.display = 'block'; ++ document.getElementById('f-std-ctrl').style.display = 'none'; ++ }; ++ this.hideModTools = function () { ++ var e = document.getElementById('f-mod-tools'); ++ if (!e) { ++ return; ++ } ++ showModTools = false; ++ setFieldValues(); ++ e.style.display = 'none'; ++ document.getElementById('f-std-ctrl').style.display = 'block'; ++ }; ++ this.toggleTopic = function (index) { ++ topics[index].selected = !topics[index].selected; ++ drawModTools(); ++ }; ++ this.toggleSelAll = function () { ++ modSelAll = !modSelAll; ++ for (var i = cPage * perPage; i < topics.length && i < (cPage + 1) * perPage; i ++) { ++ if (topics[i].movedTo != '') { ++ continue; ++ } ++ topics[i].selected = modSelAll; ++ } ++ drawModTools(); ++ }; ++ this.restoreSelected = function () { ++ if (locked) { ++ return; ++ } ++ locked = true; ++ if (pageUpdater) { ++ window.clearTimeout(pageUpdater); ++ pageUpdater = null; ++ } ++ ++ var tlist = getSelectedTopics(true); ++ x_restoreTopics(forumId, tlist.join('#'), function (data) { pageContents.parse(data); }); ++ }; ++ this.deleteSelected = function () { ++ if (locked) { ++ return; ++ } ++ locked = true; ++ if (pageUpdater) { ++ window.clearTimeout(pageUpdater); ++ pageUpdater = null; ++ } ++ ++ var tlist = getSelectedTopics(false); ++ x_deleteTopics(forumId, tlist.join('#'), function (data) { pageContents.parse(data); }); ++ }; ++ this.increaseSticky = function () { ++ if (locked) { ++ return; ++ } ++ locked = true; ++ if (pageUpdater) { ++ window.clearTimeout(pageUpdater); ++ pageUpdater = null; ++ } ++ ++ var tlist = getSelectedTopics(false); ++ x_changeTopicsLevel(forumId, tlist.join('#'), 1, function (data) { pageContents.parse(data); }); ++ }; ++ this.decreaseSticky = function () { ++ if (locked) { ++ return; ++ } ++ locked = true; ++ if (pageUpdater) { ++ window.clearTimeout(pageUpdater); ++ pageUpdater = null; ++ } ++ ++ var tlist = getSelectedTopics(false); ++ x_changeTopicsLevel(forumId, tlist.join('#'), -1, function (data) { pageContents.parse(data); }); ++ }; ++ this.setSticky = function (value) { ++ var e = document.getElementById('mt-set-level'); ++ if (e) { e.disabled = true; } ++ e = document.getElementById('mt-set-level'); if (e) { e.disabled = true; } ++ e = document.getElementById('mt-move-to'); if (e) { e.disabled = true; } ++ ++ if (locked) { ++ window.setTimeout("pageContents.setSticky(" + value + ")", 250); ++ return; ++ } ++ locked = true; ++ if (pageUpdater) { ++ window.clearTimeout(pageUpdater); ++ pageUpdater = null; ++ } ++ ++ var tlist = getSelectedTopics(false); ++ x_setTopicsLevel(forumId, tlist.join('#'), value, function (data) { pageContents.parse(data); }); ++ }; ++ this.lockTopics = function () { ++ if (locked) { ++ return; ++ } ++ locked = true; ++ if (pageUpdater) { ++ window.clearTimeout(pageUpdater); ++ pageUpdater = null; ++ } ++ ++ var tlist = getSelectedTopics(false); ++ x_setTopicsLock(forumId, tlist.join('#'), 1, function (data) { pageContents.parse(data); }); ++ }; ++ this.unlockTopics = function () { ++ if (locked) { ++ return; ++ } ++ locked = true; ++ if (pageUpdater) { ++ window.clearTimeout(pageUpdater); ++ pageUpdater = null; ++ } ++ ++ var tlist = getSelectedTopics(false); ++ x_setTopicsLock(forumId, tlist.join('#'), 0, function (data) { pageContents.parse(data); }); ++ }; ++ this.moveTo = function (dest) { ++ e = document.getElementById('mt-set-level'); if (e) { e.disabled = true; } ++ e = document.getElementById('mt-move-to'); if (e) { e.disabled = true; } ++ ++ if (locked) { ++ window.setTimeout("pageContents.moveTo(" + dest + ")", 250); ++ return; ++ } ++ locked = true; ++ if (pageUpdater) { ++ window.clearTimeout(pageUpdater); ++ pageUpdater = null; ++ } ++ ++ var tlist = getSelectedTopics(false); ++ x_moveTopics(forumId, tlist.join('#'), dest, function (data) { pageContents.parse(data); }); ++ }; ++ ++ // Page management ++ this.nextPage = function () { this.jumpToPage(cPage + 1); }; ++ this.prevPage = function () { this.jumpToPage(cPage - 1); }; ++ this.jumpToPage = function (value) { ++ cPage = parseInt(value, 10); ++ displayContents(); ++ }; ++ ++ // Mark topics as read ++ this.markRead = function () { ++ if (locked) { ++ window.setTimeout("pageContents.markRead()", 250); ++ return; ++ } ++ locked = true; ++ if (pageUpdater) { ++ window.clearTimeout(pageUpdater); ++ pageUpdater = null; ++ } ++ x_forumRead(forumId, function (data) { pageContents.parse(data); }); ++ }; ++}; ++ ++ ++CategoryView = function () { ++ ++ var me = this; ++ var tst = ' style="width: 100%; margin: 0px; padding: 0px; border-width: 0px"'; ++ var cst = ' style="margin: 0px; padding: 0px; border-width: 0px; vertical-align: middle"'; ++ ++ var pageUpdater = null; ++ var pageMD5 = null; ++ var viewId = null; ++ var contents = null; ++ var locked = false; ++ var players = []; ++ ++ var parseContents, parseForum, displayContents, displayForums; ++ ++ parseContents = function (lines) { ++ var line = lines.shift().split('#'); ++ var obj = {}; ++ var cc, dc; ++ ++ obj.id = line.shift(); ++ obj.isCategory = (line.shift() == 'F'); ++ obj.hasUnread = (line.shift() == '1'); ++ cc = parseInt(line.shift(), 10); ++ dc = parseInt(line.shift(), 10); ++ ++ if (obj.isCategory) { ++ obj.typeName = lines.shift(); ++ } ++ obj.title = lines.shift(); ++ ++ var da = new Array(); ++ for (var i = 0; i < dc; i ++) { ++ da.push(lines.shift()); ++ } ++ obj.description = da.join('
'); ++ ++ if (obj.id == '/') { ++ obj.title = CategoryView.ovTitle; ++ obj.description = CategoryView.ovDescription; ++ } ++ ++ obj.contents = new Array(); ++ for (var i = 0; i < cc; i ++) { ++ obj.contents.push( obj.isCategory ? parseForum(lines) : parseContents(lines) ); ++ } ++ ++ return obj; ++ }; ++ parseForum = function (lines) { ++ var line = lines.shift().split('#'); ++ var obj = { }; ++ var dc; ++ ++ obj.id = line.shift(); ++ dc = parseInt(line.shift(), 10); ++ obj.isDeleted = (line.shift() == '1'); ++ obj.deletedAt = line.shift(); ++ obj.deletedBy = line.shift(); ++ obj.topics = line.shift(); ++ obj.posts = line.shift(); ++ obj.isUnread = (line.shift() == '1'); ++ ++ if (parseInt(obj.posts, 10) > 0) { ++ line = lines.shift().split('#'); ++ obj.lastAuthor = line.shift(); ++ obj.lastTimestamp = line.shift(); ++ } ++ ++ obj.title = lines.shift(); ++ var desc = new Array(); ++ for (var i = 0; i < dc; i ++) { ++ desc.push(lines.shift()); ++ } ++ obj.description = desc.join('
'); ++ ++ return obj; ++ }; ++ displayContents = function (obj, depth) { ++ var d = (typeof depth == 'undefined') ? 0 : depth; ++ var mw = d * 8; ++ var htag = 'h' + (d + 1); ++ ++ var str = ''; ++ if (d > 0) { ++ str += ''; ++ } ++ ++ var rText = ''; ++ if (obj.isCategory) { ++ rText = '
' + obj.typeName; ++ if (obj.hasUnread) { ++ rText += '
' + CategoryView.markRead + ''; ++ } ++ rText += '
'; ++ } ++ ++ str += '' + rText + '
<' + htag + '>' + obj.title + '' ++ + (obj.description != '' ? ('

' + obj.description + '

') : '') ++ + '
' ++ + ' 
'; ++ ++ if (! obj.isCategory) { ++ for (var i in obj.contents) { ++ str += displayContents(obj.contents[i], d + 1); ++ } ++ } else { ++ str += displayForums(obj); ++ } ++ ++ return str; ++ }; ++ displayForums = function (obj) { ++ if (obj.contents.length == 0) { ++ return '

' + CategoryView.empty + '

'; ++ } ++ ++ var str = '' ++ + ''; ++ ++ for (var i in obj.contents) { ++ var forum = obj.contents[i]; ++ str += '' ++ + ''; ++ if (forum.isDeleted) { ++ str += ''; ++ } else { ++ str += ''; ++ } ++ str += ''; ++ } ++ ++ str += '
 ' ++ + CategoryView.headers.name + '' + CategoryView.headers.topics ++ + '' + CategoryView.headers.posts ++ + '' + CategoryView.headers.lastPost ++ + '
'
++				+ CategoryView.forumIcon[forum.isUnread ? 'unread' : 'read']
++				+ '' + forum.title + '' ++ + (forum.description != '' ? ('
' + forum.description) : '') + '
' ++ + CategoryView.deletedAt + '' + formatDate(forum.deletedAt) + '' ++ + CategoryView.by + '' + players[forum.deletedBy] + '' ++ + formatNumber(forum.topics) ++ + '' ++ + formatNumber(forum.posts) ++ + ''; ++ if (forum.posts != 0) { ++ str += formatDate(forum.lastTimestamp) + '
' + CategoryView.by ++ + '' + players[forum.lastAuthor] + ''; ++ } else { ++ str += CategoryView.noPosts; ++ } ++ str += '
'; ++ return str; ++ }; ++ ++ this.parse = function (data) { ++ if (data != '-') { ++ if (data.indexOf('\n') == -1) { ++ // IE, first update ++ viewId = data; ++ this.update(); ++ return; ++ } ++ ++ var lines = data.split('\n'); ++ pageMD5 = lines.shift(); ++ viewId = lines.shift(); ++ ++ contents = parseContents(lines); ++ players = parseNames(lines); ++ ++ document.getElementById('f-page').innerHTML = displayContents(contents); ++ } ++ ++ pageUpdater = window.setTimeout("pageContents.update()", 10000); ++ locked = false; ++ }; ++ ++ this.update = function () { ++ if (locked) { ++ return; ++ } ++ pageUpdater = null; ++ locked = true; ++ x_getView(viewId, pageMD5, function (data) { pageContents.parse(data); }); ++ }; ++ ++ this.markRead = function (id) { ++ if (locked) { ++ window.setTimeout("pageContents.markRead('" + id + "')", 250); ++ return; ++ } ++ locked = true; ++ if (pageUpdater) { ++ window.clearTimeout(pageUpdater); ++ pageUpdater = null; ++ } ++ x_categoryRead(id, viewId, function (data) { pageContents.parse(data); }); ++ }; ++ ++ this.setFieldValues = function () { }; ++}; ++ ++ ++ ++ForumsLayout = function () { ++ ++ var init = document.getElementById('f-params').innerHTML.split('#'); ++ var me = this; ++ ++ // Read the page's parameters ++ var menuVisible = (init.shift() == '1'); ++ var needData = (init.shift() == '1'); ++ var pageType = init.shift(); ++ ++ // Read the current menu entry ++ MenuItem.current = document.getElementById('f-menu-current').innerHTML; ++ ++ // Initialize other private variables here ++ var menuMD5 = ''; ++ var menuTree = null; ++ var menuUpdater = null; ++ var menuFDisabled = false; ++ var page = null; ++ ++ // Initialize private methods here ++ var parseMenuData, drawLayout, drawMenu; ++ parseMenuData = function(data) { ++ menuUpdater = null; ++ if (! menuVisible) { ++ menuFDisabled = false; ++ return; ++ } ++ ++ var lines = data.split('\n'); ++ var line = lines.shift(); ++ ++ if (line != '-') { ++ menuMD5 = line; ++ menuTree = new MenuItem(lines); ++ drawMenu(); ++ } ++ menuFDisabled = false; ++ menuUpdater = window.setTimeout("main.updateMenu()", 5000); ++ }; ++ drawMenu = function () { ++ if (! menuTree) { ++ menuVisible = false; ++ drawLayout(); ++ return; ++ } ++ ++ var tst = ' style="width: 100%; margin: 0px; padding: 0px; border-width: 0px"'; ++ var cst = ' style="margin: 0px; padding: 0px; border-width: 0px; vertical-align: middle"'; ++ var str = '' + ForumsLayout.menuTitle + '' + ForumsLayout.hideMenu ++ + '' + ' '; ++ ++ var x = new Array(); ++ menuTree.draw(x); ++ ++ str += x.join('') + ''; ++ document.getElementById('f-menu').innerHTML = str; ++ }; ++ drawLayout = function () { ++ var mContents; ++ if (document.getElementById('f-page')) { ++ mContents = document.getElementById('f-page').innerHTML; ++ } else { ++ mContents = ' '; ++ } ++ ++ var mainMargin; ++ var str = '
(loading menu)'; ++ mainMargin = 326; ++ } else { ++ str += ';text-align:center; width: 16px">' ++ + ForumsLayout.menuText.join('
') + '
'; ++ mainMargin = 42; ++ } ++ str += '
 
'; ++ document.getElementById('f-contents').innerHTML = str; ++ document.getElementById('f-page').innerHTML = mContents; ++ if (mContents != ' ') { ++ pageContents.setFieldValues(); ++ } ++ }; ++ ++ // Public methods ++ this.updateMenu = function() { ++ menuFDisabled = true; ++ x_getMenu(menuMD5, parseMenuData); ++ }; ++ this.hideMenu = function() { ++ if (menuFDisabled || !menuVisible) { ++ if (menuFDisabled) { ++ window.setTimeout("main.hideMenu()", 250); ++ } ++ return; ++ } ++ if (menuUpdater) { ++ window.clearTimeout(menuUpdater); ++ } ++ menuUpdater = menuMD5 = menuTree = null; ++ menuVisible = false; ++ x_hideMenu(function () {}); ++ drawLayout(); ++ }; ++ this.showMenu = function() { ++ if (menuFDisabled || menuVisible) { ++ if (menuFDisabled) { ++ window.setTimeout("main.showMenu()", 250); ++ } ++ return; ++ } ++ menuFDisabled = true; ++ menuVisible = true; ++ drawLayout(); ++ x_showMenu(parseMenuData); ++ }; ++ this.menuOpen = function(id) { ++ if (menuFDisabled || !menuVisible) { ++ if (menuFDisabled) { ++ window.setTimeout("main.menuOpen('" + id + "')", 250); ++ } ++ return; ++ } ++ menuFDisabled = true; ++ if (menuUpdater) { ++ window.clearTimeout(menuUpdater); ++ menuUpdater = null; ++ } ++ x_menuOpen(id, parseMenuData); ++ }; ++ this.menuClose = function(id) { ++ if (menuFDisabled || !menuVisible) { ++ if (menuFDisabled) { ++ window.setTimeout("main.menuClose('" + id + "')", 250); ++ } ++ return; ++ } ++ menuFDisabled = true; ++ if (menuUpdater) { ++ window.clearTimeout(menuUpdater); ++ menuUpdater = null; ++ } ++ x_menuClose(id, parseMenuData); ++ }; ++ ++ useGIFs = needData; ++ pageContents = new ForumsLayout.pages[pageType]; ++ drawLayout(); ++ if (needData) { ++ if (menuVisible) { ++ x_getMenu(parseMenuData); ++ } ++ } else { ++ if (menuVisible) { ++ parseMenuData(document.getElementById('f-menu-init').innerHTML); ++ } ++ } ++ pageContents.parse(document.getElementById('f-page-init').innerHTML); ++}; ++ ++ForumsLayout.pages = { ++ vCat: CategoryView, ++ vForum: ForumView, ++ vTopic: TopicView ++}; +diff -Naur beta5//site/static/beta5/js/pg_overview-en.js forums//site/static/beta5/js/pg_overview-en.js +--- beta5//site/static/beta5/js/pg_overview-en.js 2011-02-05 10:09:56.434335002 +0100 ++++ forums//site/static/beta5/js/pg_overview-en.js 2011-03-12 15:10:45.561300049 +0100 +@@ -84,26 +84,7 @@ + str += '

Fleets

Total fleet power: '+formatNumber(flOverview[0])+'

'; + str += '

Money

Daily Profit: €'+formatNumber(moOverview[2])+'

'; + +- str += '

Forums

'; +- var i,j,k,a=new Array(); +- for (i=0;i1 ? 's' : ''))); +- a.push(''+name+': ' + j); +- }} +- +- if (aForums.length) +- { +- k = 0; +- for (j=0;j1 ? 's' : ''))); +- a.push('Alliance forums: ' + j); +- } +- str += a.join('
') + '

'; ++ str += '

Forums

' + forums.output(-1, false) + '

'; + + str += '

Planets

'+formatNumber(unOverview[0])+' planets

'; + str += '

Next ticks

'; +@@ -188,30 +169,7 @@ + str += 'Daily Profit: €'+formatNumber(moOverview[2])+'
'; + str += 'More details...

'; + +- str += '

Forums

'; +- var j,a=new Array(),s; +- for (i=0;i (view)'; +- for (j=0;j' + forums[j].name + ': '; +- s += makeTopicsText(forums[j].nTopics, forums[j].nUnread); +- } +- a.push(s); +- }} +- +- if (aForums.length) +- { +- s = 'Alliance Forums (view)'; +- for (j=0;j' + aForums[j].name + ': '; +- s += makeTopicsText(aForums[j].nTopics, aForums[j].nUnread); +- } +- a.push(s); +- } +- str += a.join('

') + '

'; ++ str += '

Forums

' + forums.output(-1, true) + '

'; + + str += '

Universe

'+formatNumber(unOverview[0])+' planets';// ('; + str += /*formatNumber(unOverview[2]) + ' at the same prot. level)*/'
'; +@@ -240,6 +198,40 @@ + } + + ++function __forumsOutput(depth, complete) { ++ var str = ''; ++ ++ if (this.id != '/') { ++ for (var i = 0; i < depth; i++) { ++ str += '      '; ++ } ++ str += '' + this.name + ': '; ++ if (complete) { ++ str += '' + this.topics + ' topic' + (this.unread > 1 ? 's' : ''); ++ if (this.unread > 0) { ++ str += ' (' + this.unread + ' unread)'; ++ } ++ } else { ++ if (this.unread == 0) { ++ str += 'no unread topics'; ++ } else { ++ str += '' + this.unread + ' unread topic' + (this.unread > 1 ? 's' : ''); ++ } ++ } ++ } ++ ++ for (var i = 0; i < this.contents.length; i ++) { ++ if (this.contents[i].type == 'F' && ! complete) { ++ continue; ++ } ++ ++ str += '
' + this.contents[i].output(depth + 1, complete); ++ } ++ ++ return str; ++} ++ ++ + function confirmBreakProtection() { + return confirm('You are about to break away from Peacekeeper protection.\n' + + 'Anyone will be able to attack your planets afterwards.\n' +diff -Naur beta5//site/static/beta5/js/pg_overview.js forums//site/static/beta5/js/pg_overview.js +--- beta5//site/static/beta5/js/pg_overview.js 2011-02-05 10:09:56.434335002 +0100 ++++ forums//site/static/beta5/js/pg_overview.js 2011-03-12 15:08:00.801300049 +0100 +@@ -1,5 +1,5 @@ + var dFolders, cFolders; +-var genForums, aForums, allianceId; ++var forums; + var plOverview, flOverview, moOverview, nResearch; + var unOverview,stDiff,ticks,tUpdate,rankings; + var complete, protection, updateTimer; +@@ -110,24 +110,25 @@ + this.name = name; + } + +-function Category(id, type, name) +-{ +- this.id = id; +- this.type = type; +- this.name = name; +- this.forums = new Array(); +-} ++function ForumsEntity(inputData) { ++ var iLine = inputData.shift().split('#'); ++ var nElements; ++ ++ this.type = iLine.shift(); ++ this.id = iLine.shift(); ++ nElements = parseInt(iLine.shift(), 10); ++ this.topics = parseInt(iLine.shift(), 10); ++ this.unread = parseInt(iLine.shift(), 10); ++ this.name = inputData.shift(); ++ this.contents = new Array(); ++ this.output = __forumsOutput; + +-function Forum(id, nTopics, nUnread, name) +-{ +- this.id = id; +- this.nTopics = nTopics; +- this.nUnread = nUnread; +- this.name = name; ++ for (var i = 0; i < nElements; i ++) { ++ this.contents.push( new ForumsEntity(inputData) ); ++ } + } + + +- + function initPage() { + overviewReceived(document.getElementById('init-data').value); + } +@@ -140,55 +141,26 @@ + } + + +-function parseComms(l) +-{ +- var i, a = l.shift().split('#'); +- var nCustom = parseInt(a[0],10), nGenCats = parseInt(a[1],10), nAForums = parseInt(a[2],10); +- allianceId = a[3]; ++function parseComms(l) { ++ var i, a, nCustom; + + // Default folders + dFolders = new Array(); +- for (i=0;i<3;i++) +- { ++ for (i=0;i<3;i++) { + a = l.shift().split('#'); + dFolders.push(new Folder('', a[0], a[1], '')); + } + + // Custom folders ++ nCustom = parseInt(l.shift(), 10); + cFolders = new Array(); +- for (i=0;i>= <:8867‹3=$‚>?=;9741*'# 9‹&‚0¿ ++6‹!$'3,‚81223ÚÛ3458@”äåæçè„,>#0‚9FEEDCCBA@Ÿ³‹ 19J¸‚%‹Œ¸tñR´B(Éà€áB… (H€À@‚S‹XÀÀñ .^ÀÈö­† 9¦›I]!9¨ÀÓ <€a…5Ejñ0BÐ$HŽ©'H´d"R F‚tÔ˜ÃÅŠ!8d¸Hd¢†/.¤#ƒ ++Ç0X°ÑF$X¸Eñ!Ĉ&Nœ@"… ++MkJžL¹²eD; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/forum_read.png forums//site/static/beta5/pics/forum_read.png +--- beta5//site/static/beta5/pics/forum_read.png 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/forum_read.png 2011-02-05 10:10:02.694335002 +0100 +@@ -0,0 +1,3 @@ ++‰PNG ++ ++ IHDR szzôbKGDÿÿÿ ½§“ pHYs  šœtIME×  0)NoÞ¸×IDATXÃí—Ík$EÆÝUÕ]ý²ÝãÌfr]ñÀ˜ä"Ä›ˆúG,( ^õà]¼yðâ%ãE<èE<+ô ãºîàÁ$ fe“ÍdÒÓåÁé!ÉNd²òÂKW¿ôT=Ýïóß÷m¥ÔDZƒÁ`~ìB)ÅéééTÈóœ4M)Ëò¢(à`&I’ÐëõÈó)% 6 “$1ÇÇÇôûý©>PÅäÍ«º”’›7oš3„žÉöã8FJ‰Öº|ôèÑŸc‡üÙ¶í_nܸÙ¶=Ñúe „0ÀΕHÐétž7Æè,ËÌýû÷Ó“““{À1°0¶Õ«Êði-ø§IB@ñ# ×ܧ0œFÂK'ÙÜÜRJæççÖº\__¯$ª¦µÎ²,ÇAkã8“ÔZcÛö¨Óé3s`ssóå(ŠnGQD’$¹çyŸ_v»Ýf³ÙüÐu]÷ì¶mcÛ6Bˆsãê^J¹ ¼;3Ïó^]YYy=ŽcvvvHÓ4¾r]·Ón·ßð}ÿ ÝWãj[>›J©÷ÃYˆÁ`@–eÕäðÂ0$Ë2ž8äyÎh4b4Mî«íüÖ­[Öxo9œõ<`vww1Æ µ&Š"jµZ6ÙßߟhþbV_¢JÇqBä—ñí²|ãûþºÖÚ‚ ðàÁ6€Rê‡ápø©ëºqÕë³~0í<`Û¶é÷û¿^Ù€ÅñÖ[Å<… €Ç×ÿ®ã?jñg}ͶIEND®B`‚ +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/forum_unread.gif forums//site/static/beta5/pics/forum_unread.gif +--- beta5//site/static/beta5/pics/forum_unread.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/forum_unread.gif 2011-02-05 10:10:02.694335002 +0100 +@@ -0,0 +1,3 @@ ++GIF89a çÖÿ˜ÿ›ÿœÿÿÿ ÿ¡ÿ¢ÿ¢ÿ¤ ÿ¥"ÿ¦"ÿ¦#ÿ©'ÿª*ÿ©2ÿ«*ÿ«+ÿª3ÿ¬+ÿ«5ÿ¬6ÿ®/ÿ®8ÿ°0ÿ±4ÿ°:ÿ²4ÿ²5ÿ±>ÿ´7ÿ³?ÿ´=ÿµ8ÿ´>ÿ·;ÿ¶Bÿ¸<ÿ¸=ÿ¹>ÿ¸Eÿ»Aÿ»BÿºHÿ¼Bÿ¼Cÿ½Dÿ¼Lÿ¾Eÿ¾Gÿ¿Fÿ¿HÿÀIÿ¿OÿÁKÿÃLÿÃMÿÅOÿÅPÿÅRÿÈUÿÉUÿÉVåÏ}ÿÉXÿÊXÿËWÿËXÿËYÿÌZåÒƒáÕ†ÿÎ]ÿÎ^ÏÕ½ìÔuÿÏ^ÿÏ`åÕ‡æÖÿÐ`ôÔnå×å؈ÿÒdå×’ÿÒgáØšÿÓeå×—ÿÔeå×å×¢ÿÔlÖÚÁáÚ¤ÿÖjíÚ†åبáÛ§åØ­ÿØkåزÿØlÿÙnÿÚoÿÚpåÚ¸óÝ‚ÿÚuÛß½ÿÛqøÜúÝxøÝ„ÿÝtø߇ÿÝ‚ÿßyøâ€øà‹ÿàxÿàyÿáyÿß…ÿà€øáŽÿâ|ÿã|ÿáˆøã‘ÿàŽÿã~ÿáŠÿß•ÿàÿߘÿä€ÿã†úåŠøä•ÿßÿá”ÿß ÿߥÿæ€øå™ÿæÿߨÿæ†ÿçƒøæÿè‚üåœøæ¢þé…øæ¦ÿé‡üéòé¬üçŸÿêˆþë‡ÿã°ÿéÿë‹ÿå¬ýìŽýìøé®ýíŒÿìŒéìÈÿìÿë“ÿí‰ÿë”ûé­ÿíÿíÿîŒÿï‘ÿï’ÿï˜ÿð–ÿñ‘ÿñ•ÿð›ÿñ–ÿðžÿò•ÿï¦ÿð¡ÿð£ÿñ ÿó›ÿó¡ÿôœòñÐÿõ›ÿ÷Ÿÿø¤ÿù§ÿù©ÿùªÿú®ÿú²ÿû³ÿûµÿü²ÿü¸ÿü¹ÿý¼ÿýÂÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\È°¡Ã‡#Jœx€WIâTô+°^»ná²5«T(L›*HÄ+Ⱦ†ñªµªÓ¤=g´0âÕB%<€¸"ÐNŒ#Bd€°AdZXJGn’â¨Ð C†5z‰Ò(cÓª]˶-Am„A¹#Ö4hΚ1S– Ù1_¹^‰ZèàO+ S`ʤÙ)Rž2Xñqj!D|HHÇ )Ll°0¡ƒ@1|SÇ”‹àÈ¡ƒÇ F’.i┊•ÛßÀðPd ‡,Z¸a#"M¨€Q³@¥h=–$Vš´gÍ–\%+¦KV¨HªF@£gÇ´,Ú3'–"9h¨0qÂÓÂ!lðÂõQà ++(ðT@t CQ°ñÉ@aü`„RT‘Å\ˆA†k(܉(¦¨âŠ ; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/forum_unread.png forums//site/static/beta5/pics/forum_unread.png +--- beta5//site/static/beta5/pics/forum_unread.png 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/forum_unread.png 2011-02-05 10:10:02.694335002 +0100 +@@ -0,0 +1,8 @@ ++‰PNG ++ ++ IHDR szzôbKGDÿÿÿ ½§“ pHYs  šœtIME×  0*×fIDATXÃí—ßkUÇ?w~ïìlv7ÙüØ´ÖÄm«4mQ¤}4hѪˆõÁG+jADýŠÔ`)>E}éC}‘öÁÁZ«XmKèj¬Ö´I!M7;;sg®w¶¤°‘M_TÈÃ9Ìœû½÷žï÷ÌÀ†mØ¿l¢SPM1€aM/ý»$ÅþïÅ#Ÿ_QSXø¥pü¼~[è¦ Ž§Ý̓íëg1_a»Å®cßvšËê+Wz‡Ç_zƒêNøý,qqøCà5lï ö¿õÁˆHD»J@¤ $¤ ¤RÇÒ%ÌE`0{¸ …^‡Þ*8&Ȉ…–[rú]ŠE HcíIÛ#ˆCh.ƒlAÜAš¢jå€ ++0߀Áøé4Èpˆ{&MÀfÓý°xê?@ÜÌVš€”­hRêÕ'ÙèæQ›÷äº?‚ÒÐ2–V…ˆÒÚ ++=}¿â6Èc˜€ÊÊFÐ8ï-O­#—×W„—Þt6ûTè–œÐ)%'?==þìùià° ¸‹‚¿üÑ€ö>n°˜‘“m§X'€ úŸèÀ± øå!Á–1I¡rJŒ}ô³:‚OßæWñü<†Na°l°ps`¹àäÀvÀö–3#v|¢û",öbâåC ŽAýkZ…ê1à nn’gÞ~ò6Íw•R_·µ@Å "—!*M—€“úánôT <âEh…,®x%­W@s®ƒ´ ++!\†8Ó„$‚T¡jã6Ð\í@µÓ§´ X=Èò³`1¼]±4sÓz2•h¾'¢¦Ö¶$™":>jËÓjUAw¥WÚ ++F¼i£Ñ€ t!6‚KéàC„ † ÂâÎë̱uzÓSKæð/ë*B€ñGkGX^(…ª_ø­Õl4/f:PÍdu½4¼Þéþ)I-£­7³îcgÛ¹^ÍNE¸fuˆþ­Š|O*öŸÑñ÷±QŽÎ°ÀËA¾¤[±ßn~,;ã'’®k@}`í£8|¾* Õ$Aù8œùRM1LmÏQr{û[@0 0l°,00-­¦¥µÁrf€w»/ ïyž|å;áÜq¥_á÷°÷õñGµª*©y¿Úe¨))CMO’ºÁ^à0°Ô­Àò_-ƒLhˆ²ä(U!¾×æ4EÓDS/‰u;Ðjd:ÐÒq!PYoéÀШbö;ÍeFu|x{ÌÍ[PÿQOÐþH=YšjW ++T6zŠ{äZõ¶Æô~Ó(ß7©rC;d½.fpsçnDÎ'aiwïí³6œŒvÖ(m«b†£®-سëÖ¬ï;«îÿZ@è» .ËÿöŸ³¿õãÿIEND®B`‚ +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/post_cn.gif forums//site/static/beta5/pics/post_cn.gif +--- beta5//site/static/beta5/pics/post_cn.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/post_cn.gif 2011-02-05 10:10:02.704335002 +0100 +@@ -0,0 +1,9 @@ ++GIF89a çÍ"$), -!0$3%8*:,;-<.>.?/?0@1A2C4E4F5G6G7H8I9J9L:K:M;N<O=P>Q?R@S@TAUBUBVCWDXEYFZG[H\H^I]I_J^J`K_KaL`LbMfKcNdOePfQjOgQiRhRlQiSnRkToSpSrUsVnX pYuXr[vYs\t]w^{]v^ y` ~`|c€a{bh„j ‡l‹j…kˆm†l‰n‡mˆn‹o ‰oŠpq Žrqts’u“v’v”w“w•x•y —z˜{™|™|$š}œ~œ'ž€€(Ÿ!ž) ‚"Ÿ‚*¡ƒ# ƒ+¢„$¢„,£…-Ÿˆ.¤†.£†5¥‡/¦ˆ0§‰1¤Œ2¨Š2¥3©‹3¦Ž4ªŒ4§5¦<¨6©‘7ª’8¬“9­”:¬”A­•B®–C¯—D°˜E±™F²šG´›HµœI¶J·žK¶ŸQµŸW· R¶ X¸¡S¹¢Tº£U»¤V½¥W¾¦X¾§_¿¨`À©aÁªbÀªh«cìd¬jÄ­eíkÅ®fįlÅ°mƱnDzoɳpÌ·{лѼ€Ó¿‚ÔÀƒÕ‹ÖÃŒ×Ä×Å”ÙÈ–ÚÉ—ÛʘÜË™ÞÌšßÍ›ÝÍ¡Þ΢ßÏ£àÐ¥áÒ¦âÔ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\È°¡CY"Jœ˜å!Á,WÐÀÁƒÇ8 Ñ„‰h1K@ƒú“§å9rÚ¨S±¡”;‡Úi;yìà‘³F˜š ++¯ø™Ät#C…ü81ÇåË6eì"g¥E—-šô Kôp¼CGæQ„YÖhšK—R4bìÈÁDO?w^®¡¹µ'Nž4%ž”Ã@ ;j‰"Çç;EµB¤Ê”gQ@M’A?~ ¡¼pLÂ)¡ ++e ++*R£<=)à€ >†¡ ÏK4š¥¤JÕ ++UnS£:5R‚`À‚ÔÁ‡ìE#¬ôòÊ(«‡Ê$ ;l‘@VhaFZÄ|ÅÌ+×lóÍ; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/post_co.gif forums//site/static/beta5/pics/post_co.gif +--- beta5//site/static/beta5/pics/post_co.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/post_co.gif 2011-02-05 10:10:02.714335002 +0100 +@@ -0,0 +1,6 @@ ++GIF89a ç‘...666:::===>>>???@@@BBBDDDEEEHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmppprrrssstttuuuvvvwwwxxxzzz|||}}}~~~€€€ƒƒƒ„„„………†††‡‡‡ˆˆˆ‰‰‰ŠŠŠŒŒŒŽŽŽ‘‘‘’’’“““”””•••–––———˜˜˜™™™ššš›››œœœžžžŸŸŸ   ¡¡¡¢¢¢£££¤¤¤¥¥¥¦¦¦§§§¨¨¨©©©ªªª«««¬¬¬­­­®®®¯¯¯°°°±±±²²²³³³´´´µµµ¶¶¶¸¸¸¹¹¹ººº»»»¼¼¼½½½¾¾¾¿¿¿ÁÁÁÂÂÂÃÃÃÄÄÄÅÅÅÉÉÉÏÏÏÑÑÑÓÓÓÔÔÔÕÕÕÖÖÖ×××ØØØÙÙÙÚÚÚÛÛÛÜÜÜÝÝÝÞÞÞßßßàààáááÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\¸‡Ã‡ ú bÄH‘"D†éÁ#bA@œT©BeŠ”(Oœ0QRÄGGGŽœ118B /j4áý„IXðhW³Ä‰“'OŒþ¼Ð0DŠ,Z´ ±d ++”&iòȲ'š H¨pAƒ‡$„Öœ ++Æ„OA[ðQ† 0ð@Xˆà ·¡àá î)H\´ñ ++0à€~`° ˆ0 $”`c 6Tj#zF Tâ„X¸A€B"4‰‚VLaÄ‚`”!A ` ÁtàA’ˆà¥_’ðƒWP1eAZô!DÄY@Ô)ÀlÉe8\±UaЄø‘x<õ”Ä3€`Á`ðAZt•E“}t‡"‡RÈ ‚Èc¸ ÁTÐ_ÈÕ…¬M¤&‰4ÂÈ"‰RH nаbh°Ãh”†YüPU8Ò®…Øà 4à=œÁFÆzq„¬òɉ#Œ$Rˆ0`Áø°mp+ÆàäƒÎ*bW2àoÌG¼eDñC<„§|ü°€HÈqŸÁD½”!a!‚àuèop!Æj QiR4]±Ê=‘G4×l³G; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/post_on.gif forums//site/static/beta5/pics/post_on.gif +--- beta5//site/static/beta5/pics/post_on.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/post_on.gif 2011-02-05 10:10:02.684335002 +0100 +@@ -0,0 +1,11 @@ ++GIF89a çÓ))& ( , 2%5(7)9+:,;-<.>.?/?0@1A2C4E4F5G6G7H8I9J9L:M;N<O=Q?R@TATAUBVCWDXE\CYFZG[H\H^I_J`KaLbMcNgLdOcO ePfQjOhRlQnRoSlUpSqTlVrUsVpYqZqZ xZwZv]t]w^{]v^ u^w_ ++za~`{b{b}d}d€f~efƒi ‡l‹jˆmŒk‰nˆn‹o Œp ++q ‹qŽrsŽrtt’u‘u“v”w“w•x–y•y —z˜{™|™|$š}œ~•,™},›~&œ'ž€Ÿ!ž) ‚"Ÿ‚*¡ƒ# ƒ+¢„$¢„,ž‡-£…-¤†.¥‡/¢Š0¦ˆ0£‹1§‰1¤Œ2¨Š2£Œ9¥3©‹3¤:¦Ž4ªŒ4¥Ž;§5¬5¦<¨6­Ž6§=©‘7®7¨‘>ª’8ª’?¬“9«“@¯‘@­”:¬”A«”G­•B®–C¯—D°˜E±™F²šG´›HµœI¶JµžP·žK¶ŸQ¸ L· R¸¡S¹¢Tº£U»¤V½¥W¿§Y¾§_¿¨`½¨fÀ©aÁªb«cìd¬jįlÅ°mƱnDzoÊ´q˵r̶sÌ·{ι}Ϻ~×Ä×Å”ÞÌšÜÌ ÝÍ¡ßÏ£àÐ¥áÒ¦âÓ§âÔ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\¸‹Ã‡ r)cÆ 1½dÁ± 1pýù³‡d9pà”á±#–4„É$D =zâ¼iƒFLË…WîTª$)R¢Du H(ž:mÒ€ù‰°J PŸ²fšTÇÆ‚ÊÚsRNŸ ³À1eªÔ(RœÚ¼Xð ®Bå¹Õ U‚g>­JEØÔžê>€ÐÃÐȱ'Ùd9¸“¬X°\ifcƒ(ùt¨Í“*ÿþË"«V­X®aÁA@!‚ ŒH¢d¨P=yÞÀ¹bPÏ­[¸Ž×¢Ç€…ç6$ÉÔ)Qé@zPzñxˆ—÷]»xþí ++”€Btž˜â”éÑ @}îÈA;ðŠ#aÀ€Áš3ç lPB L‘J)†ˆÁ4EŸ@[D"Œ0®(Ñ2Àq@‚PŸ,2… ++)áFy”A\}œc ++B@ `ð -” Æ M¼ð 2<[T…(·(A2šWB #¤Â I@a 0ÄÀ€üqÆŠYAÉ(ðPà€Xê€:¸ …àUÆ_‹³Å@ðApB”-¼0ƒ<øPÄ…(BÈbä…2ƈñÀŒ°Á%à5è[ ‚É#ˆüÁ…A[üÂL¥Å @A ++Œp”1èàbt‚Õ$xIÌ8S©P£ ++XÂiDŸ¤2 ++'”RäAXˆ"4ÈX*k &œ C C aÊ+¢Ò‰%j¨6¹HÍ1ÞR Ã 0‘†+·´¢J)¡Ü!,¶ñBs,àP®¹R‹*ŸäqíBVˆ² 3ÂìI€tÄÂË-µ¸’ ++Vtôlä’Œ0z°ˆ-¾ôr"b”l2„bD‰#‹„¸ˆÝLÙWTA´ÐH'­ô@; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/post_oo.gif forums//site/static/beta5/pics/post_oo.gif +--- beta5//site/static/beta5/pics/post_oo.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/post_oo.gif 2011-02-05 10:10:02.684335002 +0100 +@@ -0,0 +1,7 @@ ++GIF89a ç¶---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~€€€‚‚‚ƒƒƒ„„„………†††‡‡‡ˆˆˆ‰‰‰ŠŠŠ‹‹‹ŒŒŒŽŽŽ‘‘‘’’’“““”””•••–––———˜˜˜™™™ššš›››œœœžžžŸŸŸ   ¡¡¡¢¢¢£££¤¤¤¥¥¥¦¦¦§§§¨¨¨©©©ªªª«««¬¬¬­­­®®®¯¯¯°°°±±±²²²³³³´´´µµµ¶¶¶···¸¸¸¹¹¹ººº»»»¼¼¼½½½¾¾¾¿¿¿ÀÀÀÁÁÁÂÂÂÃÃÃÄÄÄÅÅÅÆÆÆÇÇÇÈÈÈÉÉÉÊÊÊËËËÌÌÌÍÍÍÎÎÎÏÏÏÐÐÐÑÑÑÒÒÒÓÓÓÔÔÔÕÕÕÖÖÖ×××ØØØÙÙÙÚÚÚÛÛÛÜÜÜÝÝÝÞÞÞßßßàààáááâââÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\¨PŠÃ‡&|HæŒ2cÄ„ÉQ"A)TÞ¼qÓfš41‚é(Q ++›9räÄqCÒä™3e2^‘ÒÒÎ;vêÀì$È–5hRŠá³¡ž=zôäÁC§‹Š Vpa£ÍE1[š”Rç?}øäÑRƒCŠ¼áê•Œ˜*b?Z$HP @ÈxhááˆJâ¼iÓÕ̘0yJ1DÙP¡Bƒ°$pqXĈ&tä,îšÓKäR©NÄú—0D(aÊ:¢¹âYÊGÀ _ôEÁ Ú(RLÑs‡ÎL6i.öV$I¤H؉aPãDŠ,¬þôÉÓc4ez7ªD©’*Uô˜i€C /²ê“&Ê“0l$Õ[$™T‚‡ |pÃèÀ 1ÈÀ kA ?„To”dbxÂ8€ƒ 1ÌPÃcAÜࢠN@×Û%¸p „@ =ƒŠ70a„Dê0%õ¦I$ ‚ "”€Â ++(ÒpC:ìÀÖD!‡lô†G&Z\  5˜0¥ 2Ôpƒ<ôàÃFh“y§IÁŠ'\˜Y)<Ø&;ôðIp‘‡ι‘g*¯„â ˜àà ƒúƒað¡uıÒX²Ä"ʤ ++˜Ð¦5ꃄcò‡Z¡ž&Y´È2ŠfšðC¦I˜Ù~‹²d›ä*J$`­‘F"‡Hzp´˜Ì (\X€ ++D4¡F#‹ bmZØzl±¸ÒIDÀ‘<Â"…bì¶Rp¢ ++&u¸±%”@²oÿ¶Ñà {$ñÄWlñ?; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_locked.gif forums//site/static/beta5/pics/topic_locked.gif +--- beta5//site/static/beta5/pics/topic_locked.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_locked.gif 2011-02-05 10:10:02.694335002 +0100 +@@ -0,0 +1,5 @@ ++GIF89a ç ++ ++ ++  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~€€€‚‚‚ƒƒƒ„„„………†††‡‡‡ˆˆˆ‰‰‰ŠŠŠ‹‹‹ŒŒŒŽŽŽ‘‘‘’’’“““”””•••–––———˜˜˜™™™ššš›››œœœžžžŸŸŸ   ¡¡¡¢¢¢£££¤¤¤¥¥¥¦¦¦§§§¨¨¨©©©ªªª«««¬¬¬­­­®®®¯¯¯°°°±±±²²²³³³´´´µµµ¶¶¶···¸¸¸¹¹¹ººº»»»¼¼¼½½½¾¾¾¿¿¿ÀÀÀÁÁÁÂÂÂÃÃÃÄÄÄÅÅÅÆÆÆÇÇÇÈÈÈÉÉÉÊÊÊËËËÌÌÌÍÍÍÎÎÎÏÏÏÐÐÐÑÑÑÒÒÒÓÓÓÔÔÔÕÕÕÖÖÖ×××ØØØÙÙÙÚÚÚÛÛÛÜÜÜÝÝÝÞÞÞßßßàààáááâââãããäääåååæææçççèèèéééêêêëëëìììíííîîîïïïðððñññòòòóóóôôôõõõööö÷÷÷øøøùùùúúúûûûüüüýýýþþþÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\È°¡Ã‡#JœÑÔ«V¡(œ$ìœ,¿xîç),,ç?“¡„¾‡µ Ú4ÒýSƒÇ jx•L9%ÚÁ,?>n<»:Cퟀ‡ò,™MWá-—‡ùºUš²X™.XÁ„Þf% ¯ñ€¿ÍÅ:&VâÎ €ƒH¤Ç˜xDTÛÿ…¿¡ Hjx„¢fýÔA€DÄæŠ Ä¥6 €$¢«žÐì‘¢¸´Š)É[Ïn`;ž1rÓEXá—»½E€ÒÍ——tCÖlt×]{׌HŽ©n zZÎÊGÓq×ÚÛo¨¦éjòIEND®B`‚ +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_poll.gif forums//site/static/beta5/pics/topic_poll.gif +--- beta5//site/static/beta5/pics/topic_poll.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_poll.gif 2011-02-05 10:10:02.694335002 +0100 +@@ -0,0 +1,9 @@ ++GIF89a Æm ++ ++ ++  !!!###$$$&&&---///111222666777888;;;<<<===>>>@@@AAABBBCCCEEEFFFGGGHHHIIIJJJLLLMMMNNNPPPRRRSSSTTTUUUWWWYYY]]]___```aaabbbcccdddeeekkklllmmmnnnooorrrssswwwzzz€€€‡‡‡ˆˆˆ‰‰‰ŒŒŒ‘‘‘’’’•••–––˜˜˜›››   ¡¡¡©©©¯¯¯³³³¶¶¶···¸¸¸ººº¼¼¼¾¾¾¿¿¿ÁÁÁÄÄÄÆÆÆÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++, þ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’‰ ++ “ˆ™†;NM?/ „Ilh[D ©‚HjfYB±³GidWA²‘´¿ÁÃÅ'PSOK(«ÀÂÄ ++-ejd_- JÙÎ *agb],äæÍÛA^f]+ñçô ++ pP —qåü=[AD‰#Bô°£ß¼…‰\xQƒ‡Ž¢Ì°¨ #"8êÀე‘ /6PÁE7$€"ƒ$:F4mâÔÉÓç?EAoæÜ S–Ÿ‹PrÜ¡cBˆ*4P`²æŒ–!¨fRXÃF–P^$˜`dL*3>2 ++à@8p`")€ø A¢Âº+Vàƒ 8€p˜—å˘3k6; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_poll.png forums//site/static/beta5/pics/topic_poll.png +--- beta5//site/static/beta5/pics/topic_poll.png 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_poll.png 2011-02-05 10:10:02.684335002 +0100 +@@ -0,0 +1,7 @@ ++‰PNG ++ ++ IHDR szzôbKGDÿÿÿ ½§“ pHYs  šœtIME×  s( éIDATXÃí—Ok$UÅ÷uuWuPiB4 H”€™Õä!!›,EAÄ0à¬üîü ºrì²ËÊE² Ñ•&nš Õ‹¦Ëêªê®zﺘî&‹ÉL'a\äìªnÝ÷Î{çÜû^Áîðš!³Ðjµˆ¢ˆz½T*•Ç"òÀƒ1æoçܽ^¯{U¾wK ñÂ0œ‚àcùÀ9‡µökí¯EQäišfÏK¬Ì:s¿ßGDÃ0|²¾¾¾½¹¹Ù\^^¦ÙlR¯×ý^¯÷Vžçÿ‡Ã?Ÿ—o®³ÕWê(²¨ª_ÏÏÏ¿¿»»«;;;lllèââb|&"Ÿ\•knIJU¥( ++)Ëk­8çPÞØã•GQ„ïûoc¾"¢"RXk ±ˆ Uu¬=eY¢ª¨ªìL&ŒãXZ­V5‚‡"òôœˆüuqqñªÞ¨¢^J Š"F=Ïó§KKKŸooo·‚ Ë2â8æèèè‡n·›÷1·OÀ÷}œsù¾ÿáÚÚõz4M‰¢ˆ“““"2ò¢èu{‹™¢QÉ`0¨©*ÖZ²,£( ++ʲë,—&½¶ f ++w¿“eÙ7yž¿wvvÆÞÞívß÷Ÿ} :[{A¬ÌAð•ˆ|/"ov:ö÷÷ñ<­­-œsÜD÷i Ì߆aøÝÂÂBX«ÕÕ5ÇÇÇ$IÂêê*žç½2oÛµZíÝf³©¾ïk£Ñk-‡‡‡ôû}VVV¨T*¯Œ€ÙHc°ÖN¶Ý÷}FîŸ æ%쨛M çœCD&ÚÏjÂÛ: ^«d˜VžyÀVD&ƒc?c¨V«“wžçQ­V±ÖâyÞåXq]÷€û@àœ£(ŠÉ`c”eI’$ ‡Ãñ©GžçÄqÌ`0 MÓqLTõÞ¨ª’i | ++<>ˆã˜óós<Ï›H’k-´ÛmŠ¢À9Çéé)ý~k-Ýn—N§CY–5U}üü2í¥ô °1­ªÕ*sss¨*eYréRŠ1†4MI’ä7৻ÿ€;üïð?î|õr¬ýŸIEND®B`‚ +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_s0_read.gif forums//site/static/beta5/pics/topic_s0_read.gif +--- beta5//site/static/beta5/pics/topic_s0_read.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_s0_read.gif 2011-02-05 10:10:02.704335002 +0100 +@@ -0,0 +1,4 @@ ++GIF89a Æx'''(((***---000111222555666777<<<>>>???CCCDDDGGGIIIJJJKKKLLLMMMNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}€€€‚‚‚ƒƒƒ„„„‡‡‡ˆˆˆ‰‰‰ŠŠŠ‹‹‹ŽŽŽ‘‘‘’’’“““”””•••–––™™™›››œœœžžžŸŸŸ   ¡¡¡¢¢¢¥¥¥¨¨¨ªªª­­­®®®¯¯¯°°°±±±²²²³³³´´´¶¶¶···¾¾¾ÃÃÃÉÉÉÐÐÐÓÓÓÔÔÔØØØÙÙÙÚÚÚáááâââãããäääæææèèèéééêêêëëëìììíííîîîïïïðððñññòòòóóóôôôõõõööö÷÷÷øøøùùùúúúûûûüüüþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++, þ€‚ƒ„…†‡ˆ‰Š‹ŒŽŠ/=CB?:73-,)('$! ++†1SUQLJGCD850+)&$§ «…7SOOJHE>>¶,,» § †3XWVâUUSSMMKKDDAA<841ó-†7vuupspûtqÞ¼é§ œ7kά!“æ‹ C9îÜ©sÇNE‰ëXœ˜oN8nä˜AaÈÆ“QÖñXGœ6rÆœ¸‡²&F‹-á„”£F¦¡6m®ÌÙF ›5>E9‡N7rêÀdÓ°„!K3ê‹#Ç 9hÖˆýbµÖ¬ùäÈiÓR3ífÊ|!q5ëÄ­]¿†[–ÐÙ¥i׶}w®¡v+^œs­œ3eÎœ!3&̇kæ›xPªiÒ 1C† ˜-t ¹¨¹rŽÚ6©ž‰[f 0ZFb¡²9oÔ´L7ÍšÚ¦O§&´£¾|úÜ´AÃ!2fƈsæËeC«%â øµ ¸q)‹±ý… jC*ô=}#ä¸`(ƒùâ¥KûöY`V ++TP…P8±DHa„DÑC;èÀÃ…ê€!h Á 6àÀ$"`@ ÀŠ°¢ÉŒ4Öhã8æˆH ; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_s0_unread.gif forums//site/static/beta5/pics/topic_s0_unread.gif +--- beta5//site/static/beta5/pics/topic_s0_unread.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_s0_unread.gif 2011-02-05 10:10:02.704335002 +0100 +@@ -0,0 +1,2 @@ ++GIF89a Æv'#($*&-(0+1,2-506172<6>8?9Cs?€t@‚vAƒwA„xB‡zCˆ{D‰|DŠ}E‹~E€FŽG‚GƒH‘ƒH’„I“…I”†J•‡J–ˆK™‹L›MœNžOŸO ‘P¡’P¢“Q¥–R¨˜TªšU­V®žW¯ŸW° X±¡X²¡Y³¢Y´£Z¶¥[·¦[¾¬_ñaɶdнhÓÀiÔÀjØÄlÙÅlÚÆmáÌpâÍqãÎqäÏræÑsèÓtéÔtêÔuëÕuìÖví×vîØwïÙwðÚxñÛxòÜyóÝyôÞzõÞzöß{÷à{øá|ùâ|úã}ûä}üå~þçÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++, þ€‚ƒ„…†‡ˆ‰Š‹ŒŽŠ-;A@=851+*'&%" †/QSOJHEAB63.)'$"§ «…5QMMHFC<<¶**»§ †1VUTâSSQQKKIIBB??:62/ó+†5tssnqnûroÚ´é÷f›6iʤs¦ C7êÔ™S‡NE‰çXœ˜/Îœ7là1aˆÆ“QÎñ8Ž›5p”¸‡²&F‹-Ý„„ƒF¦¡6m®Ì¹š4>EGÎœ6pæÀTÓp„!K3ê{‡8fÒˆíbµÖ¬ùàÀYÓ 2ídÆtq5ëÄ­]¿†[–ÐÙ¥i׶}w®¡t+^œs-œ2cÊ”æK‡kæ›xŽªgΘ!#FŒ—,t ±¨¹2ŽÚ5©–‰;&Œ/XBR¡rŽ8mд<÷LšÚ¦O§&”£¾|úج1£¡1d€ñR¦ËeC«%â øu¸q)ƒ±ÝE jC(ô=m#]ä¸^({éÂeKûöW`Vˆ RHáN0‘ÄFABø°Ã9à Ã…â`!`€ 2Ѐ$P ÀŠ°bÉŒ4Öhã8æˆH ; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_s10_read.gif forums//site/static/beta5/pics/topic_s10_read.gif +--- beta5//site/static/beta5/pics/topic_s10_read.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_s10_read.gif 2011-02-05 10:10:02.704335002 +0100 +@@ -0,0 +1,12 @@ ++GIF89a çŽ ++ ++ ++ '''(((***---000111222555666777<<<===>>>???CCCDDDGGGIIIJJJKKKLLLNNNOOOPPPQQQRRRSSSTTTUUUVVVWWW^^^```bbbcccdddfffjjjkkklllmmmnnnpppqqqrrrtttzzz}}}€€€‚‚‚ƒƒƒ„„„‡‡‡ˆˆˆ‰‰‰ŠŠŠ‹‹‹ŽŽŽ‘‘‘’’’“““”””•••–––™™™ššš›››œœœžžžŸŸŸ   ¡¡¡¢¢¢¥¥¥¨¨¨©©©ªªª­­­®®®¯¯¯°°°±±±²²²³³³´´´µµµ¶¶¶···»»»¾¾¾ÃÃÃÉÉÉÐÐÐÓÓÓÔÔÔØØØÙÙÙÚÚÚÛÛÛÜÜÜßßßáááâââãããäääæææèèèéééêêêëëëìììíííîîîïïïðððñññòòòóóóôôôõõõööö÷÷÷øøøùùùúúúûûûüüüþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\È°¡Ã‡#*ôÁdŠ'IŒá±Ç 2\¨H¢Ã‡ 4X˜Á 2hÄtÙre ++•#D~èÀQCÆ ++#8?9Cs?€t@‚vAƒwA„xB‡zCˆ{D‰|DŠ}E‹~E€FŽG‚GƒH‘ƒH’„I“…I”†J•‡J–ˆK™‹LšŒM›MœNžOŸO ‘P¡’P¢“Q¥–R¨˜T©™TªšU­V®žW¯ŸW° X±¡X²¡Y³¢Y´£Zµ¤Z¶¥[·¦[»ª]¾¬_ñaɶdнhÓÀiÔÀjØÄlÙÅlÚÆmÛÇmÜÈnßÊoáÌpâÍqãÎqäÏræÑsèÓtéÔtêÔuëÕuìÖví×vîØwïÙwðÚxñÛxòÜyóÝyôÞzõÞzöß{÷à{øá|ùâ|úã}ûä}üå~þçÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\È°¡Ã‡#*ôÁdŠ'IŒá±Ç 2\¨H¢Ã‡ 4X˜Á 2hÄtÙre ++•#D~èÀQCÆ ++#>>???CCCDDDGGGIIIJJJKKKLLLNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}€€€‚‚‚ƒƒƒ„„„‡‡‡ˆˆˆ‰‰‰ŠŠŠ‹‹‹ŽŽŽ‘‘‘’’’“““”””•••–––™™™›››œœœžžžŸŸŸ   ¡¡¡¢¢¢¥¥¥¨¨¨ªªª­­­®®®¯¯¯°°°±±±²²²³³³´´´¶¶¶···¾¾¾ÃÃÃÉÉÉÐÐÐÓÓÓÔÔÔØØØÙÙÙÚÚÚáááâââãããäääæææèèèéééêêêëëëìììíííîîîïïïðððñññòòòóóóôôôõõõööö÷÷÷øøøùùùúúúûûûüüüþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++, þ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘Š.962,+('&#  ++†0RTPKIFBC74/*(%#© ­…6RNNIGD==¸++½© †2WVUäTTRRLLJJCC@@;730õ,†6uttoroýæÀqãæ6oܨ1£f /% á°c‡Ž:)R¤ƒ±â>9tà´‰Sæ„¡SnTI$8oØÄc"ŸÊ›1¾|32Nš†hàL @E0Ù¤Y£(²¡‹Ê™CÇM:2×<$a(TŠECÆi³&Î5h½p-äõkÑ8ôqؼL“¦L2^Ftýj',œ±eϦ]K¨-Ô·qçÖ½›×‹:#K¦óV¦2fÌŒFKÇ7÷U|StV4hΔ3拽…ZÜl)nQ7ZÍÜ%#æË—," ­`IgN7i^¢±‹F ïÖ®aR¡‘ß>~mØœY£ð̘2bÂ|1ãų!Ùu,ËfÝ»›Ãôö²åµ¡üªºÉ¾&óÝ/›}áE\ÐGŸrÂS<ÅM(ÄFQÄ?ðÀƒ9ìàa‡9\`ˆd0Á‰ 8°ÀŠPÀ ãÈ(ÀH¢ãŽ<öè#"; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_s1_unread.gif forums//site/static/beta5/pics/topic_s1_unread.gif +--- beta5//site/static/beta5/pics/topic_s1_unread.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_s1_unread.gif 2011-02-05 10:10:02.704335002 +0100 +@@ -0,0 +1,3 @@ ++GIF89a Æw'#($*&-(0+1,2-506172<6>8?9Cs?€t@‚vAƒwA„xB‡zCˆ{D‰|DŠ}E‹~E€FŽG‚GƒH‘ƒH’„I“…I”†J•‡J–ˆK™‹L›MœNžOŸO ‘P¡’P¢“Q¥–R¨˜TªšU­V®žW¯ŸW° X±¡X²¡Y³¢Y´£Z¶¥[·¦[¾¬_ñaɶdнhÓÀiÔÀjØÄlÙÅlÚÆmáÌpâÍqãÎqäÏræÑsèÓtéÔtêÔuëÕuìÖví×vîØwïÙwðÚxñÛxòÜyóÝyôÞzõÞzöß{÷à{øá|ùâ|úã}ûä}üå~þçÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++, þ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘Š.962,+('&#  ++†0RTPKIFBC74/*(%#© ­…6RNNIGD==¸++½© †2WVUäTTRRLLJJCC@@;730õ,†6uttoroýæÀqãæ6oܨ1£f /% á°c‡Ž:)R¤ƒ±â>9tà´‰Sæ„¡SnTI$8oØÄc"ŸÊ›1¾|32Nš†hàL @E0Ù¤Y£(²¡‹Ê™CÇM:2×<$a(TŠECÆi³&Î5h½p-äõkÑ8ôqؼL“¦L2^Ftýj',œ±eϦ]K¨-Ô·qçÖ½›×‹:#K¦óV¦2fÌŒFKÇ7÷U|StV4hΔ3拽…ZÜl)nQ7ZÍÜ%#æË—," ­`IgN7i^¢±‹F ïÖ®aR¡‘ß>~mØœY£ð̘2bÂ|1ãų!Ùu,ËfÝ»›Ãôö²åµ¡üªºÉ¾&óÝ/›}áE\ÐGŸrÂS<ÅM(ÄFQÄ?ðÀƒ9ìàa‡9\`ˆd0Á‰ 8°ÀŠPÀ ãÈ(ÀH¢ãŽ<öè#"; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_s2_read.gif forums//site/static/beta5/pics/topic_s2_read.gif +--- beta5//site/static/beta5/pics/topic_s2_read.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_s2_read.gif 2011-02-05 10:10:02.694335002 +0100 +@@ -0,0 +1,7 @@ ++GIF89a Æw'''(((***---000111222555666777<<<>>>???CCCDDDGGGIIIJJJKKKLLLNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}€€€‚‚‚ƒƒƒ„„„‡‡‡ˆˆˆ‰‰‰ŠŠŠ‹‹‹ŽŽŽ‘‘‘’’’“““”””•••–––™™™›››œœœžžžŸŸŸ   ¡¡¡¢¢¢¥¥¥¨¨¨ªªª­­­®®®¯¯¯°°°±±±²²²³³³´´´¶¶¶···¾¾¾ÃÃÃÉÉÉÐÐÐÓÓÓÔÔÔØØØÙÙÙÚÚÚáááâââãããäääæææèèèéééêêêëëëìììíííîîîïïïðððñññòòòóóóôôôõõõööö÷÷÷øøøùùùúúúûûûüüüþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++, þ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘Š.962,+('&#  ++†0RTPKIFBC74/*(%#© ­…6RNNIGD==¸++½© †2WVUäTTRRLLJJCC@@;730õ,†6uttoroýæÀqãæ6oܨ1£f /% á°c‡Ž:)R¤ƒ±â>9tà´‰Sæ„¡SnTI$8oØÄc"ŸÊ›1¾|32Nš†hà¤Q a²I³F PdCí=JÇM:2×<$a(FÔ¢üŠÆi³&Î5h½p-äuhÑ}òEÙ¼L“¦L2^Ftý ++ ,€±eϦ]K¨-ηtâέ{7¯!u"K–,öX3d̘#Œ–oî“ ++ÀjQ4¨Ï”3拽…ZÜl)'®›Ófî’óåK†V°¤3'Ž›4/ÑØE£fwk×° ©ÐÈo¿6lάQxfL1a¾˜ñòÙlŠ:–e³Æî]Îax{ÙòÚP ++~UÝ`_£ùîÎ_xÑó͇h…œ0ÅODñDJ q„E1Ä<ð C;tÈaâAL`b, "0Ä8@1 ++0€’ä¨ãŽ<öˆH ; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_s2_unread.gif forums//site/static/beta5/pics/topic_s2_unread.gif +--- beta5//site/static/beta5/pics/topic_s2_unread.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_s2_unread.gif 2011-02-05 10:10:02.694335002 +0100 +@@ -0,0 +1,7 @@ ++GIF89a Æw'#($*&-(0+1,2-506172<6>8?9Cs?€t@‚vAƒwA„xB‡zCˆ{D‰|DŠ}E‹~E€FŽG‚GƒH‘ƒH’„I“…I”†J•‡J–ˆK™‹L›MœNžOŸO ‘P¡’P¢“Q¥–R¨˜TªšU­V®žW¯ŸW° X±¡X²¡Y³¢Y´£Z¶¥[·¦[¾¬_ñaɶdнhÓÀiÔÀjØÄlÙÅlÚÆmáÌpâÍqãÎqäÏræÑsèÓtéÔtêÔuëÕuìÖví×vîØwïÙwðÚxñÛxòÜyóÝyôÞzõÞzöß{÷à{øá|ùâ|úã}ûä}üå~þçÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++, þ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘Š.962,+('&#  ++†0RTPKIFBC74/*(%#© ­…6RNNIGD==¸++½© †2WVUäTTRRLLJJCC@@;730õ,†6uttoroýæÀqãæ6oܨ1£f /% á°c‡Ž:)R¤ƒ±â>9tà´‰Sæ„¡SnTI$8oØÄc"ŸÊ›1¾|32Nš†hà¤Q a²I³F PdCí=JÇM:2×<$a(FÔ¢üŠÆi³&Î5h½p-äuhÑ}òEÙ¼L“¦L2^Ftý ++ ,€±eϦ]K¨-ηtâέ{7¯!u"K–,öX3d̘#Œ–oî“ ++ÀjQ4¨Ï”3拽…ZÜl)'®›Ófî’óåK†V°¤3'Ž›4/ÑØE£fwk×° ©ÐÈo¿6lάQxfL1a¾˜ñòÙlŠ:–e³Æî]Îax{ÙòÚP ++~UÝ`_£ùîÎ_xÑó͇h…œ0ÅODñDJ q„E1Ä<ð C;tÈaâAL`b, "0Ä8@1 ++0€’ä¨ãŽ<öˆH ; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_s3_read.gif forums//site/static/beta5/pics/topic_s3_read.gif +--- beta5//site/static/beta5/pics/topic_s3_read.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_s3_read.gif 2011-02-05 10:10:02.704335002 +0100 +@@ -0,0 +1,3 @@ ++GIF89a Æw'''(((***---000111222555666777<<<>>>???CCCDDDGGGIIIJJJKKKLLLNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}€€€‚‚‚ƒƒƒ„„„‡‡‡ˆˆˆ‰‰‰ŠŠŠ‹‹‹ŽŽŽ‘‘‘’’’“““”””•••–––™™™›››œœœžžžŸŸŸ   ¡¡¡¢¢¢¥¥¥¨¨¨ªªª­­­®®®¯¯¯°°°±±±²²²³³³´´´¶¶¶···¾¾¾ÃÃÃÉÉÉÐÐÐÓÓÓÔÔÔØØØÙÙÙÚÚÚáááâââãããäääæææèèèéééêêêëëëìììíííîîîïïïðððñññòòòóóóôôôõõõööö÷÷÷øøøùùùúúúûûûüüüþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++, þ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘Š.962,+('&#  ++†0RTPKIFBC74/*(%#© ­…6RNNIGD==¸++½© †2WVUäTTRRLLJJCC@@;730õ,†6uttoroýæÀqãæ6oܨ1£f /% á°c‡Ž:)R¤ƒ±â>9tà´‰Sæ„¡SnTI$8oØÄc"ŸÊ›1¾|32Nš†hà€"ÑŠDa²I³F PdC‹Ú!*gNR:2×<$a(FT£B]猚³^¸òzóèTõqâ$M“¦L2^FtýúV,²fѪ%ÄV¥Û¤ráÒµ‹Wo!u"K®Ct_R6Dɘ13F -! ¹¸I9ì›±kˆ¢9Sf̘/Zjq“b¢nR¸KFÌ—/YDZÁ’Îœ8nÒ¼DcšÞ¯aˤB#¿}üÚ°9³Fá™1eÄ„ùbÆ hC´)êH–Í»w;‡ñíeKlC)øÑ!¨}Íæ»_tö…]pQ_}X„VÈ SLñDO4¡GQDCüÀ:ä°Ã‡æp!dÁ(6àÀ,"`@ ÀŒ0£ ÉŽ<öèãˆ; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_s3_unread.gif forums//site/static/beta5/pics/topic_s3_unread.gif +--- beta5//site/static/beta5/pics/topic_s3_unread.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_s3_unread.gif 2011-02-05 10:10:02.684335002 +0100 +@@ -0,0 +1,3 @@ ++GIF89a Æw'#($*&-(0+1,2-506172<6>8?9Cs?€t@‚vAƒwA„xB‡zCˆ{D‰|DŠ}E‹~E€FŽG‚GƒH‘ƒH’„I“…I”†J•‡J–ˆK™‹L›MœNžOŸO ‘P¡’P¢“Q¥–R¨˜TªšU­V®žW¯ŸW° X±¡X²¡Y³¢Y´£Z¶¥[·¦[¾¬_ñaɶdнhÓÀiÔÀjØÄlÙÅlÚÆmáÌpâÍqãÎqäÏræÑsèÓtéÔtêÔuëÕuìÖví×vîØwïÙwðÚxñÛxòÜyóÝyôÞzõÞzöß{÷à{øá|ùâ|úã}ûä}üå~þçÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++, þ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘Š.962,+('&#  ++†0RTPKIFBC74/*(%#© ­…6RNNIGD==¸++½© †2WVUäTTRRLLJJCC@@;730õ,†6uttoroýæÀqãæ6oܨ1£f /% á°c‡Ž:)R¤ƒ±â>9tà´‰Sæ„¡SnTI$8oØÄc"ŸÊ›1¾|32Nš†hà€"ÑŠDa²I³F PdC‹Ú!*gNR:2×<$a(FT£B]猚³^¸òzóèTõqâ$M“¦L2^FtýúV,²fѪ%ÄV¥Û¤ráÒµ‹Wo!u"K®Ct_R6Dɘ13F -! ¹¸I9ì›±kˆ¢9Sf̘/Zjq“b¢nR¸KFÌ—/YDZÁ’Îœ8nÒ¼DcšÞ¯aˤB#¿}üÚ°9³Fá™1eÄ„ùbÆ hC´)êH–Í»w;‡ñíeKlC)øÑ!¨}Íæ»_tö…]pQ_}X„VÈ SLñDO4¡GQDCüÀ:ä°Ã‡æp!dÁ(6àÀ,"`@ ÀŒ0£ ÉŽ<öèãˆ; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_s4_read.gif forums//site/static/beta5/pics/topic_s4_read.gif +--- beta5//site/static/beta5/pics/topic_s4_read.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_s4_read.gif 2011-02-05 10:10:02.704335002 +0100 +@@ -0,0 +1,5 @@ ++GIF89a ç… '''(((***+++,,,---000111222555666777999;;;<<<>>>???CCCDDDFFFGGGIIIJJJKKKLLLNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}€€€‚‚‚ƒƒƒ„„„………‡‡‡ˆˆˆ‰‰‰ŠŠŠ‹‹‹ŽŽŽ‘‘‘’’’“““”””•••–––™™™ššš›››œœœžžžŸŸŸ   ¡¡¡¢¢¢¥¥¥¨¨¨ªªª­­­®®®¯¯¯°°°±±±²²²³³³´´´¶¶¶···¾¾¾ÃÃÃÉÉÉÎÎÎÐÐÐÓÓÓÔÔÔØØØÙÙÙÚÚÚÞÞÞáááâââãããäääæææèèèéééêêêëëëìììíííîîîïïïðððñññòòòóóóôôôõõõööö÷÷÷øøøùùùúúúûûûüüüþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\È°¡Ã‡#*¼aÄI$C€ð¨A#Œ,R” 1‚ƒ‡ 2P€ÐÀ /`¸\©"ÅÉ“ >r̈ႅ‰:hÐ’BË‚@¼hÑRe ++”#GpÒ Ñ… !:¤Ì Âƒ<ÈŒ C /^°`±båÉ“%KŠé¡£n  $¨ >}ùáÃç¯=}øࡃ'Ž6. ++!DH¡A—)S„¹ò^@‚üìù3†ÁšSoV-´ ?}ôüó"¯êÛš1¿î3úÏÚ}à€Y*ºž;yðG:¼8#Ùyþ·0¸£:¡4Øñ3g4uðÈg3¾`ùÛÄ)GÐäOÜqÇsÈÁ ä™GÈdàñG|óÕGÐ}ªåGÈ F¸! "XÐ ƒ¤¨â Äíõ„ 4(tćg¨`Ð ·±@_|4ALØQÇqÄÑÆ&dÃm‚÷ÇzÀám(m´aÆ ++ÑÀš üÁǯÙA xl©ä’M4ƒf|íÅ×zÔ‘‡buÄ1o´A9ô$eº –•yX opÉL$_‚†g4؆m°±†“NZ†ŽÁðÅ[t±E8?9Cs?€t@‚vAƒwA„xB…yB‡zCˆ{D‰|DŠ}E‹~E€FŽG‚GƒH‘ƒH’„I“…I”†J•‡J–ˆK™‹LšŒM›MœNŽNžOŸO ‘P¡’P¢“Q¥–R¨˜TªšU­V®žW¯ŸW° X±¡X²¡Y³¢Y´£Z¶¥[·¦[¾¬_ñaɶdλgнhÓÀiÔÀjØÄlÙÅlÚÆmÞÊoáÌpâÍqãÎqäÏræÑsèÓtéÔtêÔuëÕuìÖví×vîØwïÙwðÚxñÛxòÜyóÝyôÞzõÞzöß{÷à{øá|ùâ|úã}ûä}üå~þçÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\È°¡Ã‡#*¼aÄI$C€ð¨A#Œ,R” 1‚ƒ‡ 2P€ÐÀ /`¸\©"ÅÉ“ >r̈ႅ‰:hÐ’BË‚@¼hÑRe ++”#GpÒ Ñ… !:¤Ì Âƒ<ÈŒ C /^°`±båÉ“%KŠé¡£n  $¨ >}ùáÃç¯=}øࡃ'Ž6. ++!DH¡A—)S„¹ò^@‚üìù3†ÁšSoV-´ ?}ôüó"¯êÛš1¿î3úÏÚ}à€Y*ºž;yðG:¼8#Ùyþ·0¸£:¡4Øñ3g4uðÈg3¾`ùÛÄ)GÐäOÜqÇsÈÁ ä™GÈdàñG|óÕGÐ}ªåGÈ F¸! "XÐ ƒ¤¨â Äíõ„ 4(tćg¨`Ð ·±@_|4ALØQÇqÄÑÆ&dÃm‚÷ÇzÀám(m´aÆ ++ÑÀš üÁǯÙA xl©ä’M4ƒf|íÅ×zÔ‘‡buÄ1o´A9ô$eº –•yX opÉL$_‚†g4؆m°±†“NZ†ŽÁðÅ[t±E>>???CCCDDDFFFGGGIIIJJJKKKLLLNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}€€€‚‚‚ƒƒƒ„„„………‡‡‡ˆˆˆ‰‰‰ŠŠŠ‹‹‹ŽŽŽ‘‘‘’’’“““”””•••–––™™™›››œœœžžžŸŸŸ   ¡¡¡¢¢¢¥¥¥¨¨¨ªªª­­­®®®¯¯¯°°°±±±²²²³³³´´´¶¶¶···¾¾¾ÃÃÃÉÉÉÐÐÐÓÓÓÔÔÔ×××ØØØÙÙÙÚÚÚÞÞÞáááâââãããäääæææèèèéééêêêëëëìììíííîîîïïïðððñññòòòóóóôôôõõõööö÷÷÷øøøùùùúúúûûûüüüþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\È°¡Ã‡#*¼a¤ $C€ð¨A#Œ,R” 1‚ƒ‡ 2P€ÐÀ Ž._¶X¡¥‰“ >r̈ႅ‰:hÐ’BË‚@ºdÉBEÊ“#GpÒ Ñ… !:¤Ì Âƒ<Ä„CöË—.]®\©Rʼn%JŠé¡£n  ++ˆÏ>}õÙ³çoŸ<|öÜ™sN5. ++4(Ð A—)S„¹òÞ?úèñ#†ÁšSoVt ?|òøyó"¯êÛš1¿æ3ÚÚ}à„ÍV$€Çž;À‘Ç`å?"ÔðH6žÇ- þîŽÀ€:ê @:w⯠_püðžZ@Âk;vÈ!Gk° Þpƒ@`z¸Ÿ|ôdnp@ÆX A7bâ‰(f€hàÇqÌ1o´a† ++$Þ¶Weü@uI‡pÀÁ†dÃm­ýAÚz¼QÇÆñl”±‚A4° ~ìaÇkuXÇVyd’Í _{ñ¥Gtà¡pÈñ†ḻÆ-I™nƒ¹—H£W®q’ÉÀW „͉‡Œ²A#k¨‘†£ŽŽcA0xá…\hESH6P8‘„F1D¸Þ: ‚[ë  €ÌP³`€DÔVkíµØ"; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_s5_unread.gif forums//site/static/beta5/pics/topic_s5_unread.gif +--- beta5//site/static/beta5/pics/topic_s5_unread.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_s5_unread.gif 2011-02-05 10:10:02.704335002 +0100 +@@ -0,0 +1,6 @@ ++GIF89a ç„  ++'#($*&+',(-(0+1,2-50617293:4<6>8?9Cs?€t@‚vAƒwA„xB…yB‡zCˆ{D‰|DŠ}E‹~E€FŽG‚GƒH‘ƒH’„I“…I”†J•‡J–ˆK™‹L›MœNŽNžOŸO ‘P¡’P¢“Q¥–R¨˜TªšU­V®žW¯ŸW° X±¡X²¡Y³¢Y´£Z¶¥[·¦[¾¬_ñaɶdнhÓÀiÔÀj×ÃkØÄlÙÅlÚÆmÞÊoáÌpâÍqãÎqäÏræÑsèÓtéÔtêÔuëÕuìÖví×vîØwïÙwðÚxñÛxòÜyóÝyôÞzõÞzöß{÷à{øá|ùâ|úã}ûä}üå~þçÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\È°¡Ã‡#*¼a¤ $C€ð¨A#Œ,R” 1‚ƒ‡ 2P€ÐÀ Ž._¶X¡¥‰“ >r̈ႅ‰:hÐ’BË‚@ºdÉBEÊ“#GpÒ Ñ… !:¤Ì Âƒ<Ä„CöË—.]®\©Rʼn%JŠé¡£n  ++ˆÏ>}õÙ³çoŸ<|öÜ™sN5. ++4(Ð A—)S„¹òÞ?úèñ#†ÁšSoVt ?|òøyó"¯êÛš1¿æ3ÚÚ}à„ÍV$€Çž;À‘Ç`å?"ÔðH6žÇ- þîŽÀ€:ê @:w⯠_püðžZ@Âk;vÈ!Gk° Þpƒ@`z¸Ÿ|ôdnp@ÆX A7bâ‰(f€hàÇqÌ1o´a† ++$Þ¶Weü@uI‡pÀÁ†dÃm­ýAÚz¼QÇÆñl”±‚A4° ~ìaÇkuXÇVyd’Í _{ñ¥Gtà¡pÈñ†ḻÆ-I™nƒ¹—H£W®q’ÉÀW „͉‡Œ²A#k¨‘†£ŽŽcA0xá…\hESH6P8‘„F1D¸Þ: ‚[ë  €ÌP³`€DÔVkíµØ"; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_s6_read.gif forums//site/static/beta5/pics/topic_s6_read.gif +--- beta5//site/static/beta5/pics/topic_s6_read.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_s6_read.gif 2011-02-05 10:10:02.694335002 +0100 +@@ -0,0 +1,8 @@ ++GIF89a ç† '''(((***+++,,,---...000111222555666777999;;;<<<>>>???CCCDDDFFFGGGHHHIIIJJJKKKLLLNNNRRRSSSTTTUUUVVVWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}€€€‚‚‚ƒƒƒ„„„………‡‡‡ˆˆˆ‰‰‰ŠŠŠ‹‹‹ŽŽŽ‘‘‘’’’“““”””•••–––™™™›››œœœžžžŸŸŸ   ¡¡¡¢¢¢£££¥¥¥¨¨¨ªªª­­­®®®¯¯¯°°°±±±²²²³³³´´´¶¶¶···¾¾¾ÃÃÃÉÉÉÐÐÐÓÓÓÔÔÔØØØÙÙÙÚÚÚÞÞÞáááâââãããäääæææèèèéééêêêëëëìììíííîîîïïïðððñññòòòóóóôôôõõõööö÷÷÷øøøùùùúúúûûûüüüþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\È°¡Ã‡#*ԑ䉓%F†üÀqƒÆ /X 8a 4TˆàÀ`0b¼d¹Bå "AxØ ãE ++!>pà²BË‚CÀpár¥J%JpÞ¸ÑsE ++#>¤ÔÀ¡ƒ?ÊCVŒ0`´hÁ‚ ++”&MÒ£.Ž‡4ÈO ?}ýéÓçïŸ=~úä©“GÎ61 )ThP!B—)S„¹òÞ@ƒþðDg†A!šSoV=ô @~öŠ##¯êÛš1¿ö3ÚƒÜF`‹æ- 6=y€#½½€ Í%ìÄ|l=aþôq[ €z ++Ý æ€ìä™ßF|Aò·%R ++€ €ìÁxÐAÇm¼0nH°A!Äïß|yÔ·àmr@ðF€x`‚é@ÈŠ,®¸ ÐA€ÖQ‡q¼F )Þ¶—Ì0…à1grÈá ++”Ãm­ÕÁtôQ$ṡnœá‚A7°6ˆ €ôÇkwxGZ*¹d“Ù _{ñÅÇv衘rÐnÔÑÆŽ=I™nƒé ˆ#[¶‘“ÕÀ× „Ý©‡º£m°±†¤’šÁcA3„F_t±;VTA…R@ÁDIa¼îj„©@ kÁ$û@  €ÐP´`€DØf«í¶Ü"; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_s6_unread.gif forums//site/static/beta5/pics/topic_s6_unread.gif +--- beta5//site/static/beta5/pics/topic_s6_unread.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_s6_unread.gif 2011-02-05 10:10:02.694335002 +0100 +@@ -0,0 +1,9 @@ ++GIF89a ç†  ++'#($*&+',(-(.)0+1,2-50617293;5<6>8?9Cs?€t@‚vAƒwA„xB…yB‡zCˆ{D‰|DŠ}E‹~E€FŽG‚GƒH‘ƒH’„I“…I”†J•‡J–ˆK™‹L›MœNžOŸO ‘P¡’P¢“Q£”Q¥–R¨˜TªšU­V®žW¯ŸW° X±¡X²¡Y³¢Y´£Z¶¥[·¦[¾¬_ñaɶdнhÓÀiÔÀjØÄlÙÅlÚÆmÞÊoáÌpâÍqãÎqäÏræÑsèÓtéÔtêÔuëÕuìÖví×vîØwïÙwðÚxñÛxòÜyóÝyôÞzõÞzöß{÷à{øá|ùâ|úã}ûä}üå~þçÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\È°¡Ã‡#*ԑ䉓%F†üÀqƒÆ /X 8a 4TˆàÀ`0b¼d¹Bå "AxØ ãE ++!>pà²BË‚CÀpár¥J%JpÞ¸ÑsE ++#>¤ÔÀ¡ƒ?ÊCVŒ0`´hÁ‚ ++”&MÒ£.Ž‡4ÈO ?}ýéÓçïŸ=~úä©“GÎ61 )ThP!B—)S„¹òÞ@ƒþðDg†A!šSoV=ô @~öŠ##¯êÛš1¿ö3ÚƒÜF`‹æ- 6=y€#½½€ Í%ìÄ|l=aþôq[ €z ++Ý æ€ìä™ßF|Aò·%R ++€ €ìÁxÐAÇm¼0nH°A!Äïß|yÔ·àmr@ðF€x`‚é@ÈŠ,®¸ ÐA€ÖQ‡q¼F )Þ¶—Ì0…à1grÈá ++”Ãm­ÕÁtôQ$ṡnœá‚A7°6ˆ €ôÇkwxGZ*¹d“Ù _{ñÅÇv衘rÐnÔÑÆŽ=I™nƒé ˆ#[¶‘“ÕÀ× „Ý©‡º£m°±†¤’šÁcA3„F_t±;VTA…R@ÁDIa¼îj„©@ kÁ$û@  €ÐP´`€DØf«í¶Ü"; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_s7_read.gif forums//site/static/beta5/pics/topic_s7_read.gif +--- beta5//site/static/beta5/pics/topic_s7_read.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_s7_read.gif 2011-02-05 10:10:02.704335002 +0100 +@@ -0,0 +1,4 @@ ++GIF89a ç„ '''(((***+++,,,---...000111222555666777999<<<>>>???CCCDDDGGGHHHIIIJJJKKKLLLNNNRRRSSSTTTUUUVVVWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}€€€‚‚‚ƒƒƒ„„„………‡‡‡ˆˆˆ‰‰‰ŠŠŠ‹‹‹ŽŽŽ‘‘‘’’’“““”””•••–––™™™›››œœœžžžŸŸŸ   ¡¡¡¢¢¢¥¥¥¨¨¨ªªª­­­®®®¯¯¯°°°±±±²²²³³³´´´¶¶¶···¾¾¾ÃÃÃÉÉÉÐÐÐÓÓÓÔÔÔØØØÙÙÙÚÚÚÝÝÝÞÞÞáááâââãããäääæææèèèéééêêêëëëìììíííîîîïïïðððñññòòòóóóôôôõõõööö÷÷÷øøøùùùúúúûûûüüüþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\È°¡Ã‡#*Äq¤ “$D‚ô°QCF -T˜(A‚ƒ‡ 0PˆàÀàŽ._¶X¡¥‰!?tÐñ¢Å :hÐ’BË‚AºdÉBEÊ$HpÖ¨Ñ3Å !:¤Ä ƒ=Ä„CöË—.]®\©Rʼn“%KŒñ±£® ƒ ++ˆÏ>}õÙ³çoŸ<|öÜ™sN5/ 4(Ð A—)S„¹òÞ?úèñ#'†A šSoVt ?|òøy#¯êÛš1¿æ3ÚÚ¤F‹f, à5s;xîGš:šGÐñ‡ù?Ìýþàyì Ôh À3Ïg1׃‡ùûkÊ<ŸZ(ƒ<ÀÎ5rÈÇ-˜§šd0Á×|õpßù9˜ FÀQ` .Ø`A8ââ‹°Àvàq0Çm”±‚A8Ü& sP|—auèe¬HÐ ·ÕñuäAssÈÁÜl°A ÕÀZ €ø±‡¯Õ‘`wÄñÆ’L:9 šñµ_zäAŠÑ‡o¸ÁÆkøh””é6~äG‚ ++îè—k˜Ñ¤A3ðayâ1G–q°±#k¨‘F¥•ŽñcA1xá…@\hESHP8¡ÄGA„ÀþJ¡ ‚$[4û@  €ÔPµ`€DÜvëí·à"; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_s7_unread.gif forums//site/static/beta5/pics/topic_s7_unread.gif +--- beta5//site/static/beta5/pics/topic_s7_unread.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_s7_unread.gif 2011-02-05 10:10:02.684335002 +0100 +@@ -0,0 +1,5 @@ ++GIF89a ç„  ++'#($*&+',(-(.)0+1,2-50617293<6>8?9Cs?€t@‚vAƒwA„xB…yB‡zCˆ{D‰|DŠ}E‹~E€FŽG‚GƒH‘ƒH’„I“…I”†J•‡J–ˆK™‹L›MœNžOŸO ‘P¡’P¢“Q¥–R¨˜TªšU­V®žW¯ŸW° X±¡X²¡Y³¢Y´£Z¶¥[·¦[¾¬_ñaɶdнhÓÀiÔÀjØÄlÙÅlÚÆmÝÉnÞÊoáÌpâÍqãÎqäÏræÑsèÓtéÔtêÔuëÕuìÖví×vîØwïÙwðÚxñÛxòÜyóÝyôÞzõÞzöß{÷à{øá|ùâ|úã}ûä}üå~þçÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\È°¡Ã‡#*Äq¤ “$D‚ô°QCF -T˜(A‚ƒ‡ 0PˆàÀàŽ._¶X¡¥‰!?tÐñ¢Å :hÐ’BË‚AºdÉBEÊ$HpÖ¨Ñ3Å !:¤Ä ƒ=Ä„CöË—.]®\©Rʼn“%KŒñ±£® ƒ ++ˆÏ>}õÙ³çoŸ<|öÜ™sN5/ 4(Ð A—)S„¹òÞ?úèñ#'†A šSoVt ?|òøy#¯êÛš1¿æ3ÚÚ¤F‹f, à5s;xîGš:šGÐñ‡ù?Ìýþàyì Ôh À3Ïg1׃‡ùûkÊ<ŸZ(ƒ<ÀÎ5rÈÇ-˜§šd0Á×|õpßù9˜ FÀQ` .Ø`A8ââ‹°Àvàq0Çm”±‚A8Ü& sP|—auèe¬HÐ ·ÕñuäAssÈÁÜl°A ÕÀZ €ø±‡¯Õ‘`wÄñÆ’L:9 šñµ_zäAŠÑ‡o¸ÁÆkøh””é6~äG‚ ++îè—k˜Ñ¤A3ðayâ1G–q°±#k¨‘F¥•ŽñcA1xá…@\hESHP8¡ÄGA„ÀþJ¡ ‚$[4û@  €ÔPµ`€DÜvëí·à"; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_s8_read.gif forums//site/static/beta5/pics/topic_s8_read.gif +--- beta5//site/static/beta5/pics/topic_s8_read.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_s8_read.gif 2011-02-05 10:10:02.704335002 +0100 +@@ -0,0 +1,7 @@ ++GIF89a ç… '''(((***+++,,,---...000111222555666777999<<<>>>???CCCDDDGGGHHHIIIJJJKKKLLLNNNRRRSSSTTTUUUVVVWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}€€€‚‚‚ƒƒƒ„„„………‡‡‡ˆˆˆ‰‰‰ŠŠŠ‹‹‹ŽŽŽ‘‘‘’’’“““”””•••–––———™™™›››œœœžžžŸŸŸ   ¡¡¡¢¢¢¥¥¥¨¨¨ªªª­­­®®®¯¯¯°°°±±±²²²³³³´´´¶¶¶···¾¾¾ÃÃÃÉÉÉÐÐÐÓÓÓÔÔÔØØØÙÙÙÚÚÚÞÞÞàààáááâââãããäääæææèèèéééêêêëëëìììíííîîîïïïðððñññòòòóóóôôôõõõööö÷÷÷øøøùùùúúúûûûüüüþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\È°¡Ã‡#*ÄqÄI%D‚ô°QCF -T˜(A‚ƒ‡ 0PˆàÀà/`¸\©"ÅÉ!?tÐñ¢Å :hÐ’BË‚A¼hÑRe ++$HpÖ¨Ñ3Å !:¤Ä ƒ=ƈ C /^°`±båÉ&LŒñ±£® ƒ $¨ >}ùáÃç¯=}øࡃ'Ž6/ !DH¡A—)S„¹ò^@‚üìù3'†A šSoV-´ ?}ôü#¯êÛš1¿î3úÏÚB ‹æ, 2sØÌóàŽ”P$4 c9@žþ ŠþØ…A„ЇPžÒxýs8?9Cs?€t@‚vAƒwA„xB…yB‡zCˆ{D‰|DŠ}E‹~E€FŽG‚GƒH‘ƒH’„I“…I”†J•‡J–ˆK—‰K™‹L›MœNžOŸO ‘P¡’P¢“Q¥–R¨˜TªšU­V®žW¯ŸW° X±¡X²¡Y³¢Y´£Z¶¥[·¦[¾¬_ñaɶdнhÓÀiÔÀjØÄlÙÅlÚÆmÞÊoàËpáÌpâÍqãÎqäÏræÑsèÓtéÔtêÔuëÕuìÖví×vîØwïÙwðÚxñÛxòÜyóÝyôÞzõÞzöß{÷à{øá|ùâ|úã}ûä}üå~þçÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\È°¡Ã‡#*ÄqÄI%D‚ô°QCF -T˜(A‚ƒ‡ 0PˆàÀà/`¸\©"ÅÉ!?tÐñ¢Å :hÐ’BË‚A¼hÑRe ++$HpÖ¨Ñ3Å !:¤Ä ƒ=ƈ C /^°`±båÉ&LŒñ±£® ƒ $¨ >}ùáÃç¯=}øࡃ'Ž6/ !DH¡A—)S„¹ò^@‚üìù3'†A šSoV-´ ?}ôü#¯êÛš1¿î3úÏÚB ‹æ, 2sØÌóàŽ”P$4 c9@žþ ŠþØ…A„ЇPžÒxýs>>???CCCDDDGGGIIIJJJKKKLLLNNNPPPQQQRRRSSSTTTUUUWWW^^^___```bbbcccdddfffkkklllmmmnnnoooqqqrrrtttzzz}}}€€€‚‚‚ƒƒƒ„„„‡‡‡ˆˆˆ‰‰‰ŠŠŠ‹‹‹ŽŽŽ‘‘‘’’’“““”””•••–––———™™™›››œœœžžžŸŸŸ   ¡¡¡¢¢¢¥¥¥§§§¨¨¨ªªª­­­®®®¯¯¯°°°±±±²²²³³³´´´¶¶¶···¹¹¹¾¾¾ÃÃÃÉÉÉÐÐÐÓÓÓÔÔÔ×××ØØØÙÙÙÚÚÚÛÛÛÝÝÝàààáááâââãããäääæææèèèéééêêêëëëìììíííîîîïïïðððñññòòòóóóôôôõõõööö÷÷÷øøøùùùúúúûûûüüüþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\È°¡Ã‡#*ì±dŠ'HŠÙ¡ã†1\¨@q 6\˜Áà2gÄtÙre ++#C|ä¸A#ÆŠ#>pàòBË‚EÈ€³%K&LpêÐѳŠ++%>¤ÜÀႃAÔ¤ACöÌ2d¼xáÂ… ++(P”ò£îŽ‹0Z´ÈP"C}*Tèï¡A† ++êHÏŸ:4 iÔhQ#F—)S^„¹òÞD‹BÄdžA"šSoV½ô"D†!ÊS#¯êÛš1¿64 Ú‡à Ñ J*'r Ä @‚G:¼xŸØ(‚3Ù‚þË0¤:å2,ºà "?â×_°ümâ”Í@ ߀ðÁÇuÄ@žy•Éqmò^|Ìwà}Å]f)@€h`A=0"∌·ˆ0H@ÑGzäq‡/ÔÃm%°‡ n`À|衇n|H·-B"#E!á5{äa‡mÀ`¬-¢"…òÚþH–G"©ä@9hÆ×^|2ˆ‚(æ‡|ä‡}ÔA£ALæWÙ`"ˆ€ƈ‡–u¼‘¤A8ðµav ++òâ€vÄhGtÌi¤kÔX f˜Æa|Á…:Y\a…T<±ÄI ¡Ä®º"A‚A,˜`‚ÄbAÈFð€ 4°À³ (ðì , ѵØf«í¶; +\ No newline at end of file +diff -Naur beta5//site/static/beta5/pics/topic_s9_unread.gif forums//site/static/beta5/pics/topic_s9_unread.gif +--- beta5//site/static/beta5/pics/topic_s9_unread.gif 1970-01-01 01:00:00.000000000 +0100 ++++ forums//site/static/beta5/pics/topic_s9_unread.gif 2011-02-05 10:10:02.704335002 +0100 +@@ -0,0 +1,10 @@ ++GIF89a çŽ ++  ++   ++  '#($*&-(0+1,2-506172;5<6=7>8?9Cs?€t@‚vAƒwA„xB‡zCˆ{D‰|DŠ}E‹~E€FŽG‚GƒH‘ƒH’„I“…I”†J•‡J–ˆK—‰K™‹L›MœNŽNžOŸO ‘P¡’P¢“Q¥–R§—S¨˜TªšU­V®žW¯ŸW° X±¡X²¡Y³¢Y´£Z¶¥[·¦[¹¨\¾¬_ñaɶdнhÓÀiÔÀj×ÃkØÄlÙÅlÚÆmÛÇmÝÉnàËpáÌpâÍqãÎqäÏræÑsèÓtéÔtêÔuëÕuìÖví×vîØwïÙwðÚxñÛxòÜyóÝyôÞzõÞzöß{÷à{øá|ùâ|úã}ûä}üå~þçÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ++ÿ, þÿ H° Áƒ*\È°¡Ã‡#*ì±dŠ'HŠÙ¡ã†1\¨@q 6\˜Áà2gÄtÙre ++#C|ä¸A#ÆŠ#>pàòBË‚EÈ€³%K&LpêÐѳŠ++%>¤ÜÀႃAÔ¤ACöÌ2d¼xáÂ… ++(P”ò£îŽ‹0Z´ÈP"C}*Tèï¡A† ++êHÏŸ:4 iÔhQ#F—)S^„¹òÞD‹BÄdžA"šSoV½ô"D†!ÊS#¯êÛš1¿64 Ú‡à Ñ J*'r Ä @‚G:¼xŸØ(‚3Ù‚þË0¤:å2,ºà "?â×_°ümâ”Í@ ߀ðÁÇuÄ@žy•Éqmò^|Ìwà}Å]f)@€h`A=0"∌·ˆ0H@ÑGzäq‡/ÔÃm%°‡ n`À|衇n|H·-B"#E!á5{äa‡mÀ`¬-¢"…òÚþH–G"©ä@9hÆ×^|2ˆ‚(æ‡|ä‡}ÔA£ALæWÙ`"ˆ€ƈ‡–u¼‘¤A8ðµav ++òâ€vÄhGtÌi¤kÔX f˜Æa|Á…:Y\a…T<±ÄI ¡Ä®º"A‚A,˜`‚ÄbAÈFð€ 4°À³ (ðì , ѵØf«í¶; +\ No newline at end of file +diff -Naur beta5//sql/13-main-forums.sql forums//sql/13-main-forums.sql +--- beta5//sql/13-main-forums.sql 2011-02-05 10:09:56.244335002 +0100 ++++ forums//sql/13-main-forums.sql 1970-01-01 01:00:00.000000000 +0100 +@@ -1,149 +0,0 @@ +--- LegacyWorlds Beta 5 +--- PostgreSQL database scripts +--- +--- 13-main-forums.sql +--- +--- Tables for the forums +--- +--- Copyright(C) 2004-2007, DeepClone Development +--- -------------------------------------------------------- +- +- +- +--- Connect to the database +-\c legacyworlds legacyworlds_admin +- +- +- +--- +--- Forum categories +--- +-CREATE TABLE main.f_category ( +- id SERIAL NOT NULL PRIMARY KEY, +- corder INT NOT NULL UNIQUE CHECK(corder >= 0), +- title VARCHAR(64) NOT NULL UNIQUE, +- description TEXT +-); +- +-GRANT SELECT,INSERT ON TABLE main.f_category TO legacyworlds; +-GRANT SELECT,UPDATE ON main.f_category_id_seq TO legacyworlds; +- +- +--- +--- Forums +--- +-CREATE TABLE main.f_forum ( +- id SERIAL NOT NULL PRIMARY KEY, +- category INT NOT NULL REFERENCES main.f_category (id) ON DELETE CASCADE, +- forder INT NOT NULL DEFAULT 0 CHECK(forder >= 0), +- title VARCHAR(64) NOT NULL, +- description TEXT, +- topics INT NOT NULL CHECK(topics >= 0), +- posts INT NOT NULL CHECK(posts >= 0), +- last_post BIGINT NULL, +- user_post BOOLEAN NOT NULL DEFAULT TRUE, +- admin_only BOOLEAN NOT NULL DEFAULT FALSE +-); +- +-CREATE UNIQUE INDEX forum_unique ON main.f_forum (category, forder); +-CREATE INDEX forum_last_post ON main.f_forum (last_post); +- +-GRANT SELECT,UPDATE,INSERT ON TABLE main.f_forum TO legacyworlds; +-GRANT SELECT,UPDATE ON main.f_forum_id_seq TO legacyworlds; +- +- +--- +--- Topics +--- +-CREATE TABLE main.f_topic ( +- id BIGSERIAL NOT NULL PRIMARY KEY, +- forum INT NOT NULL REFERENCES main.f_forum (id) ON DELETE CASCADE, +- first_post BIGINT NOT NULL, +- last_post BIGINT NULL, +- sticky BOOLEAN NOT NULL DEFAULT FALSE, +- deleted INT NULL +-); +- +-CREATE INDEX topic_forum ON main.f_topic (forum); +-CREATE INDEX topic_fpost ON main.f_topic (first_post); +-CREATE INDEX topic_lpost ON main.f_topic (last_post); +- +-GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE main.f_topic TO legacyworlds; +-GRANT SELECT,UPDATE ON main.f_topic_id_seq TO legacyworlds; +- +- +--- +--- Posts +--- +-CREATE TABLE main.f_post ( +- id BIGSERIAL PRIMARY KEY, +- forum INT NOT NULL REFERENCES main.f_forum (id) ON DELETE CASCADE, +- topic BIGINT NULL REFERENCES main.f_topic (id) ON DELETE CASCADE, +- author BIGINT NOT NULL REFERENCES main.account (id), +- reply_to BIGINT NULL REFERENCES main.f_post (id) ON DELETE SET NULL, +- moment INT NOT NULL DEFAULT INT4(EXTRACT(EPOCH FROM NOW())), +- title VARCHAR(100) NOT NULL, +- contents TEXT NOT NULL, +- enable_code BOOLEAN NOT NULL DEFAULT TRUE, +- enable_smileys BOOLEAN NOT NULL DEFAULT TRUE, +- edited INT NULL, +- edited_by BIGINT NULL REFERENCES main.account (id), +- deleted INT NULL +-); +- +-CREATE INDEX post_forum ON main.f_post (forum); +-CREATE INDEX post_topic ON main.f_post (topic); +-CREATE INDEX post_author ON main.f_post (author); +-CREATE INDEX post_editor ON main.f_post (edited_by); +-CREATE INDEX post_reply ON main.f_post (reply_to); +- +-ALTER TABLE main.f_forum ADD FOREIGN KEY (last_post) REFERENCES main.f_post (id) ON DELETE SET NULL; +-ALTER TABLE main.f_topic ADD FOREIGN KEY (first_post) REFERENCES main.f_post (id) ON DELETE CASCADE; +-ALTER TABLE main.f_topic ADD FOREIGN KEY (last_post) REFERENCES main.f_post (id) ON DELETE SET NULL; +- +-GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE main.f_post TO legacyworlds; +-GRANT SELECT,UPDATE ON main.f_post_id_seq TO legacyworlds; +- +- +--- +--- Read topics +--- +-CREATE TABLE main.f_read ( +- reader BIGINT NOT NULL REFERENCES main.account (id), +- topic BIGINT NOT NULL REFERENCES main.f_topic (id) ON DELETE CASCADE, +- PRIMARY KEY (reader, topic) +-); +- +-GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE main.f_read TO legacyworlds; +- +- +--- +--- Smileys and forum codes +--- +-CREATE TABLE main.f_smiley ( +- smiley VARCHAR(20) NOT NULL PRIMARY KEY, +- file VARCHAR(20) NOT NULL +-); +-CREATE TABLE main.f_code ( +- p_reg_exp VARCHAR(40) NOT NULL PRIMARY KEY, +- replacement VARCHAR(80) NOT NULL +-); +-GRANT SELECT ON main.f_smiley TO legacyworlds; +-GRANT SELECT ON main.f_code TO legacyworlds; +- +- +--- +--- Admins, mods, losers +--- Not everything is useful in the current version so meh. +--- +-CREATE TABLE main.f_admin ( +- "user" BIGINT NOT NULL REFERENCES main.account (id) PRIMARY KEY, +- category INT NULL REFERENCES main.f_category (id) ON DELETE CASCADE +-); +-GRANT SELECT,INSERT,UPDATE,DELETE ON main.f_admin TO legacyworlds; +- +-CREATE TABLE main.f_moderator ( +- "user" BIGINT NOT NULL REFERENCES main.account (id) PRIMARY KEY, +- forum INT NULL REFERENCES main.f_forum (id) ON DELETE CASCADE +-); +-GRANT SELECT,INSERT,UPDATE,DELETE ON main.f_moderator TO legacyworlds; +diff -Naur beta5//sql/14-main-forums.sql forums//sql/14-main-forums.sql +--- beta5//sql/14-main-forums.sql 1970-01-01 01:00:00.000000000 +0100 ++++ forums//sql/14-main-forums.sql 2011-02-05 10:10:01.774335002 +0100 +@@ -0,0 +1,227 @@ ++-- LegacyWorlds Beta 5 ++-- PostgreSQL database scripts ++-- ++-- 14-main-forums.sql ++-- ++-- Execute the generic forums installation script, ++-- then add tables for the general forums ++-- ++-- Copyright(C) 2004-2007, DeepClone Development ++-- -------------------------------------------------------- ++ ++ ++ ++-- Install the generic forums ++\i forums/FORUMS.sql ++ ++ ++-- ++-- Create category type for general forums ++-- ++SELECT forums.add_category_type( 'main/gforums', 'en', 'General forums' ); ++-- SELECT forums.add_category_type( 'main/gforums', 'fr', 'Forums généraux ' ); ++ ++ ++ ++-- ++-- The general forums' categories ++-- ++CREATE TABLE main.gf_category ( ++ category BIGINT PRIMARY KEY REFERENCES forums.category (id) ON DELETE CASCADE, ++ t_string VARCHAR(15) NULL, ++ t_is_game BOOLEAN NULL, ++ UNIQUE(t_string, t_is_game) ++); ++ ++GRANT SELECT,INSERT,DELETE ON main.gf_category TO legacyworlds; ++ ++ ++ ++-- ++-- The general forums' access list ++-- ++-- Access type: ++-- - MO: moderator- (and admin-) only access, not visible to most users ++-- - MP: moderator- (and admin-) only posting, standard users can't create new topics ++-- - UP: users can view the forum and create topics, but can't create polls; this is the default ++-- - UL: users can view the forum, create topics and create polls ++-- ++CREATE TABLE main.gf_forum ( ++ forum BIGINT PRIMARY KEY REFERENCES forums.t_forum (id) ON DELETE CASCADE, ++ access_type CHAR(2) NOT NULL DEFAULT 'UP' CHECK( access_type IN ('MO', 'MP', 'UP', 'UL') ) ++); ++ ++GRANT SELECT,INSERT,UPDATE,DELETE ON main.gf_forum TO legacyworlds; ++ ++-- Trigger function & definition for main.gf_forum ++CREATE OR REPLACE FUNCTION main.trgf_gf_forum_check () RETURNS TRIGGER AS $$ ++BEGIN ++ PERFORM g.category FROM main.gf_category g, forums.forum f ++ WHERE f.id = NEW.forum AND g.category = f.category; ++ IF NOT FOUND THEN ++ RAISE EXCEPTION 'Forum #% is not a general forum', NEW.forum; ++ END IF; ++ RETURN NEW; ++END; ++$$ LANGUAGE plpgsql; ++CREATE TRIGGER trg_gf_forum_check BEFORE INSERT OR UPDATE ON main.gf_forum ++ FOR EACH ROW EXECUTE PROCEDURE main.trgf_gf_forum_check(); ++ ++ ++-- ++-- Bans on the general forums ++-- ++CREATE TABLE main.gf_ban ( ++ account BIGINT PRIMARY KEY REFERENCES main.account (id) ON DELETE CASCADE, ++ until INT NOT NULL ++); ++ ++GRANT SELECT,INSERT,UPDATE,DELETE ON main.gf_ban TO legacyworlds; ++ ++ ++ ++-- ++-- General forums administrators ++-- ++-- List of people who can create, remove or modify general forums. ++-- An admin is either a general admin (all GF categories) or the admin for a specific category ++-- ++CREATE TABLE main.gf_admin ( ++ account BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, ++ category BIGINT NULL REFERENCES main.gf_category (category) ON DELETE CASCADE, ++ UNIQUE( account, category ) ++); ++ ++GRANT SELECT,INSERT,DELETE ON main.gf_admin TO legacyworlds; ++ ++-- Trigger function & definition that makes sure that you're either a general admin or a category-specific admin, ++-- not both. ++CREATE OR REPLACE FUNCTION main.trgf_gf_admin_check () RETURNS TRIGGER AS $$ ++BEGIN ++ IF NEW.category IS NULL THEN ++ PERFORM * FROM main.gf_admin WHERE account = NEW.account AND category IS NOT NULL; ++ IF FOUND THEN ++ RAISE EXCEPTION 'User #% is already a cat-specific admin', NEW.account; ++ END IF; ++ DELETE FROM main.gf_cat_moderator WHERE account = NEW.account; ++ DELETE FROM main.gf_forum_moderator WHERE account = NEW.account; ++ ELSE ++ PERFORM * FROM main.gf_category WHERE category = NEW.category; ++ IF NOT FOUND THEN ++ RAISE EXCEPTION 'Category #% is not a general category', NEW.category; ++ END IF; ++ PERFORM * FROM main.gf_admin WHERE account = NEW.account AND category IS NULL; ++ IF FOUND THEN ++ RAISE EXCEPTION 'User #% is already a general admin', NEW.account; ++ END IF; ++ DELETE FROM main.gf_cat_moderator WHERE account = NEW.account AND category = NEW.category; ++ DELETE FROM main.gf_forum_moderator WHERE account = NEW.account AND forum IN ( ++ SELECT id FROM forums.forum WHERE category = NEW.category ++ ); ++ END IF; ++ RETURN NEW; ++END; ++$$ LANGUAGE plpgsql; ++CREATE TRIGGER trg_gf_admin_check BEFORE INSERT ON main.gf_admin ++ FOR EACH ROW EXECUTE PROCEDURE main.trgf_gf_admin_check(); ++ ++ ++ ++ ++-- ++-- General forums moderators, by category ++-- ++-- List of people who can moderate whole categories of the general forums. ++-- These people should not already be listed in the admin list for the category. ++-- ++CREATE TABLE main.gf_cat_moderator ( ++ account BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, ++ category BIGINT NULL REFERENCES main.gf_category (category) ON DELETE CASCADE, ++ UNIQUE( account, category ) ++); ++ ++GRANT SELECT,INSERT,DELETE ON main.gf_cat_moderator TO legacyworlds; ++ ++-- Trigger function & definition that makes sure that you're either a general mod or a category-specific mod, ++-- and that you're not an admin. ++CREATE OR REPLACE FUNCTION main.trgf_gf_cmod_check () RETURNS TRIGGER AS $$ ++BEGIN ++ IF NEW.category IS NULL THEN ++ PERFORM * FROM main.gf_cat_moderator WHERE account = NEW.account AND category IS NOT NULL; ++ IF FOUND THEN ++ RAISE EXCEPTION 'User #% is already a cat-specific moderator', NEW.account; ++ END IF; ++ PERFORM * FROM main.gf_admin WHERE account = NEW.account AND category IS NULL; ++ IF FOUND THEN ++ RAISE EXCEPTION 'User #% is already a general administrator', NEW.account; ++ END IF; ++ DELETE FROM main.gf_forum_moderator WHERE account = NEW.account; ++ ELSE ++ PERFORM * FROM main.gf_category WHERE category = NEW.category; ++ IF NOT FOUND THEN ++ RAISE EXCEPTION 'Category #% is not a general category', NEW.category; ++ END IF; ++ PERFORM * FROM main.gf_cat_moderator WHERE account = NEW.account AND category IS NULL; ++ IF FOUND THEN ++ RAISE EXCEPTION 'User #% is already a general moderator', NEW.account; ++ END IF; ++ PERFORM * FROM main.gf_admin ++ WHERE account = NEW.account ++ AND (category = NEW.category OR category IS NULL); ++ IF FOUND THEN ++ RAISE EXCEPTION 'User #% is already an administrator for category', NEW.account; ++ END IF; ++ DELETE FROM main.gf_forum_moderator WHERE account = NEW.account AND forum IN ( ++ SELECT id FROM forums.forum WHERE category = NEW.category ++ ); ++ END IF; ++ RETURN NEW; ++END; ++$$ LANGUAGE plpgsql; ++CREATE TRIGGER trg_gf_cmod_check BEFORE INSERT ON main.gf_cat_moderator ++ FOR EACH ROW EXECUTE PROCEDURE main.trgf_gf_cmod_check(); ++ ++ ++ ++ ++-- ++-- General forums moderators, by forum ++-- ++-- List of people who can moderate specific forums. ++-- ++CREATE TABLE main.gf_forum_moderator ( ++ account BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, ++ forum BIGINT NOT NULL REFERENCES forums.t_forum (id) ON DELETE CASCADE, ++ UNIQUE( account, forum ) ++ -- FIXME: add constraint to make sure that if someone's a mod for the forum's category or an admin ++ -- for the category in question, that someone can't be added as a forum-specific moderator ++); ++ ++GRANT SELECT,INSERT,DELETE ON main.gf_forum_moderator TO legacyworlds; ++ ++-- Trigger function & definition that makes sure that you're not a forums' category mod or admin. ++CREATE OR REPLACE FUNCTION main.trgf_gf_fmod_check() RETURNS TRIGGER AS $$ ++DECLARE ++ cid BIGINT; ++BEGIN ++ SELECT INTO cid g.category FROM main.gf_category g, forums.forum f ++ WHERE f.id = NEW.forum AND g.category = f.category; ++ IF NOT FOUND THEN ++ RAISE EXCEPTION 'Forum #% is not a general forum', NEW.forum; ++ END IF; ++ ++ PERFORM * FROM main.gf_cat_moderator WHERE account = NEW.account AND (category IS NULL OR category = cid); ++ IF FOUND THEN ++ RAISE EXCEPTION 'User #% is already a moderator for forum #%', NEW.account, NEW.forum; ++ END IF; ++ ++ PERFORM * FROM main.gf_admin WHERE account = NEW.account AND (category IS NULL OR category = cid); ++ IF FOUND THEN ++ RAISE EXCEPTION 'User #% is already an administrator for forum #%', NEW.account, NEW.forum; ++ END IF; ++ ++ RETURN NEW; ++END; ++$$ LANGUAGE plpgsql; ++CREATE TRIGGER trg_gf_fmod_check BEFORE INSERT ON main.gf_forum_moderator ++ FOR EACH ROW EXECUTE PROCEDURE main.trgf_gf_fmod_check(); +diff -Naur beta5//sql/15-main-gf-functions.sql forums//sql/15-main-gf-functions.sql +--- beta5//sql/15-main-gf-functions.sql 1970-01-01 01:00:00.000000000 +0100 ++++ forums//sql/15-main-gf-functions.sql 2011-02-05 10:10:01.774335002 +0100 +@@ -0,0 +1,205 @@ ++-- LegacyWorlds Beta 5 ++-- PostgreSQL database scripts ++-- ++-- 15-main-gf-functions.sql ++-- ++-- Creates the general forums access functions ++-- ++-- Copyright(C) 2004-2007, DeepClone Development ++-- -------------------------------------------------------- ++ ++ ++ ++-- ++-- main.init_general_forums() ++-- ++-- This function initialises the general forums by creating ++-- a category for the main GF category. ++ ++CREATE OR REPLACE FUNCTION main.init_general_forums() RETURNS BIGINT AS $$ ++DECLARE ++ cid BIGINT; ++BEGIN ++ SELECT INTO cid * FROM main.gf_category WHERE t_string IS NULL; ++ IF FOUND THEN ++ RETURN cid; ++ END IF; ++ ++ SELECT INTO cid forums.make_category( 'main/gforums' ); ++ INSERT INTO main.gf_category (category, t_string, t_is_game ) ++ VALUES (cid, NULL, NULL); ++ ++ RETURN cid; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- main.init_version_forums( version ) ++-- ++-- This function initialises the general forums category ++-- for a specific version of LW ++ ++CREATE OR REPLACE FUNCTION main.init_version_forums( v TEXT ) RETURNS BIGINT AS $$ ++DECLARE ++ cid BIGINT; ++BEGIN ++ SELECT INTO cid * FROM main.gf_category WHERE t_string = v AND NOT t_is_game; ++ IF FOUND THEN ++ RETURN cid; ++ END IF; ++ ++ SELECT INTO cid forums.make_category( 'main/gforums' ); ++ INSERT INTO main.gf_category (category, t_string, t_is_game ) ++ VALUES (cid, v, FALSE); ++ ++ RETURN cid; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- main.init_game_forums( game ) ++-- ++-- This function initialises the general forums category ++-- for a specific LW game ++ ++CREATE OR REPLACE FUNCTION main.init_game_forums( g TEXT ) RETURNS BIGINT AS $$ ++DECLARE ++ cid BIGINT; ++BEGIN ++ SELECT INTO cid * FROM main.gf_category WHERE t_string = g AND t_is_game; ++ IF FOUND THEN ++ RETURN cid; ++ END IF; ++ ++ SELECT INTO cid forums.make_category( 'main/gforums' ); ++ INSERT INTO main.gf_category (category, t_string, t_is_game ) ++ VALUES (cid, g, TRUE); ++ ++ RETURN cid; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- main.get_gf_categories( version , game ) ++-- ++-- Returns the list of all available general forum categories for a version and game ++ ++CREATE OR REPLACE FUNCTION main.get_gf_categories( ver TEXT, game TEXT ) RETURNS SETOF BIGINT AS $$ ++ SELECT category FROM main.gf_category ++ WHERE t_string IS NULL ++ OR (t_string = $1 AND NOT t_is_game) ++ OR (t_string = $2 AND t_is_game) ++ ORDER BY category; ++$$ LANGUAGE SQL; ++ ++ ++-- ++-- main.get_gf_list( version , game ) ++-- ++-- Returns the list of all available general forums for a version and game ++ ++CREATE OR REPLACE FUNCTION main.get_gf_list( ver TEXT, game TEXT ) RETURNS SETOF BIGINT AS $$ ++ SELECT id FROM forums.t_forum ++ WHERE category IN ( SELECT * FROM main.get_gf_categories( $1, $2 ) ) ++ ORDER BY category, f_order; ++$$ LANGUAGE SQL; ++ ++ ++-- ++-- main.get_gforums_privs( user , forum ) ++-- ++-- Returns the set of privileges an user has over a specific general forum ++ ++CREATE OR REPLACE FUNCTION main.get_gforums_privs( aid BIGINT, fid BIGINT, OUT can_view BOOLEAN, OUT can_post BOOLEAN, OUT can_create BOOLEAN, OUT can_poll BOOLEAN, OUT is_mod BOOLEAN, OUT is_admin BOOLEAN) AS $$ ++DECLARE ++ cid BIGINT; ++ r TEXT; ++BEGIN ++ -- Get the category's ID ++ SELECT INTO cid category FROM forums.t_forum WHERE id = fid; ++ IF NOT FOUND THEN ++ is_admin := FALSE; ++ is_mod := FALSE; ++ can_view := FALSE; ++ can_post := FALSE; ++ can_create := FALSE; ++ can_poll := FALSE; ++ RETURN; ++ END IF; ++ ++ -- Check if this is a general category ++ PERFORM * FROM main.gf_category WHERE category = cid; ++ IF NOT FOUND THEN ++ RETURN; ++ END IF; ++ ++ -- Fetch category admin details ++ PERFORM * FROM main.gf_admin WHERE (category IS NULL OR category = cid) AND account = aid; ++ IF FOUND THEN ++ can_view := TRUE; ++ can_post := TRUE; ++ can_create := TRUE; ++ can_poll := TRUE; ++ is_mod := TRUE; ++ is_admin := TRUE; ++ RETURN; ++ END IF; ++ is_admin := FALSE; ++ ++ -- Fetch category mod details ++ PERFORM * FROM main.gf_cat_moderator WHERE (category IS NULL OR category = cid) AND account = aid; ++ IF FOUND THEN ++ can_view := TRUE; ++ can_post := TRUE; ++ can_create := TRUE; ++ can_poll := TRUE; ++ is_mod := TRUE; ++ RETURN; ++ END IF; ++ ++ -- Fetch forum mod details ++ PERFORM * FROM main.gf_forum_moderator WHERE forum = fid AND account = aid; ++ IF FOUND THEN ++ can_view := TRUE; ++ can_post := TRUE; ++ can_create := TRUE; ++ can_poll := TRUE; ++ is_mod := TRUE; ++ RETURN; ++ END IF; ++ is_mod := false; ++ ++ -- Check forum access ++ SELECT INTO r access_type FROM main.gf_forum WHERE forum = fid; ++ IF NOT FOUND THEN ++ r := 'UP'; ++ END IF; ++ ++ -- Check for mod-only forum ++ IF r = 'MO' THEN ++ can_view := FALSE; ++ can_post := FALSE; ++ can_create := FALSE; ++ can_poll := FALSE; ++ RETURN; ++ END IF; ++ can_view := TRUE; ++ ++ -- Check for bans ++ PERFORM * FROM main.gf_ban WHERE account = aid; ++ IF FOUND THEN ++ can_post := FALSE; ++ can_create := FALSE; ++ can_poll := FALSE; ++ RETURN; ++ END IF; ++ ++ -- Set can_post, can_create and can_poll depending on the access type ++ can_post := TRUE; ++ can_create := (r = 'UP' OR r = 'UL'); ++ can_poll := (r = 'UL'); ++END; ++$$ LANGUAGE plpgsql; +diff -Naur beta5//sql/15-main-uforums.sql forums//sql/15-main-uforums.sql +--- beta5//sql/15-main-uforums.sql 1970-01-01 01:00:00.000000000 +0100 ++++ forums//sql/15-main-uforums.sql 2011-02-05 10:10:01.774335002 +0100 +@@ -0,0 +1,332 @@ ++-- LegacyWorlds Beta 5 ++-- PostgreSQL database scripts ++-- ++-- 15-main-uforums.sql ++-- ++-- Add tables and functions to manage the user forums ++-- ++-- Copyright(C) 2004-2007, DeepClone Development ++-- -------------------------------------------------------- ++ ++ ++-- ++-- Access types for forums: ++-- - 'P': public ++-- - 'W': password ++-- - 'I': invite-only ++-- ++-- User access flag: ++-- - 'M': moderator ++-- - 'L': can start topics, post and create polls ++-- - 'T': can start topics and post ++-- - 'P': can post ++-- - 'R': can read ++ ++ ++-- ++-- Create category type ++-- ++SELECT forums.add_category_type( 'main/uforums', 'en', 'User forums' ); ++ ++ ++-- ++-- main.uf_get_access_mode( forum ) ++-- ++-- Returns the access mode for a specific user forum ++ ++CREATE OR REPLACE FUNCTION main.uf_get_access_mode( fid BIGINT ) RETURNS TEXT AS $$ ++DECLARE ++ cid BIGINT; ++ am TEXT; ++BEGIN ++ -- Check if the forum exists and is an user forum; if it is, get the access mode ++ SELECT INTO am access_mode FROM main.user_forum WHERE forum = fid; ++ IF NOT FOUND THEN ++ RETURN NULL; ++ END IF; ++ ++ IF am IS NULL THEN ++ -- Forum uses defaults, get its category ++ SELECT INTO cid category FROM forums.t_forum WHERE id = fid; ++ SELECT INTO am default_access FROM main.user_category; ++ END IF; ++ ++ RETURN am; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- main.uf_get_user_access( user, forum ) ++-- ++-- Returns the user access mode for a specific user and forum combination ++ ++CREATE OR REPLACE FUNCTION main.uf_get_user_access( aid BIGINT, fid BIGINT ) RETURNS TEXT AS $$ ++DECLARE ++ fua TEXT; ++ r RECORD; ++BEGIN ++ -- Read the category's data ++ SELECT INTO r c.account AS fo, c.user_access AS am FROM main.user_category c, forums.t_forum f ++ WHERE c.category = f.category AND f.id = fid; ++ ++ -- Try reading the subscription value ++ SELECT INTO fua access_mode FROM main.uf_subscription WHERE account = aid; ++ IF NOT FOUND THEN ++ -- No subscription - check if we're the owner ++ IF r.fo = aid THEN ++ RETURN 'A'; ++ END IF; ++ RETURN NULL; ++ END IF; ++ ++ -- We got a subscription value ++ IF fua IS NOT NULL THEN ++ RETURN fua; ++ END IF; ++ ++ -- Check if the forum exists and get its default user access ++ SELECT INTO fua user_access FROM main.user_forum WHERE forum = fid; ++ IF NOT FOUND THEN ++ RETURN NULL; ++ END IF; ++ ++ -- If we had a default user access mode, return it ++ IF fua IS NOT NULL THEN ++ RETURN fua; ++ END IF; ++ ++ -- Return the category's default access mode ++ RETURN r.am; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- main.uf_get_category( user ) ++-- ++-- Gets the ID of the user's forums category. If it doesn't exist, create it. ++ ++CREATE OR REPLACE FUNCTION main.uf_get_category( aid BIGINT ) RETURNS BIGINT AS $$ ++DECLARE ++ cid BIGINT; ++BEGIN ++ SELECT INTO cid category FROM main.user_category WHERE account = aid; ++ IF NOT FOUND THEN ++ cid := forums.make_category( 'main/uforums' ); ++ INSERT INTO main.user_category (category, account) VALUES (cid, aid); ++ END IF; ++ RETURN cid; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- main.uf_create_forum( user, forum_order, forum_title, forum_description, user_access, access_mode, password ) ++-- ++-- Creates an user forum. ++-- Return values: ++-- any positive value: identifier of the new forum ++-- -1: the user has 20 forums already ++-- -2: invalid access modes ++-- -3: a forum with the same name already exists ++-- -4: failure in the generic forums code ++ ++CREATE OR REPLACE FUNCTION main.uf_create_forum( aid BIGINT, fo INT, ttl TEXT, dsc TEXT, ua TEXT, am TEXT, pass TEXT) RETURNS BIGINT AS $$ ++DECLARE ++ cid BIGINT; ++ fid BIGINT; ++ n INT; ++ cam TEXT; ++BEGIN ++ -- Get the user's category and check if it's possible to create the user's new forum ++ cid := main.uf_get_category( aid ); ++ SELECT INTO n COUNT(*) FROM forums.t_forum WHERE category = cid AND deleted IS NULL; ++ IF n = 20 THEN ++ RETURN -1; ++ END IF; ++ ++ -- Check the access mode ++ SELECT INTO cam default_access FROM main.user_category WHERE category = cid; ++ IF (am = 'W' AND pass IS NULL) OR (am IS NULL AND cam = 'W' AND pass IS NULL) THEN ++ RETURN -2; ++ END IF; ++ ++ -- Check the forum's title ++ PERFORM * FROM forums.t_forum WHERE category = cid AND title = ttl; ++ IF FOUND THEN ++ RETURN -3; ++ END IF; ++ ++ -- Create the forum ++ fid := forums.make_forum( cid, fo, ttl, dsc ); ++ IF fid IS NULL THEN ++ RETURN -4; ++ END IF; ++ IF (am = 'W' OR (am IS NULL AND cam = 'W')) THEN ++ INSERT INTO main.user_forum (forum, access_mode, password, user_access) VALUES ( fid, am, pass, ua ); ++ ELSE ++ INSERT INTO main.user_forum (forum, access_mode, user_access) VALUES ( fid, am, ua ); ++ END IF; ++ ++ RETURN fid; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++ ++ ++-- ++-- Category / user mapping ++-- ++-- If the account gets disabled (QUIT, INAC), we need to mark the forums for deletion. ++-- If the account gets re-enabled (STD), we need to restore the forums. ++-- If the account gets kicked, we need to delete the forums immediately. ++ ++CREATE TABLE main.user_category ( ++ account BIGINT PRIMARY KEY REFERENCES main.account (id) ON DELETE CASCADE, ++ category BIGINT NOT NULL UNIQUE REFERENCES forums.category (id) ON DELETE CASCADE, ++ default_access CHAR(1) NOT NULL DEFAULT 'I' CHECK( default_access IN ('P', 'I') ), ++ user_access CHAR(1) NOT NULL DEFAULT 'T' CHECK( user_access IN ('M', 'L', 'T', 'P', 'R') ) ++); ++ ++GRANT SELECT,INSERT,UPDATE,DELETE ON main.user_category TO legacyworlds; ++ ++-- Trigger function & definition for the account table that allows handling the users' categories. ++CREATE OR REPLACE FUNCTION main.trgf_account_user_forums() RETURNS TRIGGER AS $$ ++DECLARE ++ cid BIGINT; ++BEGIN ++ -- No status change, proceed ++ IF NEW.status = OLD.status THEN ++ RETURN NEW; ++ END IF; ++ ++ -- Get the user's category ++ SELECT INTO cid category FROM main.user_category WHERE account = NEW.id; ++ IF NOT FOUND THEN ++ RETURN NEW; ++ END IF; ++ ++ -- Check the new status ++ IF NEW.status = 'INAC' OR NEW.status = 'QUIT' THEN ++ PERFORM forums.delete_forums( cid, NEW.id ); ++ ELSIF NEW.status = 'STD' THEN ++ PERFORM forums.restore_forums( cid ); ++ ELSIF NEW.status = 'KICKED' THEN ++ DELETE FROM forums.category WHERE id = cid; ++ END IF; ++ ++ RETURN NEW; ++END; ++$$ LANGUAGE plpgsql; ++CREATE TRIGGER trg_account_user_forums BEFORE UPDATE ON main.account ++ FOR EACH ROW EXECUTE PROCEDURE main.trgf_account_user_forums(); ++ ++ ++ ++-- ++-- User forums ++-- ++ ++CREATE TABLE main.user_forum ( ++ forum BIGINT PRIMARY KEY REFERENCES forums.t_forum (id) ON DELETE CASCADE, ++ access_mode CHAR(1) DEFAULT NULL CHECK( access_mode IS NULL ++ OR access_mode IN ('P', 'W', 'I') ), ++ password VARCHAR(64) DEFAULT NULL, ++ user_access CHAR(1) DEFAULT NULL CHECK( user_access IS NULL ++ OR user_access IN ('M', 'L', 'T', 'P', 'R') ), ++ CHECK( (main.uf_get_access_mode(forum) = 'W' AND password IS NOT NULL) ++ OR (main.uf_get_access_mode(forum) <> 'W' AND password IS NULL) ) ++); ++ ++GRANT SELECT,INSERT,UPDATE,DELETE ON main.user_forum TO legacyworlds; ++ ++ ++-- ++-- Subscriptions ++-- ++-- An user can't subscribe to one of his own forums. ++-- ++ ++CREATE TABLE main.uf_subscription ( ++ forum BIGINT NOT NULL REFERENCES main.user_forum (forum) ON DELETE CASCADE, ++ account BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, ++ access_mode CHAR(1) DEFAULT NULL CHECK( access_mode IS NULL ++ OR access_mode IN ('M', 'L', 'T', 'P', 'R') ), ++ PRIMARY KEY( forum, account ) ++); ++ ++GRANT SELECT,INSERT,UPDATE,DELETE ON main.uf_subscription TO legacyworlds; ++ ++-- Trigger function & definition ++CREATE OR REPLACE FUNCTION main.trgf_uf_subscription_check() RETURNS TRIGGER AS $$ ++DECLARE ++ fo BIGINT; ++BEGIN ++ IF TG_OP = 'INSERT' THEN ++ SELECT INTO fo account FROM main.user_category c, forums.t_forum f ++ WHERE c.category = f.category AND f.id = NEW.forum; ++ IF FOUND AND fo = NEW.account THEN ++ RETURN NULL; ++ END IF; ++ ELSIF TG_OP = 'UPDATE' THEN ++ NEW.forum = OLD.forum; ++ NEW.account = OLD.account; ++ END IF; ++ RETURN NEW; ++END; ++$$ LANGUAGE plpgsql; ++CREATE TRIGGER trg_uf_subscription_check BEFORE INSERT OR UPDATE ON main.uf_subscription ++ FOR EACH ROW EXECUTE PROCEDURE main.trgf_uf_subscription_check(); ++ ++ ++-- ++-- Invites ++-- ++-- An user can only be invited to an invite-only user forum. ++-- An user can be invited if he's not already a subscriber to the forum. ++-- An user can't be invited to one of his own forums. ++-- ++ ++CREATE TABLE main.uf_invite ( ++ forum BIGINT NOT NULL REFERENCES main.user_forum (forum) ON DELETE CASCADE, ++ account BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, ++ sent_on INT NOT NULL DEFAULT UNIX_TIMESTAMP( NOW() ), ++ PRIMARY KEY( forum, account ) ++); ++ ++GRANT SELECT,INSERT,DELETE ON main.uf_invite TO legacyworlds; ++ ++-- Trigger function & definition ++CREATE OR REPLACE FUNCTION main.trgf_uf_invite_check() RETURNS TRIGGER AS $$ ++DECLARE ++ m TEXT; ++ fo BIGINT; ++BEGIN ++ -- Check the forum's access mode ++ m := main.uf_get_access_mode( NEW.forum ); ++ IF m IS NULL OR m <> 'I' THEN ++ RETURN NULL; ++ END IF; ++ -- Check for a subscription by the user ++ PERFORM * FROM main.uf_subscription WHERE forum = NEW.forum AND account = NEW.account; ++ IF FOUND THEN ++ RETURN NULL; ++ END IF; ++ -- Check for an existing invite for the user ++ PERFORM * FROM main.uf_invite WHERE forum = NEW.forum AND account = NEW.account; ++ IF FOUND THEN ++ RETURN NULL; ++ END IF; ++ -- Check if the user is actually the forum's owner ++ SELECT INTO fo account FROM main.user_category c, forums.t_forum f ++ WHERE c.category = f.category AND f.id = NEW.forum; ++ IF FOUND AND fo = NEW.account THEN ++ RETURN NULL; ++ END IF; ++ RETURN NEW; ++END; ++$$ LANGUAGE plpgsql; ++CREATE TRIGGER trg_uf_invite_check BEFORE INSERT ON main.uf_invite ++ FOR EACH ROW EXECUTE PROCEDURE main.trgf_uf_invite_check(); +diff -Naur beta5//sql/18-main-functions.sql forums//sql/18-main-functions.sql +--- beta5//sql/18-main-functions.sql 2011-02-05 10:09:56.244335002 +0100 ++++ forums//sql/18-main-functions.sql 2011-02-05 10:10:01.774335002 +0100 +@@ -27,8 +27,13 @@ + -- + -- Function that registers a game + -- +-CREATE OR REPLACE FUNCTION main.register_game (version TEXT, game_name TEXT) RETURNS VOID AS $$ ++CREATE OR REPLACE FUNCTION main.register_game (ver TEXT, gn TEXT) RETURNS VOID AS $$ ++BEGIN + INSERT INTO main.ranking_game (ranking, game) +- SELECT id, $2 FROM main.ranking_def +- WHERE version = $1; +-$$ LANGUAGE SQL; ++ SELECT id, gn FROM main.ranking_def ++ WHERE version = ver; ++ PERFORM main.init_general_forums(); ++ PERFORM main.init_version_forums( ver ); ++ PERFORM main.init_game_forums( gn ); ++END; ++$$ LANGUAGE plpgsql; +diff -Naur beta5//sql/19-main-values.sql forums//sql/19-main-values.sql +--- beta5//sql/19-main-values.sql 2011-02-05 10:09:56.244335002 +0100 ++++ forums//sql/19-main-values.sql 2011-03-12 15:05:03.511300054 +0100 +@@ -77,49 +77,6 @@ + \. + + +-COPY main.f_smiley FROM STDIN; +-:-?\\) smile +-[:;]-?p razz +-:-?D lol +-[:;]-> biggrin +-;-?\\) wink +-;-?D mrgreen +-:-?\\( sad +-:evil: evil +-:smile: smile +-:happy: smile +-:wink: wink +-:sad: sad +-:unhappy: sad +-:'\\( cry +-:cry: cry +-:crying: cry +-:grin: biggrin +-:lol: lol +-:tongue: razz +-:rofl: mrgreen +-[:;]-?\\| neutral +-:neutral: neutral +-\. +- +- +-COPY main.f_code FROM STDIN; +-\\[b\\](.*?)\\[\\/b\\] $1 +-\\[u\\](.*?)\\[\\/u\\] $1 +-\\[i\\](.*?)\\[\\/i\\] $1 +-\\[sep(arator)?\\]


+-\\[item\\](.*?)\\[\\/item\\]
  • $1
+-\\[quote\\](.*?)\\[\\/quote\\]
$1
+-\\[quote=([^\\]]+)\\](.*?)\\[\\/quote\\]
$1 said:
$2
+-\\[link=(http[^\\]]+)\\](.+?)\\[\\/link\\] $2 +-\\[code\\](.*?)\\[\\/code\\]
$1
+-<\\/li><\\/ul>\\s*(\\s*)*