commit 33f85866982998931d9433f71692f6f12d343fc9 Author: Emmanuel Benoît Date: Sun Jan 10 11:01:49 2016 +0100 Added full source code 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 0000000..1ec4ed4 Binary files /dev/null and b/misc/Legacy Worlds.wdgt/Default.png differ diff --git a/misc/Legacy Worlds.wdgt/Icon.png b/misc/Legacy Worlds.wdgt/Icon.png new file mode 100755 index 0000000..533d09d Binary files /dev/null and b/misc/Legacy Worlds.wdgt/Icon.png differ diff --git a/misc/Legacy Worlds.wdgt/Info.plist b/misc/Legacy Worlds.wdgt/Info.plist new file mode 100755 index 0000000..2397481 --- /dev/null +++ b/misc/Legacy Worlds.wdgt/Info.plist @@ -0,0 +1,16 @@ + + + + + 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 0000000..1ec4ed4 Binary files /dev/null and b/misc/Legacy Worlds.wdgt/images/blue.png differ diff --git a/misc/Legacy Worlds.wdgt/lib/Base.js b/misc/Legacy Worlds.wdgt/lib/Base.js new file mode 100644 index 0000000..a713dfd --- /dev/null +++ b/misc/Legacy Worlds.wdgt/lib/Base.js @@ -0,0 +1,189 @@ +/** This function defines the Base class, from which all other classes are inherited. + * The Base class defines a somewhat sound framework for inheritance. + */ +Base = function () { + if (arguments.length) { + if (this == window) { + Base.prototype.extend.call(arguments[0], arguments.callee.prototype); + } else { + this.extend(arguments[0]); + } + } +}; + + +/** This class property lists the functions to be called when the page + * containing the code is first loaded. + */ +Base.initialisers = new Array(); + +/** This class property lists the functions to be called when the page + * containing the code is unloaded. + */ +Base.destructors = new Array(); + + +/** This class method adds a function to the list of initialisers. + */ +Base.registerInitialiser = function (initialiser) { + Base.initialisers.push(initialiser); +}; + +/** This class method adds a function to the list of destructors. + */ +Base.registerDestructor = function (destructor) { + Base.destructors.push(destructor); +}; + + +Base.prototype = { + /** The extend() method adds a set of methods and properties to an existing object. + * @param addition an object containing the methods and properties to add. + * @returns the extended object. + */ + extend: function (addition) { + var _o = Base.prototype.overload; + + var _p = { toSource: null }; + var _l = ['toString', 'valueOf']; + if (Base._inherits) { + _l.push('constructor'); + } + + for (var i in _l) { + var _n = _l[i]; + if (addition[_n] != _p[_n]) { + _o.call(this, _n, addition[_n]); + _p[_n] = addition[_n]; + } + } + + for (var i in addition) { + if (_p[i]) { + continue; + } + if (addition[i] instanceof Function) { + _o.call(this, i, addition[i]); + } else { + this[i] = addition[i]; + } + } + + return this; + }, + + /** The overload() method overloads a single method in an object; it + * creates a new function that sets this.base to the old value then + * calls the new code. + * @param name the name of the method to be overloaded. + * @param value the Function object for the new method. + * @returns the value for the overloaded method. + */ + overload: function (name, value) { + if (value == this[name] || name == 'base') { + return; + } + + var _v = value, _ov = this[name]; + value = function () { + var _b = this.base; + this.base = _ov; + var _r = _v.apply(this, arguments); + this.base = _b; + return _r; + }; + value.valueOf = function() { + return _v; + }; + value.toString = function () { + return String(_v); + }; + + return this[name] = value; + } +}; + + +/** This class property stores the list of namespaces to preserve when onunload is called. */ +Base._preserve = new Array(); + + +/** This static method is used to define class inheritance. + * @param members an object containing the new methods and properties to be added or overloaded. + * @param cMembers an object containing the methods and properties to be added as static members. + * @param name a string indicating the namespace this object is supposed to define; it is used + * to preserve namespaces from being deleted before the onunload() handler. + * @return the object corresponding to the newly created class. + */ +Base.inherits = function (members, cMembers, name) { + if (!members) { + members = { }; + } + + var _e = Base.prototype.extend; + var _b = this; + + Base._inherits = true; + var _p = new _b; + _e.call(_p, members); + delete Base._inherits; + + var _c = _p.constructor; + _p.constructor = this; + + var _cl = function() { + this.base = _b; + if (!Base._inherits) { + _c.apply(this, arguments); + } + this.constructor = _cl; + delete this.base; + }; + _cl.inherits = this.inherits; + _cl.toString = function() { + return String(_c); + }; + _cl.prototype = _p; + + if (!cMembers) { + cMembers = { }; + } + _e.call(_cl, cMembers); + + if (cMembers["onLoad"] instanceof Function) { + Base.registerInitialiser(_cl["onLoad"]); + } + if (cMembers["onUnload"] instanceof Function) { + Base.registerDestructor(_cl["onUnload"]); + } + + if (name && name != "") { + Base._preserve[name] = _cl; + } + + return _cl; +}; + + +/** Set the page's onload() handler. */ +window.onload = function () { + var _b = Base; + for (var i in _b.initialisers) { + if (typeof _b.initialisers[i] == 'undefined') { + continue; + } + _b.initialisers[i](); + } + + /** Set the page's onunload() handler. */ + window.onunload = function () { + Base = _b; + for (var i in _b._preserve) { + eval(i + " = _b._preserve[i]"); + } + + for (var i = _b.destructors.length - 1; i >= 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>=<:88673=$>?=;9741*'# 9&0 ++6!$'3,812233458@,>#09FEEDCCBA@ 19J%tRB(B (H@SX .^ 9I]!9 <a5Ej0B$H'Hd"RFtԘŊ!8dHd/.# ++0XсF$XE!Ĉ&N@" ++MkJLeD; +\ 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 szzbKGD pHYs  tIME  0)No޸IDATXk$EU]fr]"ěG,( ^]y%EmDZ`~B)T4M)(`&I)% 6 $1>Pͫ7o38FJֺ|џcٶ_nܸٶ=e 0ΕHt7,ӓ{10իi-IB@#ܧ0FK'RJֺ\__$β,Ak8Zc3s`ss(nGQD$y_vfu]¶mc6Bs^J ;3^]YYy=cvvvH4r]n} Wj[>JY`@e0$28yh4b4M֭[xo9<`vww1Ơ&"jZ6ߟhbV_JqB| 6Rpq~0<`۶^[< ?jg}ͶIENDB` +\ 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+56/804:45>7?=8>;B<=>EABHBCDLEGFHIOKLMOPRUUV}XXWXYZ҃Ն]^սu^`Շ`n׍؈dגgؚeחeםעlڤjچبۧحkزlnopڸ݂u߽q܁x݄t߇݂yxyy߅||~ߕߘߝߠߥߨ! ++,  H*\ȰÇ#JxWIT+^n5T(L*H+񪵪Ӥ=g0B%<"N#BdAdZXJGnРC5z(cӪ]˶-AmA#4hΚ1S 1_^ZO+ S`ʤ)R2Xqj!D|HH )Ll0@1|SǔȡǏ F.i┊Pd ,Za#"MQ@h=$Vg͖\%+KVHF@g,3'"9h0q!lQ ++(T@t CQ@a`RT\Ak(܉( ; +\ 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 szzbKGD pHYs  tIME  0*fIDATXkU?w~lv7شm4mQ}4hѪG+jAD`)>E}C}ZXmKjִI!M7;;sgwM_TȁÝ9̜mؿlSPM1aM/ý$#_QSXp~[ ̓g1_aŮcv+Wz_zN,qqC5l HDJ@$ R%E`0{ ^*8&Ȉ[r]E HcI#Ch.lAAj ++04p{&Mfx?@VhR'Q?2V ++=}6cF8-O#Wt6T薜)%'?==i >nmX' !1IrJ}:OW<Nalps`v3#v|",bC AkZ1 nng~6wR_@ "!*MnT <Eh,x%W@s ++!\8ӄ$Tj6\@ӧX=`1]4sz2h'֍$":>jjUAwW ++Fit!6KC ̱uzSK/*BGkGX^(_l4/f:Pdu4)I-7cg۹^NEfuˆ|O*QΰA[n~,;'k@}`8|* $A8RM1LmQr{[@0 0l,00-rfw/ y|;q_G*ye))CMO^0ԭ_-Lh(U!4EDS/u;jd:q!PYoШb;eFu|x{[PQOH=YjW ++T6z{Z~(7rC;d.fpsnD'aiw6v(mb-سց;Z@ .IENDB` +\ 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` ~`|ca{bhj ljkmlnmno opq rqtsuvvwwxy z{||$}~'(!)"*#+$,-..5/012233445<6789:ABCDEFGHIJKQWRXSTUVWX_`abh«cìd¬jĭeíkŮfįlŰmƱnDzoɳp̷{лѼӿ‹ÌčŔȖɗʘ˙͛̚͡΢ϣХҦԮ! ++,  H*\ȰCY"J!,W8 фh1K@9rڨS;i;yF ++t#C816e"gE- KpCGQYhKR4bDO?w^'N4%@ ;j";EBʔgQ@MA?~ pL) ++e ++*R<=) > K4J ++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*\Ç  bH"D#bA@TBe(O0QRGGG118B /j4IXhWĉ'O0D,Zd ++&iȲ' HpA$֜ ++ƐOA[Q 0@X )H\ ++0~` 0 $`c 6Tj#zF TXAB"4VLaĂ`!A `tA_WP1eAZ!DY@)le8\Uax<3``AZtE}t"R c TЁ_ՅM&4"RH nаbhhYPU8 4=Fzq#$R0`mp+*bW2oGeDC<|HqD!a!uop!j QiR4]=G4lG; +\ 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}df~efi ljmknno p ++q qrsrttuuvwwxyy z{||$}~,},~&'!)"*#+$,--./001122933:44;55<66=77>8?9@@:AGBCDEFGHIJPKQLRSTUVWY_`fab«cìd¬jįlŰmƱnDzoʴq˵r̶s̷{ι}Ϻ~čŔ̠̚͡ϣХҦӧԮ! ++,  H*\Ç r)c 1d 1pd9p±#4$D =ziFL˅WT$)RDuH(:mҀJ PfTƂsRN 1e(RڼX𠮍B Ug>JEԞ>ȱ'd9X\ifcƒ(t͓*"VXaA@! HdP=ybPϭ[עǀ6$)Q@zPzx]x ++Bt @}A;#a3 lPB LJ)4E@[D"0(2q@P,2 ++)FyA\}c ++B@ ` - M 2<[T((A2WB # I@a 0qƊYA(PX: U_@ApB-0>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~! ++,  H*\PÇ&|H2cĄQ"A)T޼qf41(Q ++9rqC3e2^;v$Ȗ5hR³=zC VpaE1[R?}RCꕌ*b?Z$HP@xhJi̘0yJ1DPB$pqXĈ&t,KRN0D(aʝ:YG _E (RLsL6i.V$IHaPD,c4ez7D*UiC /&ʓ0l$[$T |p 1 kA ?Todbx8 1PcA N@%p @ =70aD0%I$ " ++(pC:ÖD!lG&Z\ 50 2p<FhyI'\Y)<&;Ipιg* auıX"ʤ ++5cZ&Y2fCI~d*J$`F"Hzp (\X ++D4F# bmZzlID<"bRp ++&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),,? 4S jxL9%,?>n<:Cퟀ,MW-UX.Xf% :&V HǘxDT HjxfAD 6 $쑢)[n`;ž1rEXᗻE͗tClt]{׌Hn zZGqojIENDB` +\ 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 HjfYBGidWAŏ'PSOK(Đ ++-ejd_- Jΐ *agb],ۏA^f]+ ++ pP q=[AD#B߼\xQ̰ #"8ე /6PE7$"$:F4m?EAo SPrܡcB*4P`挖!fRXFP^$`dL*3>2 ++@8p`")A+V 8p˘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 szzbKGD pHYs  tIME  s( IDATXOk$UuuWuPiB4 H!!,EA0 rE ѕ&n Ջzﺘ&L'a\n{^!jzT*"1o܏^{UwK 0c9kEQifK:sGD0|\^^lR^V?oW(_Ͽ;;;lllb|&"\knIJU( ++)k8PGQoc""RXk  Uu=eYL&XZV5"uqqި^J "F=KKKooo 28n1O}sz4M"2u{Q`0*Z,( ++ʲ,& f ++we7ywvvv}:[{AA|/"ov:<-sDi ߆aBX5$I*2o۵Zfkk-}VVVT*HcN}F %쨛M CD&j: ^dVyVD&c?cVwQVyXq]@(`ceI$ Gq`0 MqLTިi | ++<>s<ϛHk-m9)~k-nNCY5U}2 1*sss*eYrR14MI7৻;?|rIENDB` +\ 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>>,,  3XWVUUSSMMKKDDAA<841-7vuupsptq޼ 7kά! C9ܩsNEXoN8nAaƓQXG6rƜ&F-ᄔF6mF 5>E9N7rdӰ!K3# 9hֈb֬iR3f|!q5ĭ][٥i׶}wv+^s3eΜ!3&kxPiҠ1C -t r6[f 0ZFb9oԴL7͚ڦO&|ܴA!2fƈseC% q) jC*=}#`(KY`V ++TPP8DHaDC;Åꀁ!h 6$"`@ Ɍ4h8H ; +\ 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@vAwAxBzC{D|D}E~EFGGHHIIJJKLMNOOPPQRTUVWWXXYYZ[[_ñaɶdнhijllmpqqrsttuuvvwwxxyyzz{{||}}~! ++, -;A@=851+*'&%" /QSOJHEAB63.)'$" 5QMMHFC<<** 1VUTSSQQKKIIBB??:62/+5tssnqnroڴf6iʤs C7ԙSNEX/Μ7l1aƓQ85p”&F-݄F6m̹4>EGΜ6pTp!K3{8f҈b֬Y 2dtq5ĭ][٥i׶}wt+^s-2cʔKkxgΘ!#F,t 25;&/XBRr8mд<LڦO&|ج11d€ReC% uq)E jC(=m#]^({eKW`V RHN0FAB9Å`!` 2Ѐ$P bɌ4h8H ; +\ 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 2htre ++#D~QC ++#8?9Cs?t@vAwAxBzC{D|D}E~EFGGHHIIJJKLMMNOOPPQRTTUVWWXXYYZZ[[]_ñaɶdнhijllmmnopqqrsttuuvvwwxxyyzz{{||}}~! ++,  H*\ȰÇ#*d'I 2\HÇ 4X 2htre ++#D~QC ++#>>???CCCDDDGGGIIIJJJKKKLLLNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}! ++, .962,+('&#  ++0RTPKIFBC74/*(%# 6RNNIGD==++ 2WVUTTRRLLJJCC@@;730,6uttoroq6oܨ1f /% c:)R>9tഉS愡SnTI$8oc"ʛ1|32NhL @E0٤Y(ʙCM:2<$a(TECi&5hp-k8qؼLL2^Ftj',eϦ]K-Էqֽא:#KV2f̌FK7U|StV4hΔ3Zl)nQ7Z%#˗," `IgN7i^F ֮aR>~m؜Y̘2b|1ų!u,fݻ嵡ɾ&/}E\GrS<M(FQ?9a9\`d0 8P (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@vAwAxBzC{D|D}E~EFGGHHIIJJKLMNOOPPQRTUVWWXXYYZ[[_ñaɶdнhijllmpqqrsttuuvvwwxxyyzz{{||}}~! ++, .962,+('&#  ++0RTPKIFBC74/*(%# 6RNNIGD==++ 2WVUTTRRLLJJCC@@;730,6uttoroq6oܨ1f /% c:)R>9tഉS愡SnTI$8oc"ʛ1|32NhL @E0٤Y(ʙCM:2<$a(TECi&5hp-k8qؼLL2^Ftj',eϦ]K-Էqֽא:#KV2f̌FK7U|StV4hΔ3Zl)nQ7Z%#˗," `IgN7i^F ֮aR>~m؜Y̘2b|1ų!u,fݻ嵡ɾ&/}E\GrS<M(FQ?9a9\`d0 8P (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==++ 2WVUTTRRLLJJCC@@;730,6uttoroq6oܨ1f /% c:)R>9tഉS愡SnTI$8oc"ʛ1|32NhQ aIF PdC=JM:2<$a(FԢi&5hp-uh}EټLL2^Ft ++ ,eϦ]K-ηtέ{7!u"K,X3d̘#o ++jQ4ϔ3Zl)'fKV3'4/Efwkװ o6lάQxfL1aِl:e]ax{P ++~U`__x͇h0ODDJ qE18?9Cs?t@vAwAxBzC{D|D}E~EFGGHHIIJJKLMNOOPPQRTUVWWXXYYZ[[_ñaɶdнhijllmpqqrsttuuvvwwxxyyzz{{||}}~! ++, .962,+('&#  ++0RTPKIFBC74/*(%# 6RNNIGD==++ 2WVUTTRRLLJJCC@@;730,6uttoroq6oܨ1f /% c:)R>9tഉS愡SnTI$8oc"ʛ1|32NhQ aIF PdC=JM:2<$a(FԢi&5hp-uh}EټLL2^Ft ++ ,eϦ]K-ηtέ{7!u"K,X3d̘#o ++jQ4ϔ3Zl)'fKV3'4/Efwkװ o6lάQxfL1aِl:e]ax{P ++~U`__x͇h0ODDJ qE1>>???CCCDDDGGGIIIJJJKKKLLLNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}! ++, .962,+('&#  ++0RTPKIFBC74/*(%# 6RNNIGD==++ 2WVUTTRRLLJJCC@@;730,6uttoroq6oܨ1f /% c:)R>9tഉS愡SnTI$8oc"ʛ1|32Nh"ъDaIF PdC!*gNR:2<$a(FTB]猚^zTq$ML2^FtV,fѪ%VۤrҵWo!u"KCt_R6Dɘ13F -! I9웱k9Sf̘/ZjqbnRKF̗/YDZΜ8nҼDcޯaB#}ڰ9F1eĄb hC)Hw;eKlC)!}_t]pQ_}XV SLDO4GQDC:Ç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@vAwAxBzC{D|D}E~EFGGHHIIJJKLMNOOPPQRTUVWWXXYYZ[[_ñaɶdнhijllmpqqrsttuuvvwwxxyyzz{{||}}~! ++, .962,+('&#  ++0RTPKIFBC74/*(%# 6RNNIGD==++ 2WVUTTRRLLJJCC@@;730,6uttoroq6oܨ1f /% c:)R>9tഉS愡SnTI$8oc"ʛ1|32Nh"ъDaIF PdC!*gNR:2<$a(FTB]猚^zTq$ML2^FtV,fѪ%VۤrҵWo!u"KCt_R6Dɘ13F -! I9웱k9Sf̘/ZjqbnRKF̗/YDZΜ8nҼDcޯaB#}ڰ9F1eĄb hC)Hw;eKlC)!}_t]pQ_}XV SLDO4GQDC:Ç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*\ȰÇ#*aI$CA#,R 1 2P/`\"ɓ >r̈ႅ:hАB˂@hRe ++#GpҠ !:̠<Ȍ C /^`bɓ%K顣n  $>}=}ࡃ'6. ++!DHA)S^@3SoV-?}"ۚ13}Y*;yG:8#y0:43g4ug3`)GOqs GdG|G}G F! "X  4(tg` @_|4ALQq&dmzm(ma ++ AxlM4f|zԑbu1oA9$e yXopL$_g4؆mNZ[tE8?9Cs?t@vAwAxByBzC{D|D}E~EFGGHHIIJJKLMMNNOOPPQRTUVWWXXYYZ[[_ñaɶdλgнhijllmopqqrsttuuvvwwxxyyzz{{||}}~! ++,  H*\ȰÇ#*aI$CA#,R 1 2P/`\"ɓ >r̈ႅ:hАB˂@hRe ++#GpҠ !:̠<Ȍ C /^`bɓ%K顣n  $>}=}ࡃ'6. ++!DHA)S^@3SoV-?}"ۚ13}Y*;yG:8#y0:43g4ug3`)GOqs GdG|G}G F! "X  4(tg` @_|4ALQq&dmzm(ma ++ AxlM4f|zԑbu1oA9$e yXopL$_g4؆mNZ[tE>>???CCCDDDFFFGGGIIIJJJKKKLLLNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}! ++,  H*\ȰÇ#*a $CA#,R 1 2P._X >r̈ႅ:hАB˂@dBEʓ#GpҠ !:̠<ĄC˗.]\Rʼn%J顣n  ++>}ٳo<|ܙsN5. ++4( A)S?#SoVt ?|y"ۚ13ڏ}V$;`?"H6- : @:w _pZ@k;v!Gk p@`z|dnp@XA7b(fhq1oa ++$޶We@uIpdmAzQlA4 ~akuXVyd͠_{Gtpḻƍ-InHWqW ͉A#kcA0x\hESH6P8F1D: [ P`DVk"; +\ 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@vAwAxByBzC{D|D}E~EFGGHHIIJJKLMNNOOPPQRTUVWWXXYYZ[[_ñaɶdнhijkllmopqqrsttuuvvwwxxyyzz{{||}}~! ++,  H*\ȰÇ#*a $CA#,R 1 2P._X >r̈ႅ:hАB˂@dBEʓ#GpҠ !:̠<ĄC˗.]\Rʼn%J顣n  ++>}ٳo<|ܙsN5. ++4( A)S?#SoVt ?|y"ۚ13ڏ}V$;`?"H6- : @:w _pZ@k;v!Gk p@`z|dnp@XA7b(fhq1oa ++$޶We@uIpdmAzQlA4 ~akuXVyd͠_{Gtpḻƍ-InHWqW ͉A#kcA0x\hESH6P8F1D: [ P`DVk"; +\ 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*\ȰÇ#*ԑ䉓%Fq /X8a 4T`0bdB "AxؠE ++!>pB˂CprJ%Jp޸sE ++#>?ʐCV0`h ++&Mң.4O ?}=~䩓GΝ61 )ThP!B)S@DgA!SoV= @~##ۚ13F`- 6=y# %|l=aq[ z ++ F|A%R ++ xAm0nHA!|yԷmr@Fx`@Ȋ, AQqF )޶01gr ++mtQ$snA76 kwxGZ*d٠_{v衘rnƎ=In#[ ݩmcA3F_t;VTAR@DIaj@ k$@ P`Df"; +\ 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@vAwAxByBzC{D|D}E~EFGGHHIIJJKLMNOOPPQQRTUVWWXXYYZ[[_ñaɶdнhijllmopqqrsttuuvvwwxxyyzz{{||}}~! ++,  H*\ȰÇ#*ԑ䉓%Fq /X8a 4T`0bdB "AxؠE ++!>pB˂CprJ%Jp޸sE ++#>?ʐCV0`h ++&Mң.4O ?}=~䩓GΝ61 )ThP!B)S@DgA!SoV= @~##ۚ13F`- 6=y# %|l=aq[ z ++ F|A%R ++ xAm0nHA!|yԷmr@Fx`@Ȋ, AQqF )޶01gr ++mtQ$snA76 kwxGZ*d٠_{v衘rnƎ=In#[ ݩmcA3F_t;VTAR@DIaj@ k$@ P`Df"; +\ 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 $DQCF -T(A 0P._X!?tА :hАB˂AdBE$Hp֨3 !:Ġ=ĄC˗.]\Rʼn%K񱣮  ++>}ٳo<|ܙsN5/ 4( A)S?#'A SoVt ?|y#ۚ13ڏFf, 5s;xG:G?y h 3g1׃k8?9Cs?t@vAwAxByBzC{D|D}E~EFGGHHIIJJKLMNOOPPQRTUVWWXXYYZ[[_ñaɶdнhijllmnopqqrsttuuvvwwxxyyzz{{||}}~! ++,  H*\ȰÇ#*q $DQCF -T(A 0P._X!?tА :hАB˂AdBE$Hp֨3 !:Ġ=ĄC˗.]\Rʼn%K񱣮  ++>}ٳo<|ܙsN5/ 4( A)S?#'A SoVt ?|y#ۚ13ڏFf, 5s;xG:G?y h 3g1׃k>>???CCCDDDGGGHHHIIIJJJKKKLLLNNNRRRSSSTTTUUUVVVWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}! ++,  H*\ȰÇ#*qI%DQCF -T(A 0P/`\"!?tА :hАB˂AhRe ++$Hp֨3 !:Ġ=ƈ C /^`b&L񱣮  $>}=}ࡃ'6/ !DHA)S^@3'A SoV-?}#ۚ13B ,2sP$4c9@ ؅APxs8?9Cs?t@vAwAxByBzC{D|D}E~EFGGHHIIJJKKLMNOOPPQRTUVWWXXYYZ[[_ñaɶdнhijllmoppqqrsttuuvvwwxxyyzz{{||}}~! ++,  H*\ȰÇ#*qI%DQCF -T(A 0P/`\"!?tА :hАB˂AhRe ++$Hp֨3 !:Ġ=ƈ C /^`b&L񱣮  $>}=}ࡃ'6/ !DHA)S^@3'A SoV-?}#ۚ13B ,2sP$4c9@ ؅APxs>>???CCCDDDGGGIIIJJJKKKLLLNNNPPPQQQRRRSSSTTTUUUWWW^^^___```bbbcccdddfffkkklllmmmnnnoooqqqrrrtttzzz}}}! ++,  H*\ȰÇ#*d'H١ㆍ1\@q 6\2gtre ++#C|A#Ɗ#>pB˂EȀ%K&Lpѳ ++%>AԤAC2dx… ++(P0ZP"C}*TA ++Hϟ:4 ihQ#F)S^DBdžA"SoV"D!S#ۚ164  J*'r @G:x(3ق0:2, "?_m@ ߀u@yqm^|w}]f)@h`A=0"∌0H@Gzq/m% n`|衇n|H-B"#E!5{am`-"HG"@9h^|2(|䁇}AALW`"ƈuA8av ++vhGtikX fa|:Y\aT<I Į"AA,`bAF 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@vAwAxBzC{D|D}E~EFGGHHIIJJKKLMNNOOPPQRSTUVWWXXYYZ[[\_ñaɶdнhijkllmmnppqqrsttuuvvwwxxyyzz{{||}}~! ++,  H*\ȰÇ#*d'H١ㆍ1\@q 6\2gtre ++#C|A#Ɗ#>pB˂EȀ%K&Lpѳ ++%>AԤAC2dx… ++(P0ZP"C}*TA ++Hϟ:4 ihQ#F)S^DBdžA"SoV"D!S#ۚ164  J*'r @G:x(3ق0:2, "?_m@ ߀u@yqm^|w}]f)@h`A=0"∌0H@Gzq/m% n`|衇n|H-B"#E!5{am`-"HG"@9h^|2(|䁇}AALW`"ƈuA8av ++vhGtikX fa|:Y\aT<I Į"AA,`bAF 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*)*
  • +-\\[manual\\](.*?)\\[\\/manual\\] $1 +-\\[manual=(\\w+)(#\\w+)?\\](.*?)\\[\\/manual\\] $3 +-\\[topic=(\\d+)\\](.*?)\\[\\/topic\\] $2 +-\. +- +- + -- Connect to the database in USER mode + \c legacyworlds legacyworlds + +diff -Naur beta5//sql/beta5/00-beta5.sql forums//sql/beta5/00-beta5.sql +--- beta5//sql/beta5/00-beta5.sql 2011-02-05 10:09:56.184335002 +0100 ++++ forums//sql/beta5/00-beta5.sql 2011-02-05 10:10:01.744335002 +0100 +@@ -44,3 +44,6 @@ + 'for a long term estimate of the bets players'' accomplishments.'); + SELECT add_ranking_description('beta5', 'p_idr', 'en', 'Inflicted Damage Ranking', + 'This ranking represents the amount of damage a player has inflicted on other players'' fleets.'); ++ ++ ++SELECT forums.add_category_type( 'beta5/aforums', 'en', 'Alliance forums' ); +diff -Naur beta5//sql/beta5/structure/01-alliance.sql forums//sql/beta5/structure/01-alliance.sql +--- beta5//sql/beta5/structure/01-alliance.sql 2011-02-05 10:09:56.174335002 +0100 ++++ forums//sql/beta5/structure/01-alliance.sql 2011-03-12 14:59:24.651300052 +0100 +@@ -20,12 +20,15 @@ + successor BIGINT REFERENCES player (id), + democracy BOOLEAN NOT NULL DEFAULT FALSE, + default_grade BIGINT NOT NULL, ++ f_category BIGINT NOT NULL DEFAULT forums.make_category('beta5/forums') ++ REFERENCES forums.category (id), + enable_tt CHAR(1) NOT NULL DEFAULT 'N' CHECK(enable_tt IN ('N', 'S', 'R')) + ); + + CREATE INDEX alliance_leader ON alliance (leader); + CREATE INDEX alliance_successor ON alliance (successor); + CREATE INDEX alliance_def_grade ON alliance (default_grade); ++CREATE INDEX alliance_f_category ON alliance (f_category); + + GRANT SELECT,INSERT,UPDATE,DELETE ON alliance TO legacyworlds; + GRANT SELECT,UPDATE ON alliance_id_seq TO legacyworlds; +diff -Naur beta5//sql/beta5/structure/02-alliance-forums.sql forums//sql/beta5/structure/02-alliance-forums.sql +--- beta5//sql/beta5/structure/02-alliance-forums.sql 2011-02-05 10:09:56.174335002 +0100 ++++ forums//sql/beta5/structure/02-alliance-forums.sql 2011-03-12 15:02:40.271300051 +0100 +@@ -10,91 +10,268 @@ + -- -------------------------------------------------------- + + +-CREATE TABLE af_forum ( +- id SERIAL PRIMARY KEY, +- alliance INT NOT NULL REFERENCES alliance(id) ON DELETE CASCADE, +- forder INT NOT NULL CHECK(forder >= 0), +- title VARCHAR(64) NOT NULL, +- description TEXT, +- topics INT NOT NULL DEFAULT 0 CHECK(topics >= 0), +- posts INT NOT NULL DEFAULT 0 CHECK(posts >= 0), +- last_post BIGINT, +- user_post BOOLEAN NOT NULL DEFAULT TRUE, +- UNIQUE(alliance, forder), +- UNIQUE(alliance, title) +-); +- +-CREATE INDEX af_forum_last_post ON af_forum (last_post); +- +-GRANT SELECT,INSERT,UPDATE,DELETE ON af_forum TO legacyworlds; +-GRANT SELECT,UPDATE ON af_forum_id_seq TO legacyworlds; +- +- +- +-CREATE TABLE af_topic ( +- id BIGSERIAL NOT NULL PRIMARY KEY, +- forum INT NOT NULL REFERENCES af_forum (id) ON DELETE CASCADE, +- first_post BIGINT NOT NULL, +- last_post BIGINT, +- sticky BOOLEAN NOT NULL DEFAULT FALSE +-); ++-- ++-- Access types for alliance forums ++-- ++-- - M: only mods can post, create topics and polls ++-- - P: only mods can create topics and polls, users can post ++-- - T: only mods can create polls, users can create topics and post ++-- - L: users can create polls or topics and post ++-- + +-CREATE INDEX af_topic_forum ON af_topic (forum); +-CREATE INDEX af_topic_first_post ON af_topic (first_post); +-CREATE INDEX af_topic_last_post ON af_topic (last_post); +- +-GRANT SELECT,INSERT,UPDATE,DELETE ON af_topic TO legacyworlds; +-GRANT SELECT,UPDATE ON af_topic_id_seq TO legacyworlds; + + ++-- ++-- Alliance forums ++-- + +-CREATE TABLE af_post ( +- id BIGSERIAL NOT NULL PRIMARY KEY, +- forum INT NOT NULL REFERENCES af_forum (id) ON DELETE CASCADE, +- topic BIGINT REFERENCES af_topic (id) ON DELETE CASCADE, +- author BIGINT NOT NULL REFERENCES player (id), +- reply_to BIGINT REFERENCES af_post (id) ON DELETE NO ACTION, +- 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, +- edited_by BIGINT REFERENCES player(id) ++CREATE TABLE alliance_forum ( ++ forum BIGINT PRIMARY KEY REFERENCES forums.t_forum (id) ON DELETE CASCADE, ++ alliance INT NOT NULL REFERENCES alliance(id) ON DELETE CASCADE, ++ access_mode CHAR(1) NOT NULL DEFAULT('T') CHECK( access_mode IN ('M', 'P', 'T', 'L') ), ++ UNIQUE( alliance, forum ) + ); + +-CREATE INDEX af_post_forum ON af_post (forum); +-CREATE INDEX af_post_topic ON af_post (topic); +-CREATE INDEX af_post_author ON af_post (author); +-CREATE INDEX af_post_reply_to ON af_post (reply_to); +-CREATE INDEX af_post_edited_by ON af_post (edited_by); +- +-ALTER TABLE af_forum ADD FOREIGN KEY (last_post) REFERENCES af_post (id) ON DELETE SET NULL; +-ALTER TABLE af_topic ADD FOREIGN KEY (first_post) REFERENCES af_post (id) ON DELETE CASCADE; +-ALTER TABLE af_topic ADD FOREIGN KEY (last_post) REFERENCES af_post (id) ON DELETE SET NULL; ++GRANT SELECT, INSERT, UPDATE ON alliance_forum TO legacyworlds; + +-GRANT SELECT,INSERT,UPDATE,DELETE ON af_post TO legacyworlds; +-GRANT SELECT,UPDATE ON af_post_id_seq TO legacyworlds; + + ++-- ++-- Alliance ranks / forum access ++-- + +-CREATE TABLE af_read ( +- reader BIGINT NOT NULL REFERENCES player (id), +- topic BIGINT NOT NULL REFERENCES af_topic (id) ON DELETE CASCADE, +- PRIMARY KEY (reader, topic) ++CREATE TABLE al_rank_forum ( ++ rank BIGINT NOT NULL REFERENCES alliance_grade (id) ON DELETE CASCADE, ++ forum INT NOT NULL REFERENCES alliance_forum (forum) ON DELETE CASCADE, ++ is_mod BOOLEAN NOT NULL DEFAULT FALSE, ++ PRIMARY KEY ( rank, forum ) + ); + +-CREATE INDEX af_read_topic ON af_read (topic); +-GRANT SELECT,INSERT,DELETE ON af_read TO legacyworlds; +- ++CREATE INDEX al_rank_forum_forum ON al_rank_forum (forum); ++GRANT SELECT,INSERT,DELETE,UPDATE ON al_rank_forum TO legacyworlds; + + +-CREATE TABLE algr_forums ( +- grade BIGINT NOT NULL REFERENCES alliance_grade (id) ON DELETE CASCADE, +- forum INT NOT NULL REFERENCES af_forum (id) ON DELETE CASCADE, +- is_mod BOOLEAN NOT NULL DEFAULT FALSE, +- PRIMARY KEY (grade, forum) +-); +- +-CREATE INDEX algr_forums_forum ON algr_forums (forum); +-GRANT SELECT,INSERT,DELETE,UPDATE ON algr_forums TO legacyworlds; ++-- ++-- create_alliance_forum( player, alliance, order, title, description, access_mode ) ++-- ++-- Creates a new forum in an alliance. Return values: ++-- any positive value: identifier of the new forum ++-- -1: alliance not found ++-- -2: the player doesn't have the necessary privileges to create the forum ++-- -3: the alliance already has 30 forums ++-- -4: a forum with the same title already exists ++-- -5: invalid access mode ++-- -6: failure to create the generic forum ++ ++CREATE OR REPLACE FUNCTION create_alliance_forum( pid BIGINT, aid BIGINT, fo INT, ttl TEXT, dsc TEXT, am TEXT) RETURNS BIGINT AS $$ ++DECLARE ++ arec RECORD; ++ prec RECORD; ++ rid BIGINT; ++ rrec RECORD; ++ nf BIGINT; ++BEGIN ++ -- Check the access mode ++ IF am NOT IN ('M','P','T','L') THEN ++ RETURN -5; ++ END IF; ++ ++ -- Get the alliance's record ++ SELECT INTO arec default_grade, f_category, leader FROM alliance WHERE id = aid; ++ IF NOT FOUND THEN ++ RETURN -1; ++ END IF; ++ ++ -- Get the player's record ++ SELECT INTO prec alliance, a_grade FROM player WHERE id = pid AND (quit IS NULL OR quit > UNIX_TIMESTAMP(NOW())) ++ AND alliance = aid AND a_status = 'IN '; ++ IF NOT FOUND THEN ++ RETURN -2; ++ END IF; ++ ++ -- Get the player's rank ++ rid := CASE prec.a_grade IS NULL ++ WHEN TRUE THEN arec.default_grade ++ ELSE prec.a_grade ++ END; ++ SELECT INTO rrec forum_admin FROM alliance_grade WHERE id = rid; ++ IF NOT FOUND THEN ++ RETURN -2; ++ END IF; ++ ++ -- Check if the player has access ++ IF NOT (pid = arec.leader OR rrec.forum_admin) THEN ++ RETURN -2; ++ END IF; ++ ++ -- Check the amount of forums the alliance has ++ SELECT INTO nf COUNT(*) FROM alliance_forum WHERE alliance = aid; ++ IF nf >= 30 THEN ++ RETURN -3; ++ END IF; ++ ++ -- Check the forum's title ++ PERFORM * FROM forums.t_forum WHERE category = arec.f_category AND title = ttl; ++ IF FOUND THEN ++ RETURN -4; ++ END IF; ++ ++ -- Create the forum ++ nf := forums.make_forum( arec.f_category, fo, ttl, dsc ); ++ IF nf IS NULL THEN ++ RETURN -6; ++ END IF; ++ INSERT INTO alliance_forum (forum, alliance, access_mode) VALUES (nf, aid, am); ++ RETURN nf; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- modify_alliance_forum( player, forum, title, description, access_mode ) ++-- ++-- This function tries to modify an existing alliance forum. Return values: ++-- 0 success ++-- -1 invalid access mode ++-- -2 the player is not in the alliance ++-- -3 the player is not a forum administrator for the alliance ++-- -4 forum not found ++ ++CREATE OR REPLACE FUNCTION modify_alliance_forum( pid BIGINT, fid BIGINT, ttl TEXT, dsc TEXT, am TEXT ) RETURNS INT AS $$ ++DECLARE ++ prec RECORD; ++ arec RECORD; ++ rid BIGINT; ++BEGIN ++ -- Check the access mode ++ IF am NOT IN ('M','P','T','L') THEN ++ RETURN -1; ++ END IF; ++ ++ -- Get the player's alliance and rank ++ SELECT INTO prec alliance, a_grade FROM player WHERE id = pid AND (quit IS NULL OR quit > UNIX_TIMESTAMP(NOW())) ++ AND alliance IS NOT NULL AND a_status = 'IN '; ++ IF NOT FOUND THEN ++ RETURN -2; ++ END IF; ++ ++ -- Check the player's privileges ++ SELECT INTO arec default_grade, leader FROM alliance WHERE id = prec.alliance; ++ IF (arec.leader <> pid) THEN ++ rid := CASE prec.a_grade IS NULL ++ WHEN TRUE THEN arec.default_grade ++ ELSE prec.a_grade ++ END; ++ PERFORM id FROM alliance_grade WHERE id = rid AND forum_admin; ++ IF NOT FOUND THEN ++ RETURN -3; ++ END IF; ++ END IF; ++ ++ -- Make sure the forum exists and hasn't been deleted ++ PERFORM f.id,af.alliance FROM forums.t_forum f, alliance_forum af ++ WHERE af.alliance = prec.alliance AND f.id = af.forum AND f.deleted IS NULL; ++ IF NOT FOUND THEN ++ RETURN -4; ++ END IF; ++ ++ -- Update both the forum and the access table ++ UPDATE forums.t_forum SET title = ttl, description = dsc WHERE id = fid; ++ UPDATE alliance_forum SET access_mode = am WHERE forum = fid; ++ RETURN 0; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- get_aforums_privs( player, forum ) ++-- ++-- Returns the set of privileges a player has over a specific alliance forum ++ ++CREATE OR REPLACE FUNCTION get_aforums_privs( pid 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; ++ rid BIGINT; ++ arec RECORD; ++ b BOOLEAN; ++BEGIN ++ -- Initialise all privileges to FALSE ++ is_admin := FALSE; ++ is_mod := FALSE; ++ can_view := FALSE; ++ can_post := FALSE; ++ can_create := FALSE; ++ can_poll := FALSE; ++ ++ -- Checks whether it's an alliance forum and get the associated data ++ SELECT INTO arec a.id, a.leader, a.default_grade, af.access_mode ++ FROM alliance a, alliance_forum af ++ WHERE af.forum = fid AND a.id = af.alliance; ++ IF NOT FOUND THEN ++ RETURN; ++ END IF; ++ ++ -- If the player is the leader, set all privileges to TRUE and return ++ IF arec.leader = pid THEN ++ is_admin := TRUE; ++ is_mod := TRUE; ++ can_view := TRUE; ++ can_post := TRUE; ++ can_create := TRUE; ++ can_poll := TRUE; ++ RETURN; ++ END IF; ++ ++ -- Checks whether the player is an actual member of the alliance and get his rank ++ SELECT INTO rid a_grade FROM player ++ WHERE id = pid AND (quit IS NULL OR quit > UNIX_TIMESTAMP(NOW())) ++ AND alliance = arec.id AND a_status = 'IN '; ++ IF NOT FOUND THEN ++ RETURN; ++ ELSIF rid IS NULL THEN ++ rid := arec.default_grade; ++ END IF; ++ ++ -- Checks whether the player is a forums admin for the alliance ++ SELECT INTO b forum_admin FROM alliance_grade WHERE id = rid; ++ IF NOT FOUND THEN ++ RETURN; ++ ELSIF b THEN ++ is_admin := TRUE; ++ is_mod := TRUE; ++ can_view := TRUE; ++ can_post := TRUE; ++ can_create := TRUE; ++ can_poll := TRUE; ++ RETURN; ++ END IF; ++ ++ -- Get the access level for this forum/rank combination ++ SELECT INTO b is_mod FROM al_rank_forum WHERE rank = rid AND forum = fid; ++ IF NOT FOUND THEN ++ RETURN; ++ ELSIF b THEN ++ is_mod := TRUE; ++ can_view := TRUE; ++ can_post := TRUE; ++ can_create := TRUE; ++ can_poll := TRUE; ++ END IF; ++ ++ -- We can view; can we post? ++ can_view := TRUE; ++ IF arec.access_mode = 'M' THEN ++ RETURN; ++ END IF; ++ ++ -- We can post; can we create topics? ++ can_post := TRUE; ++ IF arec.access_mode = 'P' THEN ++ RETURN; ++ END IF; ++ ++ -- We can create topics; can we create polls? ++ can_create := TRUE; ++ can_poll := (arec.access_mode = 'L'); ++END; ++$$ LANGUAGE plpgsql; +diff -Naur beta5//sql/forums/00-schema.sql forums//sql/forums/00-schema.sql +--- beta5//sql/forums/00-schema.sql 1970-01-01 01:00:00.000000000 +0100 ++++ forums//sql/forums/00-schema.sql 2011-02-05 10:10:01.764335002 +0100 +@@ -0,0 +1,18 @@ ++-- LegacyWorlds Beta 5 ++-- PostgreSQL database scripts ++-- ++-- forums/00-schema.sql ++-- ++-- Initialises the schema for the forums ++-- ++-- Copyright(C) 2004-2007, DeepClone Development ++-- -------------------------------------------------------- ++ ++ ++-- Connect to the database ++\c legacyworlds legacyworlds_admin ++ ++-- Create the forums schema ++CREATE SCHEMA forums; ++GRANT USAGE ON SCHEMA forums TO legacyworlds; ++ +diff -Naur beta5//sql/forums/01-forums.sql forums//sql/forums/01-forums.sql +--- beta5//sql/forums/01-forums.sql 1970-01-01 01:00:00.000000000 +0100 ++++ forums//sql/forums/01-forums.sql 2011-02-05 10:10:01.764335002 +0100 +@@ -0,0 +1,77 @@ ++-- LegacyWorlds Beta 5 ++-- PostgreSQL database scripts ++-- ++-- forums/01-forums.sql ++-- ++-- Categories, forums, topics and posts ++-- ++-- Copyright(C) 2004-2007, DeepClone Development ++-- -------------------------------------------------------- ++ ++ ++ ++-- ++-- Create the table of forum category types ++-- ++CREATE TABLE forums.category_type ( ++ id SERIAL PRIMARY KEY, ++ lib_path VARCHAR(24) NOT NULL UNIQUE ++); ++ ++GRANT SELECT ON forums.category_type TO legacyworlds; ++SELECT register_serial_table('forums', 'category_type'); ++ ++ ++ ++-- ++-- Create the table of translations for category types ++-- ++CREATE TABLE forums.cat_type_text ( ++ id INT NOT NULL REFERENCES forums.category_type (id) ON DELETE CASCADE, ++ lang VARCHAR(4) NOT NULL REFERENCES main.lang (txt) ON DELETE CASCADE, ++ name VARCHAR(32) NOT NULL, ++ PRIMARY KEY( id, lang ) ++); ++ ++CREATE INDEX i_forums_cat_type_lang ON forums.cat_type_text (lang); ++ ++GRANT SELECT ON forums.cat_type_text TO legacyworlds; ++ ++ ++-- ++-- Create the category table to store forum groups ++-- ++CREATE TABLE forums.category ( ++ id BIGSERIAL PRIMARY KEY, ++ acl_lib INT NOT NULL REFERENCES forums.category_type (id) ON DELETE CASCADE ++); ++ ++CREATE INDEX i_forums_category_acl_lib ON forums.category (acl_lib); ++ ++GRANT SELECT,INSERT,DELETE ON forums.category TO legacyworlds; ++GRANT SELECT,UPDATE ON forums.category_id_seq TO legacyworlds; ++ ++SELECT register_serial_table('forums', 'category'); ++ ++ ++-- ++-- Create the forums table ++-- ++CREATE TABLE forums.t_forum ( ++ id BIGSERIAL PRIMARY KEY, ++ category BIGINT NOT NULL REFERENCES forums.category (id) ON DELETE CASCADE, ++ f_order INT NOT NULL CHECK( f_order >= 0 ), ++ title VARCHAR(64) NOT NULL, ++ description TEXT, ++ deleted INT NULL, ++ deleted_by BIGINT NULL REFERENCES main.account (id) ON DELETE SET NULL, ++ UNIQUE( category, f_order ), ++ UNIQUE( category, title ) ++); ++ ++CREATE INDEX i_forum_deleted_by ON forums.t_forum (deleted_by); ++ ++GRANT SELECT,INSERT,UPDATE,DELETE ON forums.t_forum TO legacyworlds; ++GRANT SELECT,UPDATE ON forums.t_forum_id_seq TO legacyworlds; ++ ++SELECT register_serial_table('forums', 't_forum'); +diff -Naur beta5//sql/forums/01-signatures.sql forums//sql/forums/01-signatures.sql +--- beta5//sql/forums/01-signatures.sql 1970-01-01 01:00:00.000000000 +0100 ++++ forums//sql/forums/01-signatures.sql 2011-02-05 10:10:01.764335002 +0100 +@@ -0,0 +1,31 @@ ++-- LegacyWorlds Beta 5 ++-- PostgreSQL database scripts ++-- ++-- forums/01-signatures.sql ++-- ++-- Signature storage table ++-- ++-- Copyright(C) 2004-2007, DeepClone Development ++-- -------------------------------------------------------- ++ ++ ++-- ++-- Create the signature storage table ++-- ++CREATE TABLE forums.signature ( ++ id BIGSERIAL PRIMARY KEY, ++ account BIGINT NOT NULL REFERENCES main.account(id) ON DELETE CASCADE, ++ sig_set INT NOT NULL DEFAULT UNIX_TIMESTAMP(NOW()), ++ sig_unset INT, ++ signature TEXT NOT NULL, ++ enable_code BOOLEAN NOT NULL, ++ enable_smileys BOOLEAN NOT NULL, ++ CHECK( sig_unset IS NULL OR sig_set < sig_unset ) ++); ++ ++CREATE UNIQUE INDEX i_forums_signature ON forums.signature (account, sig_set); ++ ++GRANT SELECT,INSERT,UPDATE,DELETE ON forums.signature TO legacyworlds; ++GRANT SELECT,UPDATE ON forums.signature_id_seq TO legacyworlds; ++ ++SELECT register_serial_table('forums', 'signature'); +diff -Naur beta5//sql/forums/02-topics.sql forums//sql/forums/02-topics.sql +--- beta5//sql/forums/02-topics.sql 1970-01-01 01:00:00.000000000 +0100 ++++ forums//sql/forums/02-topics.sql 2011-02-05 10:10:01.764335002 +0100 +@@ -0,0 +1,92 @@ ++-- LegacyWorlds Beta 5 ++-- PostgreSQL database scripts ++-- ++-- forums/02-topics.sql ++-- ++-- Topics, posts and post texts ++-- ++-- Copyright(C) 2004-2007, DeepClone Development ++-- -------------------------------------------------------- ++ ++ ++-- ++-- Create the topics table ++-- ++CREATE TABLE forums.t_topic ( ++ id BIGSERIAL PRIMARY KEY, ++ forum BIGINT NOT NULL REFERENCES forums.t_forum (id) ON DELETE CASCADE, ++ sticky_level INT2 NOT NULL DEFAULT 0 CHECK( sticky_level >= 0 AND sticky_level <= 10 ), ++ moved_from BIGINT NULL REFERENCES forums.t_forum (id) ON DELETE SET NULL, ++ deleted INT NULL, ++ deleted_by BIGINT NULL REFERENCES main.account (id) ON DELETE CASCADE, ++ locked BOOLEAN NOT NULL DEFAULT FALSE ++); ++ ++CREATE INDEX i_topic_forum ON forums.t_topic (forum); ++CREATE INDEX i_topic_moved_from ON forums.t_topic (moved_from); ++CREATE INDEX i_topic_deleted_by ON forums.t_topic (deleted_by); ++ ++GRANT SELECT,INSERT,UPDATE,DELETE ON forums.t_topic TO legacyworlds; ++GRANT SELECT,UPDATE ON forums.t_topic_id_seq TO legacyworlds; ++ ++SELECT register_serial_table('forums', 't_topic'); ++ ++ ++-- ++-- Create the post table ++-- ++ ++CREATE TABLE forums.t_post ( ++ id BIGSERIAL PRIMARY KEY, ++ topic BIGINT NOT NULL REFERENCES forums.t_topic (id) ON DELETE CASCADE, ++ depth INT NOT NULL DEFAULT 0, ++ reply_to BIGINT REFERENCES forums.t_post (id), ++ signature BIGINT REFERENCES forums.signature (id) ON DELETE SET NULL, ++ deleted INT, ++ deleted_by BIGINT REFERENCES main.account (id) ON DELETE CASCADE ++); ++ ++CREATE INDEX i_post_topic ON forums.t_post (topic); ++CREATE INDEX i_post_reply_to ON forums.t_post (reply_to); ++CREATE INDEX i_post_signature ON forums.t_post (signature); ++CREATE INDEX i_deleted_by ON forums.t_post (deleted_by); ++ ++GRANT SELECT,INSERT,UPDATE,DELETE ON forums.t_post TO legacyworlds; ++GRANT SELECT,UPDATE ON forums.t_post_id_seq TO legacyworlds; ++ ++SELECT register_serial_table('forums', 't_post'); ++ ++ ++-- ++-- Create the table to store post texts ++-- ++ ++CREATE TABLE forums.post_text ( ++ post BIGINT NOT NULL REFERENCES forums.t_post (id) ON DELETE CASCADE, ++ moment INT NOT NULL DEFAULT UNIX_TIMESTAMP(NOW()), ++ author BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, ++ title VARCHAR(100) NOT NULL, ++ contents TEXT NOT NULL, ++ enable_code BOOLEAN NOT NULL, ++ enable_smileys BOOLEAN NOT NULL, ++ PRIMARY KEY( post, moment ) ++); ++ ++CREATE INDEX i_post_text_author ON forums.post_text (author); ++ ++GRANT SELECT,INSERT,DELETE ON forums.post_text TO legacyworlds; ++ ++ ++-- ++-- Create a table that stores the last time an user read some topic ++-- ++CREATE TABLE forums.topic_read ( ++ topic BIGINT NOT NULL REFERENCES forums.t_topic (id) ON DELETE CASCADE, ++ read_by BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, ++ read_at INT NOT NULL DEFAULT UNIX_TIMESTAMP(NOW()), ++ PRIMARY KEY( topic, read_by ) ++); ++ ++CREATE INDEX i_topic_read_by ON forums.topic_read (read_by); ++ ++GRANT SELECT,INSERT,UPDATE,DELETE ON forums.topic_read TO legacyworlds; +diff -Naur beta5//sql/forums/03-polls.sql forums//sql/forums/03-polls.sql +--- beta5//sql/forums/03-polls.sql 1970-01-01 01:00:00.000000000 +0100 ++++ forums//sql/forums/03-polls.sql 2011-02-05 10:10:01.764335002 +0100 +@@ -0,0 +1,55 @@ ++-- LegacyWorlds Beta 5 ++-- PostgreSQL database scripts ++-- ++-- forums/03-polls.sql ++-- ++-- Forum polls, poll options and votes ++-- ++-- Copyright(C) 2004-2007, DeepClone Development ++-- -------------------------------------------------------- ++ ++ ++-- ++-- Create the polls table ++-- ++CREATE TABLE forums.poll ( ++ topic BIGINT PRIMARY KEY REFERENCES forums.t_topic (id) ON DELETE CASCADE, ++ title VARCHAR(64) NOT NULL, ++ closed BOOLEAN NOT NULL DEFAULT FALSE, ++ deleted INT NULL, ++ deleted_by BIGINT NULL REFERENCES main.account(id) ON DELETE SET NULL ++); ++ ++CREATE INDEX i_poll_deleted_by ON forums.poll (deleted_by); ++ ++GRANT SELECT,INSERT,UPDATE,DELETE ON forums.poll TO legacyworlds; ++ ++ ++-- ++-- Table for a poll's options ++-- ++CREATE TABLE forums.poll_option ( ++ id BIGSERIAL PRIMARY KEY, ++ poll BIGINT NOT NULL REFERENCES forums.t_topic (id) ON DELETE CASCADE, ++ po_order INT NOT NULL CHECK( po_order >= 0 ), ++ title VARCHAR(64) NOT NULL, ++ UNIQUE( poll, po_order ), ++ UNIQUE( poll, title ) ++); ++ ++GRANT SELECT,INSERT,UPDATE,DELETE ON forums.poll_option TO legacyworlds; ++GRANT SELECT,UPDATE ON forums.poll_option_id_seq TO legacyworlds; ++ ++SELECT register_serial_table('forums', 'poll_option'); ++ ++ ++-- ++-- Table for a poll's votes ++-- ++CREATE TABLE forums.poll_vote ( ++ vote BIGINT NOT NULL REFERENCES forums.poll_option (id) ON DELETE CASCADE, ++ account BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, ++ PRIMARY KEY( vote, account ) ++); ++ ++GRANT SELECT,INSERT,DELETE ON forums.poll_vote TO legacyworlds; +diff -Naur beta5//sql/forums/10-views.sql forums//sql/forums/10-views.sql +--- beta5//sql/forums/10-views.sql 1970-01-01 01:00:00.000000000 +0100 ++++ forums//sql/forums/10-views.sql 2011-02-05 10:10:01.764335002 +0100 +@@ -0,0 +1,73 @@ ++-- LegacyWorlds Beta 5 ++-- PostgreSQL database scripts ++-- ++-- forums/10-views.sql ++-- ++-- Defines a few views corresponding to some common ++-- queries ++-- ++-- Copyright(C) 2004-2007, DeepClone Development ++-- -------------------------------------------------------- ++ ++ ++-- ++-- View for individual posts ++-- ++ ++CREATE VIEW forums.post ++ AS SELECT p.id AS id, t.forum AS forum, p.topic AS topic, t1.author AS author, ++ p.reply_to AS reply_to, p.signature AS signature, p.deleted AS deleted, ++ p.deleted_by AS deleted_by, p.depth AS depth, t1.moment AS post_moment, ++ t2.moment AS last_change, t2.author AS last_author, ++ t2.contents AS contents, t2.title AS title, ++ t2.enable_code AS enable_code, t2.enable_smileys AS enable_smileys ++ FROM forums.t_post p, forums.t_topic t, forums.post_text t1, forums.post_text t2 ++ WHERE t1.post = p.id AND t1.moment = (SELECT MIN(moment) FROM forums.post_text WHERE post = p.id) ++ AND t2.post = p.id AND t2.moment = (SELECT MAX(moment) FROM forums.post_text WHERE post = p.id) ++ AND t.id = p.topic; ++ ++GRANT SELECT ON forums.post TO legacyworlds; ++ ++ ++-- ++-- View for forum topics ++-- ++ ++CREATE VIEW forums.topic_fp_lp ++ AS SELECT t.id, t.forum, MIN(p.post_moment) AS fp_moment, ++ MAX(p.last_change) AS lc_moment, COUNT(p.*) AS posts ++ FROM forums.t_topic t ++ LEFT JOIN forums.post p ON (p.topic = t.id AND ( ++ (t.deleted IS NULL AND p.deleted IS NULL) ++ OR (t.deleted IS NOT NULL AND p.deleted = t.deleted) )) ++ GROUP BY t.id, t.forum, t.deleted; ++ ++ ++CREATE VIEW forums.topic ++ AS SELECT t.*, tt.posts AS posts, ++ fp.title, fp.id AS first_post, fp.author AS fp_author, fp.post_moment AS fp_moment, ++ lp.author AS lc_author, lp.last_change AS lc_moment ++ FROM forums.t_topic t ++ LEFT JOIN forums.topic_fp_lp tt ON (t.id = tt.id) ++ LEFT JOIN forums.post fp ON (fp.topic = t.id AND fp.post_moment = tt.fp_moment) ++ LEFT JOIN forums.post lp ON (lp.topic = t.id AND lp.last_change = tt.lc_moment) ++ ORDER BY sticky_level DESC, lc_moment DESC; ++ ++GRANT SELECT ON forums.topic TO legacyworlds; ++ ++ ++-- ++-- View for forums ++-- ++ ++CREATE VIEW forums.forum ++ AS SELECT f.id AS id, f.category AS category, f.f_order AS f_order, ++ f.title AS title, f.description AS description, ++ f.deleted AS deleted, f.deleted_by AS deleted_by, ++ (SELECT COUNT(*) FROM forums.t_topic WHERE forum = f.id AND deleted IS NULL) AS topics, ++ (SELECT COUNT(*) FROM forums.post WHERE forum = f.id AND deleted IS NULL) AS posts ++ FROM forums.t_forum f ++ ORDER BY category, f_order; ++ ++GRANT SELECT ON forums.forum TO legacyworlds; ++ +diff -Naur beta5//sql/forums/11-access-functions.sql forums//sql/forums/11-access-functions.sql +--- beta5//sql/forums/11-access-functions.sql 1970-01-01 01:00:00.000000000 +0100 ++++ forums//sql/forums/11-access-functions.sql 2011-02-05 10:10:01.764335002 +0100 +@@ -0,0 +1,869 @@ ++-- LegacyWorlds Beta 5 ++-- PostgreSQL database scripts ++-- ++-- forums/11-access-functions.sql ++-- ++-- Functions to access the forums and topics ++-- ++-- Copyright(C) 2004-2007, DeepClone Development ++-- -------------------------------------------------------- ++ ++ ++-- -------------------------------------------------------- ++-- LIST OF ALL REQUIRED FUNCTIONS ++-- -------------------------------------------------------- ++-- ++-- Functions listed with a '*' have been implemented. ++-- Functions listed with a '!' need modifications. ++-- ++-- ++-- GENERAL CLEAN-UP ++-- ++-- forum_cleanup() * ++-- Causes a general clean-up by deleting old stuff ++-- that had been marked for deletion earlier. ++-- ++-- ++-- SIGNATURE MANAGEMENT ++-- ++-- set_signature( account, set_all, new_signature, enable_code, enable_smileys ) * ++-- Sets a new signature for the account ++-- ++-- get_signature( account, timestamp ) * ++-- Returns the complete data for the account's ++-- signature at the specified timestamp. ++-- ++-- ++-- CATEGORIES AND FORUMS MANAGEMENT ++-- ++-- add_category_type( library_path , language , description ) * ++-- Creates a category type with the specified library as ++-- its handler if it doesn't exist, then add the description. ++-- ++-- make_category( access_library ) * ++-- Creates a category and returns its ID ++-- ++-- make_forum( category, f_order, title, description ) * ++-- Creates a forum and returns its ID ++-- ++-- move_up( forum ) * ++-- Moves a forum up in the list of forums in a category ++-- ++-- move_down( forum ) * ++-- Moves a forum down in the list of forums in a category ++-- ++-- delete_forum( forum , administrator ) * ++-- Delete a forum and its contents ++-- ++-- restore_forum( forum ) * ++-- Restores a deleted forum to its previous state ++-- ++-- delete_forums( category, administrator ) ++-- Deletes all forums in a category ++-- ++-- restore_forums( category ) ++-- Restore all forums in a category ++-- ++-- get_last_post( forum ) * ++-- Returns the data from the last post in the specified forum ++-- ++-- get_read_topics( forum , user ) * ++-- Returns the amount of topics an user has read since they ++-- were last updated ++-- ++-- mark_forum_read( forum, read_by ) * ++-- Marks all of a forum's topics as read by some user ++-- ++-- ++-- TOPICS AND POSTS MANAGEMENT ++-- ++-- mark_topic_read( topic, read_by ) * ++-- Marks a topic as read by some user ++-- ++-- create_topic( forum, author, sticky_level, title, contents, enable_code, enable_smileys, sig ) * ++-- Creates a new topic in a forum and returns its ++-- ID ++-- ++-- move_topic( topic, forum, user ) * ++-- Moves a topic from a forum to another ++-- ++-- add_reply( reply_to, author, title, contents, enable_code, enable_smileys, signature ) * ++-- Posts a reply to a post ++-- ++-- edit_post( post, author, contents, enable_code, enable_smileys, change_signature, signature ) * ++-- Modifies a post ++-- ++-- delete_post( post, moderator ) * ++-- Marks a post as deleted; delete the topic if the ++-- post is the topic's "main" post ++-- ++-- restore_post( post ) * ++-- Restores a deleted post or, if that post was the ++-- first of a topic, restore the whole topic ++-- ++-- ++-- FORUM POLLS ++-- ++-- create_poll( topic, title ) * ++-- Adds a new poll to the database and returns the ++-- new row ++-- ++-- create_option( poll, order, title ) * ++-- Adds a poll option at the specified order and ++-- returns the new row ++-- ++-- delete_option( poll, order ) * ++-- Removes a poll option ++-- ++-- move_opt_up( poll, order ) * ++-- Moves a poll option up in the list and returns a ++-- boolean to indicate success or failure ++-- ++-- move_opt_down( poll, order ) * ++-- Moves a poll option down in the list and returns a ++-- boolean to indicate success or failure ++-- ++-- set_vote( user, poll, option) * ++-- Sets the vote on a forum poll ++-- ++ ++ ++ ++ ++-- -------------------------------------------------------- ++-- GENERAL CLEAN-UP ++-- -------------------------------------------------------- ++ ++-- ++-- forum_cleanup() ++-- ++-- Causes a general clean-up by deleting old stuff that had been marked for deletion earlier. ++ ++CREATE OR REPLACE FUNCTION forums.forum_cleanup() RETURNS VOID AS $$ ++DECLARE ++ crec RECORD; ++ frec RECORD; ++ fod INT; ++ nts INT; ++BEGIN ++ -- Start by locking the tables ++ LOCK TABLE forums.category, forums.t_forum, forums.t_topic, forums.t_post IN ACCESS EXCLUSIVE MODE; ++ ++ -- Now get the categories containing forums to be deleted ++ nts := UNIX_TIMESTAMP( NOW() ) - 28 * 24 * 3600; ++ FOR crec IN SELECT DISTINCT category FROM forums.t_forum ++ WHERE deleted IS NOT NULL AND deleted <= nts ++ LOOP ++ -- For each category, go through the list of forums ++ fod := 0; ++ FOR frec IN SELECT id,deleted FROM forums.t_forum WHERE category = crec.category ++ LOOP ++ -- This forum is to be deleted; remove it ++ IF (frec.deleted IS NOT NULL AND frec.deleted <= nts) THEN ++ DELETE FROM forums.t_forum WHERE id = frec.id; ++ fod := fod + 1; ++ ++ -- This forum is not to be deleted, however its order must be fixed ++ ELSIF (fod > 1) THEN ++ UPDATE forums.t_forum SET f_order = f_order - fod WHERE id = frec.id; ++ END IF; ++ END LOOP; ++ END LOOP; ++ ++ -- Delete topics and posts that were not part of forums to be deleted ++ DELETE FROM forums.t_topic WHERE deleted IS NOT NULL AND deleted <= nts; ++ DELETE FROM forums.t_post WHERE deleted IS NOT NULL AND deleted <= nts; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++ ++ ++-- -------------------------------------------------------- ++-- SIGNATURE MANAGEMENT ++-- -------------------------------------------------------- ++ ++-- ++-- forums.set_signature( account, set_all, new_signature, enable_code, enable_smileys ) ++-- ++-- Sets a new signature for the account ++ ++CREATE OR REPLACE FUNCTION forums.set_signature ( a BIGINT, s BOOLEAN, t TEXT, ec BOOLEAN, es BOOLEAN ) RETURNS VOID AS $$ ++DECLARE ++ sid BIGINT; ++BEGIN ++ -- Mark the previous signature as "ended" ++ UPDATE forums.signature ++ SET sig_unset = UNIX_TIMESTAMP(NOW()) - 1 ++ WHERE account = a AND sig_unset IS NULL; ++ ++ -- If there *is* a new signature, insert it ++ IF NOT(t IS NULL OR t = '') THEN ++ INSERT INTO forums.signature (account, signature, enable_code, enable_smileys) ++ VALUES ( a, t, ec, es ); ++ sid := last_inserted('signature'); ++ ELSE ++ sid := NULL; ++ END IF; ++ ++ -- Change all of the posts' signatures to the new one and delete ++ -- the old signatures if needed. ++ IF s THEN ++ UPDATE forums.t_post ++ SET signature = sid ++ WHERE author = a; ++ IF sid IS NOT NULL THEN ++ DELETE FROM forums.signature ++ WHERE account = a AND id <> sid; ++ ELSE ++ DELETE FROM forums.signature ++ WHERE account = a; ++ END IF; ++ END IF; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.get_signature( account, timestamp ) ++-- ++-- Returns the complete data for the account's signature at the specified timestamp. ++ ++CREATE OR REPLACE FUNCTION forums.get_signature ( a BIGINT, ts INT ) RETURNS forums.signature AS $$ ++ SELECT * FROM forums.signature ++ WHERE account = $1 AND sig_set <= $2 ++ AND (sig_unset IS NULL OR sig_unset >= $2); ++$$ LANGUAGE SQL; ++ ++ ++ ++ ++-- -------------------------------------------------------- ++-- FORUMS AND CATEGORIES MANAGEMENT ++-- -------------------------------------------------------- ++ ++-- ++-- forums.add_category_type( library_path , language , description ) ++-- ++-- Creates a category type with the specified library as its handler if it doesn't exist, then add the description. ++ ++CREATE OR REPLACE FUNCTION forums.add_category_type ( lp TEXT, lg TEXT, dsc TEXT ) RETURNS VOID AS $$ ++DECLARE ++ ct INT; ++BEGIN ++ SELECT INTO ct id FROM forums.category_type WHERE lib_path = lp; ++ IF NOT FOUND THEN ++ INSERT INTO forums.category_type (lib_path) VALUES (lp); ++ ct := last_inserted( 'category_type' ); ++ END IF; ++ ++ INSERT INTO forums.cat_type_text (id, lang, name) VALUES (ct, lg, dsc); ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.make_category( access_library ) ++-- ++-- Creates a category and returns its ID ++ ++CREATE OR REPLACE FUNCTION forums.make_category ( al TEXT ) RETURNS BIGINT AS $$ ++DECLARE ++ ct INT; ++BEGIN ++ SELECT INTO ct id FROM forums.category_type WHERE lib_path = al; ++ IF NOT FOUND THEN ++ RETURN NULL; ++ END IF; ++ ++ INSERT INTO forums.category (acl_lib) VALUES ( ct ); ++ RETURN last_inserted( 'category' ); ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.make_forum( category, f_order, title, description ) ++-- ++-- Creates a forum and returns its ID ++ ++CREATE OR REPLACE FUNCTION forums.make_forum( cid BIGINT, fo INT, tt TEXT, dsc TEXT ) ++ RETURNS BIGINT AS $$ ++DECLARE ++ rec RECORD; ++ fid BIGINT; ++ rfo INT; ++BEGIN ++ IF fo IS NULL THEN ++ SELECT INTO rfo MAX(f_order) + 1 FROM forums.t_forum WHERE category = cid; ++ ELSE ++ rfo := fo; ++ FOR rec IN SELECT id FROM forums.t_forum ++ WHERE category = cid AND f_order >= rfo ++ ORDER BY f_order DESC ++ FOR UPDATE ++ LOOP ++ UPDATE forums.t_forum SET f_order = f_order + 1 WHERE id = rec.id; ++ END LOOP; ++ END IF; ++ ++ INSERT INTO forums.t_forum (category, f_order, title, description) ++ VALUES (cid, rfo, tt, dsc); ++ fid := last_inserted( 't_forum' ); ++ RETURN fid; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.move_up( forum ) ++-- ++-- Moves a forum up in the list of forums in a category ++ ++CREATE OR REPLACE FUNCTION forums.move_up ( fid BIGINT ) RETURNS BOOLEAN AS $$ ++DECLARE ++ cid BIGINT; ++ fo INT; ++ mfo INT; ++BEGIN ++ SELECT INTO fo COUNT(*) FROM forums.t_forum WHERE id = fid; ++ IF fo = 0 THEN ++ RETURN FALSE; ++ END IF; ++ SELECT INTO cid category FROM forums.t_forum WHERE id = fid; ++ ++ PERFORM * FROM forums.t_forum WHERE category = cid FOR UPDATE; ++ SELECT INTO fo f_order FROM forums.t_forum WHERE id = fid; ++ IF fo = 0 THEN ++ RETURN FALSE; ++ END IF; ++ ++ SELECT INTO mfo MAX(f_order) + 1 FROM forums.t_forum ++ WHERE category = (SELECT category FROM forums.t_forum WHERE id = fid); ++ ++ UPDATE forums.t_forum SET f_order = mfo WHERE id = fid; ++ UPDATE forums.t_forum SET f_order = fo WHERE category = cid AND f_order = fo - 1; ++ UPDATE forums.t_forum SET f_order = fo - 1 WHERE id = fid; ++ ++ RETURN TRUE; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.move_down( forum ) ++-- ++-- Moves a forum down in the list of forums in a category ++ ++CREATE OR REPLACE FUNCTION forums.move_down ( fid BIGINT ) RETURNS BOOLEAN AS $$ ++DECLARE ++ cid BIGINT; ++ fo INT; ++ mfo INT; ++BEGIN ++ SELECT INTO fo COUNT(*) FROM forums.t_forum WHERE id = fid; ++ IF fo = 0 THEN ++ RETURN FALSE; ++ END IF; ++ SELECT INTO cid category FROM forums.t_forum WHERE id = fid; ++ ++ PERFORM * FROM forums.t_forum WHERE category = cid FOR UPDATE; ++ SELECT INTO fo f_order FROM forums.t_forum WHERE id = fid; ++ SELECT INTO mfo MAX(f_order) FROM forums.t_forum ++ WHERE category = (SELECT category FROM forums.t_forum WHERE id = fid); ++ IF fo = mfo THEN ++ RETURN FALSE; ++ END IF; ++ ++ UPDATE forums.t_forum SET f_order = mfo + 1 WHERE id = fid; ++ UPDATE forums.t_forum SET f_order = fo WHERE category = cid AND f_order = fo + 1; ++ UPDATE forums.t_forum SET f_order = fo + 1 WHERE id = fid; ++ ++ RETURN TRUE; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.delete_forum( forum , administrator ) ++-- ++-- Delete a forum and its contents ++ ++CREATE OR REPLACE FUNCTION forums.delete_forum( fid BIGINT, aid BIGINT ) RETURNS VOID AS $$ ++DECLARE ++ cts INT; ++BEGIN ++ -- Check if the forum exists and hasn't been deleted ++ PERFORM * FROM forums.t_forum WHERE id = fid AND deleted IS NULL; ++ IF NOT FOUND THEN ++ RETURN; ++ END IF; ++ ++ -- Marks the forum and its contents as deleted ++ cts := UNIX_TIMESTAMP( NOW () ); ++ UPDATE forums.t_forum SET deleted = cts, deleted_by = aid WHERE id = fid; ++ -- FIXME Argh! f_order! headache! ++ UPDATE forums.t_post SET deleted = cts, deleted_by = aid ++ WHERE topic IN (SELECT id FROM forums.t_topic WHERE forum = fid AND deleted IS NULL) ++ AND deleted IS NULL; ++ UPDATE forums.t_topic SET deleted = cts, deleted_by = aid ++ WHERE forum = fid AND deleted IS NULL; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.restore_forum( forum ) ++-- ++-- Restores a deleted forum to its previous state ++ ++CREATE OR REPLACE FUNCTION forums.restore_forum( fid BIGINT ) RETURNS VOID AS $$ ++DECLARE ++ ts INT; ++BEGIN ++ SELECT INTO ts deleted FROM forums.t_forum ++ WHERE id = fid AND deleted IS NOT NULL; ++ IF NOT FOUND THEN ++ RETURN; ++ END IF; ++ ++ -- Marks the forum and its contents as deleted ++ UPDATE forums.t_forum SET deleted = NULL, deleted_by = NULL WHERE id = fid; ++ -- FIXME Argh! f_order! headache! ++ UPDATE forums.t_post SET deleted = NULL, deleted_by = NULL ++ WHERE topic IN (SELECT id FROM forums.t_topic WHERE forum = fid AND deleted = ts) ++ AND deleted = ts; ++ UPDATE forums.t_topic SET deleted = NULL, deleted_by = NULL ++ WHERE forum = fid AND deleted = ts; ++END; ++$$ LANGUAGE plpgsql; ++ ++-- ++-- forums.delete_forums( category, administrator ) ++-- ++-- Deletes all forums in a category ++ ++CREATE OR REPLACE FUNCTION forums.delete_forums( cid BIGINT, aid BIGINT ) RETURNS VOID AS $$ ++DECLARE ++ f RECORD; ++BEGIN ++ FOR f IN SELECT id FROM forums.t_forum WHERE category = cid AND deleted IS NULL ++ LOOP ++ PERFORM forums.delete_forum( f.id, aid ); ++ END LOOP; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.restore_forums( category ) ++-- ++-- Restore all forums in a category ++ ++CREATE OR REPLACE FUNCTION forums.restore_forums( cid BIGINT ) RETURNS VOID AS $$ ++DECLARE ++ f RECORD; ++BEGIN ++ FOR f IN SELECT id FROM forums.t_forum WHERE category = cid AND deleted IS NOT NULL ++ LOOP ++ PERFORM forums.restore_forum( f.id ); ++ END LOOP; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.get_last_post( forum ) ++-- ++-- Returns the data from the last post in the specified forum ++ ++CREATE OR REPLACE FUNCTION forums.get_last_post( fid BIGINT ) RETURNS forums.post AS $$ ++ SELECT * FROM forums.post ++ WHERE forum = $1 AND deleted IS NULL ++ AND last_change = (SELECT MAX(last_change) FROM forums.post WHERE forum = $1 AND deleted IS NULL); ++$$ LANGUAGE SQL; ++ ++ ++-- ++-- forums.get_read_topics( forum , user ) ++-- ++-- Returns the amount of topics an user has read since they were last updated ++ ++CREATE OR REPLACE FUNCTION forums.get_read_topics( fid BIGINT, aid BIGINT ) RETURNS BIGINT AS $$ ++ SELECT COUNT(DISTINCT p.topic) ++ FROM forums.post p ++ LEFT JOIN forums.topic_read r ON (p.topic = r.topic AND p.last_change <= r.read_at) ++ WHERE p.forum = $1 AND r.read_by = $2 AND p.deleted IS NULL; ++$$ LANGUAGE SQL; ++ ++ ++-- ++-- forums.mark_forum_read( forum, read_by ) ++-- ++-- Marks all of a forum's topics as read by some user ++ ++CREATE OR REPLACE FUNCTION forums.mark_forum_read( fid BIGINT, aid BIGINT ) RETURNS VOID AS $$ ++DECLARE ++ rec RECORD; ++BEGIN ++ FOR rec IN SELECT id FROM forums.t_topic WHERE deleted IS NULL AND forum = fid FOR UPDATE ++ LOOP ++ PERFORM forums.mark_topic_read( rec.id, aid ); ++ END LOOP; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++ ++-- -------------------------------------------------------- ++-- TOPICS AND POSTS MANAGEMENT ++-- -------------------------------------------------------- ++ ++-- ++-- forums.mark_topic_read( topic, read_by ) ++-- ++-- Marks a topic as read by some user ++ ++CREATE OR REPLACE FUNCTION forums.mark_topic_read ( tid BIGINT, rid BIGINT ) RETURNS VOID AS $$ ++DECLARE ++ rr RECORD; ++BEGIN ++ SELECT INTO rr * FROM forums.topic_read WHERE topic = tid AND read_by = rid FOR UPDATE; ++ IF NOT FOUND THEN ++ INSERT INTO forums.topic_read (topic, read_by) VALUES (tid, rid); ++ ELSE ++ UPDATE forums.topic_read SET read_at = UNIX_TIMESTAMP(NOW()) ++ WHERE topic = tid AND read_by = rid; ++ END IF; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++ ++-- ++-- forums.create_topic( forum, author, sticky_level, title, contents, enable_code, enable_smileys, sig ) ++-- ++-- Creates a new topic in a forum and returns its ID ++ ++CREATE OR REPLACE FUNCTION forums.create_topic( ++ fid BIGINT, aid BIGINT, sl INT, ++ ttl TEXT, c TEXT, ec BOOLEAN, ++ es BOOLEAN, sig BIGINT ) ++ RETURNS BIGINT AS $$ ++DECLARE ++ tid BIGINT; ++ pid BIGINT; ++BEGIN ++ INSERT INTO forums.t_topic (forum, sticky_level) VALUES (fid, sl); ++ SELECT INTO tid last_inserted('t_topic'); ++ ++ INSERT INTO forums.t_post (topic, signature) VALUES (tid, sig); ++ SELECT INTO pid last_inserted('t_post'); ++ ++ INSERT INTO forums.post_text (post, author, title, contents, enable_code, enable_smileys) ++ VALUES (pid, aid, ttl, c, ec, es); ++ INSERT INTO forums.topic_read (topic, read_by) VALUES (tid, aid); ++ ++ RETURN tid; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.move_topic( topic, forum, user ) * ++-- ++-- Moves a topic from a forum to another ++ ++CREATE OR REPLACE FUNCTION forums.move_topic( tid BIGINT, did BIGINT, aid BIGINT ) RETURNS VOID AS $$ ++DECLARE ++ cfid BIGINT; ++BEGIN ++ SELECT INTO cfid forum FROM forums.t_topic WHERE id = tid; ++ IF NOT FOUND THEN ++ RETURN; ++ END IF; ++ ++ UPDATE forums.t_topic SET moved_from = cfid, forum = did WHERE id = tid; ++ DELETE FROM forums.topic_read WHERE topic = tid; ++ PERFORM forums.mark_topic_read( tid, aid ); ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.add_reply( reply_to, author, title, contents, enable_code, enable_smileys, signature ) ++-- ++-- Posts a reply to a post ++ ++CREATE OR REPLACE FUNCTION forums.add_reply ( ++ rid BIGINT, aid BIGINT, ttl TEXT, ++ c TEXT, ec BOOLEAN, es BOOLEAN, ++ sig BIGINT ) ++ RETURNS BIGINT AS $$ ++DECLARE ++ rr RECORD; ++ pid BIGINT; ++BEGIN ++ SELECT INTO rr * FROM forums.t_post WHERE id = rid AND deleted IS NULL; ++ IF NOT FOUND THEN ++ RETURN -1; ++ END IF; ++ ++ INSERT INTO forums.t_post (topic, reply_to, signature, depth) ++ VALUES (rr.topic, rid, sig, rr.depth + 1); ++ SELECT INTO pid last_inserted('t_post'); ++ ++ INSERT INTO forums.post_text (post, author, title, contents, enable_code, enable_smileys) ++ VALUES (pid, aid, ttl, c, ec, es); ++ ++ PERFORM forums.mark_topic_read( rr.topic, aid ); ++ ++ RETURN pid; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.edit_post( post, author, contents, enable_code, enable_smileys, change_signature, signature ) ++-- ++-- Modifies a post ++ ++CREATE OR REPLACE FUNCTION forums.edit_post ( ++ pid BIGINT, aid BIGINT, ttl TEXT, ++ c TEXT, ec BOOLEAN, es BOOLEAN, ++ chsig BOOLEAN, sig BIGINT ) ++ RETURNS VOID AS $$ ++DECLARE ++ tid BIGINT; ++BEGIN ++ PERFORM * FROM forums.t_post WHERE id = pid AND deleted IS NULL; ++ IF NOT FOUND THEN ++ RETURN; ++ END IF; ++ ++ INSERT INTO forums.post_text (post, author, title, contents, enable_code, enable_smileys) ++ VALUES (pid, aid, ttl, c, ec, es); ++ IF chsig THEN ++ UPDATE forums.t_post SET signature = sig WHERE id = pid; ++ END IF; ++ ++ SELECT INTO tid topic FROM forums.t_post WHERE id = pid; ++ PERFORM forums.mark_topic_read( tid, aid ); ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.delete_post( post, moderator ) ++-- ++-- Marks a post as deleted; delete the topic if the post is the topic's "main" post ++ ++CREATE OR REPLACE FUNCTION forums.delete_post ( pid BIGINT, mid BIGINT ) RETURNS VOID AS $$ ++DECLARE ++ ppid BIGINT; ++ cts INT; ++BEGIN ++ SELECT INTO ppid reply_to FROM forums.t_post WHERE id = pid AND deleted IS NULL; ++ IF NOT FOUND THEN ++ RETURN; ++ END IF; ++ ++ cts := UNIX_TIMESTAMP(NOW()); ++ IF ppid IS NULL THEN ++ -- Delete the topic ++ SELECT INTO ppid topic FROM forums.t_post WHERE id = pid; ++ UPDATE forums.t_topic SET deleted = cts, deleted_by = mid WHERE id = ppid; ++ UPDATE forums.poll SET deleted = cts, deleted_by = mid WHERE topic = ppid; ++ UPDATE forums.t_post SET deleted = cts, deleted_by = mid ++ WHERE topic = ppid AND deleted IS NULL; ++ ELSE ++ -- Delete the post and reparent replies; ++ UPDATE forums.t_post SET deleted = cts, deleted_by = mid WHERE id = pid; ++ UPDATE forums.t_post SET reply_to = ppid, depth = depth - 1 WHERE reply_to = pid; ++ END IF; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.restore_post( post ) ++-- ++-- Restores a deleted post or, if that post was the first of a topic, restore the whole topic ++ ++CREATE OR REPLACE FUNCTION forums.restore_post ( pid BIGINT ) RETURNS VOID AS $$ ++DECLARE ++ r RECORD; ++BEGIN ++ SELECT INTO r reply_to, deleted, topic FROM forums.t_post ++ WHERE id = pid AND deleted IS NOT NULL; ++ IF NOT FOUND THEN ++ RETURN; ++ END IF; ++ ++ IF r.reply_to IS NULL THEN ++ -- Restore the topic ++ UPDATE forums.t_post SET deleted = NULL, deleted_by = NULL ++ WHERE topic = r.topic AND deleted = r.deleted; ++ UPDATE forums.t_topic SET deleted = NULL, deleted_by = NULL ++ WHERE id = r.topic; ++ UPDATE forums.poll SET deleted = NULL, deleted_by = NULL ++ WHERE topic = r.topic AND deleted = r.deleted; ++ ELSE ++ -- Restore the post ++ UPDATE forums.t_post SET deleted = NULL, deleted_by = NULL ++ WHERE id = pid; ++ END IF; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++ ++ ++-- -------------------------------------------------------- ++-- FORUM POLLS ++-- -------------------------------------------------------- ++ ++-- ++-- forums.create_poll( topic, title ) ++-- ++-- Adds a new poll to the database and returns the new row ++ ++CREATE OR REPLACE FUNCTION forums.create_poll( t_id BIGINT, ttl TEXT ) RETURNS forums.poll AS $$ ++DECLARE ++ rv forums.poll; ++BEGIN ++ INSERT INTO forums.poll (topic, title) VALUES (t_id, ttl); ++ SELECT INTO rv * FROM forums.poll WHERE topic = t_id; ++ RETURN rv; ++EXCEPTION WHEN unique_violation OR foreign_key_violation THEN ++ rv.topic := NULL; ++ rv.title := NULL; ++ RETURN rv; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.create_option( poll, order, title ) ++-- ++-- Adds a poll option at the specified order and returns the new row ++ ++CREATE OR REPLACE FUNCTION forums.create_option( p_id BIGINT, oo INT, ttl TEXT ) RETURNS forums.poll_option AS $$ ++DECLARE ++ rv forums.poll_option; ++ rec RECORD; ++ noid BIGINT; ++BEGIN ++ FOR rec IN SELECT * FROM forums.poll_option ++ WHERE poll = p_id AND po_order >= oo ++ ORDER BY po_order DESC ++ FOR UPDATE ++ LOOP ++ UPDATE forums.poll_option SET po_order = po_order + 1 WHERE id = rec.id; ++ END LOOP; ++ ++ INSERT INTO forums.poll_option (poll, po_order, title) VALUES (p_id, oo, ttl); ++ SELECT INTO noid last_inserted('poll_option'); ++ ++ SELECT INTO rv * FROM forums.poll_option WHERE id = noid; ++ RETURN rv; ++ ++EXCEPTION WHEN foreign_key_violation OR unique_violation THEN ++ rv.id := NULL; ++ rv.poll := NULL; ++ RETURN rv; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.delete_option( poll, order ) ++-- ++-- Removes a poll option ++ ++CREATE OR REPLACE FUNCTION forums.delete_option( p_id BIGINT, oo INT ) RETURNS VOID AS $$ ++DECLARE ++ rec RECORD; ++BEGIN ++ PERFORM * FROM forums.poll_option WHERE poll = p_id AND po_order > oo FOR UPDATE; ++ DELETE FROM forums.poll_option WHERE poll = p_id AND po_order = oo; ++ FOR rec IN SELECT id FROM forums.poll_option WHERE poll = p_id AND po_order > oo ORDER BY po_order ASC ++ LOOP ++ UPDATE forums.poll_option SET po_order = po_order - 1 WHERE id = rec.id; ++ END LOOP; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.move_opt_up( poll, order ) ++-- ++-- Moves a poll option up in the list and returns a boolean to indicate success or failure ++ ++CREATE OR REPLACE FUNCTION forums.move_opt_up( p_id BIGINT, oo INT ) RETURNS BOOLEAN AS $$ ++DECLARE ++ c BIGINT; ++BEGIN ++ IF oo = 0 THEN ++ RETURN FALSE; ++ END IF; ++ ++ SELECT INTO c COUNT(*) FROM forums.poll_option WHERE poll = p_id; ++ IF NOT FOUND OR oo >= c THEN ++ RETURN FALSE; ++ END IF; ++ ++ UPDATE forums.poll_option SET po_order = c WHERE poll = p_id AND po_order = oo - 1; ++ UPDATE forums.poll_option SET po_order = po_order - 1 WHERE poll = p_id AND po_order = oo; ++ UPDATE forums.poll_option SET po_order = oo WHERE poll = p_id AND po_order = c; ++ RETURN TRUE; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.move_opt_down( poll, order ) ++-- ++-- Moves a poll option down in the list and returns a boolean to indicate success or failure ++ ++CREATE OR REPLACE FUNCTION forums.move_opt_down( p_id BIGINT, oo INT ) RETURNS BOOLEAN AS $$ ++DECLARE ++ c BIGINT; ++BEGIN ++ SELECT INTO c COUNT(*) FROM forums.poll_option WHERE poll = p_id; ++ IF NOT FOUND OR oo >= c - 1 THEN ++ RETURN FALSE; ++ END IF; ++ ++ UPDATE forums.poll_option SET po_order = c WHERE poll = p_id AND po_order = oo + 1; ++ UPDATE forums.poll_option SET po_order = po_order + 1 WHERE poll = p_id AND po_order = oo; ++ UPDATE forums.poll_option SET po_order = oo WHERE poll = p_id AND po_order = c; ++ RETURN TRUE; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++-- ++-- forums.set_vote( user, poll, option ) ++-- ++-- Sets the vote on a forum poll ++ ++CREATE OR REPLACE FUNCTION forums.set_vote( u_id BIGINT, p_id BIGINT, o_id BIGINT ) RETURNS VOID AS $$ ++DECLARE ++ p BIGINT; ++BEGIN ++ -- Delete the previous vote ++ DELETE FROM forums.poll_vote ++ WHERE account = u_id ++ AND vote IN (SELECT id FROM forums.poll_option WHERE poll = p_id); ++ ++ IF o_id IS NOT NULL THEN ++ -- Add the new vote if that is possible ++ SELECT INTO p poll FROM forums.poll_option WHERE id = o_id; ++ IF NOT FOUND OR p <> p_id THEN ++ RETURN; ++ END IF; ++ INSERT INTO forums.poll_vote (vote, account) VALUES (o_id, u_id); ++ END IF; ++END; ++$$ LANGUAGE plpgsql; +diff -Naur beta5//sql/forums/FORUMS.sql forums//sql/forums/FORUMS.sql +--- beta5//sql/forums/FORUMS.sql 1970-01-01 01:00:00.000000000 +0100 ++++ forums//sql/forums/FORUMS.sql 2011-02-05 10:10:01.764335002 +0100 +@@ -0,0 +1,19 @@ ++-- LegacyWorlds Beta 5 ++-- PostgreSQL database scripts ++-- ++-- forums/FORUMS.sql ++-- ++-- Install the forums' generic tables and code ++-- ++-- Copyright(C) 2004-2007, DeepClone Development ++-- -------------------------------------------------------- ++ ++ ++-- Execute the forums' installation scripts ++\i forums/00-schema.sql ++\i forums/01-forums.sql ++\i forums/01-signatures.sql ++\i forums/02-topics.sql ++\i forums/03-polls.sql ++\i forums/10-views.sql ++\i forums/11-access-functions.sql +diff -Naur beta5//sql/INSTALL.sql forums//sql/INSTALL.sql +--- beta5//sql/INSTALL.sql 2011-02-05 10:09:56.244335002 +0100 ++++ forums//sql/INSTALL.sql 2011-03-12 15:03:56.721300053 +0100 +@@ -10,16 +10,20 @@ + + \i 00-init.sql + \i 01-inheritance.sql ++ + \i 10-main.sql + \i 11-main-enums.sql + \i 12-main-tables.sql + \i 13-main-donations.sql +-\i 13-main-forums.sql ++-- \i 13-main-forums.sql + \i 13-main-links.sql + \i 13-main-manual.sql + \i 13-main-proxy.sql ++\i 14-main-forums.sql ++\i 15-main-gf-functions.sql + \i 18-main-functions.sql + \i 19-main-values.sql ++ + \i 25-ctf-maps.sql + \i 25-predefined-alliances.sql + \i 30-beta5.sql +diff -Naur beta5//sql/tools/destroy_alliance_forums.sql forums//sql/tools/destroy_alliance_forums.sql +--- beta5//sql/tools/destroy_alliance_forums.sql 1970-01-01 01:00:00.000000000 +0100 ++++ forums//sql/tools/destroy_alliance_forums.sql 2011-02-05 10:10:01.714335002 +0100 +@@ -0,0 +1,11 @@ ++ALTER TABLE alliance DROP COLUMN f_category; ++ ++-- grep 'CREATE TABLE' beta5/structure/02-alliance-forums.sql | sed -e 's/CREATE/DROP/' -e 's/ ($/ CASCADE;/' >>tools/destroy_alliance_forums.sql ++-- grep 'CREATE OR REPLACE FUNCTION' beta5/structure/02-alliance-forums.sql | sed -e 's/CREATE OR REPLACE/DROP/' -e 's/).*$/) CASCADE;/' >> tools/destroy_alliance_forums.sql ++ ++DROP TABLE alliance_forum CASCADE; ++DROP TABLE al_rank_forum CASCADE; ++ ++DROP FUNCTION create_alliance_forum( pid BIGINT, aid BIGINT, fo INT, ttl TEXT, dsc TEXT, am TEXT) CASCADE; ++DROP FUNCTION modify_alliance_forum( pid BIGINT, fid BIGINT, ttl TEXT, dsc TEXT, am TEXT ) CASCADE; ++DROP FUNCTION get_aforums_privs( pid 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) CASCADE; +diff -Naur beta5//sql/tools/destroy_forums.sql forums//sql/tools/destroy_forums.sql +--- beta5//sql/tools/destroy_forums.sql 1970-01-01 01:00:00.000000000 +0100 ++++ forums//sql/tools/destroy_forums.sql 2011-02-05 10:10:01.714335002 +0100 +@@ -0,0 +1,39 @@ ++DROP SCHEMA forums CASCADE; ++ ++SET search_path="b5",public; ++\i tools/destroy_alliance_forums.sql ++ ++SET search_path="b5m0",public; ++\i tools/destroy_alliance_forums.sql ++ ++SET search_path=public; ++-- grep 'CREATE TABLE' 1[45]*.sql | awk -F: '{print $2}' | sed -e 's/CREATE/DROP/' -e 's/ ($/ CASCADE;/' >>tools/destroy_forums.sql ++-- grep 'CREATE OR REPLACE FUNCTION' 1[45]*.sql | awk -F: '{print $2}' | sed -e 's/CREATE OR REPLACE/DROP/' -e 's/).*$/) CASCADE;/' >> tools/destroy_forums.sql ++ ++DROP TABLE main.gf_category CASCADE; ++DROP TABLE main.gf_forum CASCADE; ++DROP TABLE main.gf_ban CASCADE; ++DROP TABLE main.gf_admin CASCADE; ++DROP TABLE main.gf_cat_moderator CASCADE; ++DROP TABLE main.gf_forum_moderator CASCADE; ++DROP TABLE main.user_category CASCADE; ++DROP TABLE main.user_forum CASCADE; ++DROP TABLE main.uf_subscription CASCADE; ++DROP TABLE main.uf_invite CASCADE; ++DROP FUNCTION main.trgf_gf_forum_check () CASCADE; ++DROP FUNCTION main.trgf_gf_admin_check () CASCADE; ++DROP FUNCTION main.trgf_gf_cmod_check () CASCADE; ++DROP FUNCTION main.trgf_gf_fmod_check() CASCADE; ++DROP FUNCTION main.init_general_forums() CASCADE; ++DROP FUNCTION main.init_version_forums( v TEXT ) CASCADE; ++DROP FUNCTION main.init_game_forums( g TEXT ) CASCADE; ++DROP FUNCTION main.get_gf_categories( ver TEXT, game TEXT ) CASCADE; ++DROP FUNCTION main.get_gf_list( ver TEXT, game TEXT ) CASCADE; ++DROP 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) CASCADE; ++DROP FUNCTION main.uf_get_access_mode( fid BIGINT ) CASCADE; ++DROP FUNCTION main.uf_get_user_access( aid BIGINT, fid BIGINT ) CASCADE; ++DROP FUNCTION main.uf_get_category( aid BIGINT ) CASCADE; ++DROP FUNCTION main.uf_create_forum( aid BIGINT, fo INT, ttl TEXT, dsc TEXT, ua TEXT, am TEXT, pass TEXT) CASCADE; ++DROP FUNCTION main.trgf_account_user_forums() CASCADE; ++DROP FUNCTION main.trgf_uf_subscription_check() CASCADE; ++DROP FUNCTION main.trgf_uf_invite_check() CASCADE; +diff -Naur beta5//sql/tools/make_alliance_forums.sql forums//sql/tools/make_alliance_forums.sql +--- beta5//sql/tools/make_alliance_forums.sql 1970-01-01 01:00:00.000000000 +0100 ++++ forums//sql/tools/make_alliance_forums.sql 2011-02-05 10:10:01.714335002 +0100 +@@ -0,0 +1,115 @@ ++ALTER TABLE alliance ADD COLUMN f_category BIGINT NOT NULL DEFAULT forums.make_category('beta5/aforums') REFERENCES forums.category (id); ++CREATE INDEX alliance_f_category ON alliance (f_category); ++\i beta5/structure/02-alliance-forums.sql ++ ++ ++ ++CREATE OR REPLACE FUNCTION get_player_uid( pid BIGINT ) RETURNS BIGINT AS $$ ++DECLARE ++ i BIGINT; ++BEGIN ++ SELECT INTO i userid FROM player WHERE id = pid; ++ RETURN i; ++END; ++$$ LANGUAGE plpgsql; ++ ++CREATE OR REPLACE FUNCTION upgrade_alf_replies(opid BIGINT, npid BIGINT) RETURNS VOID AS $$ ++DECLARE ++ rep RECORD; ++ nrid BIGINT; ++BEGIN ++ -- For each reply ++ FOR rep IN SELECT * FROM af_post WHERE reply_to = opid ++ LOOP ++ -- Post the reply ++ SELECT INTO nrid forums.add_reply( npid, get_player_uid(rep.author), rep.title, rep.contents, ++ rep.enable_code, rep.enable_smileys, NULL ); ++ UPDATE forums.post_text SET moment = rep.moment WHERE post = nrid; ++ ++ -- Check for edited post ++ IF rep.edited IS NOT NULL THEN ++ INSERT INTO forums.post_text (post, moment, author, title, contents, enable_code, enable_smileys) ++ VALUES (nrid, rep.edited, get_player_uid(rep.edited_by), rep.title, rep.contents, ++ rep.enable_code, rep.enable_smileys); ++ END IF; ++ ++ -- Handle replies ++ PERFORM upgrade_alf_replies( rep.id, nrid ); ++ END LOOP; ++END; ++$$ LANGUAGE plpgsql; ++ ++CREATE OR REPLACE FUNCTION upgrade_alf_contents(ofid INT, nfid BIGINT) RETURNS VOID AS $$ ++DECLARE ++ top RECORD; ++ fpost RECORD; ++ ntid BIGINT; ++ fpid BIGINT; ++BEGIN ++ RAISE NOTICE 'Importing alliance forum #% (as #%)', ofid, nfid; ++ ++ -- For each topic ++ FOR top IN SELECT * FROM af_topic WHERE forum = ofid ++ LOOP ++ -- Create it ++ SELECT INTO fpost * FROM af_post WHERE id = top.first_post; ++ SELECT INTO ntid forums.create_topic( ++ nfid, get_player_uid(fpost.author), CASE top.sticky WHEN true THEN 10 ELSE 0 END, ++ fpost.title, fpost.contents, fpost.enable_code, fpost.enable_smileys, NULL ++ ); ++ ++ -- Get the first post and fix its timestamp ++ SELECT INTO fpid id FROM forums.t_post WHERE topic = ntid; ++ UPDATE forums.post_text SET moment = fpost.moment WHERE post = fpid; ++ ++ -- Add an entry if it has been edited ++ IF fpost.edited IS NOT NULL THEN ++ INSERT INTO forums.post_text (post, moment, author, title, contents, enable_code, enable_smileys) ++ VALUES (fpid, fpost.edited, get_player_uid(fpost.edited_by), fpost.title, fpost.contents, ++ fpost.enable_code, fpost.enable_smileys); ++ END IF; ++ ++ PERFORM upgrade_alf_replies( fpost.id, fpid ); ++ END LOOP; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++CREATE OR REPLACE FUNCTION upgrade_alliance( aid INT, cid BIGINT ) RETURNS VOID AS $$ ++DECLARE ++ af RECORD; ++ arf RECORD; ++ nfid BIGINT; ++BEGIN ++ FOR af IN SELECT * FROM af_forum WHERE alliance = aid ++ LOOP ++ SELECT INTO nfid forums.make_forum( cid, af.forder, af.title, af.description ); ++ INSERT INTO alliance_forum VALUES ( nfid, aid, CASE af.user_post WHEN TRUE THEN 'T' ELSE 'P' END); ++ FOR arf IN SELECT * FROM algr_forums WHERE forum = af.id ++ LOOP ++ INSERT INTO al_rank_forum VALUES (arf.grade, nfid, arf.is_mod); ++ END LOOP; ++ PERFORM upgrade_alf_contents( af.id, nfid ); ++ END LOOP; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++CREATE OR REPLACE FUNCTION upgrade_alliance_forums() RETURNS VOID AS $$ ++DECLARE ++ al RECORD; ++BEGIN ++ FOR al IN SELECT * FROM alliance ++ LOOP ++ RAISE NOTICE 'Upgrading forums for alliance #% [%]', al.id, al.tag; ++ PERFORM upgrade_alliance( al.id, al.f_category ); ++ END LOOP; ++END; ++$$ LANGUAGE plpgsql; ++ ++SELECT upgrade_alliance_forums(); ++ ++DROP FUNCTION upgrade_alliance_forums() ; ++DROP FUNCTION upgrade_alliance( INT, BIGINT ) ; ++DROP FUNCTION upgrade_alf_contents( INT, BIGINT) ; ++DROP FUNCTION get_player_uid( BIGINT ) ; +diff -Naur beta5//sql/tools/make_forums.sql forums//sql/tools/make_forums.sql +--- beta5//sql/tools/make_forums.sql 1970-01-01 01:00:00.000000000 +0100 ++++ forums//sql/tools/make_forums.sql 2011-02-05 10:10:01.714335002 +0100 +@@ -0,0 +1,168 @@ ++\i 14-main-forums.sql ++\i 15-main-gf-functions.sql ++\i 15-main-uforums.sql ++ ++SELECT forums.add_category_type( 'beta5/aforums', 'en', 'Alliance forums' ); ++ ++ ++CREATE OR REPLACE FUNCTION upgrade_gen_replies(opid BIGINT, npid BIGINT) RETURNS VOID AS $$ ++DECLARE ++ rep RECORD; ++ nrid BIGINT; ++BEGIN ++ -- For each reply ++ FOR rep IN SELECT * FROM main.f_post WHERE reply_to = opid AND deleted IS NULL ++ LOOP ++ -- Post the reply ++ SELECT INTO nrid forums.add_reply( npid, rep.author, rep.title, rep.contents, ++ rep.enable_code, rep.enable_smileys, NULL ); ++ UPDATE forums.post_text SET moment = rep.moment WHERE post = nrid; ++ ++ -- Check for edited post ++ IF rep.edited IS NOT NULL THEN ++ INSERT INTO forums.post_text (post, moment, author, title, contents, enable_code, enable_smileys) ++ VALUES (nrid, rep.edited, rep.edited_by, rep.title, rep.contents, ++ rep.enable_code, rep.enable_smileys); ++ END IF; ++ ++ -- Handle replies ++ PERFORM upgrade_gen_replies( rep.id, nrid ); ++ END LOOP; ++END; ++$$ LANGUAGE plpgsql; ++ ++CREATE OR REPLACE FUNCTION upgrade_gen_forum(ofid INT, nfid BIGINT) RETURNS VOID AS $$ ++DECLARE ++ top RECORD; ++ fpost RECORD; ++ ntid BIGINT; ++ fpid BIGINT; ++BEGIN ++ RAISE NOTICE 'Importing general forum #% (as #%)', ofid, nfid; ++ ++ -- For each topic ++ FOR top IN SELECT * FROM main.f_topic WHERE forum = ofid AND deleted IS NULL ++ LOOP ++ -- Create it ++ SELECT INTO fpost * FROM main.f_post WHERE id = top.first_post; ++ SELECT INTO ntid forums.create_topic( ++ nfid, fpost.author, CASE top.sticky WHEN true THEN 10 ELSE 0 END, ++ fpost.title, fpost.contents, fpost.enable_code, fpost.enable_smileys, NULL ++ ); ++ ++ -- Get the first post and fix its timestamp ++ SELECT INTO fpid id FROM forums.t_post WHERE topic = ntid; ++ UPDATE forums.post_text SET moment = fpost.moment WHERE post = fpid; ++ ++ -- Add an entry if it has been edited ++ IF fpost.edited IS NOT NULL THEN ++ INSERT INTO forums.post_text (post, moment, author, title, contents, enable_code, enable_smileys) ++ VALUES (fpid, fpost.edited, fpost.edited_by, fpost.title, fpost.contents, ++ fpost.enable_code, fpost.enable_smileys); ++ END IF; ++ ++ PERFORM upgrade_gen_replies( fpost.id, fpid ); ++ END LOOP; ++END; ++$$ LANGUAGE plpgsql; ++ ++ ++CREATE OR REPLACE FUNCTION upgrade_forums() RETURNS VOID AS $$ ++DECLARE ++ mcid BIGINT; ++ cid_v BIGINT; ++ cid_b5 BIGINT; ++ cid_m5 BIGINT; ++ fid BIGINT; ++BEGIN ++ -- Main forums ++ SELECT INTO mcid main.init_general_forums(); ++ -- Announcements ++ SELECT INTO fid forums.make_forum( mcid, 0, 'Announcements', ++ 'Updates on the game''s progress, new versions, etc...'); ++ INSERT INTO main.gf_forum VALUES ( fid, 'MP' ); ++ PERFORM upgrade_gen_forum( 1, fid ); ++ -- The Pub ++ SELECT INTO fid forums.make_forum( mcid, 1, 'The Pub', 'Discuss whatever''s on your mind in here.'); ++ INSERT INTO main.gf_forum VALUES ( fid, 'UL' ); ++ PERFORM upgrade_gen_forum( 3, fid ); ++ -- M&A ++ SELECT INTO fid forums.make_forum( mcid, 2, 'Malcontents and Anarchists', ++ 'Forum in which public executions take place.'); ++ INSERT INTO main.gf_forum VALUES ( fid, 'MP' ); ++ PERFORM upgrade_gen_forum( 4, fid ); ++ -- Admin policies ++ SELECT INTO fid forums.make_forum( mcid, 3, '[ADMIN] Main moderators/admins board', ++ 'Forum only available to Legacy Worlds administrators and moderators.'); ++ INSERT INTO main.gf_forum VALUES ( fid, 'MO' ); ++ PERFORM upgrade_gen_forum( 12, fid ); ++ ++ ++ -- Beta 5 *version* forums ++ SELECT INTO cid_v main.init_version_forums('beta5'); ++ -- New / Improved features ++ SELECT INTO fid forums.make_forum( cid_v, 0, 'New / Improved features', ++ 'Your ideas about how to improve the game and our response to these ideas.' ); ++ PERFORM upgrade_gen_forum( 2, fid); ++ -- Bugs and Problems ++ SELECT INTO fid forums.make_forum( cid_v, 1, 'Bugs and Problems', ++ 'Whine about what you think is wrong with Beta 5 in this forum.' ); ++ PERFORM upgrade_gen_forum( 7, fid); ++ -- Help ++ SELECT INTO fid forums.make_forum( cid_v, 2, 'Help', 'Ask the staff and other players for advice.' ); ++ PERFORM upgrade_gen_forum( 8, fid); ++ ++ ++ -- Beta 5 *game* forums ++ SELECT INTO cid_b5 main.init_game_forums('beta5'); ++ -- General Discussion ++ SELECT INTO fid forums.make_forum( cid_b5, 0, 'General Discussion', ++ 'Discuss what''s going on in the Beta 5 universe.' ); ++ PERFORM upgrade_gen_forum( 5, fid ); ++ -- Alliance Recruitment ++ SELECT INTO fid forums.make_forum( cid_b5, 1, 'Alliance Recruitment', ++ 'Advertise for your alliance and recruit new members through this forum.' ); ++ PERFORM upgrade_gen_forum( 6, fid ); ++ -- Marketplace Advertisement ++ SELECT INTO fid forums.make_forum( cid_b5, 2, 'Marketplace Advertisement', ++ 'Advertise for items you''ve put on sale in the marketplace or technologies you''re willing to provide through diplomacy.' ); ++ PERFORM upgrade_gen_forum( 9, fid ); ++ ++ ++ -- Beta 5 *match* forums ++ SELECT INTO cid_m5 main.init_game_forums('b5match'); ++ -- General Discussion ++ SELECT INTO fid forums.make_forum( cid_m5, 0, 'General Discussion', ++ 'Discuss what''s going on in the Beta 5 match.' ); ++ PERFORM upgrade_gen_forum( 11, fid ); ++ -- Alliance Recruitment ++ SELECT INTO fid forums.make_forum( cid_m5, 1, 'Alliance Recruitment', ++ 'Advertise for your alliance and recruit new members through this forum.' ); ++ -- Marketplace Advertisement ++ SELECT INTO fid forums.make_forum( cid_m5, 2, 'Marketplace Advertisement', ++ 'Advertise for items you''ve put on sale in the marketplace or technologies you''re willing to provide through diplomacy.' ); ++ ++ ++ -- Add admins and mods ++ INSERT INTO main.gf_admin (account) VALUES (1); ++ INSERT INTO main.gf_admin (account) VALUES (2); ++ INSERT INTO main.gf_admin (account) VALUES (3); ++ INSERT INTO main.gf_admin (account) VALUES (4); ++ INSERT INTO main.gf_cat_moderator (account) VALUES (7); ++ INSERT INTO main.gf_cat_moderator (account) VALUES (8); ++END; ++$$ LANGUAGE plpgsql; ++ ++SELECT upgrade_forums(); ++ ++DROP FUNCTION upgrade_forums() ; ++DROP FUNCTION upgrade_gen_forum(ofid INT, nfid BIGINT) ; ++DROP FUNCTION upgrade_gen_replies(BIGINT, BIGINT) ; ++ ++SET search_path=b5,main,public; ++\i tools/make_alliance_forums.sql ++SET search_path=b5m0,main,public; ++\i tools/make_alliance_forums.sql ++SET search_path=public; ++ ++VACUUM ANALYZE; diff --git a/planetgen/generate.pl b/planetgen/generate.pl new file mode 100755 index 0000000..11ed5b4 --- /dev/null +++ b/planetgen/generate.pl @@ -0,0 +1,270 @@ +#!/usr/bin/perl + +# +# Planet Generator +# +# Syntax: ./generate.pl +# +# Generates a random POVRay script for a planet +# + + +sub genSingle +{ + # Load template + open(TMPL, "; + close TMPL; + + # Generate texture + $pSize = sprintf("%.2f", 1 + rand() * 0.75); + @types = ("agate", "bozo", "granite", "wood"); + $type = $types[int(rand() * @types)]; + $ambient = rand() * .25; + $shining = rand() * .1; + $txt = "pigment {\n\t\t\t$type\n"; + + # Generate turbulence + @tm = (int(rand() * 3), int(rand() * 3), int(rand() * 3)); + $toc = 1 + int(rand() * 3); + $tol = sprintf "%.2f", 1 + rand() * 2; + $too = sprintf "%.2f", 0.25 + rand() * 0.5; + $txt .= "\t\t\twarp { turbulence <" . join(",", @tm) . "> octaves $too lambda $tol omega $too }\n"; + + # Generate color map + $mainColor = int(rand() * 3); + $secColor = int(rand() * 3); + $nColors = int(rand() * 10) + 5; + $current = 0; + $txt .= "\t\t\tcolor_map {\n"; + for ($i=0;$i<$nColors;$i++) + { + $mx = (1 - $current) / ($nColors - $i); + $cs = sprintf "%.2f", ($i == $nColors - 1) ? (1 - $current) : (rand() * $mx); + $cs += $current; + $txt .= "\t\t\t\t[$cs color rgb <"; + @cg = map { sprintf "%.2f", $_ } (0.4 + rand() * 0.5, 0.2 + rand() * 0.5, rand() * 0.5); + @rc = (); + for ($j=0;$j<3;$j++) + { + $rc[$j] = ($mainColor == $j ? $cg[0] : ($secColor == $j ? $cg[1] : $cg[2])); + } + $txt .= join(",",@rc) . ">]\n"; + $current = $cs; + + } + $txt .= "\t\t\t}\n"; + + # Random pigment rotation + $txt .= "\t\t\trotate x * " . int(rand() * 360) . "\n"; + $txt .= "\t\t\trotate y * " . int(rand() * 360) . "\n"; + $txt .= "\t\t\trotate z * " . int(rand() * 360) . "\n"; + $txt .= "\t\t}\n"; + + # Generate finish + $txt .= "\t\tfinish { ambient $ambient phong $shining }\n"; + + # Light source rotation + $lsr = int(rand() * 358); + + open(TARGET, ">$ref") or die "couldn't create target file\n"; + + # Rings + if ($pSize < 1.25 && rand() < 0.5) + { + print TARGET "#declare RINGS = 1;\n"; + $rhrad = $pSize + 0.05 + sprintf("%.2f", rand() * 0.2); + $rrad = $rhrad + 0.3 + sprintf("%.2f", rand() * 0.2); + $t = rand(); + $b = 0.6 + rand() * 0.2; + if ($t < 0.25) + { + @cg = map { sprintf "%.2f", $_ } ($b + rand() * 0.1, $b + rand() * 0.05, $b); + @rc = (); + for ($j=0;$j<3;$j++) + { + $rc[$j] = ($mainColor == $j ? $cg[0] : ($secColor == $j ? $cg[1] : $cg[2])); + } + ($rr, $rg, $rb, $rt) = (@rc,sprintf("%.2f",0.2+rand()*0.15)); + } + else + { + ($rr, $rg, $rb, $rt) = map { sprintf "%.2f", $_ } ($b,$b,$b,0.2+rand()*0.15); + } + + $rcmap = "\t\t\t\t[0.00 color rgbt <$rr,$rg,$rb,1>]\n"; + $rcmap .= "\t\t\t\t[0.05 color rgbt <$rr,$rg,$rb,$rt>]"; + $acc = 0; + $n = 0.05; + $oldlv = 1; + while ($n < 0.99) + { + $n += 0.05; + $acc += rand(); + next if $acc < (.5/($rrad-$rhrad)); + $acc = 0; + + do { $lv = 1 + int(rand() * 3); } while ($lv == $oldlv); + $oldlv = $lv; + $lv *= $rt; + ($lv > 1) && ($lv = 1); + $rcmap .= "\n\t\t\t\t[" . sprintf("%.2f", $n) . " color rgbt <$rr,$rg,$rb,$lv>]"; + } + $rcmap .= "\t\t\t\t[1.00 color rgbt <$rr,$rg,$rb,1>]\n"; + + do { $rrot = int(rand() * 60) - 30; } while (abs($rrot) < 10); + do { $rrot2 = int(rand() * 60) - 30; } while (abs($rrot2) < 10); + } + + foreach $l (@tmpl) + { + $l =~ s/PSIZE/$pSize/; + $l =~ s/TEXTURE/$txt/; + $l =~ s/LSROT/$lsr/; + $l =~ s/RRAD/$rrad/; + $l =~ s/RHRAD/$rhrad/; + $l =~ s/RROTZ/$rrot/; + $l =~ s/RROTY/$rrot2/; + $l =~ s/RR/$rr/; + $l =~ s/RG/$rg/; + $l =~ s/RB/$rb/; + $l =~ s/RT/$rt/; + $l =~ s/RCMAP/$rcmap/; + print TARGET $l; + } + close TARGET; +} + + +sub genCluster +{ + # Load template + open(TMPL, "; + close TMPL; + + $nPlanets = 2 + int(rand() * 2); + $psBase = 1.2 / $nPlanets; + $psRand = 0.55 / $nPlanets; + $mainColor = int(rand() * 3); + @ps = @tx = @cx = @cy = @cz = (); + for ($p=0;$p<$nPlanets;$p++) + { + $pSize = sprintf("%.2f", $psBase + rand() * $psRand); + + # Generate texture + @types = ("agate", "bozo", "granite", "wood"); + $type = $types[int(rand() * @types)]; + $ambient = rand() * .25; + $shining = rand() * .1; + $txt = "pigment {\n\t\t\t$type\n"; + + # Generate turbulence + @tm = (int(rand() * 3), int(rand() * 3), int(rand() * 3)); + $toc = 1 + int(rand() * 3); + $tol = sprintf "%.2f", 1 + rand() * 2; + $too = sprintf "%.2f", 0.25 + rand() * 0.5; + $txt .= "\t\t\twarp { turbulence <" . join(",", @tm) . "> octaves $too lambda $tol omega $too }\n"; + + # Generate color map + $secColor = int(rand() * 3); + $nColors = int(rand() * 10) + 5; + $current = 0; + $txt .= "\t\t\tcolor_map {\n"; + for ($i=0;$i<$nColors;$i++) + { + $mx = (1 - $current) / ($nColors - $i); + $cs = sprintf "%.2f", ($i == $nColors - 1) ? (1 - $current) : (rand() * $mx); + $cs += $current; + $txt .= "\t\t\t\t[$cs color rgb <"; + @cg = map { sprintf "%.2f", $_ } (0.4 + rand() * 0.5, 0.2 + rand() * 0.5, rand() * 0.5); + @rc = (); + for ($j=0;$j<3;$j++) + { + $rc[$j] = ($mainColor == $j ? $cg[0] : ($secColor == $j ? $cg[1] : $cg[2])); + } + $txt .= join(",",@rc) . ">]\n"; + $current = $cs; + + } + $txt .= "\t\t\t}\n"; + + # Random pigment rotation + $txt .= "\t\t\trotate x * " . int(rand() * 360) . "\n"; + $txt .= "\t\t\trotate y * " . int(rand() * 360) . "\n"; + $txt .= "\t\t\trotate z * " . int(rand() * 360) . "\n"; + $txt .= "\t\t}\n"; + + # Generate finish + $txt .= "\t\tfinish { ambient $ambient phong $shining }\n"; + + push @ps, $pSize; + push @tx, $txt; + } + + # Generate coordinates + if ($nPlanets == 2) + { + $e = 1.5 - ($ps[0] + $ps[1]); + $w = ($e + rand() * $e) / 2; + @cx = ($w+$ps[0], -$w-$ps[1]); + @cy = @cz = (0, 0); + } + else + { + $e1 = $ps[0] + $ps[1]; + $e2 = $ps[2] + $ps[1]; + $e3 = $ps[0] + $ps[2]; + $m = (($e1>$e2 && $e1>$e3) ? $e1 : (($e2>$e1 && $e2>$e3) ? $e2 : $e3)); + $e = 1.3 - $m; + $w = ($e + rand() * $e) / 2; + $rw = $w + $m; + $d = $rw * cos(3.1415926535/6); + @cx = ($d, $d * cos(2*3.1415926535/3), $d * cos(4*3.1415926535/3)); + @cy = (0, $d * sin(2*3.1415926535/3), $d * sin(4*3.1415926535/3)); + @cz = (0, 0, 0); + } + $rotx = int(rand() * 360); + $roty = int(rand() * 360); + $rotz = int(rand() * 360); + + print join(',', @cx) . "\n"; + print join(',', @cy) . "\n"; + + # Light source rotation + $lsr = int(rand() * 358); + + open(TARGET, ">$ref") or die "couldn't create target file\n"; + foreach $l (@tmpl) + { + $l =~ s/LSROT/$lsr/; + $l =~ s/ROTX/$rotx/; + $l =~ s/ROTY/$roty/; + $l =~ s/ROTZ/$rotz/; + $l =~ s/NP;/$nPlanets;/; + for ($i=0;$i<$nPlanets;$i++) + { + $pSize = $ps[$i]; + $txt = $tx[$i]; + $x = $cx[$i]; + $y = $cy[$i]; + $z = $cz[$i]; + $np = $i + 1; + $l =~ s/PSIZE$np/$pSize/; + $l =~ s/TEXTURE$np/$txt/; + $l =~ s/X$np/$x/; + $l =~ s/Y$np/$y/; + $l =~ s/Z$np/$z/; + } + print TARGET $l; + } + close TARGET; +} + + + +$ref = $ARGV[0]; +die "Syntax: $0 \n" if $ref eq ""; + +if (rand() < 0.9) { genSingle(); } +else { genCluster(); } diff --git a/planetgen/planetmaker.pl b/planetgen/planetmaker.pl new file mode 100755 index 0000000..80433e6 --- /dev/null +++ b/planetgen/planetmaker.pl @@ -0,0 +1,45 @@ +#!/usr/bin/perl + +# +# Checks the /tmp/pgen/req--- files at regular +# intervals and generate planets accordingly. +# +# Syntax : ./planetmaker.pl +# + +$outDir = "/mnt/planets"; + +close(STDIN); +close(STDERR); +close(STDOUT); +if (fork()) +{ + exit(0); +} + +chdir("/opt/planetgen"); +mkdir("/tmp/pgen", 01777) if (! -d "/tmp/pgen"); +while (1) +{ + chop($find = `find /tmp/pgen/ -name 'req-*-*-*'`); + if ($find ne "") + { + @list = split /\n/, $find; + + foreach my $name (@list) + { + unlink($name); + my ($junk,$version,$first,$count) = split /-/, $name; + + for ($i=$first;$i<$first+$count;$i++) + { + my $fn = "/tmp/pgen/out.pov"; + `./generate.pl $fn`; + `povray +W80 +H80 +O$outDir/$version/l/$i.png -D -V $fn >/dev/null 2>&1`; + `povray +W30 +H30 +O$outDir/$version/s/$i.png -D -V $fn >/dev/null 2>&1`; + unlink($fn); + } + } + } + sleep(60); +} diff --git a/planetgen/template.pov b/planetgen/template.pov new file mode 100644 index 0000000..8a70389 --- /dev/null +++ b/planetgen/template.pov @@ -0,0 +1,41 @@ +background { +// color rgbt <.15,.13,.11,1> + color rgbt <0,0,0,1> +} + +light_source { + <0, 1000, -1000> color rgb <1,1,1> + rotate z*LSROT +} + +camera { + location <0,0,-4> + look_at <0,0,0> + right x + up y +} + +sphere { + <0,0,0>, PSIZE + texture { + TEXTURE + } +} + +#ifdef (RINGS) +disc { + <0,0,0>, <0,1,0>, RRAD, RHRAD + no_shadow + texture { + pigment { + function {x*x + z*z} + color_map { +RCMAP + } + } + finish { ambient 0.6 } + } + rotate z*RROTZ + rotate y*RROTY +} +#end diff --git a/planetgen/template2.pov b/planetgen/template2.pov new file mode 100644 index 0000000..dfe0622 --- /dev/null +++ b/planetgen/template2.pov @@ -0,0 +1,50 @@ +#declare NPLANETS = NP; + +background { +// color rgbt <.15,.13,.11,1> + color rgbt <0,0,0,1> +} + +light_source { + <0, 1000, -1000> color rgb <1,1,1> + rotate z*LSROT +} + +camera { + location <0,0,-4> + look_at <0,0,0> + right x + up y +} + +sphere { + , PSIZE1 + texture { + TEXTURE1 + } + rotate x*ROTX + rotate y*ROTY + rotate z*ROTZ +} + +sphere { + , PSIZE2 + texture { + TEXTURE2 + } + rotate x*ROTX + rotate y*ROTY + rotate z*ROTZ +} + +#if (NPLANETS > 2) +sphere { + , PSIZE3 + texture { + TEXTURE3 + } + rotate x*ROTX + rotate y*ROTY + rotate z*ROTZ +} +#end diff --git a/scripts/config.inc b/scripts/config.inc new file mode 100644 index 0000000..ae11713 --- /dev/null +++ b/scripts/config.inc @@ -0,0 +1,63 @@ + "localhost", + "dbuser" => "legacy", + "dbpass" => "", + "dbname" => "legacy", + + // Path and URL to static contents + "staticdir" => dirname(__FILE__) . "/../site/static", + "staticurl" => "http://www.legacyworlds.com/static", + + // Path to game scripts + "scriptdir" => dirname(__FILE__), + + // Path to the cache + "cachedir" => "/tmp/lwcache", + + // Debugging level + "debug" => 2, + + // Maintenance mode + "maintenance" => null, +/* + "maintenance" => array( + // Hour/Minutes/Seconds/Month/Day/Year + "until" => mktime(10, 20, 0, 1, 11, 2007), + "reason" => "Upgrading the MySQL server." + ), +*/ + + // Mac widget version numbers and location + "latestWidget" => 1, + "oldestWidget" => 1, + "widgetURL" => "http://www.legacyworlds.com/downloads/LegacyWorlds-Dashboard-latest.zip", + + // Version numbers to make us feel good + "v_engine" => "0.85a", + "v_game" => "Beta 5", + "v_rev" => "2284", + + // Control script fifo and directory + "cs_fifo" => "/tmp/.lwFifo", + "cs_path" => "/tmp/.lwControl", + + // Trace users? + "trace" => array(), + + // Do we need to actually send emails? + "sendmails" => false +); + +if (file_exists($config['cachedir'] . "/maintenance.ser")) { + $__maintenanceFile = fopen($config['cachedir'] . "/maintenance.ser", "r"); + $config['maintenance'] = unserialize(fgets($__maintenanceFile)); + fclose($__maintenanceFile); +} + +?> diff --git a/scripts/control.pl b/scripts/control.pl new file mode 100755 index 0000000..2624899 --- /dev/null +++ b/scripts/control.pl @@ -0,0 +1,574 @@ +#!/usr/bin/perl + +###################################################################### +# LegacyWorlds Beta 5 +# System control script +# +# Without arguments: +# should run as root; creates a FIFO from which it reads commands. +# +# With arguments: +# writes arguments to the pipe +###################################################################### + + +### +## Configuration +# + +$fifoPath = "/tmp/.lwFifo"; +$ctrlPath = "/tmp/.lwControl"; +$fsUser = 0; +$fsGroup = 33; + + + + +### +## Main code below +# + +use IO::File; +require POSIX; + +# If we have arguments, write to the FIFO and exit +if (@ARGV > 0) { + if ($ARGV[0] eq '--start') { + &start(); + } elsif ($ARGV[0] eq '--stop') { + &stop(); + } else { + &sendMessage(@ARGV); + exit(0); + } +} + +# Find the script's path +use FindBin qw($Bin); +if (! -f "$Bin/legacyworlds.xml") { + die "$0: could not find 'legacyworlds.xml' in '$Bin'\n"; +} +$lwConf = "$Bin/legacyworlds.xml"; + +# Fork and detach +$pid = fork(); +if ($pid < 0) { + die("$0: failed to fork\n"); +} elsif ($pid != 0) { + exit(0); +} + +# Detach +POSIX::setsid(); +close STDIN; close STDOUT; close STDERR; +open(STDIN, "/dev/null"); open(STDERR, ">/dev/null"); + +# First create the pipe if it doesn't exist +if (! -p $fifoPath) { + if (-e $fifoPath) { + die "$0: '$fifoPath' is not a pipe\n"; + } else { + POSIX::mkfifo($fifoPath, 0400) + or die "$0: unable to create '$fifoPath'\n"; + } +} +# Set the pipe's owner and group +if ($> == 0) { + chown $fsUser, $fsGroup, $fifoPath; +} else { + chown $>, $fsGroup, $fifoPath; +} +chmod 0620, $fifoPath; + +# Create the control directory if needed +if (! -d $ctrlPath) { + if (-e $ctrlPath) { + die "$0: '$ctrlPath' is not a directory\n"; + } else { + mkdir $ctrlPath, 0700 + or die "$0: unable to create '$ctrlPath'\n"; + } +} +# Set the owner and group +if ($> == 0) { + chown $fsUser, $fsGroup, $ctrlPath; +} else { + chown $>, $fsGroup, $ctrlPath; +} +chmod 0770, $ctrlPath; + +# Define commands +%commands = ( + DIE => \&endController, + MERGE => \&mergeConfiguration, + TMPID => \&tickManagerID, + TMINIT => \&tickManagerStart, + TMSTOP => \&tickManagerStop, + READY => \&gameReady, + START => \&gameStart, + SETEND => \&gameEnd, + NOEND => \&gameCancelEnd, + "END" => \&gameChangeEnd, + SETDEF => \&setDefaultGame, + BOTON => \&startBot, + BOTOFF => \&killBot, + PCPID => \&proxyDetectorID, + PCON => \&startProxyDetector, + PCOFF => \&stopProxyDetector +); + +# Reader loop +while (1) { + # Wait for someone to write to the pipe + close(FIFO); + open(FIFO, "< $fifoPath") + or die "$0: unable to open '$fifoPath'\n"; + + # Read it + $command = ; + next unless defined $command; + chomp($command); + next if $command =~ /[^A-Za-z0-9\s]/; + + # Extract the actual command + my @args = split /\s+/, $command; + $command = shift @args; + + # Check if the command is allowed + next unless defined $commands{$command}; + + #print "Got command $command, arguments = (" . join(', ', @args) . ")\n"; + &{$commands{$command}}(@args); +} + + + +### +## Helper functions +# + +sub sendMessage { + my @args = @_; + die "$0: FIFO '$fifoPath' doesn't exist\n" unless -p $fifoPath; + open(FIFO, "> $fifoPath") or die "$0: unable to open FIFO '$fifoPath'\n"; + print FIFO (join(' ', @args) . "\n"); + close(FIFO); +} + + +sub start { + $pid = fork(); + if ($pid == -1) { + die "$0: could not fork\n"; + } elsif ($pid) { + print "LegacyWorlds - Initialising game\n"; + print " -> Controller\n"; + sleep(1); + print " -> Proxy detector\n"; + &sendMessage("PCON"); + sleep(1); + print " -> Ticks manager\n"; + &sendMessage("TMINIT"); + sleep(1); + print " -> IRC bot\n"; + &sendMessage("BOTON"); + exit(0); + } +} + + +sub stop { + print "LegacyWolrds - Shutting down\n"; + print " -> IRC bot\n"; + &sendMessage("BOTOFF"); + sleep(1); + print " -> Ticks manager\n"; + &sendMessage("TMSTOP"); + sleep(1); + print " -> Proxy detector\n"; + &sendMessage("PCOFF"); + sleep(1); + print " -> Controller\n"; + &sendMessage("DIE"); + exit(0); +} + + +### +## Command functions +# + +# +# Function that terminates the controller +# +sub endController { + exit(0); +} + + +# +# Function that adds a new game +# +sub mergeConfiguration { + my $sourceFile = shift; + + return unless -f "$ctrlPath/config.$sourceFile.xml"; + + # Read the new snippet + open(NEWCONF, "< $ctrlPath/config.$sourceFile.xml"); + my @newConfiguration = ; + close(NEWCONF); + + # Read the old configuration + open(OLDCONF, "< $lwConf"); + my @oldConfiguration = ; + close(OLDCONF); + + # Merge it + my @merged = (); + foreach my $oldLine (@oldConfiguration) { + if ($oldLine =~ /^\s+<\/Games>\s*$/) { + @merged = (@merged, @newConfiguration, "\n"); + } + @merged = (@merged, $oldLine); + } + + # Write the new configuration file + open(OLDCONF, "> $lwConf"); + print OLDCONF @merged; + close OLDCONF; + + # Remove the source file + unlink "$ctrlPath/config.$sourceFile.xml"; +} + +# +# Tick manager PID update +# +sub tickManagerID { + my $pid = shift; + return unless $pid; + + open(PIDFILE, "> $ctrlPath/tickManager.pid"); + print PIDFILE "$pid " . time() . "\n"; + close(PIDFILE); +} + +# +# Start tick manager +# +sub tickManagerStart { + return if &tickManagerStatus(); + return unless -f "$Bin/ticks.php"; + if ($> == 0) { + system("su - lwticks"); + } else { + system("cd $Bin; php ticks.php"); + } +} + +# +# Stop tick manager +# +sub tickManagerStop { + my $pid; + return unless ($pid = &tickManagerStatus()); + kill 15, $pid; + unlink("$ctrlPath/tickManager.pid"); +} + +# +# Start IRC bot +# +sub startBot { + &killBot(); + if ($> == 0) { + system("su - lwbot"); + } else { + system("cd $Bin/../ircbot; (php bot.php &) /dev/null 2>&1"); + } +} + +# +# Stop IRC bot +# +sub killBot { + return unless -f "$ctrlPath/ircbot.pid"; + my $pid = `cat $ctrlPath/ircbot.pid`; + kill 15, $pid; + unlink("$ctrlPath/ircbot.pid"); +} + +# +# Check tick manager status +# +sub tickManagerStatus { + return 0 unless -f "$ctrlPath/tickManager.pid"; + + open(PIDFILE, "< $ctrlPath/tickManager.pid"); + my $data = ; + close(PIDFILE); + + chomp($data); + my ($pid, $time) = split / /, $data; + return (time() - $time < 22 ? $pid : 0); +} + +# +# Make a game ready +# +sub gameReady { + my $gName = shift; + + # Read the current configuration + open(OLDCONF, "< $lwConf"); + my @oldConfiguration = ; + close(OLDCONF); + + # Generate new configuration + my @newConf = (); + foreach my $line (@oldConfiguration) { + if ($line =~ / $lwConf"); + print NEWCONF @newConf; + close(NEWCONF); +} + +# +# Start the game earlier or later +# +sub gameStart { + my $gName = shift; + my $when = shift; + + return if ($when ne "EARLY" && $when ne "LATE"); + $when = ($when eq 'EARLY') ? -1 : 1; + $when *= 24 * 60 * 60; + + # Read the current configuration + open(OLDCONF, "< $lwConf"); + my @oldConfiguration = ; + close(OLDCONF); + + # Generate new configuration + my @newConf = (); + my $state = 0; + foreach my $line (@oldConfiguration) { + if ($state == 0 && $line =~ //) { + $state = 0; + } elsif ($line =~ //) { + my $fTick = $1; + $fTick += $when; + $line =~ s/\sfirst="[0-9]+"/ first="$fTick"/; + } + } + push @newConf, $line; + } + + # Write configuration file + open(NEWCONF, "> $lwConf"); + print NEWCONF @newConf; + close(NEWCONF); +} + +# +# Set a running game to end +# +sub gameEnd { + my $gName = shift; + my $when = shift; + + $when = $when * 60 * 60 + time(); + + # Read the current configuration + open(OLDCONF, "< $lwConf"); + my @oldConfiguration = ; + close(OLDCONF); + + # Generate new configuration + my @newConf = (); + my $state = 0; + foreach my $line (@oldConfiguration) { + if ($state == 0 && $line =~ //) { + $state = 0; + } elsif ($line =~ //) { + my $fTick = $1; + $line =~ s/\sfirst="[0-9]+"/ first="$fTick" last="$when"/; + } + } + push @newConf, $line; + } + + # Write configuration file + open(NEWCONF, "> $lwConf"); + print NEWCONF @newConf; + close(NEWCONF); +} + +# +# Set an ending game's status back to running/victory +# +sub gameCancelEnd { + my $gName = shift; + + # Read the current configuration + open(OLDCONF, "< $lwConf"); + my @oldConfiguration = ; + close(OLDCONF); + + # Generate new configuration + my @newConf = (); + my $state = 0; + foreach my $line (@oldConfiguration) { + if ($state == 0 && $line =~ //) { + $state = 0; + } elsif ($line =~ / $lwConf"); + print NEWCONF @newConf; + close(NEWCONF); +} + +# +# End the game earlier or later +# +sub gameChangeEnd { + my $gName = shift; + my $when = shift; + + return if ($when ne "EARLY" && $when ne "LATE" && $when ne "NOW"); + if ($when ne 'NOW') { + $when = ($when eq 'EARLY') ? -1 : 1; + $when *= 24 * 60 * 60; + } + + # Read the current configuration + open(OLDCONF, "< $lwConf"); + my @oldConfiguration = ; + close(OLDCONF); + + # Generate new configuration + my @newConf = (); + my $state = 0; + foreach my $line (@oldConfiguration) { + if ($state == 0 && $line =~ //) { + $state = 0; + } elsif ($line =~ / $lwConf"); + print NEWCONF @newConf; + close(NEWCONF); +} + +# +# Changes the default game +# +sub setDefaultGame { + my $gName = shift; + return if ($gName eq ""); + + # Read the current configuration + open(OLDCONF, "< $lwConf"); + my @oldConfiguration = ; + close(OLDCONF); + + # Generate new configuration + my @newConf = (); + foreach my $line (@oldConfiguration) { + if ($line =~ / $lwConf"); + print NEWCONF @newConf; + close(NEWCONF); +} + +# +# Proxy detector PID update +# +sub proxyDetectorID { + my $pid = shift; + return unless $pid; + + open(PIDFILE, "> $ctrlPath/proxyDetector.pid"); + print PIDFILE "$pid " . time() . "\n"; + close(PIDFILE); +} + +# +# Start proxy detector +# +sub startProxyDetector { + return if &proxyDetectorStatus(); + return unless -f "$Bin/proxycheck.php"; + if ($> == 0) { + system("su - lwproxy"); + } else { + system("cd $Bin; php proxycheck.php"); + } +} + +# +# Stop proxy detector +# +sub stopProxyDetector { + my $pid; + return unless ($pid = &proxyDetectorStatus()); + kill 15, $pid; + unlink("$ctrlPath/proxyDetector.pid"); +} + +# +# Check proxy detector status +# +sub proxyDetectorStatus { + return 0 unless -f "$ctrlPath/proxyDetector.pid"; + + open(PIDFILE, "< $ctrlPath/proxyDetector.pid"); + my $data = ; + close(PIDFILE); + + chomp($data); + my ($pid, $time) = split / /, $data; + return (time() - $time < 22 ? $pid : 0); +} diff --git a/scripts/game/admin/beta5/library.inc b/scripts/game/admin/beta5/library.inc new file mode 100644 index 0000000..2aca16d --- /dev/null +++ b/scripts/game/admin/beta5/library.inc @@ -0,0 +1,106 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->msgs = $this->lib->game->getLib('beta5/msg'); + $this->planets = $this->lib->game->getLib('beta5/planet'); + } + + + function getPlanetsModList($mode) { + $modes = array("r", "o", "p", "w"); + if (!in_array($mode, $modes)) { + $mode = $modes[0]; + } + + $queries = array( + "r" => "SELECT id,name,owner FROM planet WHERE status = 0 AND NOT mod_check AND force_rename IS NULL ORDER BY id", + "o" => "SELECT id,name,owner FROM planet WHERE status = 0 AND mod_check AND force_rename IS NULL ORDER BY id", + "p" => "SELECT id,name,owner FROM planet WHERE status = 0 AND mod_check AND force_rename IS NOT NULL AND force_rename>renamed ORDER BY id", + "w" => "SELECT id,name,owner FROM planet WHERE status = 0 AND NOT mod_check AND force_rename IS NOT NULL ORDER BY id", + ); + return array($mode,$this->db->query($queries[$mode])); + } + + + function validatePlanetName($id) { + $this->db->query("UPDATE planet SET mod_check=TRUE,force_rename=NULL WHERE id=$id AND status=0"); + } + + + function resetPlanet($id, $from, $manual = true) { + $q = $this->db->query("SELECT owner,name FROM planet WHERE id=$id AND status=0"); + if (!dbCount($q)) { + return; + } + list($owner, $name) = dbFetchArray($q); + + do { + $rn = strtoupper(substr(md5(uniqid(rand())), 0, 7)); + $q = $this->db->query("SELECT id FROM planet WHERE name='P-[$rn]'"); + } while(dbCount($q)); + $this->planets->call('rename', $id, "P-[$rn]"); + + $this->db->query("UPDATE planet SET ifact=3,mfact=3,turrets=3,pop=2000,mod_check=TRUE,force_rename=NULL WHERE id=$id"); + + if (!is_null($owner)) { + $this->planets->call('ownerChange', $id); + $this->msgs->call('send', $owner, 'warnname', array( + "moderator" => $from, + "planet" => $id, + "p_name" => $name, + "event" => ($manual ? 'NEUT' : 'ANEUT') + )); + } + } + + + function sendPlanetWarning($id, $from) { + $ts = time(); + $q = $this->db->query("SELECT owner,name FROM planet WHERE id=$id"); + list($owner, $name) = dbFetchArray($q); + + $this->db->query("UPDATE planet SET mod_check=TRUE,renamed=$ts-(16*86400),force_rename=$ts WHERE id=$id"); + $this->msgs->call('send', $owner, 'warnname', array( + "moderator" => $from, + "planet" => $id, + "p_name" => $name, + "event" => 'WARN' + )); + } + + + function sendSpam($from, $subject, $contents) { + // Create the "spam record" + $spamID = $this->db->query( + "INSERT INTO admin_spam(sent_by, subject, contents) VALUES (" + . "$from, '" . addslashes($subject) . "', '" . addslashes($contents) . "')" + ); + if (!$spamID) { + logText("Admin spam '$subject' could not be sent in game " . $this->game->text, LOG_ERR); + return; + } + + // Get the list of all active players + $q = $this->db->query( + "SELECT id FROM player WHERE quit IS NULL OR UNIX_TIMESTAMP(NOW()) - quit < 86400" + ); + if (!($q && dbCount($q))) { + logText("Could not fetch the list of players while sending admin spam '$subject'", LOG_ERR); + return; + } + + // Spam, spam, spam! + while ($r = dbFetchArray($q)) { + $this->msgs->call('send', $r[0], 'admin', array( + "spam" => $spamID + ), 'IN'); + } + } +} + +?> diff --git a/scripts/game/beta5/actions.inc b/scripts/game/beta5/actions.inc new file mode 100644 index 0000000..824c036 --- /dev/null +++ b/scripts/game/beta5/actions.inc @@ -0,0 +1,1193 @@ +game = $game; + $this->lib = $game->getLib(); + $this->alliance = $game->getLib('beta5/alliance'); + $this->bq = $game->getLib('beta5/bq'); + $this->fleets = $game->getLib('beta5/fleet'); + $this->forums = $game->getLib('beta5/forums'); + $this->map = $game->getLib('beta5/map'); + $this->move = $game->getLib('beta5/moving'); + $this->msgs = $game->getLib('beta5/msg'); + $this->planets = $game->getLib('beta5/planet'); + $this->players = $game->getLib('beta5/player'); + $this->rules = $game->getLib('beta5/rules'); + $this->sales = $game->getLib('beta5/sale'); + $this->standby = $game->getLib('beta5/standby'); + $this->tech = $game->getLib('beta5/tech'); + } + + + //-------------------------------------------------------------------------------------------------------------------------------- + // GENERIC FUNCTIONS + //-------------------------------------------------------------------------------------------------------------------------------- + + /** Checks whether a user plays this version; returns null if he + * doesn't or his player ID otherwise. + */ + function doesUserPlay($u) { + return $this->lib->call('doesUserPlay', $u); + } + + /** Checks whether a user has played this version. */ + function hasPlayed($u) { + return $this->lib->call('hasPlayed', $u); + } + + /** Get player count. */ + function getPlayerCount() { + return $this->lib->call('getPlayerCount'); + } + + /** Register to this game */ + function register($uid, $planet, $nick) { + return $this->lib->call('register', $uid, $planet, $nick); + } + + function leaveGame($id, $reason) { + return $this->lib->call('leaveGame', $id, $reason); + } + + // Called when a player enters vacation mode + function startVacation($player) { + return $this->lib->call('startVacation', $player); + } + + // Called when a player leaves vacation mode + function leaveVacation($player) { + return $this->lib->call('leaveVacation', $player); + } + + // Is the game finished? + function isFinished() { + return $this->lib->call('isFinished'); + } + + // Generate a listing for the generic Listing JS component + function generateListing($data, $conf, $param, $md5) { + return $this->lib->call('listing', $data, $conf, $param, $md5); + } + + + //-------------------------------------------------------------------------------------------------------------------------------- + // PLAYER-RELATED FUNCTIONS + //-------------------------------------------------------------------------------------------------------------------------------- + + // Returns a player's ID using his name to locate him + function getPlayer($name) { + return $this->players->call('getPlayerId', $name); + } + + // Returns player information for the user identified by $uid + function getPlayerInfo($id, $quitOk = false) { + return $this->players->call('get', $id, $quitOk); + } + + // Returns the name of a player's first planet + function getFirstPlanet($id) { + return $this->players->call('getFirstPlanet', $id); + } + + // Returns the name of a player + function getPlayerName($id) { + return $this->players->call('getName', $id); + } + + // Returns the list of a player's planets + function getPlayerPlanets($pid) { + return $this->players->call('getPlanets', $pid); + } + + // Returns the list of a player's fleets + function getPlayerFleets($pid) { + return $this->players->call('getFleets', $pid); + } + + // Returns a player's total fleet power + function getPlayerFleetPower($pid) { + return $this->players->call('getPower', $pid); + } + + // Loads rules for a player + function loadPlayerRules($pid) { + return $this->rules->call('get', $pid); + } + + // Checks whether a player is restrained or not + function isPlayerRestrained($pid) { + return $this->players->call('isRestrained', $pid); + } + + // Transfers cash from an account to another + function transferFunds($s, $d, $a) { + return $this->players->call('transferFunds', $s, $d, $a); + } + + // Checks whether a player is in another's enemy list + function isPlayerEnemy($pid, $aid) { + return $this->players->call('isEnemy', $pid, $aid); + } + + // Checks whether an alliance is in a player's enemy list + function isAllianceEnemy($pid, $aid) { + return $this->players->call('isAllianceEnemy', $pid, $aid); + } + + // Get the list of enemy players + function getEnemyPlayers($pid) { + return $this->players->call('getEnemies', $pid); + } + + // Get the list of enemy alliances + function getEnemyAlliances($pid) { + return $this->players->call('getEnemyAlliances', $pid); + } + + // Remove enemy players from a player's enemy list + function removeEnemyPlayer($pid, $eid) { + return $this->players->call('removeEnemy', $pid, $eid); + } + + // Remove enemy alliances from a player's enemy list + function removeEnemyAlliance($pid, $eid) { + return $this->players->call('removeEnemyAlliance', $pid, $eid); + } + + // Adds a player to the enemy list + function addEnemyPlayer($pid, $eid) { + return $this->players->call('addEnemy', $pid, $eid); + } + + // Adds an alliance to the enemy list + function addEnemyAlliance($pid, $eid) { + return $this->players->call('addEnemyAlliance', $pid, $eid); + } + + // Sets players' fleets to attack mode when they're added to an enemy list + function makeEnemies($pid, $elist) { + return $this->players->call('makeEnemies', $pid, $elist); + } + + // Checks whether a player is in another's ally list + function isPlayerAlly($pid, $aid) { + return $this->players->call('isAlly', $pid, $aid); + } + + // Get the list of a player's trusted allies + function getPlayerAllies($pid) { + return $this->players->call('getAllies', $pid); + } + + // Get the list of players who have a player as a trusted ally + function getPlayerIsAlly($pid) { + return $this->players->call('isAllyOf', $pid); + } + + // Reorders the list of allies after it's been modified + function reorderPlayerAllies($pid) { + return $this->players->call('reorderAllies', $pid); + } + + // Move an ally down the list + function moveAllyDown($pid, $it) { + return $this->players->call('moveAllyDown', $pid, $it); + } + + // Move an ally up the list + function moveAllyUp($pid, $it) { + return $this->players->call('moveAllyUp', $pid, $it); + } + + // Removes an ally from the list + function removeAlly($pid, $it) { + return $this->players->call('removeAlly', $pid, $it); + } + + // Adds an ally to the list + function addAlly($pid, $aid) { + return $this->players->call('addAlly', $pid, $aid); + } + + // Get the list of players in a player's T.A. blacklist + function getTAListBans($pid) { + return $this->players->call('getTAListBans', $pid); + } + + // Adds a player to another's T.A. blacklist + function addTAListBan($pid, $bpid) { + return $this->players->call('addTAListBan', $pid, $bpid); + } + + // Checks a player's T.A. blacklist for another player + function checkTAListBan($pid, $bpid) { + return $this->players->call('checkTAListBan', $pid, $bpid); + } + + // Removes a player from another's T.A. blacklist + function delTAListBan($pid, $bpid) { + return $this->players->call('delTAListBan', $pid, $bpid); + } + + // Returns a summary of the player's diplomatic relations + function getDiploSummary($pid) { + return $this->players->call('getDiploSummary', $pid); + } + + // Returns a player's protection level + function getProtectionLevel($pid) { + return $this->players->call('getProtectionLevel', $pid); + } + + // Marks a player as quitting + function setPlayerQuit($pid) { + return $this->players->call('setQuit', $pid); + } + + // Marks a player as no longer quitting + function cancelPlayerQuit($pid) { + return $this->players->call('cancelQuit', $pid); + } + + // Checks whether a player is currently online + function isPlayerOnline($pid) { + return $this->players->call('isOnline', $pid); + } + + // Checks whether a player is on vacation + function isOnVacation($pid) { + return $this->players->call('isOnVacation', $pid); + } + + // Checks players that have set a player as trusted allies in order to know whether the player can control their fleets + function checkPlayerAllies($pid) { + return $this->players->call('checkAllies', $pid); + } + + // Assign a planet to a player + function assignPlanet($pid, $p) { + return $this->player->call('assign', $pid, $p); + } + + // Assigns the player a new planet after he loses all of his + function reassignPlanet($player, $name) { + return $this->players->call('reassign', $player, $name); + } + + // Returns the amount of planets a player controls + function getPlanetCount($pl) { + return $this->players->call('getPlanetCount', $pl); + } + + // Returns the real number of planets a player owns, not counting planets being sold, + // abandonned or destroyed + function getRealPlanetCount($pl) { + return $this->players->call('getRealPlanetCount', $pl); + } + + //-------------------------------------------------------------------------------------------------------------------------------- + // RESEARCH AND LAWS + //-------------------------------------------------------------------------------------------------------------------------------- + + // Returns the amount of research points a player gains daily + function getResearchPoints($pid) { + return $this->tech->call('getPoints', $pid); + } + + // Returns an array containing research budget allocations + function getResearchBudget($pid) { + return $this->tech->call('getBudget', $pid); + } + + // Modifies the research budget + function setResearchBudget($pid, $ba) { + return $this->tech->call('setBudget', $pid, $ba); + } + + // Checks whether a player has researched a technology + function checkPlayerResearch($pid, $rid, $bt = false) { + return $this->tech->call('has', $pid, $rid, $bt); + } + + // Checks whether a player has already researched a technology's dependencies + function checkResearchDependencies($pid, $rid) { + return $this->tech->call('checkDependencies', $pid, $rid); + } + + // Get a list of scientific assistance offers sent and received by a player + function getResearchOffers($pid) { + return $this->tech->call('getOffers', $pid); + } + + // Checks whether a player can make an offer or if he has already done so. + function checkPlayerOffer($pid) { + return $this->tech->call('checkOffer', $pid); + } + + // Makes a research offer + function makeResearchOffer($cpid, $tpid, $tid, $pr) { + return $this->tech->call('makeOffer', $cpid, $tpid, $tid, $pr); + } + + // Accepts a research offer + function acceptResearchOffer($pid, $oid) { + return $this->tech->call('acceptOffer', $pid, $oid); + } + + // Declines a research offer + function declineResearchOffer($pid, $oid) { + return $this->tech->call('declineOffer', $pid, $oid); + } + + // Returns the list of research topics for a player; the 'status argument determines + // the type of list to return: 1 for implemented, 0 for completed, and -1 for "almost + // completed" (more than 75%) + function getResearchTopics($pid, $type) { + return $this->tech->call('getTopics', $pid, $type); + } + + // Returns data about a research topic: identifier, title, cost, description + function getResearchData($lang, $id) { + return $this->tech->call('getTopicData', $lang, $id); + } + + // Returns the list of laws available to a player, as well as their status + function getLaws($pl) { + return $this->tech->call('getLaws', $pl); + } + + // Implements a technology for a player + function implementTechnology($pl, $id) { + return $this->tech->call('implement', $pl, $id); + } + + // Enacts or revoke a law + function switchLaw($pl, $id) { + return $this->tech->call('switchLaw', $pl, $id); + } + + + //-------------------------------------------------------------------------------------------------------------------------------- + // MAP FUNCTIONS + //-------------------------------------------------------------------------------------------------------------------------------- + + // Returns statistics about the whole universe + function getUniverseSummary() { + return $this->map->call('getUniverse'); + } + + // Returns statistics about a protection zone + function getProtZoneSummary($pz) { + return $this->map->call('getProtectionZone', $pz); + } + + // Returns data about a system at specified coordinates + function getSystemAt($x, $y) { + return $this->map->call('at', $x, $y); + } + + // Returns map data for planets in a specified system + function getSystemPlanets($sid) { + return $this->map->call('getSystem', $sid); + } + + // Get planets within a distance of a set of coordinates + function getPlanetsAround($x,$y,$d) { + return $this->map->call('getPlanetsAround', $x, $y, $d); + } + + + //-------------------------------------------------------------------------------------------------------------------------------- + // MARKETPLACE FUNCTIONS + //-------------------------------------------------------------------------------------------------------------------------------- + + function getSales($pList) { + return $this->sales->call('getPublicSales', $pList); + } + + function getDirectSales($pid) { + return $this->sales->call('getDirectSales', $pid); + } + + function getSentOffers($pid) { + return $this->sales->call('getSentOffers', $pid); + } + + function getSalesHistoryFrom($player) { + return $this->sales->call('getHistoryFrom', $player); + } + + function getSalesHistoryTo($player) { + return $this->sales->call('getHistoryTo', $player); + } + + function getPlanetSale($id) { + return $this->sales->call('getPlanetSale', $id); + } + + function getFleetSale($sId) { + return $this->sales->call('getFleetSale', $sId); + } + + function newSale($player, $public, $auction, $expires, $price, $target, $planet, $fleet) { + return $this->sales->call('sell', $player, $public, $auction, $expires, $price, $target, $planet, $fleet); + } + + function cancelTransfer($pid,$id) { + return $this->sales->call('cancelTransfer', $pid,$id); + } + + function cancelSale($pid,$id) { + return $this->sales->call('cancel', $pid,$id); + } + + function isDirectOffer($pid, $oid) { + return $this->sales->call('isDirectOffer', $pid, $oid); + } + + function buy($pid, $offer) { + return $this->sales->call('buy', $pid, $offer); + } + + function placeBid($pid, $oid, $price) { + return $this->sales->call('bid', $pid, $oid, $price); + } + + function declinePrivate($id) { + return $this->sales->call('decline', $id); + } + + + //-------------------------------------------------------------------------------------------------------------------------------- + // FLEETS MANAGEMENT FUNCTIONS + //-------------------------------------------------------------------------------------------------------------------------------- + + // Returns all of the locations at which a player has stationned fleets + function getFleetLocations($pid) { + return $this->fleets->call('getPlayerLocations', $pid); + } + + // Returns the list of fleets in orbit around a planet + function getFleetsAt($pid, $pl = null) { + return $this->fleets->call('getLocation', $pid, $pl); + } + + // Invalidates the fleet cache + function invFleetCache($id = null) { + return $this->fleets->call('invCache', $id); + } + + // Returns data regarding a fleet + function getFleet($id) { + return $this->fleets->call('get', $id); + } + + // Returns data regarding current fleets + function getFleetStats($pid) { + return $this->fleets->call('getStats', $pid); + } + + // Computes a fleet's power + function getFleetPower($pl, $t, $g, $f, $c, $b) { + // FIXME: turrets + $tp = $this->planets->call('getPower', $pl, $t); + return $this->fleets->call('getPower', $pl, $g, $f, $c, $b) + $tp; + } + + // Computes a fleet's upkeep + function getFleetUpkeep($pl, $g, $f, $c, $b) { + return $this->fleets->call('getUpkeep', $pl, $g, $f, $c, $b); + } + + // Switches a fleet's status + function switchFleetStatus($id) { + return $this->fleets->call('switchStatus', $id); + } + + // Handles a fleet's arrival + function fleetArrival($fid, $dest, $from, $nStatus = null) { + return $this->fleets->call('arrival', $fid, $dest, $from, $nStatus); + } + + + // Changes a fleet's orders + function setFleetOrders($fid, $newDest, $newDelay, $attack = null) { + return $this->fleets->call('setOrders', $fid, $newDest, $newDelay, $attack); + } + + + // Sends messages related to fleet movements + function sendFleetMoveMessages() { + return $this->fleets->call('sendMoveMessages'); + } + + + // Merge fleets + function mergeFleets($fIds, $okOwners, $newName) { + return $this->fleets->call('merge', $fIds, $okOwners, $newName); + } + + + // Automatically split a fleet + function splitFleetAuto($fid, $newName, $count) { + return $this->fleets->call('autoSplit', $fid, $newName, $count); + } + + + // Manually split a fleet in player-specified fleets + function splitFleetManually($fid, $newName, $count, $sg, $sf, $sc, $sb) { + return $this->fleets->call('split', $fid, $newName, $count, $sg, $sf, $sc, $sb); + } + + + // Disbands a fleet and removes any sales / movement / stand-by + // entries associated with it + function disbandFleet($fId, $final = false) { + return $this->fleets->call('disband', $fId, $final); + } + + // Renames a fleet + function renameFleet($fid, $name) { + return $this->fleets->call('rename', $fid, $name); + } + + + //-------------------------------------------------------------------------------------------------------------------------------- + // MOVING OBJECTS MANAGEMENT FUNCTIONS + //-------------------------------------------------------------------------------------------------------------------------------- + + // Generate a new moving object entry + function newMovingObject($srcId, $dstId, $speed, $cruisers, $wait) { + return $this->move->call('newObject', $srcId, $dstId, $speed, $cruisers, $wait); + } + + // Clone a moving object + function cloneMovingObject($oid) { + return $this->move->call('cloneObject', $oid); + } + + // Get a moving object's current location + function getObjectLocation($oid) { + return $this->move->call('getLocation', $oid); + } + + // Redirects a moving object to a new destination + function redirectObject($oid, $newDest, $speed, $cruisers, $newWait) { + return $this->move->call('redirect', $oid, $newDest, $speed, $cruisers, $newWait); + } + + // Stop a moving object + function stopMovement($oid, $newWait) { + return $this->move->call('stop', $oid, $newWait); + } + + // Get an object's current trajectory + function getObjectTrajectory($oid) { + return $this->move->call('getTrajectory', $oid); + } + + // Compute an object's trajectory + function getTrajectory($srcId, $dstId, $speed, $cruisers) { + return $this->move->call('computeTrajectory', $srcId, $dstId, $speed, $cruisers); + } + + + // Generates a new Hyperspace stand-by order + function newHSWait($time, $location,$origin = null,$spent = null) { + return $this->standby->call('create', $time, $location, $origin, $spent); + } + + + // Checks whether fleets can be destroyed while waiting in hyperspace at a given location + function waitCanDestroy($location, $owner) { + return $this->standby->call('canDestroy', $location, $owner); + } + + + // Computes the probability for fleet destruction when standing by in Hyperspace + function waitGetLossProb($timeSpent) { + return $this->standby->call('getLossProb', $timeSpent); + } + + + //-------------------------------------------------------------------------------------------------------------------------------- + // PLANET MANAGEMENT + //-------------------------------------------------------------------------------------------------------------------------------- + + // Renames a planet + function renamePlanet($pid, $name) { + return $this->planets->call('rename', $pid, $name); + } + + // Returns data regarding a planet with a specified name + function getPlanetByName($n) { + return $this->planets->call('byName', $n); + } + + // Returns data regarding a planet with a specified identifier + function getPlanetById($id) { + return $this->planets->call('byId', $id); + } + + // Updates a planet's maximum population + function updateMaxPopulation($id, $formerOwner, $newOwner) { + return $this->planets->call('updateMaxPopulation', $id, $formerOwner, $newOwner); + } + + // Gets a planet's owner + function getPlanetOwner($pid) { + return $this->planets->call('getOwner', $pid); + } + + // Computes a planet's income + function getPlanetIncome($owner, $pop, $happ, $ifact, $mfact, $turrets, $corruption) { + return $this->planets->call('getIncome', $owner, $pop, $happ, $ifact, $mfact, $turrets, $corruption); + } + + // Build factories on a planet + function buildFactories($id, $nb, $t) { + return $this->planets->call('buildFactories', $id, $nb, $t); + } + + // Check a planet to see whether it's possible to destroy factories + function checkDestroyFactories($id, $nb, $t) { + return $this->planets->call('checkDestroyFactories', $id, $nb, $t); + } + + // Try to destroy turrets on a planet + function destroyTurrets($id, $nb) { + return $this->planets->call('destroyTurrets', $id, $nb); + } + + // Destroy factories on a planet + function destroyFactories($id, $nb, $t) { + return $this->planets->call('destroyFactories', $id, $nb, $t); + } + + // Updates a planet's factory history and delete old history records + function updateFactHistory($pid, $mil, $nb) { + return $this->planets->call('updateFactHistory', $pid, $mil, $nb); + } + + // Updates happiness for a planet + function updateHappiness($id) { + return $this->planets->call('updateHappiness', $id); + } + + // Abandon a planet + function setPlanetAbandon($pid, $start = true) { + return $this->planets->call('setAbandon', $pid, $start); + } + + // Destroy a planet + function setPlanetBoom($pid, $start = true) { + return $this->planets->call('setBoom', $pid, $start); + } + + function updateAttackStatus($planet) { + return $this->planets->call('updateMilStatus', $planet); + } + + // Returns data regarding current planets + function getPlanetStats($pid) { + return $this->planets->call('getStats', $pid); + } + + + // Returns the player's empire-wide probe policy + function getPlayerPolicy($id) { + $q = dbQuery("SELECT probe_policy FROM player WHERE id=$id"); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($r) = dbFetchArray($q); + return $r; + } + + // Sets the player's empire-wide probe policy + function setPlayerPolicy($id, $pol) { + dbQuery("UPDATE player SET probe_policy='$pol' WHERE id=$id"); + } + + // Returns a planet's probe policy + function getPlanetPolicy($id) { + $q = dbQuery("SELECT probe_policy FROM planet WHERE id=$id"); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($r) = dbFetchArray($q); + return $r; + } + + // Sets a planet's probe policy + function setPlanetPolicy($id, $pol) { + $ps = is_null($pol) ? "NULL" : "'$pol'"; + dbQuery("UPDATE planet SET probe_policy=$ps WHERE id=$id"); + } + + + //-------------------------------------------------------------------------------------------------------------------------------- + // BUILD QUEUE MANAGEMENT + //-------------------------------------------------------------------------------------------------------------------------------- + + // Returns the contents of a planet's build queue + function getBuildQueue($id) { + return $this->bq->call('get', $id); + } + + // Flush a planet's build queue + function flushBuildQueue($id) { + return $this->bq->call('flush', $id); + } + + + // Add items to a planet's build queue + function addToBuildQueue($id, $nb, $type) { + return $this->bq->call('append', $id, $nb, $type); + } + + // Removes an item from a build queue + function deleteBuildQueueItem($pid, $item) { + return $this->bq->call('remove', $pid, $item); + } + + // Reorders the build queue + function reorderBuildQueue($pid) { + return $this->bq->call('reorder', $pid); + } + + // Computes the cost of replacing a set of build queue items + function getReplacementCost($pid, $items, $rCost) { + return $this->bq->call('getReplacementCost', $pid, $items, $rCost); + } + + // Replaces items in a build queue + function replaceItems($pid, $items, $nb, $type) { + return $this->bq->call('replace', $pid, $items, $nb, $type); + } + + // Returns the length of a planet's build queue + function getQueueLength($pl) { + return $this->bq->call('getLength', $pl); + } + + // Moves an item down the build queue + function moveItemDown($pl, $it) { + return $this->bq->call('moveDown', $pl, $it); + } + + // Moves an item up the build queue + function moveItemUp($pl, $it) { + return $this->bq->call('moveUp', $pl, $it); + } + + + //-------------------------------------------------------------------------------------------------------------------------------- + // ALLIANCE MANAGEMENT + //-------------------------------------------------------------------------------------------------------------------------------- + + // Get alliance identifier by tag + function getAlliance($tag) { + return $this->alliance->call('getId', $tag); + } + + // Get alliance data + function getAllianceInfo($id) { + return $this->alliance->call('get', $id); + } + + // Create an alliance + function createAlliance($tag, $name, $founder) { + return $this->alliance->call('create', $tag, $name, $founder); + } + + // Get a list of IDs for the players having a role equivalent to Hyperiums' "Keepers" + function getAllianceKeepers($a) { + return $this->alliance->call('getKeepers', $a); + } + + // Get the list of all people in an alliance that can vote, as well as the current leader + function getAllianceVoters($a) { + return $this->alliance->call('getVoters', $a); + } + + // Sends a request to join an alliance + function sendJoinRequest($p, $a) { + return $this->alliance->call('sendRequest', $p, $a); + } + + // Cancels a request to join an alliance + function cancelJoinRequest($p, $a) { + return $this->alliance->call('cancelRequest', $p, $a); + } + + // Reads the list of ranks associated with an alliance + function getAllianceRanks($aid) { + return $this->alliance->call('getRanks', $aid); + } + + // Returns the privileges associated with a rank + function getRankPrivileges($id) { + return $this->alliance->call('getRankPrivileges', $id); + } + + // Returns the list of privileges a player has inside an alliance + function getAlliancePrivileges($p) { + return $this->alliance->call('getPrivileges', $p); + } + + // Switches an alliance's government mode + function setAllianceDemo($aid, $demo) { + return $this->alliance->call('setDemocratic', $aid, $demo); + } + + // Changes the successor for the current leader + function setAllianceSuccessor($aid, $sid) { + return $this->alliance->call('setSuccessor', $aid, $sid); + } + + // Makes the alliance leader step down from power + function allianceStepDown($aid, $isLeave = false) { + return $this->alliance->call('stepDown', $aid, $isLeave); + } + + // Leave an alliance + function leaveAlliance($pid, $isKick = false) { + return $this->alliance->call('leave', $pid, $isKick); + } + + // Get the list of pending requests for an alliance + function getAllianceRequests($aid) { + return $this->alliance->call('getRequests', $aid); + } + + // Accepts a request to join an alliance + function acceptAllianceRequest($aid, $pid, $kid) { + return $this->alliance->call('acceptRequest', $aid, $pid, $kid); + } + + // Rejects a request to join an alliance + function rejectAllianceRequest($aid, $pid, $kid) { + return $this->alliance->call('rejectRequest', $aid, $pid, $kid); + } + + // Returns the list of planets belonging to players of the alliance + function getAlliancePlanets($aid) { + return $this->alliance->call('getPlanets', $aid); + } + + // Reads the list of alliance members and their ranks + function getAllianceMembers($aid) { + return $this->alliance->call('getMembers', $aid); + } + + // Get the list of alliance candidates as well as the number of votes they have + function getAllianceCandidates($aid) { + return $this->alliance->call('getCandidates', $aid); + } + + // Changes a player's vote + function setAllianceVote($pid,$v) { + return $this->alliance->call('setVote', $pid,$v); + } + + // Adds a new candidate for an alliance's presidency + function newAllianceCandidate($aid,$pid) { + return $this->alliance->call('addCandidate', $aid,$pid); + } + + // Removes a candidate for an alliance's presidency + function removeAllianceCandidate($aid,$pid) { + return $this->alliance->call('removeCandidate', $aid,$pid); + } + + // Makes a player the new president for an alliance + function takePresidency($aid, $pid) { + return $this->alliance->call('takePresidency', $aid, $pid); + } + + // Reads the list of an alliance's forums + function getAllianceForums($aid) { + return $this->alliance->call('getForums', $aid); + } + + // Reads the list of an alliance's forums, with details + function getAllianceForumsComplete($aid) { + return $this->alliance->call('getForumsComplete', $aid); + } + + // Creates an alliance forum + function newAllianceForum($aid, $name, $userPost, $after, $description) { + return $this->alliance->call('createForum', $aid, $name, $userPost, $after, $description); + } + + // Destroys an alliance forum + function deleteAllianceForum($id) { + return $this->alliance->call('deleteForum', $id); + } + + // Moves an alliance forum up or down in the list + function moveAllianceForum($id, $up) { + return $this->alliance->call('moveForum', $id, $up); + } + + // Modifies an alliance forum + function modifyAllianceForum($id, $name, $userPost, $description) { + return $this->alliance->call('modifyForum', $id, $name, $userPost, $description); + } + + // Modifies the access list for an alliance forum + function setForumAccess($id, $readers, $mods) { + return $this->alliance->call('setForumAccess', $id, $readers, $mods); + } + + // Creates a new rank within the alliance + function createAllianceRank($aid, $name, $privs, $kick, $change, $fread, $fmod) { + return $this->alliance->call('createRank', $aid, $name, $privs, $kick, $change, $fread, $fmod); + } + + // Modifies a new rank within the alliance + function modifyAllianceRank($rid, $name, $privs, $kick, $change, $fread, $fmod) { + return $this->alliance->call('modifyRank', $rid, $name, $privs, $kick, $change, $fread, $fmod); + } + + // Deletes a rank from an alliance's ACL + function deleteAllianceRank($aid, $rId, $nrId) { + return $this->alliance->call('deleteRank', $aid, $rId, $nrId); + } + + // Get the number of alliance members at a certain rank + function getPCountRank($aid, $rId) { + return $this->alliance->call('getRankSize', $aid, $rId); + } + + // Change an alliance member's rank + function changeMemberRank($pid, $rid) { + return $this->alliance->call('changeRank', $pid, $rid); + } + + // Kick an alliance member + function kickAllianceMember($pid) { + return $this->alliance->call('kick', $pid); + } + + // Get the list of planets under attack in an alliance + function getAllianceMilitary($aid) { + return $this->alliance->call('getMilitary', $aid); + } + + + //-------------------------------------------------------------------------------------------------------------------------------- + // MESSAGES AND FOLDERS + //-------------------------------------------------------------------------------------------------------------------------------- + + // Lists custom folders for a player + function getCustomFolders($pid) { + return $this->msgs->call('getCustomFolders', $pid); + } + + // Creates a custom folder + function createCustomFolder($pid, $name) { + return $this->msgs->call('createFolder', $pid, $name); + } + + // Renames a custom folder + function renameCustomFolder($fid, $name) { + return $this->msgs->call('renameFolder', $fid, $name); + } + + // Flushes messages from a folder + function flushFolder($pid, $ft, $fid) { + return $this->msgs->call('flushFolder', $pid, $ft, $fid); + } + + // Deletes a custom folder + function deleteCustomFolder($pid, $fid) { + return $this->msgs->call('deleteFolder', $pid, $fid); + } + + // Get count of all messages in a player's folder + function getAllMessages($pid, $ft, $cfid = null) { + return $this->msgs->call('getAll', $pid, $ft, $cfid); + } + + // Get count of new messages in a player's folder + function getNewMessages($pid, $ft = null, $cfid = null) { + return $this->msgs->call('getNew', $pid, $ft, $cfid); + } + + // Get headers for all messages inside a folder + function getMessageHeaders($pid, $fld, $cfid) { + return $this->msgs->call('getHeaders', $pid, $fld, $cfid); + } + + // Returns complete information about a message + function getCompleteMessage($mId, $pid) { + return $this->msgs->call('get', $mId, $pid); + } + + // Sets a message status to "read" + function setMessageRead($mId) { + return $this->msgs->call('setRead', $mId); + } + + // Sets a message as deleted + function deleteMessage($mId, $pId) { + return $this->msgs->call('delete', $mId, $pId); + } + + // Moves a message + function moveMessage($mId, $pId, $tTp, $tId) { + return $this->msgs->call('move', $mId, $pId, $tTp, $tId); + } + + // Sends a message from player to player + function sendMessagePlayer($src, $dst, $sub, $msg, $rep = null) { + return $this->msgs->call('sendToPlayer', $src, $dst, $sub, $msg, $rep); + } + + // Sends a message from player to planet + function sendMessagePlanet($src, $dst, $sub, $msg, $rep = null) { + return $this->msgs->call('sendToPlanet', $src, $dst, $sub, $msg, $rep); + } + + // Sends a message from a player to his own alliance + function sendMessageAlliance($src, $dst, $sub, $msg, $rep = null) { + return $this->msgs->call('sendInAlliance', $src, $dst, $sub, $msg, $rep); + } + + + //-------------------------------------------------------------------------------------------------------------------------------- + // FORUMS + //-------------------------------------------------------------------------------------------------------------------------------- + + function getCatsAndForums($player) { + return $this->forums->call('getStructure', $player); + } + + function getForumTopics($f, $first, $count) { + return $this->forums->call('getTopics', $f, $first, $count); + } + + function newForumTopic($a, $fid, $sub, $txt, $ec, $es, $st) { + return $this->forums->call('newTopic', $a, $fid, $sub, $txt, $ec, $es, $st); + } + + function getForumTopic($tid) { + return $this->forums->call('getTopic', $tid); + } + + function getForumPosts($tid, $thr, $o, $cnt, $fst) { + return $this->forums->call('getPosts', $tid, $thr, $o, $cnt, $fst); + } + + function getForumPost($pid) { + return $this->forums->call('getPost', $pid); + } + + function getForumLatest($aForums, $gForums, $nb, $first) { + return $this->forums->call('getLatest', $aForums, $gForums, $nb, $first); + } + + function forumSearchPosts($aForums, $gForums, $nb, $first, $text, $complete, $sField, $sOrder) { + return $this->forums->call('searchPosts', $aForums, $gForums, $nb, $first, $text, $complete, $sField, $sOrder); + } + + function forumSearchTopics($aForums, $gForums, $nb, $first, $text, $complete, $sField, $sOrder) { + return $this->forums->call('searchTopics', $aForums, $gForums, $nb, $first, $text, $complete, $sField, $sOrder); + } + + function postForumReply($a, $post, $sub, $txt, $ec, $es) { + return $this->forums->call('reply', $a, $post, $sub, $txt, $ec, $es); + } + + function editForumPost($a, $pid, $sub, $txt, $ec, $es) { + return $this->forums->call('edit', $a, $pid, $sub, $txt, $ec, $es); + } + + function isTopicRead($topic, $player) { + return $this->forums->call('isRead', $topic, $player); + } + + function markAsRead($topic, $player) { + return $this->forums->call('markRead', $topic, $player); + } + + function markAsUnread($topic, $player) { + return $this->forums->call('markUnread', $topic, $player); + } + + function updateForumLast($forum) { + return $this->forums->call('updateLast', $forum); + } + + function deleteForumTopic($forum, $topic) { + return $this->forums->call('deleteTopic', $forum, $topic); + } + + function switchTopicSticky($forum, $topic) { + return $this->forums->call('switchSticky', $forum, $topic); + } + + function moveForumTopic($forum, $topic, $dest, $user) { + return $this->forums->call('moveTopic', $forum, $topic, $dest, $user); + } + + function deleteSinglePost($postId) { + return $this->forums->call('deletePost', $postId); + } + + function markForumAsRead($fid, $pid) { + return $this->forums->call('markForumRead', $fid, $pid); + } + + // Get the amount of read topics in an alliance forum + function getReadTopics($fid, $pid) { + return $this->forums->call('getRead', $fid, $pid); + } + + + //-------------------------------------------------------------------------------------------------------------------------------- + // ECM/ECCM COMPUTATIONS + //-------------------------------------------------------------------------------------------------------------------------------- + + function getInformationLevel($ecm, $eccm) { + return $this->ecm->call('getInformationLevel', $ecm, $eccm); + } + + function scrambleFleetPower($level, $power) { + return $this->ecm->call('scrambleFleetPower', $level, $power); + } +} + +?> diff --git a/scripts/game/beta5/actions/addTrustedAlly.inc b/scripts/game/beta5/actions/addTrustedAlly.inc new file mode 100644 index 0000000..e89fbbe --- /dev/null +++ b/scripts/game/beta5/actions/addTrustedAlly.inc @@ -0,0 +1,120 @@ + "beta5/player" + )); + } + + public function run($player, $allyName) { + // Check if the player ID is not null + if (is_null($player)) { + return self::playerNotFound; + } + $player = (int) $player; + + // Check if the player is valid + $playerRecord = $this->players->call('get', $player); + if (is_null($playerRecord)) { + return self::playerNotFound; + } + + // Check if the player is on vacation + if ($this->players->call('isOnVacation', $player)) { + return self::playerOnVacation; + } + + // Check the ally's name + $allyName = preg_replace('/\s+/', ' ', trim($allyName)); + if ($allyName == "") { + return self::noAllyName; + } elseif (strlen($allyName) > 15) { + return self::invalidAllyName; + } + + // Check the ally's record + $ally = $this->players->call('getPlayerId', $allyName); + if (is_null($ally)) { + return self::allyNotFound; + } elseif ($ally == $player) { + return self::allyIsPlayer; + } + + // Check the enemy list + if ($this->players->call('isEnemy', $player, $ally)) { + return self::allyIsEnemy; + } + + // Check the blacklist + if ($this->players->call('checkTAListBan', $ally, $player)) { + return self::playerBlacklisted; + } + + // Check the player's current TA list + $taList = $this->players->call('getAllies', $player); + if (count($taList) == 5) { + return self::maxPlayerTrust; + } + foreach ($taList as $id => $data) { + if ($data['id'] == $ally) { + return self::allyAlreadyListed; + } + } + + // Check the reverse TA list + $taList = $this->players->call('isAllyOf', $ally); + if (count($taList) == 5) { + return self::maxAllyTrust; + } + + // Add to the player's list + $this->players->call('addAlly', $player, $ally); + + // Return all trusted allies data + return $this->game->action('getTrustedAllies', $player); + } + +} + +?> diff --git a/scripts/game/beta5/actions/banTrustingAlly.inc b/scripts/game/beta5/actions/banTrustingAlly.inc new file mode 100644 index 0000000..9fdcf9f --- /dev/null +++ b/scripts/game/beta5/actions/banTrustingAlly.inc @@ -0,0 +1,101 @@ + "beta5/player" + )); + } + + public function run($player, $name) { + // Check if the player ID is not null + if (is_null($player)) { + return self::playerNotFound; + } + $player = (int) $player; + + // Check if the player is valid + $playerRecord = $this->players->call('get', $player); + if (is_null($playerRecord)) { + return self::playerNotFound; + } + + if ($this->players->call('isOnVacation', $player)) { + return self::playerOnVacation; + } + + // Check the name of the player to ban + $name = preg_replace('/\s+/', ' ', trim($name)); + if ($name == "") { + return self::emptyName; + } elseif (strlen($name) > 15) { + return self::invalidName; + } + + // Examine the player to ban + $toBan = $this->players->call("getPlayerId", $name); + if (is_null($toBan)) { + return self::targetNotFound; + } elseif ($toBan == $player) { + return self::targetIsPlayer; + } + + // Check if the target player is already banned + if ($this->players->call('checkTAListBan', $toBan, $player)) { + return self::alreadyBanned; + } + + // Remove the current player from the banned player's TA list + $reverseList = $this->players->call('isAllyOf', $player); + foreach ($reverseList as $id => $data) { + if ($id == $toBan) { + $this->players->call('removeAlly', $toBan, $reverseList[$toBan]['level']); + $this->players->call('reorderAllies', $toBan); + break; + } + } + + // Add the ban + $this->players->call('addTAListBan', $player, $toBan); + + return $this->game->action("getTrustedAllies", $player); + } + +} + +?> diff --git a/scripts/game/beta5/actions/getCommsOverview.inc b/scripts/game/beta5/actions/getCommsOverview.inc new file mode 100644 index 0000000..36e3739 --- /dev/null +++ b/scripts/game/beta5/actions/getCommsOverview.inc @@ -0,0 +1,91 @@ + "beta5/forums", + "msgs" => "beta5/msg" + )); + } + + public function run($player) { + if (is_null($player)) { + return null; + } + + // Messages in default folders + $folders = array(); + $dfld = array('IN', 'INT', 'OUT'); + foreach ($dfld as $f) { + $pm = $this->msgs->call('getAll', $player, $f); + $pmn = $this->msgs->call('getNew', $player, $f); + $folders[$f] = array($pm, $pmn); + } + + // Custom folders + $folders["CUS"] = array(); + $cFold = $this->msgs->call('getCustomFolders', $player); + foreach ($cFold as $cfid => $cfn) { + $pm = $this->msgs->call('getAll', $player, 'CUS', $cfid); + $pmn = $this->msgs->call('getNew', $player, 'CUS', $cfid); + $folders["CUS"][$cfid] = array($pm, $pmn, $cfn); + } + + // Forums + $cats = $this->forums->call('getStructure', $player); + $forums = array( + "general" => array(), + "alliance" => array() + ); + foreach ($cats as $c) { + if (!count($c['forums'])) { + continue; + } + + if ($c['type'] == 'A') { + $forums['allianceID'] = $c['id']; + foreach ($c['forums'] as $f) { + array_push($forums['alliance'], array( + $f['id'], $f['topics'], $f['unread'], $f['title'] + )); + } + } else { + $gCat = array( + "id" => $c['id'], + "type" => $c['type'], + "title" => $c['title'], + "forums" => array() + ); + foreach ($c['forums'] as $f) { + array_push($gCat['forums'], array( + $f['id'], $f['topics'], $f['unread'], $f['title'] + )); + } + array_push($forums['general'], $gCat); + } + } + + return array( + "folders" => $folders, + "forums" => $forums + ); + } + +} + +?> diff --git a/scripts/game/beta5/actions/getEmpireOverview.inc b/scripts/game/beta5/actions/getEmpireOverview.inc new file mode 100644 index 0000000..d61b2b9 --- /dev/null +++ b/scripts/game/beta5/actions/getEmpireOverview.inc @@ -0,0 +1,69 @@ + "beta5/planet", + "players" => "beta5/player", + "fleets" => "beta5/fleet", + "techs" => "beta5/tech" + )); + } + + public function run($player) { + if (is_null($player)) { + return null; + } + + // Get general statistics + $genStats = $this->planets->call('getStats', $player); + $fleetStats = $this->fleets->call('getStats', $player); + $planets = $this->players->call('getPlanets', $player); + + // Additional planet data + $income = 0; + foreach ($planets as $id => $name) { + $pInfo = $this->planets->call('byId', $id); + $pIncome = $this->planets->call('getIncome', $pInfo); + $income += $pIncome[0]; + } + + // Get research data + $rPoints = $this->techs->call('getPoints', $player); + $rBudget = $this->techs->call('getBudget', $player); + $nNew = count($this->techs->call('getTopics', $player, 0)); + $nForeseen = count($this->techs->call('getTopics', $player, -1)) / 2; + + // Return data + return array( + "planetStats" => $genStats, + "planets" => $planets, + "fleetStats" => $fleetStats, + "techStats" => array( + "points" => $rPoints, + "budget" => $rBudget, + "new" => $nNew, + "foreseen" => $nForeseen + ), + "income" => $income, + "profit" => $income - $fleetStats['upkeep'] + ); + } + +} + +?> diff --git a/scripts/game/beta5/actions/getOverview.inc b/scripts/game/beta5/actions/getOverview.inc new file mode 100644 index 0000000..f373bb5 --- /dev/null +++ b/scripts/game/beta5/actions/getOverview.inc @@ -0,0 +1,38 @@ + 'beta5/player' + )); + } + + public function run($player, $language) { + if (is_null($player)) { + return null; + } + + return array( + "protection" => $this->players->call('getProtectionLevel', $player), + "comms" => $this->game->action('getCommsOverview', $player), + "universe" => $this->game->action('getUniverseOverview', $player, $language), + "empire" => $this->game->action('getEmpireOverview', $player) + ); + } +} + +?> diff --git a/scripts/game/beta5/actions/getTrustedAllies.inc b/scripts/game/beta5/actions/getTrustedAllies.inc new file mode 100644 index 0000000..25cac14 --- /dev/null +++ b/scripts/game/beta5/actions/getTrustedAllies.inc @@ -0,0 +1,54 @@ + "beta5/player" + )); + } + + public function run($player) { + // Check if the player ID is not null + if (is_null($player)) { + return null; + } + $player = (int) $player; + + // Check if the player is valid + $playerRecord = $this->players->call('get', $player); + if (is_null($playerRecord)) { + return null; + } + + // Return data + return array( + "allies" => $this->players->call('getAllies', $player), + "reverse" => $this->players->call('isAllyOf', $player), + "blacklist" => $this->players->call('getTAListBans', $player) + ); + } + +} + +?> diff --git a/scripts/game/beta5/actions/getUniverseOverview.inc b/scripts/game/beta5/actions/getUniverseOverview.inc new file mode 100644 index 0000000..d867a5e --- /dev/null +++ b/scripts/game/beta5/actions/getUniverseOverview.inc @@ -0,0 +1,80 @@ + "main", + "beta5" => "beta5", + "map" => "beta5/map", + "players" => "beta5/player", + "rankings" => "main/rankings" + )); + } + + public function run($player, $language) { + if (is_null($player)) { + return null; + } + + return array( + "summary" => $this->map->call('getUniverse'), + "ticks" => $this->getTicks($language), + "rankings" => $this->getPlayerRankings($player) + ); + } + + private function getTicks($language) { + $ticks = array(); + $info = $this->main->call('getTicks', $language); + foreach ($info as $tid => $td) { + if (! $td['game']) { + continue; + } + array_push($ticks, array($tid, $td['first'], $td['interval'], $td['last'], $td['name'])); + } + return $ticks; + } + + private function getRanking($type, $name) { + $rt = $this->rankings->call('getType', $type); + $r = $this->rankings->call('get', $rt, $name); + if (!$r) { + $r = array('',''); + } + return $r; + } + + private function getPlayerRankings($pid) { + $pc = $this->beta5->call('getPlayerCount'); + + $pName = $this->players->call('getName', $pid); + $gr = $this->getRanking('p_general', $pName); + $cr = $this->getRanking('p_civ', $pName); + $mr = $this->getRanking('p_financial', $pName); + $fr = $this->getRanking('p_military', $pName); + $or = $this->getRanking('p_round', $pName); + $ir = $this->getRanking('p_idr', $pName); + + return array( + $pc, $gr['points'], $gr['ranking'], $cr['points'], $cr['ranking'], + $mr['points'], $mr['ranking'], $fr['points'], $fr['ranking'], + $or['points'], $or['ranking'], $ir['points'], $ir['ranking'] + ); + } +} + +?> diff --git a/scripts/game/beta5/actions/removeTrustingAllies.inc b/scripts/game/beta5/actions/removeTrustingAllies.inc new file mode 100644 index 0000000..bd19230 --- /dev/null +++ b/scripts/game/beta5/actions/removeTrustingAllies.inc @@ -0,0 +1,79 @@ + "beta5/player" + )); + } + + public function run($player, $removeList) { + // Check if the player ID is not null + if (is_null($player)) { + return self::playerNotFound; + } + $player = (int) $player; + + // Check if the player is valid + $playerRecord = $this->players->call('get', $player); + if (is_null($playerRecord)) { + return self::playerNotFound; + } + + if ($this->players->call('isOnVacation', $player)) { + return self::playerOnVacation; + } + + // Check if the player is listed as an ally for these players + $trustedBy = $this->players->call('isAllyOf', $player); + $removeList = array_unique($removeList); + foreach ($removeList as $removePlayer) { + if (!array_key_exists((int) $removePlayer, $trustedBy)) { + return self::trustingPlayerNotFound; + } + } + + // Remove the player from their lists and reorder + foreach ($removeList as $removePlayer) { + $this->players->call('removeAlly', (int) $removePlayer, + $trustedBy[(int) $removePlayer]['level']); + $this->players->call('reorderAllies', $removePlayer); + } + + return $this->game->action("getTrustedAllies", $player); + } + +} + +?> diff --git a/scripts/game/beta5/alliance/library.inc b/scripts/game/beta5/alliance/library.inc new file mode 100644 index 0000000..878a167 --- /dev/null +++ b/scripts/game/beta5/alliance/library.inc @@ -0,0 +1,94 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Changes a player's vote + function setVote($pid,$v) { + $this->db->query("UPDATE player SET a_vote=$v WHERE id=$pid"); + $this->players[$pid] = null; + } + + // Marks a tech trading order as obeyed + public function obeyOrder($pid) { + $this->db->query("UPDATE tech_trade_order SET obeyed = UNIX_TIMESTAMP( NOW() ) WHERE player = $pid"); + } + + // Get the timestamp of the latest tech trading orders and the next time it'll be possible to + // change the orders + public function getLatestTechOrders($alliance) { + // Get latest submission, if any + $q = $this->db->query("SELECT MAX(submitted) FROM tech_trade_order WHERE alliance = $alliance"); + list($sub) = dbFetchArray($q); + $sub = (int) $sub; + + // Get delays + $interval = 2 * $this->lib->game->ticks['battle']->interval; + $now = time(); + if ($now - $sub >= $interval) { + $next = 0; + } else { + $next = $sub + $interval; + } + + return array($sub, $next); + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/acceptRequest.inc b/scripts/game/beta5/alliance/library/acceptRequest.inc new file mode 100644 index 0000000..1231c60 --- /dev/null +++ b/scripts/game/beta5/alliance/library/acceptRequest.inc @@ -0,0 +1,51 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->msgs = $this->lib->game->getLib('beta5/msg'); + } + + + // Accepts a request to join an alliance + function run($aid, $pid, $kid) { + $q = $this->db->query("SELECT id FROM player WHERE alliance=$aid AND a_status='REQ' AND id=$pid"); + if (!($q && dbCount($q) == 1)) { + return; + } + + $a = $this->lib->call('get', $aid); + if (is_null($a)) { + return; + } + + $tag = addslashes($a['tag']); + $tm = time(); + + $this->msgs->call('send', $pid, 'alint', array( + "msg_type" => 10, + "alliance" => $aid, + "tag" => $a['tag'], + "player" => $pid, + )); + + $l = $this->lib->call('getKeepers', $aid); + foreach ($l as $id) { + if ($id == $kid) { + continue; + } + $this->msgs->call('send', $id, 'alint', array( + "msg_type" => 11, + "alliance" => $aid, + "tag" => $a['tag'], + "player" => $pid, + )); + } + + $this->db->query("UPDATE player SET a_status='IN',a_vote=NULL,a_grade=NULL WHERE id=$pid"); + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/addCandidate.inc b/scripts/game/beta5/alliance/library/addCandidate.inc new file mode 100644 index 0000000..0861447 --- /dev/null +++ b/scripts/game/beta5/alliance/library/addCandidate.inc @@ -0,0 +1,35 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Adds a new candidate for an alliance's presidency + function run($aid,$pid) { + $this->db->query("INSERT INTO alliance_candidate(alliance,candidate) VALUES($aid,$pid)"); + $q = $this->db->query("SELECT id FROM alliance_candidate WHERE candidate=$pid"); + list($id) = dbFetchArray($q); + $this->db->query("UPDATE player SET a_vote=$id WHERE id=$pid"); + + $a = $this->lib->call('get', $aid); + $tag = addslashes($a['tag']); + $tm = time(); + + $vl = $this->lib->call('getVoters', $aid); + foreach ($vl as $id) { + if ($id == $pid) { + continue; + } + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($id,$tm,'alint','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=$id AND sent_on=$tm AND ftype='INT' ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_alint VALUES($mid,$aid,'$tag',$pid,16)"); + } + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/cancelRequest.inc b/scripts/game/beta5/alliance/library/cancelRequest.inc new file mode 100644 index 0000000..9af40d6 --- /dev/null +++ b/scripts/game/beta5/alliance/library/cancelRequest.inc @@ -0,0 +1,29 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Cancels a request to join an alliance + function run($p, $a) { + $this->db->query("UPDATE player SET alliance=NULL,a_status=NULL,a_vote=NULL WHERE id=$p"); + + $ainf = $this->lib->call('get', $a); + $tag = addslashes($ainf['tag']); + $l = $this->lib->call('getKeepers', $a); + $tm = time(); + + foreach ($l as $id) { + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($id,$tm,'alint','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=$id AND sent_on=$tm AND ftype='INT'"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_alint VALUES($mid,$a,'$tag',$p,1)"); + } + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/changeRank.inc b/scripts/game/beta5/alliance/library/changeRank.inc new file mode 100644 index 0000000..33970d9 --- /dev/null +++ b/scripts/game/beta5/alliance/library/changeRank.inc @@ -0,0 +1,35 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + } + + + // Change an alliance member's rank + function run($pid, $rid) { + $this->db->query("UPDATE player SET a_grade=$rid WHERE alliance IS NOT NULL AND a_status='IN' AND id=$pid"); + $q = $this->db->query( + "SELECT r.can_vote,r.can_be_cand,a.leader FROM player p,alliance a,alliance_grade r " + . "WHERE p.id=$pid AND r.alliance=p.alliance AND a.id=p.alliance AND " + . "((p.a_grade IS NOT NULL AND p.a_grade=r.id) OR (p.a_grade IS NULL AND r.id=a.default_grade))" + ); + list($cv,$cc,$lid) = dbFetchArray($q); + $lockedAlliances = (int) $this->game->params['lockalliances']; + $lockedAlliances = ($lockedAlliances > 1) ? 1 : 0; + if ($pid != $lid && ! $lockedAlliances) { + if ($cv == 0) { + $this->db->query("UPDATE player SET a_vote=NULL WHERE id=$pid"); + } + if ($cc == 0) { + $this->db->query("DELETE FROM alliance_candidate WHERE candidate=$pid"); + } + } + // FIXME: message + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/create.inc b/scripts/game/beta5/alliance/library/create.inc new file mode 100644 index 0000000..38b5ff3 --- /dev/null +++ b/scripts/game/beta5/alliance/library/create.inc @@ -0,0 +1,39 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Create an alliance + function run($tag, $name, $founder) { + $t = addslashes($tag); + + $this->db->query("INSERT INTO alliance_grade(name) VALUES ('DEFGRADE-$t')"); + $q = $this->db->query("SELECT id FROM alliance_grade WHERE name='DEFGRADE-$t' AND alliance IS NULL"); + list($gid) = dbFetchArray($q); + + $this->db->query( + "INSERT INTO alliance(tag,name,leader,successor,default_grade) VALUES('$t','" + . addslashes($name) . "',$founder,NULL,$gid)" + ); + + $i = $this->lib->call('getId', $tag); + if (is_null($i)) { + return null; + } + $this->db->query("UPDATE alliance_grade SET name=NULL,alliance=$i WHERE name='DEFGRADE-$t' AND alliance IS NULL"); + $this->db->query("UPDATE player SET alliance=$i,a_status='IN',a_vote=NULL WHERE id=$founder"); + + $rkLib =& $this->lib->game->getLib('main/rankings'); + $rt = $rkLib->call('getType', 'a_general'); + $rkLib->call('append', $rt, $tag); + + return $i; + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/createForum.inc b/scripts/game/beta5/alliance/library/createForum.inc new file mode 100644 index 0000000..ad64a31 --- /dev/null +++ b/scripts/game/beta5/alliance/library/createForum.inc @@ -0,0 +1,29 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Creates an alliance forum + function run($aid, $name, $userPost, $after, $description) { + if ($after == -1) { + $ao = -1; + } else { + $q = $this->db->query("SELECT forder FROM af_forum WHERE id=$after"); + list($ao) = dbFetchArray($q); + } + $this->db->query("UPDATE af_forum SET forder=forder+1 WHERE forder>$ao AND alliance=$aid"); + $ao ++; + $this->db->query( + "INSERT INTO af_forum(alliance,forder,title,description,user_post) VALUES(" + . "$aid,$ao,'" . addslashes($name) . "','". addslashes($description) . "'," + . ($userPost?'TRUE':'FALSE') . ")" + ); + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/createRank.inc b/scripts/game/beta5/alliance/library/createRank.inc new file mode 100644 index 0000000..a9a6a71 --- /dev/null +++ b/scripts/game/beta5/alliance/library/createRank.inc @@ -0,0 +1,47 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Creates a new rank within the alliance + function run($aid, $name, $privs, $kick, $change, $fread, $fmod) { + $n = addslashes($name); + $ll = array('NO', 'PY', 'PL', 'PLD'); + $lt = array('NO', 'SL', 'SR', 'VL', 'MR'); + $this->db->query( + "INSERT INTO alliance_grade(alliance,name,list_access,attacks,can_set_grades," + . "can_kick,can_accept,forum_admin,dipl_contact,can_vote,can_be_cand,tech_trade)" + . " VALUES($aid,'$n','".$ll[$privs['list_access']]."',".dbBool($privs['attacks']) + . ",".dbBool($privs['can_set_grades']).",".dbBool($privs['can_kick'])."," + . dbBool($privs['can_accept']).','.dbBool($privs['forum_admin'])."," + . dbBool($privs['dipl_contact']).",".dbBool($privs['can_vote'])."," + . dbBool($privs['can_be_cand']).",'".$lt[$privs['tech_trade']] . "')" + ); + $q = $this->db->query("SELECT id FROM alliance_grade WHERE alliance=$aid AND name='$n'"); + if (!($q && dbCount($q))) { + return; + } + list($rid) = dbFetchArray($q); + + foreach ($kick as $id) { + $this->db->query("INSERT INTO algr_kick VALUES($rid,$id)"); + } + foreach ($change as $id) { + $this->db->query("INSERT INTO algr_chgrade VALUES($rid,$id)"); + } + + foreach ($fread as $id) { + $this->db->query("INSERT INTO algr_forums VALUES($rid,$id,FALSE)"); + } + foreach ($fmod as $id) { + $this->db->query("INSERT INTO algr_forums VALUES($rid,$id,TRUE)"); + } + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/deleteForum.inc b/scripts/game/beta5/alliance/library/deleteForum.inc new file mode 100644 index 0000000..0b048bc --- /dev/null +++ b/scripts/game/beta5/alliance/library/deleteForum.inc @@ -0,0 +1,20 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Destroys an alliance forum + function run($id) { + $q = $this->db->query("SELECT alliance,forder FROM af_forum WHERE id=$id"); + list($aid,$o) = dbFetchArray($q); + $this->db->query("DELETE FROM af_forum WHERE id=$id"); + $this->db->query("UPDATE af_forum SET forder=forder-1 WHERE alliance=$aid AND forder>$o"); + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/deleteRank.inc b/scripts/game/beta5/alliance/library/deleteRank.inc new file mode 100644 index 0000000..fe1b5e0 --- /dev/null +++ b/scripts/game/beta5/alliance/library/deleteRank.inc @@ -0,0 +1,18 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Deletes a rank from an alliance's ACL + function run($aid, $rId, $nrId) { + $this->db->query("UPDATE player SET a_grade=$nrId WHERE alliance=$aid AND a_status='IN' AND a_grade=$rId"); + $this->db->query("DELETE FROM alliance_grade WHERE alliance=$aid AND id=$rId"); + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/get.inc b/scripts/game/beta5/alliance/library/get.inc new file mode 100644 index 0000000..6590b6b --- /dev/null +++ b/scripts/game/beta5/alliance/library/get.inc @@ -0,0 +1,30 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Get alliance data + function run($id) { + $q = $this->db->query("SELECT * FROM alliance WHERE id = $id"); + if (!($q && dbCount($q) == 1)) { + return null; + } + $r = dbFetchHash($q); + $q = $this->db->query( + "SELECT COUNT(*),ROUND(AVG(s.x)),ROUND(AVG(s.y)) FROM player y,planet p,system s " + . "WHERE y.alliance=$id AND y.a_status='IN' AND p.owner=y.id AND s.id=p.system" + ); + list($r['nplanets'],$r['avgx'],$r['avgy']) = dbFetchArray($q); + if (is_null($r['avgx'])) { + $r['avgx'] = $r['avgy'] = 0; + } + return $r; + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/getCandidates.inc b/scripts/game/beta5/alliance/library/getCandidates.inc new file mode 100644 index 0000000..9699835 --- /dev/null +++ b/scripts/game/beta5/alliance/library/getCandidates.inc @@ -0,0 +1,29 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Get the list of alliance candidates as well as the number of votes they have + function run($aid) { + $l = array(); + $q = $this->db->query("SELECT c.id,c.candidate,COUNT(*) FROM alliance_candidate c," + . "player p WHERE c.alliance=$aid AND p.a_vote=c.id GROUP BY c.id, c.candidate"); + while ($r = dbFetchArray($q)) { + $q2 = $this->db->query("SELECT p.name,a.name FROM player p,account a WHERE p.id={$r[1]} AND p.userid=a.id"); + $r2 = dbFetchArray($q2); + $l[$r[0]] = array( + "votes" => $r[2], + "pid" => $r[1], + "name" => is_null($r2[0]) ? $r2[1] : $r2[0] + ); + } + return $l; + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/getForums.inc b/scripts/game/beta5/alliance/library/getForums.inc new file mode 100644 index 0000000..a31a755 --- /dev/null +++ b/scripts/game/beta5/alliance/library/getForums.inc @@ -0,0 +1,27 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Reads the list of an alliance's forums + function run($aid) { + $q = $this->db->query("SELECT id,forder,title,description,user_post FROM af_forum WHERE alliance=$aid ORDER BY forder"); + $rs = array(); + while ($r = dbFetchArray($q)) { + $rs[$r[0]] = array( + "order" => $r[1], + "title" => $r[2], + "description" => $r[3], + "user_post" => ($r[4] == 't') + ); + } + return $rs; + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/getForumsComplete.inc b/scripts/game/beta5/alliance/library/getForumsComplete.inc new file mode 100644 index 0000000..7b6e333 --- /dev/null +++ b/scripts/game/beta5/alliance/library/getForumsComplete.inc @@ -0,0 +1,34 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->players = $this->lib->game->getLib('beta5/player'); + } + + + // Reads the list of an alliance's forums, with details + function run($aid) { + $q = $this->db->query("SELECT * FROM af_forum WHERE alliance=$aid ORDER BY forder"); + $a = array(); + while ($rs = dbFetchHash($q)) { + if ($rs['last_post'] != "") { + $q2 = $this->db->query("SELECT author,moment FROM af_post WHERE id=".$rs['last_post']); + list($au,$mo) = dbFetchArray($q2); + $au = $this->players->call('getName', $au); + $rs['last'] = array( + "moment" => $mo, + "author" => $au + ); + } else { + $rs['last'] = null; + } + array_push($a, $rs); + } + return $a; + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/getId.inc b/scripts/game/beta5/alliance/library/getId.inc new file mode 100644 index 0000000..89d4316 --- /dev/null +++ b/scripts/game/beta5/alliance/library/getId.inc @@ -0,0 +1,21 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Get alliance identifier by tag + function run($tag) { + $q = $this->db->query("SELECT id FROM alliance WHERE LOWER(tag)='" . addslashes(strtolower($tag)) . "'"); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($id) = dbFetchArray($q); + return $id; + } +} +?> diff --git a/scripts/game/beta5/alliance/library/getKeepers.inc b/scripts/game/beta5/alliance/library/getKeepers.inc new file mode 100644 index 0000000..92ca0c3 --- /dev/null +++ b/scripts/game/beta5/alliance/library/getKeepers.inc @@ -0,0 +1,46 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Get a list of IDs for the players that can accept new members into an alliance + function run($a) { + $q = $this->db->query("SELECT leader FROM alliance WHERE id=$a"); + list($id) = dbFetchArray($q); + if (!is_null($id)) { + $l = array($id); + } else { + $l = array(); + } + + $q = $this->db->query("SELECT id FROM alliance_grade WHERE alliance=$a AND can_accept"); + if (!$q || dbCount($q) == 0) { + return $l; + } + $gl = array(); + while ($r = dbFetchArray($q)) { + list($g) = $r; + array_push($gl, $g); + } + + $q = $this->db->query( + "SELECT p.id FROM player p, alliance a WHERE a.id=$a AND p.alliance=a.id AND p.a_status='IN' " + . "AND (p.a_grade IN (" . join(',',$gl) . ") OR (p.a_grade IS NULL AND a.default_grade IN (".join(',',$gl).")))" + ); + while ($r = dbFetchArray($q)) { + list($p) = $r; + if (is_null($id) || $p != $id) { + array_push($l, $p); + } + } + + return $l; + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/getMembers.inc b/scripts/game/beta5/alliance/library/getMembers.inc new file mode 100644 index 0000000..1712961 --- /dev/null +++ b/scripts/game/beta5/alliance/library/getMembers.inc @@ -0,0 +1,27 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Reads the list of alliance members and their ranks + function run($aid) { + $q = $this->db->query( + "SELECT p.id,p.name,a.name,p.a_grade FROM player p,account a " + . "WHERE p.alliance=$aid AND p.a_status='IN' AND a.id=p.userid"); + $rs = array(); + while ($r = dbFetchArray($q)) { + $rs[$r[0]] = array( + "name" => is_null($r[1]) ? $r[2] : $r[1], + "rank" => $r[3] + ); + } + return $rs; + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/getMilitary.inc b/scripts/game/beta5/alliance/library/getMilitary.inc new file mode 100644 index 0000000..d8241a8 --- /dev/null +++ b/scripts/game/beta5/alliance/library/getMilitary.inc @@ -0,0 +1,34 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Get the list of planets under attack in an alliance + function run($aid) { + $q = $this->db->query( + "SELECT p.id AS id,p.name AS name,l.id AS owner,a.v_players AS pllist," + . "a.v_friendly AS friendly,a.v_enemy AS enemy,s.x AS x,s.y AS y,p.orbit AS orbit " + . " FROM planet p,attacks a,player l,system s " + . "WHERE l.alliance=$aid AND l.a_status='IN' AND p.owner=l.id AND a.planet=p.id AND s.id=p.system" + ); + $rs = array(); + while ($r = dbFetchHash($q)) { + $r['f_list'] = $r['e_list'] = array(); + if ($r['pllist'] == 't') { + $q2 = $this->db->query("SELECT DISTINCT owner,attacking FROM fleet WHERE location=".$r['id']); + while ($r2=dbFetchArray($q2)) { + array_push($r[($r2[1] == 't' ? "e" : "f").'_list'], $r2[0]); + } + } + $rs[$r['id']] = $r; + } + return $rs; + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/getPlanets.inc b/scripts/game/beta5/alliance/library/getPlanets.inc new file mode 100644 index 0000000..8fb2dc7 --- /dev/null +++ b/scripts/game/beta5/alliance/library/getPlanets.inc @@ -0,0 +1,42 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->planets = $this->lib->game->getLib('beta5/planet'); + $this->players = $this->lib->game->getLib('beta5/player'); + } + + + // Returns the list of planets belonging to players of the alliance + function run($aid) { + $rs = array(); + $q = $this->db->query("SELECT p.id,p.name,a.name FROM player p,account a WHERE a.id=p.userid AND p.alliance=$aid AND p.a_status='IN'"); + while ($r = dbFetchArray($q)) { + $oid = $r[0]; $oname = ($r[1] == "") ? $r[2] : $r[1]; + $ppl = $this->players->call('getPlanets', $oid); + + foreach ($ppl as $pid => $pname) { + $pinf = $this->planets->call('byId', $pid); + $rs[$pid] = array( + "name" => $pname, + "ownerId" => $oid, + "owner" => $oname, + "x" => $pinf['x'], + "y" => $pinf['y'], + "orbit" => $pinf['orbit'] + 1, + "fact" => $pinf['ifact'] + $pinf['mfact'], + "turrets" => $pinf['turrets'], + ); + $q2 = $this->db->query("SELECT COUNT(*) FROM fleet WHERE location=$pid AND attacking"); + list($ua) = dbFetchArray($q2); + $rs[$pid]['attack'] = ($ua != 0); + } + } + return $rs; + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/getPrivileges.inc b/scripts/game/beta5/alliance/library/getPrivileges.inc new file mode 100644 index 0000000..15e6ad4 --- /dev/null +++ b/scripts/game/beta5/alliance/library/getPrivileges.inc @@ -0,0 +1,79 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->players = $this->lib->game->getLib('beta5/player'); + } + + + // Returns the list of privileges a player has inside an alliance + function run($p) { + $nr = array( + 'list_access' => 0, + 'tech_trade' => 0, + 'attacks' => 0, + 'can_set_grades' => 0, + 'can_kick' => 0, + 'can_accept' => 0, + 'forum_admin' => 0, + 'dipl_contact' => 0, + 'can_vote' => 0, + 'can_be_cand' => 0, + 'is_leader' => 0 + ); + + $pi = $this->players->call('get', $p); + if (is_null($pi['aid'])) { + return $nr; + } + + $a = $this->lib->call('get', $pi['aid']); + if (is_null($a)) { + return $nr; + } + + if ($a['leader'] == $p) { + $pr = array( + 'list_access' => 3, + 'tech_trade' => ($a['enable_tt'] == 'N' ? 0 : 4), + 'attacks' => 1, + 'can_set_grades' => 1, + 'can_kick' => 1, + 'can_accept' => 1, + 'forum_admin' => 1, + 'dipl_contact' => 1, + 'can_vote' => 0, + 'can_be_cand' => 1, + 'is_leader' => 1 + ); + + // Get all ranks (-> kick, change) + $q = $this->db->query("SELECT id FROM alliance_grade WHERE alliance=".$a['id']); + $ar = array(); + while ($r = dbFetchArray($q)) { + array_push($ar, $r[0]); + } + $pr['kick_ranks'] = $pr['change_ranks'] = $ar; + + // Forums + $pr['f_read'] = $pr['f_mod'] = array(); + $q = $this->db->query("SELECT id FROM af_forum WHERE alliance=".$a['id']); + while ($r = dbFetchArray($q)) { + array_push($pr['f_mod'], $r[0]); + } + } elseif (is_null($pi['a_grade'])) { + $pr = $this->lib->call('getRankPrivileges', $a['default_grade']); + } else { + $pr = $this->lib->call('getRankPrivileges', $pi['a_grade']); + } + if ($a['democracy'] == "f") { + $pr['can_vote'] = $pr['can_be_cand'] = 'f'; + } + return $pr; + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/getRankPrivileges.inc b/scripts/game/beta5/alliance/library/getRankPrivileges.inc new file mode 100644 index 0000000..e201fd1 --- /dev/null +++ b/scripts/game/beta5/alliance/library/getRankPrivileges.inc @@ -0,0 +1,96 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns the privileges associated with a rank + function run($id) { + $q = $this->db->query("SELECT * FROM alliance_grade WHERE id=$id"); + $pr = dbFetchHash($q); + $pr['is_leader'] = 0; + + // Transform list access + switch ($pr['list_access']) : + case 'NO ': $pr['list_access'] = 0; break; + case 'PY ': $pr['list_access'] = 1; break; + case 'PL ': $pr['list_access'] = 2; break; + case 'PLD': $pr['list_access'] = 3; break; + endswitch; + + // Transform tech trade tool access + $q = $this->db->query("SELECT enable_tt FROM alliance WHERE id = {$pr['alliance']}"); + list($enableTT) = dbFetchArray($q); + switch ($pr['tech_trade']) : + case 'NO': $pr['tech_trade'] = 0; break; + case 'SL': $pr['tech_trade'] = ($enableTT != 'N' ? 1 : 0); break; + case 'SR': $pr['tech_trade'] = ($enableTT == 'N' ? 0 : ($enableTT == 'S' ? 1 : 2)); break; + case 'VL': $pr['tech_trade'] = ($enableTT != 'N' ? 3 : 0); break; + case 'MR': $pr['tech_trade'] = ($enableTT != 'N' ? 4 : 0); break; + endswitch; + + // Transform PostgreSQL booleans into something usable by the current scripts + $fields = array("attacks", "can_set_grades", "can_kick", "can_accept", + "can_be_kicked", "forum_admin", "dipl_contact", "can_vote", "can_be_cand"); + foreach ($fields as $f) { + $pr[$f] = ($pr[$f] == 't') ? 1 : 0; + } + + // Get kickable ranks + $a = array(); + if ($pr['can_kick']) { + $q = $this->db->query("SELECT kick FROM algr_kick WHERE grade = $id AND kick <> $id"); + if (!dbCount($q)) { + $q = $this->db->query( + "SELECT id FROM alliance_grade " + . "WHERE alliance = {$pr['alliance']} AND id <> $id" + ); + } + while ($r = dbFetchArray($q)) { + array_push($a, $r[0]); + } + } + $pr['kick_ranks'] = $a; + + // Get changeable ranks + $a = array(); + if ($pr['can_set_grades']) { + $q = $this->db->query("SELECT can_change FROM algr_chgrade WHERE grade=$id AND can_change!=$id"); + if (!dbCount($q)) { + $q = $this->db->query( + "SELECT id FROM alliance_grade " + . "WHERE alliance={$pr['alliance']} AND id <> $id" + ); + } + while ($r = dbFetchArray($q)) { + array_push($a, $r[0]); + } + } + $pr['change_ranks'] = $a; + + // Forum privileges + $pr['f_read'] = $pr['f_mod'] = array(); + if ($pr['forum_admin'] == 0) { + $q = $this->db->query("SELECT forum,is_mod FROM algr_forums WHERE grade=$id"); + while ($r = dbFetchArray($q)) { + array_push($pr[($r[1] == 't') ? "f_mod" : "f_read"], $r[0]); + } + } else { + $q = $this->db->query( + "SELECT f.id FROM af_forum f,alliance_grade r " + . "WHERE r.id = $id AND f.alliance = r.alliance" + ); + while ($r = dbFetchArray($q)) { + array_push($pr['f_mod'], $r[0]); + } + } + + return $pr; + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/getRankSize.inc b/scripts/game/beta5/alliance/library/getRankSize.inc new file mode 100644 index 0000000..d6eb75d --- /dev/null +++ b/scripts/game/beta5/alliance/library/getRankSize.inc @@ -0,0 +1,32 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Get the number of alliance members at a certain rank + function run($aid, $rId) { + $a = $this->lib->call('get', $aid); + $qs = "SELECT COUNT(*) FROM player WHERE alliance=$aid AND a_status='IN' AND "; + if ($rId == $a['default_grade']) { + $qs .= "(a_grade=$rId OR a_grade IS NULL)"; + } else { + $qs .= "a_grade=$rId"; + } + if (!is_null($a['leader'])) { + $qs .= " AND id<>" . $a['leader']; + } + $q = $this->db->query($qs); + if (!($q && dbCount($q))) { + return 0; + } + list($r) = dbFetchArray($q); + return $r; + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/getRanks.inc b/scripts/game/beta5/alliance/library/getRanks.inc new file mode 100644 index 0000000..cdc5aa0 --- /dev/null +++ b/scripts/game/beta5/alliance/library/getRanks.inc @@ -0,0 +1,22 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Reads the list of ranks associated with an alliance + function run($aid) { + $q = $this->db->query("SELECT id,name FROM alliance_grade WHERE alliance=$aid"); + $rs = array(); + while ($r = dbFetchArray($q)) { + $rs[$r[0]] = is_null($r[1]) ? "-" : $r[1]; + } + return $rs; + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/getRequests.inc b/scripts/game/beta5/alliance/library/getRequests.inc new file mode 100644 index 0000000..0fb964e --- /dev/null +++ b/scripts/game/beta5/alliance/library/getRequests.inc @@ -0,0 +1,22 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Get the list of pending requests for an alliance + function run($aid) { + $q = $this->db->query("SELECT p.id,p.name,a.name FROM player p, account a WHERE p.alliance=$aid AND p.a_status='REQ' AND a.id=p.userid"); + $rs = array(); + while ($r = dbFetchArray($q)) { + $rs[$r[0]] = ($r[1] == "") ? $r[2] : $r[1]; + } + return $rs; + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/getTechList.inc b/scripts/game/beta5/alliance/library/getTechList.inc new file mode 100644 index 0000000..50ec63c --- /dev/null +++ b/scripts/game/beta5/alliance/library/getTechList.inc @@ -0,0 +1,93 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + $this->players = $this->game->getLib('beta5/player'); + } + + public function run($alliance, $hasRequests) { + // Get tech trading privileges for all players in the alliance + $players = $this->getPlayers($alliance, $hasRequests); + + // Get the technology list for each player that is authorized to submit it, + // and get requests for players who can submit requests + $techList = array(); + foreach ($players as $player => $priv) { + // Ignore unprivileged players + if ($priv == 0) { + continue; + } + + // Get list and player status + list($lastSub, $list) = $this->getPlayerList($player); + $techList[$player] = array( + "submitted" => $lastSub, + "list" => $list, + "vacation" => $this->players->call('isOnVacation', $player) ? 1 : 0, + "restrict" => $this->players->call('isRestrained', $player) ? 1 : 0 + ); + + // Ignore players who can't submit requess + if ($priv == 1) { + continue; + } + $techList[$player]['requests'] = $this->lib->call('getTechRequests', $player); + } + + return $techList; + } + + + private function getPlayers($alliance, $hasRequests) { + $q = $this->db->query( + "SELECT p.id, (CASE p.id = a.leader WHEN TRUE THEN 'MR' ELSE r.tech_trade END) " + . "FROM player p, alliance a, alliance_grade r " + . "WHERE p.alliance = $alliance AND p.a_status = 'IN' AND r.alliance = p.alliance " + . "AND (p.a_grade = r.id OR p.a_grade IS NULL AND r.name IS NULL) " + . "AND a.id = p.alliance" + ); + + $players = array(); + while ($r = dbFetchArray($q)) { + switch ($r[1]) { + case 'NO': $priv = 0; break; + case 'SL': $priv = 1; break; + default: $priv = $hasRequests ? 2 : 1; break; + } + $players[$r[0]] = $priv; + } + + return $players; + } + + + private function getPlayerList($player) { + $list = array(); + $subAt = 0; + + $q = $this->db->query("SELECT tech, status, submitted FROM tech_trade_list WHERE member = $player"); + while ($r = dbFetchArray($q)) { + $list[$r[0]] = $r[1]; + $subAt = $r[2]; + } + + return array($subAt, $list); + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/getTechOrder.inc b/scripts/game/beta5/alliance/library/getTechOrder.inc new file mode 100644 index 0000000..2e95280 --- /dev/null +++ b/scripts/game/beta5/alliance/library/getTechOrder.inc @@ -0,0 +1,36 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + public function run($player, $send) { + $q = $this->db->query( + "SELECT tech, " . ($send ? "send_to" : "player") . ", submitted, obeyed FROM tech_trade_order " + . "WHERE " . ($send ? "player" : "send_to") . " = $player " + . "FOR UPDATE" + ); + if (!dbCount($q)) { + return array(null, null, null, null); + } + return dbFetchArray($q); + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/getTechOrders.inc b/scripts/game/beta5/alliance/library/getTechOrders.inc new file mode 100644 index 0000000..80ad632 --- /dev/null +++ b/scripts/game/beta5/alliance/library/getTechOrders.inc @@ -0,0 +1,58 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + public function run($alliance) { + // Get the orders for all players + $q = $this->db->query( + "SELECT player, send_to, tech, submitted, obeyed FROM tech_trade_order " + . "WHERE alliance = $alliance" + ); + + // Create the list of orders + $orders = array(); + while ($r = dbFetchArray($q)) { + if (is_null($r[4])) { + $r[4] = 0; + } + + if (!is_array($orders[$r[0]])) { + $orders[$r[0]] = array(); + } + $orders[$r[0]]['sendTo'] = $r[1]; + $orders[$r[0]]['sendTech'] = $r[2]; + $orders[$r[0]]['sendSub'] = $r[3]; + $orders[$r[0]]['sendDone'] = $r[4]; + + if (!is_array($orders[$r[1]])) { + $orders[$r[1]] = array(); + } + $orders[$r[1]]['recvFrom'] = $r[0]; + $orders[$r[1]]['recvTech'] = $r[2]; + $orders[$r[1]]['recvSub'] = $r[3]; + $orders[$r[1]]['recvDone'] = $r[4]; + } + + return $orders; + } + +} + +?> diff --git a/scripts/game/beta5/alliance/library/getTechRequests.inc b/scripts/game/beta5/alliance/library/getTechRequests.inc new file mode 100644 index 0000000..08764d8 --- /dev/null +++ b/scripts/game/beta5/alliance/library/getTechRequests.inc @@ -0,0 +1,38 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + public function run($player) { + // Get the player's current requests, if any + $q = $this->db->query( + "SELECT tech FROM tech_trade_request " + . "WHERE player = $player ORDER BY priority" + ); + + $requests = array(); + while ($r = dbFetchArray($q)) { + array_push($requests, $r[0]); + } + + return $requests; + } + +} + +?> diff --git a/scripts/game/beta5/alliance/library/getTechSubmission.inc b/scripts/game/beta5/alliance/library/getTechSubmission.inc new file mode 100644 index 0000000..34b8d28 --- /dev/null +++ b/scripts/game/beta5/alliance/library/getTechSubmission.inc @@ -0,0 +1,38 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + } + + public function run($player) { + $q = $this->db->query( + "SELECT submitted FROM tech_trade_list WHERE member = $player LIMIT 1" + ); + if (!dbCount($q)) { + return array(true, null, null); + } + + list($when) = dbFetchArray($q); + $nextSubmission = $when + $this->game->ticks['battle']->interval; + return array($nextSubmission <= time(), $when, $nextSubmission); + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/getVoters.inc b/scripts/game/beta5/alliance/library/getVoters.inc new file mode 100644 index 0000000..d41ebdd --- /dev/null +++ b/scripts/game/beta5/alliance/library/getVoters.inc @@ -0,0 +1,46 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Get the list of all people in an alliance that can vote, as well as the current leader + function run($a) { + $q = $this->db->query("SELECT leader FROM alliance WHERE id=$a"); + list($id) = dbFetchArray($q); + if (!is_null($id)) { + $l = array($id); + } else { + $l = array(); + } + + $q = $this->db->query("SELECT id FROM alliance_grade WHERE alliance=$a AND can_vote"); + if (!$q || dbCount($q) == 0) { + return $l; + } + $gl = array(); + while ($r = dbFetchArray($q)) { + list($g) = $r; + array_push($gl, $g); + } + + $q = $this->db->query( + "SELECT p.id FROM player p, alliance a WHERE a.id=$a AND p.alliance=a.id AND p.a_status='IN' " + . "AND (p.a_grade IN (" . join(',',$gl) . ") OR (p.a_grade IS NULL AND a.default_grade IN (".join(',',$gl).")))" + ); + while ($r = dbFetchArray($q)) { + list($p) = $r; + if (is_null($id) || $p != $id) { + array_push($l, $p); + } + } + + return $l; + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/kick.inc b/scripts/game/beta5/alliance/library/kick.inc new file mode 100644 index 0000000..0f8a4b9 --- /dev/null +++ b/scripts/game/beta5/alliance/library/kick.inc @@ -0,0 +1,18 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Kick an alliance member + function run($pid) { + $this->lib->call('leave', $pid, true); + // FIXME: message + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/leave.inc b/scripts/game/beta5/alliance/library/leave.inc new file mode 100644 index 0000000..42d0d43 --- /dev/null +++ b/scripts/game/beta5/alliance/library/leave.inc @@ -0,0 +1,84 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->players = $this->lib->game->getLib('beta5/player'); + $this->rankings = $this->lib->game->getLib('main/rankings'); + } + + + // Leave an alliance + function run($pid, $isKick = false) { + $p = $this->players->call('get', $pid); + if (is_null($p) || is_null($p['aid'])) { + return; + } + + $aid = $p['aid']; + $a = $this->lib->call('get', $aid); + if (is_null($a)) { + return; + } + + // Delete tech trading data + $this->db->query("DELETE FROM tech_trade_list WHERE member = $pid"); + $this->db->query("DELETE FROM tech_trade_request WHERE player = $pid"); + $this->db->query("DELETE FROM tech_trade_order WHERE player = $pid OR send_to = $pid"); + + $msgId = 9; + $mpid = $pid; + if ($pid == $a['leader']) { + if (!is_null($a['successor'])) { + $this->lib->call('stepDown', $aid, true); + $msgId = 14; + $mpid = $a['successor']; + } else { + $this->db->query("UPDATE alliance SET leader=NULL,democracy=TRUE WHERE id=$aid"); + $this->db->query("UPDATE alliance_grade SET can_vote=TRUE,can_be_cand=TRUE WHERE alliance=$aid"); + $msgId = 15; + } + } elseif ($pid == $a['successor']) { + $this->db->query("UPDATE alliance SET successor=NULL WHERE id=$aid"); + } + + if ($a['democracy'] == 't') { + $this->db->query("DELETE FROM alliance_candidate WHERE alliance=$aid AND candidate=".$pid); + } + + $this->db->query("UPDATE player SET alliance=NULL,a_vote=NULL,a_status=NULL,a_grade=NULL WHERE id=$pid"); + + $q = $this->db->query("SELECT COUNT(*) FROM player WHERE alliance=$aid AND a_status='IN'"); + list($pc) = dbFetchArray($q); + if ($pc == 0) { + $this->db->query("UPDATE player SET alliance=NULL,a_vote=NULL,a_status=NULL,a_grade=NULL WHERE alliance=$aid"); + $rt = $this->rankings->call('getType', 'a_general'); + $this->rankings->call('delete', $rt, $a['tag']); + $this->db->query("DELETE FROM alliance WHERE id=$aid"); + return; + } + + if ($isKick) { + return; + } + + if ($msgId == 9) { + $l = $this->lib->call('getKeepers', $aid); + } else { + $l = array_keys($this->lib->call('getMembers', $aid)); + } + $tag = addslashes($a['tag']); + $tm = time(); + l::FIXME("Use message API"); // FIXME + foreach ($l as $id) { + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($id,$tm,'alint','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=$id AND sent_on=$tm AND ftype='INT' ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_alint VALUES($mid,$aid,'$tag',$mpid,$msgId)"); + } + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/modifyForum.inc b/scripts/game/beta5/alliance/library/modifyForum.inc new file mode 100644 index 0000000..108978b --- /dev/null +++ b/scripts/game/beta5/alliance/library/modifyForum.inc @@ -0,0 +1,20 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Modifies an alliance forum + function run($id, $name, $userPost, $description) { + $this->db->query( + "UPDATE af_forum SET title='".addslashes($name)."',user_post=".($userPost?'TRUE':'FALSE').",description='".addslashes($description) + ."' WHERE id=$id" + ); + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/modifyRank.inc b/scripts/game/beta5/alliance/library/modifyRank.inc new file mode 100644 index 0000000..ca14ffe --- /dev/null +++ b/scripts/game/beta5/alliance/library/modifyRank.inc @@ -0,0 +1,77 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Modifies a new rank within the alliance + function run($rid, $name, $privs, $kick, $change, $fread, $fmod) { + $ll = array('NO ','PY ','PL ','PLD'); + $lt = array('NO', 'SL', 'SR', 'VL', 'MR'); + if (is_null($name)) { + $n = "NULL"; + } else { + $n = "'".addslashes($name)."'"; + } + $qs = "UPDATE alliance_grade SET name=$n"; + foreach ($privs as $p => $v) { + $qs .= ",$p="; + if ($p == "list_access") { + $qs .= "'" . $ll[$v] . "'"; + } elseif ($p == "tech_trade") { + $qs .= "'" . $lt[$v] . "'"; + } else { + $qs .= dbBool($v); + } + } + $qs .= " WHERE id=$rid"; + $this->db->query($qs); + + $q = $this->db->query("SELECT alliance FROM alliance_grade WHERE id=$rid"); + list($aid) = dbFetchArray($q); + $q = $this->db->query("SELECT default_grade,leader FROM alliance WHERE id=$aid"); + list($did, $lid) = dbFetchArray($q); + if ($rid == $did) { + $n = " IS NULL"; + } else { + $n = "=$rid"; + } + + if ($privs['can_vote'] == 0) { + $this->db->query("UPDATE player SET a_vote=NULL WHERE a_grade$n AND alliance=$aid AND a_status='IN'" . (is_null($lid) ? "": " AND id<>$lid")); + } + if ($privs['can_be_cand'] == 0) { + $q = $this->db->query("SELECT id FROM player WHERE a_grade$n AND alliance=$aid AND a_status='IN'" . (is_null($lid) ? "": " AND id<>$lid")); + $a = array(); + while ($r = dbFetchArray($q)) { + array_push($a, $r[0]); + } + if (count($a)) { + $this->db->query("DELETE FROM alliance_candidate WHERE candidate IN (".join(',',$a).")"); + } + } + + $this->db->query("DELETE FROM algr_kick WHERE grade=$rid"); + foreach ($kick as $id) { + $this->db->query("INSERT INTO algr_kick VALUES($rid,$id)"); + } + $this->db->query("DELETE FROM algr_chgrade WHERE grade=$rid"); + foreach ($change as $id) { + $this->db->query("INSERT INTO algr_chgrade VALUES($rid,$id)"); + } + + $this->db->query("DELETE FROM algr_forums WHERE grade=$rid"); + foreach ($fread as $id) { + $this->db->query("INSERT INTO algr_forums VALUES($rid,$id,".dbBool(0).")"); + } + foreach ($fmod as $id) { + $this->db->query("INSERT INTO algr_forums VALUES($rid,$id,".dbBool(1).")"); + } + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/moveForum.inc b/scripts/game/beta5/alliance/library/moveForum.inc new file mode 100644 index 0000000..5bb8fca --- /dev/null +++ b/scripts/game/beta5/alliance/library/moveForum.inc @@ -0,0 +1,36 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Moves an alliance forum up or down in the list + function run($id, $up) { + $q = $this->db->query("SELECT alliance,forder FROM af_forum WHERE id=$id"); + list($aid,$o) = dbFetchArray($q); + if ($o == 0 && $up) { + return; + } + + $q = $this->db->query("SELECT MAX(forder) FROM af_forum WHERE alliance=$aid"); + list($mo) = dbFetchArray($q); + if ($o == $mo && !$up) { + return; + } + + $this->db->query("UPDATE af_forum SET forder=" . ($mo+1) . " WHERE id=$id"); + if ($up) { + $this->db->query("UPDATE af_forum SET forder=forder+1 WHERE alliance=$aid AND forder=".($o-1)); + $this->db->query("UPDATE af_forum SET forder=".($o-1)." WHERE id=$id"); + } else { + $this->db->query("UPDATE af_forum SET forder=forder-1 WHERE alliance=$aid AND forder=".($o+1)); + $this->db->query("UPDATE af_forum SET forder=".($o+1)." WHERE id=$id"); + } + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/rejectRequest.inc b/scripts/game/beta5/alliance/library/rejectRequest.inc new file mode 100644 index 0000000..b6d4bb8 --- /dev/null +++ b/scripts/game/beta5/alliance/library/rejectRequest.inc @@ -0,0 +1,51 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->msgs = $this->lib->game->getLib('beta5/msg'); + } + + + // Rejects a request to join an alliance + function run($aid, $pid, $kid) { + $q = $this->db->query("SELECT id FROM player WHERE alliance=$aid AND a_status='REQ' AND id=$pid"); + if (!($q && dbCount($q) == 1)) { + return; + } + + $a = $this->lib->call('get', $aid); + if (is_null($a)) { + return; + } + + $tag = addslashes($a['tag']); + $tm = time(); + + $this->msgs->call('send', $pid, 'alint', array( + "msg_type" => 12, + "alliance" => $aid, + "tag" => $tag, + "player" => $pid, + )); + + $l = $this->lib->call('getKeepers', $aid); + foreach ($l as $id) { + if ($id == $kid) { + continue; + } + $this->msgs->call('send', $id, 'alint', array( + "msg_type" => 13, + "alliance" => $aid, + "tag" => $tag, + "player" => $pid, + )); + } + + $this->db->query("UPDATE player SET a_status=NULL,alliance=NULL,a_vote=NULL,a_grade=NULL WHERE id=$pid"); + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/removeCandidate.inc b/scripts/game/beta5/alliance/library/removeCandidate.inc new file mode 100644 index 0000000..039cc4a --- /dev/null +++ b/scripts/game/beta5/alliance/library/removeCandidate.inc @@ -0,0 +1,32 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Removes a candidate for an alliance's presidency + function run($aid,$pid) { + $this->db->query("DELETE FROM alliance_candidate WHERE candidate=$pid"); + + $a = $this->lib->call('get', $aid); + $tag = addslashes($a['tag']); + $tm = time(); + + $vl = $this->lib->call('getVoters', $aid); + foreach ($vl as $id) { + if ($id == $pid) { + continue; + } + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($id,$tm,'alint','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=$id AND sent_on=$tm AND ftype='INT' ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_alint VALUES($mid,$aid,'$tag',$pid,17)"); + } + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/sendRequest.inc b/scripts/game/beta5/alliance/library/sendRequest.inc new file mode 100644 index 0000000..85f9532 --- /dev/null +++ b/scripts/game/beta5/alliance/library/sendRequest.inc @@ -0,0 +1,29 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Sends a request to join an alliance + function run($p, $a) { + $this->db->query("UPDATE player SET alliance=$a,a_status='REQ',a_vote=NULL WHERE id=$p"); + + $ainf = $this->lib->call('get', $a); + $tag = addslashes($ainf['tag']); + $l = $this->lib->call('getKeepers', $a); + $tm = time(); + + foreach ($l as $id) { + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($id,$tm,'alint','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=$id AND sent_on=$tm AND ftype='INT'"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_alint VALUES($mid,$a,'$tag',$p,0)"); + } + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/setDemocratic.inc b/scripts/game/beta5/alliance/library/setDemocratic.inc new file mode 100644 index 0000000..516b09b --- /dev/null +++ b/scripts/game/beta5/alliance/library/setDemocratic.inc @@ -0,0 +1,47 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Switches an alliance's government mode + function run($aid, $demo) { + $a = $this->lib->call('get', $aid); + if (is_null($a)) { + return null; + } + $ad = ($a['democracy'] != "f"); + if ($ad == $demo) { + return; + } + + $this->db->query("UPDATE alliance SET democracy=".dbBool($demo)." WHERE id=$aid"); + if ($demo) { + $this->db->query("INSERT INTO alliance_candidate(alliance,candidate) VALUES ($aid,".$a['leader'].")"); + $q = $this->db->query("SELECT id FROM alliance_candidate WHERE candidate=".$a['leader']." AND alliance=$aid"); + list($cid) = dbFetchArray($q); + $this->db->query("UPDATE player SET a_vote=$cid WHERE id=".$a['leader']); + $msId = 4; + } else { + $this->db->query("DELETE FROM alliance_candidate WHERE alliance=$aid"); + $msId = 5; + } + + $q = $this->db->query("SELECT id FROM player WHERE alliance=$aid AND a_status='IN' AND id<>".$a['leader']); + $tm = time(); + $tag = addslashes($a['tag']); + while ($r = dbFetchArray($q)) { + $id = $r[0]; + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($id,$tm,'alint','INT',TRUE)"); + $q2 = $this->db->query("SELECT id FROM message WHERE player=$id AND sent_on=$tm AND ftype='INT' AND mtype='alint' ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q2); + $this->db->query("INSERT INTO msg_alint VALUES($mid,$aid,'$tag',".$a['leader'].",$msId)"); + } + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/setForumAccess.inc b/scripts/game/beta5/alliance/library/setForumAccess.inc new file mode 100644 index 0000000..120849d --- /dev/null +++ b/scripts/game/beta5/alliance/library/setForumAccess.inc @@ -0,0 +1,23 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Modifies the access list for an alliance forum + function run($id, $readers, $mods) { + $this->db->query("DELETE FROM algr_forums WHERE forum=$id"); + foreach ($readers as $rid) { + $this->db->query("INSERT INTO algr_forums VALUES($rid,$id,false)"); + } + foreach ($mods as $rid) { + $this->db->query("INSERT INTO algr_forums VALUES($rid,$id,true)"); + } + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/setSuccessor.inc b/scripts/game/beta5/alliance/library/setSuccessor.inc new file mode 100644 index 0000000..c921568 --- /dev/null +++ b/scripts/game/beta5/alliance/library/setSuccessor.inc @@ -0,0 +1,38 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Changes the successor for the current leader + function run($aid, $sid) { + $a = $this->lib->call('get', $aid); + if (is_null($a) || $a['successor'] == $sid) { + return; + } + $tag = addslashes($a['tag']); + $tm = time(); + + if (!is_null($a['successor'])) { + $id = $a['successor']; + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($id,$tm,'alint','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=$id AND sent_on=$tm AND ftype='INT' AND mtype='alint' ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_alint VALUES($mid,$aid,'$tag',".$a['leader'].",2)"); + } + + $this->db->query("UPDATE alliance SET successor=".(is_null($sid)?"NULL":$sid) . " WHERE id=$aid"); + if (!is_null($sid)) { + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($sid,$tm,'alint','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=$sid AND sent_on=$tm AND ftype='INT' AND mtype='alint' ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_alint VALUES($mid,$aid,'$tag',".$a['leader'].",3)"); + } + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/setTechRequests.inc b/scripts/game/beta5/alliance/library/setTechRequests.inc new file mode 100644 index 0000000..aaff2e5 --- /dev/null +++ b/scripts/game/beta5/alliance/library/setTechRequests.inc @@ -0,0 +1,46 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + public function run($player, $requests) { + // Delete the player's requests + $this->db->query("DELETE FROM tech_trade_request WHERE player = $player"); + + // Get the player's alliance; if we're here, then the player *is* in an alliance + $q = $this->db->query("SELECT alliance FROM player WHERE id = $player"); + list($alliance) = dbFetchArray($q); + + // Delete the player's requests and reinserts them while removing any tech that is now "seen" + $this->db->query("DELETE FROM tech_trade_request WHERE player = $player"); + $prio = 0; + foreach ($requests as $req) { + $this->db->query( + "INSERT INTO tech_trade_request (alliance, player, priority, tech) " + . "VALUES ($alliance, $player, $prio, $req)" + ); + $prio ++; + } + } + +} + +?> diff --git a/scripts/game/beta5/alliance/library/setTechTradeMode.inc b/scripts/game/beta5/alliance/library/setTechTradeMode.inc new file mode 100644 index 0000000..e8fd667 --- /dev/null +++ b/scripts/game/beta5/alliance/library/setTechTradeMode.inc @@ -0,0 +1,34 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + public function run($alliance, $mode) { + if (!in_array($mode, self::$okModes)) { + return false; + } + + $x = $this->db->query("UPDATE alliance SET enable_tt = '$mode' WHERE id = $alliance"); + return !!$x; + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/stepDown.inc b/scripts/game/beta5/alliance/library/stepDown.inc new file mode 100644 index 0000000..9c117fd --- /dev/null +++ b/scripts/game/beta5/alliance/library/stepDown.inc @@ -0,0 +1,58 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Makes the alliance leader step down from power + function run($aid, $isLeave = false) { + $a = $this->lib->call('get', $aid); + if (is_null($a) || is_null($a['successor'])) { + return; + } + $this->db->query("UPDATE alliance SET leader=".$a['successor'].",successor=NULL WHERE id=$aid"); + if ($a['democracy'] == 't') { + $q = $this->db->query("SELECT id FROM alliance_candidate WHERE candidate=".$a['successor']); + if (dbCount($q) == 1) { + $q2 = $this->db->query("SELECT id FROM alliance_candidate WHERE candidate=".$a['leader']); + list($r1) = dbFetchArray($q); + list($r2) = dbFetchArray($q2); + $this->db->query("UPDATE player SET a_vote=$r2 WHERE a_vote=$r1"); + $this->db->query("DELETE FROM alliance_candidate WHERE candidate=".$a['successor']); + } else { + $q = $this->db->query("SELECT id FROM alliance_candidate WHERE candidate={$a['leader']}"); + list($id) = dbFetchArray($q); + $this->db->query("UPDATE player SET a_vote=$id WHERE id={$a['successor']}"); + } + $this->db->query("UPDATE alliance_candidate SET candidate=".$a['successor']." WHERE candidate=".$a['leader']); + } + + if ($isLeave) { + return; + } + + $tm = time(); + $tag = addslashes($a['tag']); + $qm = $this->db->query("SELECT id FROM player WHERE alliance=$aid AND a_status='IN'"); + while ($r = dbFetchArray($qm)) { + $id = $r[0]; + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($id,$tm,'alint','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=$id AND sent_on=$tm AND ftype='INT' AND mtype='alint' ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q); + if ($id == $a['leader']) { + $st = $a['successor'].",6"; + } elseif ($id == $a['successor']) { + $st = $a['leader'].",7"; + } else { + $st = $a['successor'].",8"; + } + $this->db->query("INSERT INTO msg_alint VALUES($mid,$aid,'$tag',$st)"); + } + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/submitTechList.inc b/scripts/game/beta5/alliance/library/submitTechList.inc new file mode 100644 index 0000000..00e3d87 --- /dev/null +++ b/scripts/game/beta5/alliance/library/submitTechList.inc @@ -0,0 +1,69 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + } + + public function run($player) { + // Get alliance + $pInfo = $this->game->getLib('beta5/player')->call('get', $player); + $alliance = $pInfo['aid']; + + // Get the player's technologies + $techLib = $this->game->getLib('beta5/tech'); + $implemented = $techLib->call('getTopics', $player, 1); + $completed = $techLib->call('getTopics', $player, 0); + $foreseen = $techLib->call('getTopics', $player, -1); + $laws = $techLib->call('getLaws', $player); + + // Delete previous tech submission + $this->db->query("DELETE FROM tech_trade_list WHERE member = $player"); + + // Insert technologies + for ($i = 0; $i < count($laws) / 2; $i ++) { + $this->db->query( + "INSERT INTO tech_trade_list (alliance, member, tech, status) " + . "VALUES ($alliance, $player, {$laws[$i * 2]}, 'L')" + ); + } + for ($i = 0; $i < count($completed); $i ++) { + $this->db->query( + "INSERT INTO tech_trade_list (alliance, member, tech, status) " + . "VALUES ($alliance, $player, {$completed[$i]}, 'N')" + ); + } + for ($i = 0; $i < count($implemented); $i ++) { + $this->db->query( + "INSERT INTO tech_trade_list (alliance, member, tech, status) " + . "VALUES ($alliance, $player, {$implemented[$i]}, 'I')" + ); + } + for ($i = 0; $i < count($foreseen) / 2; $i ++) { + $this->db->query( + "INSERT INTO tech_trade_list (alliance, member, tech, status) " + . "VALUES ($alliance, $player, {$foreseen[$i * 2]}, 'F')" + ); + } + + // Update request list + $this->lib->call('updateRequests', $player); + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/submitTechOrders.inc b/scripts/game/beta5/alliance/library/submitTechOrders.inc new file mode 100644 index 0000000..68e9510 --- /dev/null +++ b/scripts/game/beta5/alliance/library/submitTechOrders.inc @@ -0,0 +1,119 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + $this->msgs = $this->game->getLib('beta5/msg'); + } + + public function run($alliance, $orders) { + // List all players included in the orders + $players = array_keys($orders); + foreach ($orders as $oPid => $order) { + if (!in_array($order[1], $players)) { + array_push($players, $order[1]); + } + } + + // Check if the players are in the alliance + $q = $this->db->query( + "SELECT id FROM player WHERE id IN (" . join(',', $players) . ") " + . "AND (alliance <> $alliance OR a_status <> 'IN ')" + ); + if (dbCount($q)) { + return false; + } + + // Get the tech lists for all players + $techList = $this->lib->call('getTechList', $alliance, false); + + // Get all dependencies + $dependencies = $this->getDependencies(); + + // For each order, check if the sender had submitted the technology as + // either New, Implemented or Law, and whether he can actually send + // technologies. Also check if the receiver had submitted his list, if + // he can receive techs and if the list doesn't contain the technology + // to be sent. Oh, and if he has the dependencies. + foreach ($orders as $sender => $order) { + list($tech, $receiver) = $order; + + if (!is_array($techList[$sender]) || !is_array($techList[$receiver]) + || $sender == $receiver || !$techList[$sender]['submitted'] + || $techList[$sender]['vacation'] || $techList[$sender]['restrict'] + || !$techList[$receiver]['submitted'] || $techList[$receiver]['vacation'] + || $techList[$receiver]['restrict'] + || !array_key_exists($tech, $techList[$sender]['list']) + || $techList[$sender]['list'][$tech] == 'F' + || array_key_exists($tech, $techList[$receiver]['list']) ) { + return false; + } + + if (is_array($dependencies[$tech])) { + foreach ($dependencies[$tech] as $dep) { + if (!array_key_exists($dep, $techList[$receiver]['list'])) { + l::trace("dep $dep not found"); + return false; + } + $dStatus = $techList[$receiver]['list'][$dep]; + if ($dStatus != 'I' && $dStatus != 'L') { + return false; + } + } + } + } + + // Delete current orders + $this->db->query("DELETE FROM tech_trade_order WHERE alliance = $alliance"); + + // Insert new orders + foreach ($orders as $sender => $order) { + list($tech, $receiver) = $order; + $this->db->query( + "INSERT INTO tech_trade_order (alliance, player, send_to, tech) " + . "VALUES($alliance, $sender, $receiver, $tech)" + ); + } + + // Send private message to all players included in the exchange + $q = $this->db->query("SELECT tag FROM alliance WHERE id = $alliance"); + list($tag) = dbFetchArray($q); + foreach ($players as $player) { + $this->msgs->call('send', $player, 'alint', array( + "msg_type" => 20, + "tag" => $tag, + "alliance" => $alliance + )); + } + return true; + } + + + private function getDependencies() { + $q = $this->db->query("SELECT research, depends_on FROM research_dep"); + $deps = array(); + while ($r = dbFetchArray($q)) { + if (!is_array($deps[$r[0]])) { + $deps[$r[0]] = array(); + } + array_push($deps[$r[0]], $r[1]); + } + return $deps; + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/takePresidency.inc b/scripts/game/beta5/alliance/library/takePresidency.inc new file mode 100644 index 0000000..e5ee7e5 --- /dev/null +++ b/scripts/game/beta5/alliance/library/takePresidency.inc @@ -0,0 +1,34 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Makes a player the new president for an alliance + function run($aid, $pid) { + $this->db->query("UPDATE alliance SET leader=$pid,successor=NULL WHERE id=$aid"); + + $a = $this->lib->call('get', $aid); + $tag = addslashes($a['tag']); + $tm = time(); + + $l = array_keys($this->lib->call('getMembers', $aid)); + foreach ($l as $id) { + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($id,$tm,'alint','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=$id AND sent_on=$tm AND ftype='INT' ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q); + if ($id == $pid) { + $mt = 18; + } else { + $mt = 19; + } + $this->db->query("INSERT INTO msg_alint VALUES($mid,$aid,'$tag',$pid,$mt)"); + } + } +} + +?> diff --git a/scripts/game/beta5/alliance/library/updateRequests.inc b/scripts/game/beta5/alliance/library/updateRequests.inc new file mode 100644 index 0000000..70dbae6 --- /dev/null +++ b/scripts/game/beta5/alliance/library/updateRequests.inc @@ -0,0 +1,75 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + public function run($player) { + // Get the player's alliance + $q = $this->db->query("SELECT alliance,a_status FROM player WHERE id = $player"); + list($alliance, $aStat) = dbFetchArray($q); + + // If the player is not a member of any alliance, remove all requests and leave + if (is_null($alliance) || $aStat != 'IN ') { + $this->db->query("DELETE FROM tech_trade_request WHERE player = $player"); + return; + } + + // Get the player's current requests, if any + $q = $this->db->query( + "SELECT alliance, tech FROM tech_trade_request " + . "WHERE player = $player ORDER BY priority" + ); + $requests = array(); + while ($r = dbFetchArray($q)) { + if ($r[0] != $alliance) { + // Requests are from another alliance; delete them and leave + $this->db->query("DELETE FROM tech_trade_request WHERE player = $player"); + return; + } + array_push($requests, $r[1]); + } + + // Get all technologies or laws that are at least foreseen + $q = $this->db->query( + "SELECT r.id FROM research_player p, research r " + . "WHERE r.id = p.research AND p.points >= 75 * r.points / 100 AND p.player = $player" + ); + $seenTechs = array(); + while ($r = dbFetchArray($q)) { + array_push($seenTechs, $r[0]); + } + + // Delete the player's requests and reinserts them while removing any tech that is now "seen" + $this->db->query("DELETE FROM tech_trade_request WHERE player = $player"); + $prio = 0; + foreach ($requests as $req) { + if (in_array($req, $seenTechs)) { + continue; + } + $this->db->query( + "INSERT INTO tech_trade_request (alliance, player, priority, tech) " + . "VALUES ($alliance, $player, $prio, $req)" + ); + $prio ++; + } + } + +} + +?> diff --git a/scripts/game/beta5/alliance/library/updateVictory.inc b/scripts/game/beta5/alliance/library/updateVictory.inc new file mode 100644 index 0000000..f6b9021 --- /dev/null +++ b/scripts/game/beta5/alliance/library/updateVictory.inc @@ -0,0 +1,54 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Compute victory conditions for an alliance + function run($aid) { + // Get the total amount of planets + $q = $this->db->query("SELECT COUNT(*) FROM planet WHERE status = 0"); + list($pCount) = dbFetchArray($q); + + // Get the amount of planets the alliance controls + $q = $this->db->query("SELECT COUNT(*) FROM planet WHERE owner IN (" + . "SELECT id FROM player WHERE alliance = $aid AND a_status='IN')"); + list($aCount) = dbFetchArray($q); + + // Compute the ratio + $pRatio = $aCount / $pCount; + if ($pRatio > 0.75) { + $pRatio = 0.75; + + // Check if the alliance was already scheduled for victory + $q = $this->db->query("SELECT time_of_victory FROM alliance_victory WHERE alliance=$aid"); + if (dbCount($q)) { + list($tov) = dbFetchArray($q); + if (time() > $tov) { + $tValue = 0.25; + } else { + $diff = $tov - time(); + if ($diff > 604800) { + $diff = 604800; + } + $tValue = (604800 - $diff) / 2419200; // 7*24*3600*4 + } + logText("tov = $tov, diff = $diff, tval = $tValue"); + } else { + $this->db->query("INSERT INTO alliance_victory(alliance, time_of_victory)" + . " VALUES ($aid, UNIX_TIMESTAMP(NOW()) + 604800)"); + $tValue = 0; + } + } else { + $tValue = 0; + $this->db->query("DELETE FROM alliance_victory WHERE alliance=$aid"); + } + + return round(($pRatio + $tValue) * 100); + } +} +?> diff --git a/scripts/game/beta5/bq/library.inc b/scripts/game/beta5/bq/library.inc new file mode 100644 index 0000000..ff17e7c --- /dev/null +++ b/scripts/game/beta5/bq/library.inc @@ -0,0 +1,50 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + // Returns the length of a planet's build queue + function getLength($pl) { + if (is_null($this->qLen[$pl])) { + $q = $this->db->query("SELECT COUNT(*) FROM buildqueue WHERE planet = $pl"); + list($this->qLen[$pl]) = dbFetchArray($q); + } + return $this->qLen[$pl]; + } + + // Moves an item down the build queue + function moveDown($pl, $it) { + $bql = $this->lib->call('getLength', $pl); + $np = $it + 1; + $this->db->query("UPDATE buildqueue SET bq_order = $bql WHERE planet = $pl AND bq_order = $it"); + $this->db->query("UPDATE buildqueue SET bq_order = $it WHERE planet = $pl AND bq_order = $np"); + $this->db->query("UPDATE buildqueue SET bq_order = $np,workunits=0 WHERE planet = $pl AND bq_order = $bql"); + } + + // Moves an item up the build queue + function moveUp($pl, $it) { + $bql = $this->lib->call('getLength', $pl); + $np = $it - 1; + $this->db->query("UPDATE buildqueue SET bq_order = $bql WHERE planet = $pl AND bq_order = $it"); + $this->db->query("UPDATE buildqueue SET bq_order = $it WHERE planet = $pl AND bq_order = $np"); + $this->db->query("UPDATE buildqueue SET bq_order = $np,workunits=0 WHERE planet = $pl AND bq_order = $bql"); + } +} + +?> diff --git a/scripts/game/beta5/bq/library/append.inc b/scripts/game/beta5/bq/library/append.inc new file mode 100644 index 0000000..8ce98bc --- /dev/null +++ b/scripts/game/beta5/bq/library/append.inc @@ -0,0 +1,35 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->rules = $this->lib->game->getLib('beta5/rules'); + } + + + // Add items to a planet's build queue + function run($id, $nb, $type) { + $q = $this->db->query("SELECT owner FROM planet WHERE id = $id"); + list($uid) = dbFetchArray($q); + $ru = $this->rules->call('get', $uid); + + $q = $this->db->query("SELECT MAX(bq_order) FROM buildqueue WHERE planet = $id"); + if (dbCount($q)) { + list($o) = dbFetchArray($q); + if (is_null($o)) { + $o = -1; + } + } else { + $o = -1; + } + $o ++; + + $this->db->query("INSERT INTO buildqueue VALUES($id, $o, $type, $nb, 0)"); + $cost = $ru['build_cost_'.$this->lib->mainClass->types[$type]] * $nb; + $this->db->query("UPDATE player SET cash = cash - $cost WHERE id = $uid"); + } +} + +?> diff --git a/scripts/game/beta5/bq/library/flush.inc b/scripts/game/beta5/bq/library/flush.inc new file mode 100644 index 0000000..6b3338e --- /dev/null +++ b/scripts/game/beta5/bq/library/flush.inc @@ -0,0 +1,32 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->rules = $this->lib->game->getLib('beta5/rules'); + } + + + // Flush a planet's build queue + function run($id) { + $q = $this->db->query("SELECT owner FROM planet WHERE id = $id"); + list($uid) = dbFetchArray($q); + if (is_null($uid)) { + return; + } + $ru = $this->rules->call('get', $uid); + + $sum = 0; + $q = $this->db->query("SELECT item,quantity FROM buildqueue WHERE planet = $id"); + while ($r = dbFetchArray($q)) { + $sum += $r[1] * $ru['build_cost_'.$this->lib->mainClass->types[$r[0]]]; + } + + $this->db->query("DELETE FROM buildqueue WHERE planet = $id"); + $this->db->query("UPDATE player SET cash = cash + $sum WHERE id = $uid"); + } +} + +?> diff --git a/scripts/game/beta5/bq/library/get.inc b/scripts/game/beta5/bq/library/get.inc new file mode 100644 index 0000000..db837c6 --- /dev/null +++ b/scripts/game/beta5/bq/library/get.inc @@ -0,0 +1,52 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->rules = $this->lib->game->getLib('beta5/rules'); + } + + + // Returns the contents of a planet's build queue + function run($id) { + $q = $this->db->query("SELECT owner,mfact,corruption FROM planet WHERE id=$id"); + list($uid, $mf, $cl) = dbFetchArray($q); + + $ru = $this->rules->call('get', $uid); + $mp = $ru['mf_productivity'] * $ru['mf_productivity_factor'] * $mf; + if ($mp == 0) { + $mp = 1; + } + $cl = $cl / 32000; + if ($cl > .1) { + $mp = floor($mp * (1.1 - $cl)); + } + + $q = $this->db->query("SELECT item,quantity,workunits FROM buildqueue WHERE planet = $id ORDER BY bq_order ASC"); + $bq = array(); + $cwu = 0; + while ($r = dbFetchArray($q)) { + $i = array( + "type" => $r[0], + "quantity" => $r[1], + "workunits" => $r[2] + ); + $units = $ru['workunits_'.$this->lib->mainClass->types[$r[0]]] * $r[1] - $r[2]; + $i['units'] = $units; + $cwu += $units; + $mod = $units % $mp; + $ttb = ($units - $mod) / $mp + ($mod ? 1 : 0); + $i['time'] = $ttb; + $mod = $cwu % $mp; + $ttb = ($cwu - $mod) / $mp + ($mod ? 1 : 0); + $i['ctime'] = $ttb; + $i['cost'] = $r[1] * $ru['build_cost_'.$this->lib->mainClass->types[$r[0]]]; + array_push($bq, $i); + } + return $bq; + } +} + +?> diff --git a/scripts/game/beta5/bq/library/getReplacementCost.inc b/scripts/game/beta5/bq/library/getReplacementCost.inc new file mode 100644 index 0000000..b2bf486 --- /dev/null +++ b/scripts/game/beta5/bq/library/getReplacementCost.inc @@ -0,0 +1,41 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->rules = $this->lib->game->getLib('beta5/rules'); + } + + // Computes the cost of replacing a set of build queue items + function run($pid, $items, $rCost) { + if (count($items) == 0) { + return 0; + } + + $q = $this->db->query("SELECT owner FROM planet WHERE id = $pid"); + list($uid) = dbFetchArray($q); + $ru = $this->rules->call('get', $uid); + + if (count($items) == 1) { + $wc = "=".$items[0]; + } else { + $wc = "IN (" . join(',', $items) . ")"; + } + $q = $this->db->query("SELECT item,quantity FROM buildqueue WHERE planet = $pid AND bq_order $wc"); + if (!dbCount($q)) { + return 0; + } + + $sum = 0; + while ($r = dbFetchArray($q)) { + $sum -= $r[1] * $ru['build_cost_'.$this->lib->mainClass->types[$r[0]]]; + $sum += $rCost; + } + + return $sum; + } +} + +?> diff --git a/scripts/game/beta5/bq/library/remove.inc b/scripts/game/beta5/bq/library/remove.inc new file mode 100644 index 0000000..cd57d7d --- /dev/null +++ b/scripts/game/beta5/bq/library/remove.inc @@ -0,0 +1,30 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->rules = $this->lib->game->getLib('beta5/rules'); + } + + + // Removes an item from a build queue + function run($pid, $item) { + $q = $this->db->query("SELECT owner FROM planet WHERE id = $pid"); + list($uid) = dbFetchArray($q); + $ru = $this->rules->call('get', $uid); + + $q = $this->db->query("SELECT item,quantity FROM buildqueue WHERE planet = $pid AND bq_order = $item"); + if (!dbCount($q)) { + return; + } + list($t,$n) = dbFetchArray($q); + $cost = $n * $ru['build_cost_'.$this->lib->mainClass->types[$t]]; + + $this->db->query("DELETE FROM buildqueue WHERE planet = $pid AND bq_order = $item"); + $this->db->query("UPDATE player SET cash = cash + $cost WHERE id = $uid"); + } +} + +?> diff --git a/scripts/game/beta5/bq/library/reorder.inc b/scripts/game/beta5/bq/library/reorder.inc new file mode 100644 index 0000000..2cd7af5 --- /dev/null +++ b/scripts/game/beta5/bq/library/reorder.inc @@ -0,0 +1,24 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Reorders the build queue + function run($pid) { + $q = $this->db->query("SELECT bq_order FROM buildqueue WHERE planet = $pid ORDER BY bq_order ASC"); + $i = 0; + while ($r = dbFetchArray($q)) { + if ($r[0] != $i) { + $this->db->query("UPDATE buildqueue SET bq_order = $i WHERE planet = $pid AND bq_order = ".$r[0]); + } + $i ++; + } + } +} + +?> diff --git a/scripts/game/beta5/bq/library/replace.inc b/scripts/game/beta5/bq/library/replace.inc new file mode 100644 index 0000000..0969848 --- /dev/null +++ b/scripts/game/beta5/bq/library/replace.inc @@ -0,0 +1,43 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->rules = $this->lib->game->getLib('beta5/rules'); + } + + // Replaces items in a build queue + function run($pid, $items, $nb, $type) { + if (count($items) == 0) { + return; + } + + $q = $this->db->query("SELECT owner FROM planet WHERE id = $pid"); + list($uid) = dbFetchArray($q); + $ru = $this->rules->call('get', $uid); + + if (count($items) == 1) { + $wc = "=".$items[0]; + } else { + $wc = "IN (" . join(',', $items) . ")"; + } + $q = $this->db->query("SELECT item,quantity FROM buildqueue WHERE planet = $pid AND bq_order $wc"); + if (!dbCount($q)) { + return; + } + + $rCost = $nb * $ru['build_cost_'.$this->lib->mainClass->types[$type]]; + $sum = 0; + while ($r = dbFetchArray($q)) { + $sum += $r[1] * $ru['build_cost_'.$this->lib->mainClass->types[$r[0]]]; + $sum -= $rCost; + } + + $this->db->query("UPDATE buildqueue SET item=$type,quantity=$nb,workunits=0 WHERE planet = $pid AND bq_order $wc"); + $this->db->query("UPDATE player SET cash = cash + ($sum) WHERE id = $uid"); + } +} + +?> diff --git a/scripts/game/beta5/ctf/library.inc b/scripts/game/beta5/ctf/library.inc new file mode 100644 index 0000000..66a0036 --- /dev/null +++ b/scripts/game/beta5/ctf/library.inc @@ -0,0 +1,99 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + } + + // This function sends a CTF-specific message to a player + public function message($player, $mType, $team, $timestamp = null) { + if (! $this->msgs) { + $this->msgs = $this->game->getLib('beta5/msg'); + } + + $this->msgs->call('send', $player, 'ctf', array( + 'msg_type' => $mType, + 'team' => $team, + 'time_stamp' => $timestamp + ), $mType ? 'INT' : 'IN'); + } + + // This function checks whether a specific system is a target + public function isTarget($system) { + // Cache the targets as this function tends to be called several + // times in a row + if (! $this->targets) { + $q = $this->db->query("SELECT system FROM ctf_target"); + if (!($q && dbCount($q))) { + return; + } + $this->targets = array(); + while ($r = dbFetchArray($q)) { + array_push($this->targets, $r[0]); + } + } + + return in_array($system, $this->targets); + } + + // This function returns the amount of points a team has + public function getTeamPoints($team) { + $q = $this->db->query("SELECT points FROM ctf_points WHERE team = $team"); + if (!($q && dbCount($q))) { + return 0; + } + list($points) = dbFetchArray($q); + return $points; + } + + // This function checks for victory + public function checkVictory() { + // Get the target count + $q = $this->db->query("SELECT COUNT(*) FROM ctf_target"); + list($tCount) = dbFetchArray($q); + + // For each team, get the count of targets held without grace + // for more than hours + $time = $this->game->params['v2time'] * 3600; + $q = $this->db->query( + "SELECT held_by, COUNT(*) FROM ctf_target " + . "WHERE held_by IS NOT NULL AND grace_expires IS NULL " + . "AND held_since <= UNIX_TIMESTAMP(NOW()) - $time " + . "GROUP BY held_by" + ); + + // If there's only one result and if the count is the same as + // the target count, we got a winner + if (dbCount($q) == 1) { + list($team, $hCount) = dbFetchArray($q); + if ($hCount == $tCount) { + return $team; + } + } + return null; + } +} + +?> diff --git a/scripts/game/beta5/ctf/library/assign.inc b/scripts/game/beta5/ctf/library/assign.inc new file mode 100644 index 0000000..e717b71 --- /dev/null +++ b/scripts/game/beta5/ctf/library/assign.inc @@ -0,0 +1,88 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->planets = $this->lib->game->getLib('beta5/planet'); + } + + public function run($player, $planetName, $team) { + logText("Assigning planet $planetName for player #$player in team #$team"); + + // Find unassigned systems marked as spawning area for the team + $system = $this->findFreeSystem($team); + + // Assign a random planet in the system + $planet = $this->assignPlanet($player, $system, $planetName); + + // Mark the system as the player's spawning point + $this->markSystem($player, $system); + + return $planet; + } + + + private function findFreeSystem($team) { + $q = $this->db->query( + "SELECT s.id FROM system s, ctf_alloc a " + . "WHERE s.id = a.system AND a.alliance = $team " + . "AND a.spawn_point AND NOT s.assigned " + . "FOR UPDATE OF s, a" + ); + + $n = rand(0, dbCount($q) - 2); + while ($n) { + dbFetchArray($q); + $n --; + } + + list($system) = dbFetchArray($q); + return $system; + } + + + private function assignPlanet($player, $system, $name) { + // Randomize orbit + $npl = 6; + $porb = rand(0, $npl - 1); + + // Give the planet to the player + $tm = time(); + $this->db->query("UPDATE planet SET name='$name', owner=$player,renamed=$tm,mod_check=FALSE " + . "WHERE system = $system AND orbit = $porb"); + + // Get planet ID and population + $q = $this->db->query("SELECT id, pop FROM planet WHERE system = $system AND orbit = $porb FOR UPDATE"); + list($plid, $cPop) = dbFetchArray($q); + + // Update happiness and maximum population + $this->planets->call('updateHappiness', $plid); + $this->planets->call('updateMaxPopulation', $plid, null, $player); + + return $plid; + } + + + private function markSystem($player, $system) { + // Mark the system itself + $this->db->query("UPDATE system SET assigned = TRUE WHERE id = $system"); + + // Mark the allocation record + $this->db->query("UPDATE ctf_alloc SET player = $player WHERE system = $system"); + } +} + +?> diff --git a/scripts/game/beta5/ctf/library/checkTargets.inc b/scripts/game/beta5/ctf/library/checkTargets.inc new file mode 100644 index 0000000..46313d3 --- /dev/null +++ b/scripts/game/beta5/ctf/library/checkTargets.inc @@ -0,0 +1,279 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + } + + + public function run() { + // Get the current status (used later to send messages) + $initialStatus = $this->getStatus(); + $this->grace = ($initialStatus[0] != 'nohold') ? (3600 * $this->game->params['v2grace']) : 0; + + // Get the list of all target systems + $q = $this->db->query("SELECT * FROM ctf_target"); + $targets = array(); + while ($r = dbFetchHash($q)) { + array_push($targets, $r); + } + + // For each target, check the system's status + foreach ($targets as $target) { + $this->checkTargetStatus($target); + } + + // Get the new status + $newStatus = $this->getStatus(); + + // Check status changes + $this->checkStatusChanges($initialStatus, $newStatus); + } + + private function getStatus() { + // Get the target count + $q = $this->db->query("SELECT COUNT(*) FROM ctf_target"); + list($tCount) = dbFetchArray($q); + + // For each team, get the count of targets held without grace + $q = $this->db->query( + "SELECT held_by, COUNT(*) FROM ctf_target " + . "WHERE held_by IS NOT NULL AND grace_expires IS NULL " + . "GROUP BY held_by" + ); + if (dbCount($q) == 1) { + list($team, $hCount) = dbFetchArray($q); + if ($hCount == $tCount) { + $q = $this->db->query("SELECT MAX(held_since) FROM ctf_target"); + list($heldSince) = dbFetchArray($q); + return array('held', $team, $heldSince); + } + } + + // For each team, get the count of targets held + $q = $this->db->query( + "SELECT held_by, COUNT(*) FROM ctf_target " + . "WHERE held_by IS NOT NULL " + . "GROUP BY held_by" + ); + if (dbCount($q) == 1) { + list($team, $gCount) = dbFetchArray($q); + if ($gCount == $tCount) { + $q = $this->db->query("SELECT MIN(grace_expires) FROM ctf_target"); + list($graceExpires) = dbFetchArray($q); + return array('grace', $team, $graceExpires); + } + } + + return array('nohold'); + } + + + private function checkTargetStatus($target) { + // Get the team for each planet in the system + $q = $this->db->query( + "SELECT y.alliance FROM planet p, player y " + . "WHERE y.id = p.owner AND p.system = {$target['system']}" + ); + + // If all planets are being held, list the alliances + $teams = array(); + $teamPlanets = array(); + while ($r = dbFetchArray($q)) { + if (array_key_exists($r[0], $teamPlanets)) { + $teamPlanets[$r[0]] ++; + } else { + $teamPlanets[$r[0]] = 1; + array_push($teams, $r[0]); + } + } + + if (count($teams) == 1 && $teamPlanets[$teams[0]] == 6) { + // One team is holding the whole system + $this->heldByTeam($target, $teams[0]); + } else { + // The system is not being held by any particular team + $this->notHeld($target); + } + } + + + private function heldByTeam($target, $team) { + $tid = $target['system']; + + if (is_null($target['held_by'])) { + // If only one team is holding the planets and the system was + // not marked as being held, mark it + logText("Target #$tid now held by team #$team"); + $this->assignTarget($target['system'], $team); + } elseif ($target['held_by'] == $team && ! is_null($target['grace_expires'])) { + // If the target was being held by the team but had a grace expiration, + // cancel grace period expiration and increase held_by accordingly + logText("Target #$tid held by team #$team again, grace cancelled"); + $gracePeriod = time() - $target['grace_expires'] + $this->grace; + $this->db->query( + "UPDATE ctf_target " + . "SET grace_expires = NULL, held_since = held_since + $gracePeriod " + . "WHERE system = {$target['system']}" + ); + } elseif ($target['held_by'] != $team && $this->grace > 0) { + // The target is now being held by another team and the game + // has support for grace periods + if (is_null($target['grace_expires'])) { + // No grace was set - set it + logText("Target #$tid held by team #$team, setting grace for previous holder #" + . $target['held_by']); + $this->setGrace($target['system'], $this->grace); + } elseif ($target['grace_expires'] <= time()) { + // Grace has expired, but a new team is holding the system + logText("Target #$tid now held by team #$team, grace ended for previous holder #" + . $target['held_by']); + $this->assignTarget($target['system'], $team); + } + } elseif ($target['held_by'] != $team) { + // The target is now being held by another team, and there is no + // grace period + logText("Target #$tid now held by team #$team, no grace for previous holder #" + . $target['held_by']); + $this->assignTarget($target['system'], $team); + } + } + + + private function notHeld($target) { + + // If the target wasn't being held before, we're done + if (is_null($target['held_by'])) { + return; + } + + $tid = $target['system']; + if ($this->grace > 0) { + + // If there is support for grace periods + if (is_null($target['grace_expires'])) { + // Grace period wasn't set - set it + $this->setGrace($target['system'], $this->grace); + logText("Target #$tid no longer held by team #{$target['held_by']}, setting grace"); + } elseif ($target['grace_expires'] <= time()) { + // Grace period expired, no-one's holding the system + logText("Target #$tid no longer held by team #{$target['held_by']}, grace ended"); + $this->unassignTarget($target['system']); + } + + } else { + // No grace periods, no-one's holding the system + logText("Target #$tid no longer held by team #{$target['held_by']}, no grace"); + $this->unassignTarget($target['system']); + } + } + + + private function assignTarget($target, $team) { + $this->db->query( + "UPDATE ctf_target " + . "SET held_by = $team, " + . "held_since = UNIX_TIMESTAMP(NOW()), " + . "grace_expires = NULL " + . "WHERE system = $target" + ); + } + + private function setGrace($target, $grace) { + $this->db->query( + "UPDATE ctf_target SET grace_expires = UNIX_TIMESTAMP(NOW()) + $grace WHERE system = $target" + ); + } + + private function unassignTarget($target) { + $this->db->query( + "UPDATE ctf_target " + . "SET held_by = NULL, held_since = NULL, grace_expires = NULL " + . "WHERE system = $target" + ); + } + + + private function checkStatusChanges($initialStatus, $newStatus) { + $winTime = $this->game->params['v2time'] * 3600; + + // Status hasn't changed + if ($initialStatus[0] == $newStatus[0] && $initialStatus[1] == $newStatus[1]) { + // Check for victory / halfway to victory + if ($initialStatus[0] == 'held') { + $halfWay = $initialStatus[2] + $winTime / 2; + $victory = $initialStatus[2] + $winTime; + $now = time(); + if ($now >= $halfWay && $now < $halfWay + 21) { + $this->statusMessage(10, 11, $newStatus[1], $victory); + } elseif ($now >= $victory) { + // If the game is finished, we shouldn't be here anyways + $this->lib->call('resetGame', $newStatus[1]); + } + } + return; + } + + // Status changed, send messages + logText("CTF Status: (" . join(',',$initialStatus) . ") -> (" . join(',',$newStatus) . ")"); + + if ($initialStatus[0] == 'nohold' && $newStatus[0] == 'held') { + // The targets are now being held by a team + $this->statusMessage(2, 3, $newStatus[1], $newStatus[2] + $winTime); + + } elseif ($initialStatus[0] == 'grace' && $newStatus[0] == 'nohold') { + // Targets are no longer being held (no grace period) + $this->statusMessage(6, 9, $initialStatus[1]); + $this->cancelAllGraces(); + + } elseif ($initialStatus[0] == 'held' && $newStatus[0] == 'nohold') { + // Targets are no longer being held (no grace period) + $this->statusMessage(5, 8, $initialStatus[1]); + + } elseif ($initialStatus[0] == 'held' && $newStatus[0] == 'grace') { + // Targets are no longer being held (with grace period) + $this->statusMessage(4, 7, $initialStatus[1], $newStatus[2]); + + } elseif ($initialStatus[0] == 'held' && $newStatus[0] == 'held') { + // Targets are now held by another team + $this->statusMessage(5, 8, $initialStatus[1]); + $this->statusMessage(2, 3, $newStatus[1], $newStatus[2] + $winTime); + + } elseif ($initialStatus[0] == 'grace' && $newStatus[0] == 'held') { + if ($initialStatus[1] != $newStatus[1]) { + // Other team gained control + $this->statusMessage(6, 9, $initialStatus[1]); + } + $this->statusMessage(2, 3, $newStatus[1], $newStatus[2] + $winTime); + } + } + + + private function statusMessage($toTeam, $toOthers, $team, $timestamp = null) { + $q = $this->db->query("SELECT id,alliance FROM player"); + while ($r = dbFetchArray($q)) { + $this->lib->call('message', $r[0], $r[1] == $team ? $toTeam : $toOthers, $team, $timestamp); + } + } + + private function cancelAllGraces() { + $this->db->query("UPDATE ctf_target SET grace_expires = NULL"); + } +} + + +?> diff --git a/scripts/game/beta5/ctf/library/joinMessage.inc b/scripts/game/beta5/ctf/library/joinMessage.inc new file mode 100644 index 0000000..f80df7a --- /dev/null +++ b/scripts/game/beta5/ctf/library/joinMessage.inc @@ -0,0 +1,38 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + public function run($team, $player) { + // List all players in the team + $q = $this->db->query( + "SELECT id FROM player WHERE alliance=$team AND id <> $player" + ); + if (!($q && dbCount($q))) { + return; + } + + // Send a message to each player + while ($r = dbFetchArray($q)) { + $this->lib->call('message', $r[0], 1, $team, $player); + } + } +} + +?> diff --git a/scripts/game/beta5/ctf/library/resetGame.inc b/scripts/game/beta5/ctf/library/resetGame.inc new file mode 100644 index 0000000..746a0c3 --- /dev/null +++ b/scripts/game/beta5/ctf/library/resetGame.inc @@ -0,0 +1,349 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + $this->fleets = $this->game->getLib('beta5/fleet'); + $this->planets = $this->game->getLib('beta5/planet'); + $this->players = $this->game->getLib('beta5/player'); + } + + public function run($winningTeam) { + // Starts by increasing the team's score + $cPoints = $this->increaseScore($winningTeam); + + // Did the team win the whole game ? + if ($cPoints == 100) { + $this->endGame($winningTeam); + return; + } + + // Neutralize all planets that are owned by players of a team in an area allocated to another team + // as well as planets owned by players in other players' spawning systems + $this->neutralizeColonies(); + + // Reset all planets that had been WHSN'd + $this->resetWHSN(); + + // Remove all WHSN penalties + $this->removePenalties(); + + // Neutralize and consolidate planets in target systems + $this->neutralizeTargets(); + + // And the dead shall rise again ... + $this->respawnPlayers(); + + // Equalize fleet sizes and send the fleets home + $this->equalizeFleets(); + + // Update corruption, happiness and attack status for all planets + $this->updatePlanets(); + + // Send messages + $this->sendRoundMessages($winningTeam); + } + + + private function increaseScore($winningTeam) { + $q = $this->db->query("SELECT points FROM ctf_points WHERE team = $winningTeam FOR UPDATE"); + if (dbCount($q)) { + list($cPoints) = dbFetchArray($q); + $cPoints = min(100, $cPoints + $this->game->params['v2points']); + $this->db->query("UPDATE ctf_points SET points = $cPoints WHERE team = $winningTeam"); + } else { + $cPoints = $this->game->params['v2points']; + $this->db->query("INSERT INTO ctf_points (team, points) VALUES ($winningTeam, $cPoints)"); + } + return $cPoints; + } + + + private function endGame($winningTeam) { + // Fetch the list of players and their teams + $q = $this->db->query("SELECT id,alliance FROM player"); + while ($r = dbFetchArray($q)) { + $this->lib->call('message', $r[0], ($r[1] == $winningTeam ? 14 : 15), $winningTeam); + } + + // Update the rankings + $this->game->getLib()->call('updateRankings'); + } + + + private function neutralizeColonies() { + // Get the list of all planets to neutralize + $q = $this->db->query( + "SELECT p.id, y.id FROM planet p, ctf_alloc a, player y " + . "WHERE p.owner = y.id AND p.system = a.system " + . "AND a.alliance <> y.alliance " + . "UNION SELECT p.id, y.id FROM planet p, ctf_alloc a, player y " + . "WHERE p.system = a.system AND y.id = p.owner AND a.alliance = y.alliance " + . "AND a.spawn_point AND a.player <> y.id" + ); + // Neutralize the planets + while ($r = dbFetchArray($q)) { + $this->planets->call('ownerChange', $r[0]); + $this->players->call('losePlanet', $r[1], $r[0]); + } + } + + + private function resetWHSN() { + // Get the list of WHSN'd planets + $q = $this->db->query( + "SELECT p.id FROM planet p, system s WHERE s.nebula = 0 AND p.status <> 0 AND p.system = s.id" + ); + // Recreate planets instead + while ($r = dbFetchArray($q)) { + $this->regenPlanet($r[0]); + } + } + + private function regenPlanet($id) { + $ttn = rand(3, 12); + + do { + $rn = strtoupper(substr(md5(uniqid(rand())), 0, 7)); + $q = $this->db->query("SELECT id FROM planet WHERE name='P-[$rn]'"); + } while (dbCount($q)); + + $q = $this->db->query("SELECT max_pop FROM planet_max_pop WHERE planet = $id AND tech_level = 0"); + list($maxPop) = dbFetchArray($q); + + $this->db->query( + "UPDATE planet SET status = 0, pop = 2000, ifact = 3, mfact = 3, " + . "turrets = $ttn, name = 'P-[$rn]', max_pop = $maxPop " + . "WHERE id = $id" + ); + } + + + private function removePenalties() { + $this->db->query("UPDATE planet SET bh_unhappiness = 0"); + $this->db->query("UPDATE player SET bh_unhappiness = 0"); + } + + + private function neutralizeTargets() { + // Reset target status + $this->db->query("UPDATE ctf_target SET held_by = NULL, held_since = NULL, grace_expires = NULL"); + + // Get the list of all planets to neutralize + $q = $this->db->query( + "SELECT p.id, y.id, p.pop FROM planet p, ctf_target t, player y " + . "WHERE p.owner = y.id AND p.system = t.system" + ); + + while ($r = dbFetchArray($q)) { + // Neutralize the planet + $this->planets->call('ownerChange', $r[0]); + $this->players->call('losePlanet', $r[1], $r[0]); + + // Compute factories and turrets for the planet + $x = ($r[2] - 2000) / 48000; + $facts = floor((($r[2] / 30) - 754 * $x * $x) / 2); + $turrets = floor(($r[2] / 22) - 510 * $x * $x); + + // Set the planet's factories and turrets, reset its corruption + $this->db->query( + "UPDATE planet SET ifact = $facts, mfact = $facts, turrets = $turrets, corruption = 0 " + . "WHERE id = {$r[0]}" + ); + } + } + + + private function respawnPlayers() { + // Get the list of players who don't have planets + $q = $this->db->query( + "SELECT id FROM player WHERE NOT hidden AND id NOT IN (" + . "SELECT DISTINCT owner FROM planet WHERE owner IS NOT NULL)" + ); + while ($r = dbFetchArray($q)) { + $this->respawn($r[0]); + } + } + + private function respawn($player) { + // Get the player's initial system + $q = $this->db->query("SELECT system FROM ctf_alloc WHERE player = $player"); + list($system) = dbFetchArray($q); + + // Choose a random planet + $orbit = rand(0, 5); + $q = $this->db->query("SELECT id FROM planet WHERE system = $system AND orbit = $orbit"); + list($planet) = dbFetchArray($q); + + // Assign the planet + $this->planets->call('ownerChange', $planet, $player); + $this->players->call('takePlanet', $player, $planet); + } + + + private function equalizeFleets() { + // Get the list of fleets + list($pFleets, $aFleets) = $this->getAllFleets(); + + // Compute fleet reduction based on alliance fleets + $fleetReductions = $this->computeReductions($aFleets); + + // Compute the reductions for each player + foreach ($pFleets as $player => $fleets) { + $team = array_shift($fleets); + $pFleets[$player] = $this->playerReduction($fleets, $aFleets[$team], $fleetReductions[$team]); + } + + // Reinsert each player's fleet at one of his planets + foreach ($pFleets as $player => $fleet) { + $this->insertFleet($player, $fleet); + } + } + + private function getAllFleets() { + // Get the list of all fleets + $q = $this->db->query( + "SELECT f.id, p.id, p.alliance FROM fleet f, player p WHERE f.owner = p.id" + ); + + // Compute totals and disband the fleets as we go + $playerFleets = array(); + $allianceFleets = array(); + while ($r = dbFetchArray($q)) { + list($fleetID, $player, $alliance) = $r; + + if (is_null($playerFleets[$player])) { + $playerFleets[$player] = array($alliance,0,0,0,0); + } + if (is_null($allianceFleets[$alliance])) { + $allianceFleets[$alliance] = array(0,0,0,0); + } + + $fleet = $this->fleets->call('get', $fleetID); + for ($i = 0; $i < 4; $i ++) { + $playerFleets[$player][$i + 1] += $fleet[self::$ftFields[$i]]; + $allianceFleets[$alliance][$i] += $fleet[self::$ftFields[$i]]; + } + + $this->fleets->call('disband', $fleetID); + } + + return array($playerFleets, $allianceFleets); + } + + private function computeReductions($fleets) { + // Find the smallest values for each type of ship + $smallest = null; + foreach ($fleets as $team => $tFleets) { + if (is_null($smallest)) { + $smallest = $tFleets; + continue; + } + + for ($i = 0; $i < 4; $i ++) { + if ($tFleets[$i] < $smallest[$i]) { + $smallest[$i] = $tFleets[$i]; + } + } + } + + // Compute reductions for each team + $reductions = array(); + foreach ($fleets as $team => $tFleets) { + $reductions[$team] = array(); + + for ($i = 0; $i < 4; $i ++) { + if ($tFleets[$i] == $smallest[$i]) { + $nAmount = 0; + } else { + $rnd = ($smallest == 0) ? rand(101, 105) : rand(98, 105); + $nAmount = $tFleets[$i] - floor($rnd * $smallest[$i] / 100); + } + + $reductions[$team][$i] = $nAmount; + } + } + + return $reductions; + } + + private function playerReduction($pFleets, $aFleets, $aReduction) { + $reduc = array(); + for ($i = 0; $i < 4; $i ++) { + if ($aFleets[$i] == 0) { + continue; + } + $ratio = $pFleets[$i] / $aFleets[$i]; + $reduction = floor($aReduction[$i] * $ratio); + $reduc[$i] = $pFleets[$i] - $reduction; + } + return $reduc; + } + + private function insertFleet($player, $fleet) { + if ($fleet[0] + $fleet[1] + $fleet[2] + $fleet[3] == 0) { + return; + } + + // Get a planet belonging to the player + $q = $this->db->query("SELECT id FROM planet WHERE owner = $player LIMIT 1"); + list($planet) = dbFetchArray($q); + + $qString1 = "INSERT INTO fleet (owner, location"; + $qString2 = ") VALUES ($player, $planet"; + + for ($i = 0; $i < 4; $i ++) { + if ($fleet[$i] == 0) { + continue; + } + $qString1 .= ", " . self::$ftFields[$i]; + $qString2 .= ", {$fleet[$i]}"; + } + + $this->db->query("$qString1$qString2)"); + } + + + private function updatePlanets() { + $this->db->query("UPDATE planet SET corruption = corruption / 2"); + + $q = $this->db->query( + "SELECT p.id FROM planet p, system s " + . "WHERE s.id = p.system AND s.nebula = 0" + ); + + while ($r = dbFetchArray($q)) { + $this->planets->call('updateHappiness', $r[0]); + $this->planets->call('updateMilStatus', $r[0]); + } + } + + + private function sendRoundMessages($winningTeam) { + $q = $this->db->query("SELECT id,alliance FROM player"); + while ($r = dbFetchArray($q)) { + $this->lib->call('message', $r[0], ($r[1] == $winningTeam ? 12 : 13), $winningTeam); + } + } +} diff --git a/scripts/game/beta5/ecm/library.inc b/scripts/game/beta5/ecm/library.inc new file mode 100644 index 0000000..81a812f --- /dev/null +++ b/scripts/game/beta5/ecm/library.inc @@ -0,0 +1,99 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function getInformationLevel($ecm, $eccm) { + // Get the probability table + $probTable = $this->getProbabilityTable($ecm, $eccm); + + // Randomly selects an information level + $p = rand(0,9999) / 10000; + $i = 0; + while ($p > $probTable[$i]) { + $p -= $probTable[$i]; + $i ++; + } + + return $i; + } + + + function getProbabilityTable($ecm, $eccm) { + if (is_null($this->probTables[$ecm.','.$eccm])) { + // Read ECM probability table + $ecmTable = $this->getECMTable($ecm); + + // Read ECCM probability table + $eccmTable = $this->getECCMTable($eccm); + + // Create combined probabilities + $probTable = array(); + $tot = 0; + for ($i=0;$i<4;$i++) { + $probTable[$i] = 0; + for ($j=0;$j<=$i;$j++) { + $probTable[$i] += $ecmTable[$i-$j] * $eccmTable[$j]; + } + $tot += $probTable[$i]; + } + $probTable[4] = 1 - $tot; + + $this->probTables[$ecm.','.$eccm] = $probTable; + } + + return $this->probTables[$ecm.','.$eccm]; + } + + + function getECMTable($level) { + if (is_null($this->ecmTable[$level])) { + $qEcm = $this->db->query("SELECT probability FROM ecm WHERE ecm_level=$level ORDER BY info_level ASC"); + if (!($qEcm && dbCount($qEcm))) { + return -1; + } + $this->ecmTable[$level] = array(); + while ($r = dbFetchArray($qEcm)) { + array_push($this->ecmTable[$level], $r[0] / 100); + } + } + return $this->ecmTable[$level]; + } + + + function getECCMTable($level) { + if (is_null($this->eccmTable[$level])) { + $qEccm = $this->db->query("SELECT probability FROM eccm WHERE eccm_level=$level ORDER BY gain ASC"); + if (!($qEccm && dbCount($qEccm))) { + return -1; + } + $this->eccmTable[$level] = array(); + while ($r = dbFetchArray($qEccm)) { + array_push($this->eccmTable[$level], $r[0] / 100); + } + } + return $this->eccmTable[$level]; + } + + + function scrambleFleetPower($level, $power) { + switch ($level) : + case 0: return 'NULL'; + case 1: $rand = rand(0,49) - 25; break; + case 2: $rand = rand(0,19) - 10; break; + case 3: case 4: $rand = 0; break; + endswitch; + return $power + ($power * $rand / 100); + } +} + +?> diff --git a/scripts/game/beta5/fleet/library.inc b/scripts/game/beta5/fleet/library.inc new file mode 100644 index 0000000..d58e1cd --- /dev/null +++ b/scripts/game/beta5/fleet/library.inc @@ -0,0 +1,50 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Invalidates the fleet cache + function invCache($id = null) { + if (is_null($id)) { + $this->fleets = array(); + } else { + $this->fleets[$id] = null; + } + } + + + // Renames a fleet + function rename($fid, $name) { + $n = addslashes($name); + $this->db->query("UPDATE fleet SET name='$n' WHERE id=$fid"); + $this->fleets[$fid] = null; + } +} + +?> diff --git a/scripts/game/beta5/fleet/library/arrival.inc b/scripts/game/beta5/fleet/library/arrival.inc new file mode 100644 index 0000000..c292b27 --- /dev/null +++ b/scripts/game/beta5/fleet/library/arrival.inc @@ -0,0 +1,151 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->move = $this->lib->game->getLib('beta5/moving'); + $this->planets = $this->lib->game->getLib('beta5/planet'); + $this->players = $this->lib->game->getLib('beta5/player'); + $this->rules = $this->lib->game->getLib('beta5/rules'); + } + + + // Handles a fleet's arrival + function run($fid, $dest, $from, $nStatus = null) { + // Get complete fleet data + $q = $this->db->query("SELECT * FROM fleet WHERE id=$fid"); + $f = dbFetchHash($q); + if (is_null($f['owner'])) { + logText("beta5/fleetArrival($fid,$dest,$from): BUG! Fleet has no owner!", LOG_ERR); + return; + } + if (is_null($nStatus)) { + $nStatus = ($f['attacking'] == 't'); + } + + // Get destination planet owner + $po = $this->planets->call('getOwner', $dest); + if (!is_null($po) && $po != $f['owner']) { + // Is the fleet owner an enemy of the planet owner? + $isEnemy = $this->players->call('isEnemy', $po, $f['owner']); + if (!$isEnemy) { + // Get fleet owner data + $foi = $this->players->call('get', $f['owner']); + // Check for enemy alliance + $isEnemy = (!is_null($foi['aid']) && $this->players->call('isAllianceEnemy', $po, $foi['aid'])); + } + } else { + $isEnemy = false; + } + + // Check whether the player already has fleets at that location, + // and if he does, get their current status + if (!$isEnemy) { + $q = $this->db->query("SELECT attacking FROM fleet WHERE location=$dest AND owner=".$f['owner']." LIMIT 1"); + if ($q && dbCount($q)) { + list($aa) = dbFetchArray($q); + $isEnemy = ($aa == 't'); + } + } + + // Set attack status + $att = ($po != $f['owner']) && ($isEnemy || $nStatus); + logText("beta5/fleetArrival($fid,$dest,$from,{$f['owner']}): Attack=".($att?1:0), LOG_DEBUG); + if (is_array($_SESSION[game::sessName()])) { + logText("Fleet $fid was being controlled by player #{$_SESSION[game::sessName()]['player']}"); + } + + if ($att) { + if (($split = $this->hsWindowCollapsing($po, $f, $dest, $from)) === true) { + return; + } + + // Switch the player's fleets to attack at that location if the fleet arriving is attacking + $this->db->query("UPDATE fleet SET attacking=TRUE,can_move='B' WHERE location=$dest AND NOT attacking AND owner=".$f['owner']); + } else { + $split = ""; + } + + // Update the fleet's record + $this->db->query("UPDATE fleet SET location=$dest,time_spent=0,attacking=".dbBool($att).",can_move='".($att?'B':'H')."'$split WHERE id=$fid"); + + // Make sure the system the fleet has arrived in can't be assigned to a new player + $pinf = $this->planets->call('byId', $dest); + $this->db->query("UPDATE system SET assigned=TRUE WHERE id=".$pinf['system']); + + // Add a fleet arrival entry to the list + if (!is_array($this->lib->mainClass->fleetArrivals[$dest])) { + $this->lib->mainClass->fleetArrivals[$dest] = array(array(), array()); + } + array_push($this->lib->mainClass->fleetArrivals[$dest][$att?1:0], array($fid, $from)); + + // Clear the fleet cache + $this->lib->call('invCache', $fid); + } + + + function hsWindowCollapsing($po, $f, $dst, $ori) { + // Apply HS window collapsing + $r = $this->rules->call('get', $po); + $rnd = rand(0,$r['prevent_hs_exit']*10); + $splitG = floor($rnd * $f['gaships'] / 100); + $splitF = floor($rnd * $f['fighters'] / 100); + $splitC = ceil($rnd * $f['cruisers'] / 100); + $splitB = ceil($rnd * $f['bcruisers'] / 100); + if (!($rnd && ($splitC || $splitB))) { + return ""; + } + + // WE HAVE A WINNER! + + if (is_null($f['moving'])) { + $or = $this->rules->call('get', $f['owner']); + } + + if ($f['gaships'] == $splitG && $f['fighters'] == $splitF && $f['cruisers'] == $splitC && $f['bcruisers'] == $splitB) { + // The complete fleet has to be delayed + logText("Fleet #{$f['id']} was prevented from dropping out of HS", LOG_DEBUG); + if (is_null($f['moving'])) { + // The fleet dropped out of Hyperspace, create a move order + $fmo = $this->move->call('newObject', $ori, $dst, $or['capital_ship_speed'], ($f['cruisers'] > 0), null); + $this->db->query("UPDATE moving_object SET changed=60,time_left=1 WHERE id=$fmo"); + $this->db->query("UPDATE fleet SET moving=$fmo,waiting=NULL WHERE id={$f['id']}"); + logText("Fleet #{$f['id']} -> created new moving object", LOG_DEBUG); + } else { + // The fleet was moving, just modify the order + $this->db->query("UPDATE moving_object SET changed=60,time_left=1 WHERE id={$f['moving']}"); + logText("Fleet #{$f['id']} -> modified existing moving object", LOG_DEBUG); + } + $fullFleet = true; + } else { + logText("Fleet {$f['id']} got split by HS windows collapsing ($splitG/$splitF/$splitC/$splitB out of {$f['gaships']}/{$f['fighters']}/{$f['cruisers']}/{$f['bcruisers']})", LOG_DEBUG); + + // Split fleet + $fullFleet = ",gaships=" . ($f['gaships'] - $splitG); + $fullFleet .= ",fighters=" . ($f['fighters'] - $splitF); + $fullFleet .= ",cruisers=" . ($f['cruisers'] - $splitC); + $fullFleet .= ",bcruisers=" . ($f['bcruisers'] - $splitB); + + if (is_null($f['moving'])) { + // The fleet dropped out of Hyperspace, create a move order + $fmo = $this->move->call('newObject', $ori, $dst, $or['capital_ship_speed'], ($f['cruisers'] > 0), null); + logText("Fleet #{$f['id']} -> created new moving object", LOG_DEBUG); + } else { + // The fleet was moving, duplicate the order + $fmo = $this->move->call('cloneObject', $f['moving']); + logText("Fleet #{$f['id']} -> cloned existing moving object", LOG_DEBUG); + } + $this->db->query("UPDATE moving_object SET changed=60,time_left=1 WHERE id=$fmo"); + + // Generate new fleet + $this->db->query("INSERT INTO fleet(owner,gaships,fighters,cruisers,bcruisers,attacking,moving) VALUES (" + . $f['owner'] . ",$splitG,$splitF,$splitC,$splitB,TRUE,$fmo)"); + } + + return $fullFleet; + } +} + +?> diff --git a/scripts/game/beta5/fleet/library/autoSplit.inc b/scripts/game/beta5/fleet/library/autoSplit.inc new file mode 100644 index 0000000..847db84 --- /dev/null +++ b/scripts/game/beta5/fleet/library/autoSplit.inc @@ -0,0 +1,83 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->standby = $this->lib->game->getLib('beta5/standby'); + $this->moving = $this->lib->game->getLib('beta5/moving'); + $this->rules = $this->lib->game->getLib('beta5/rules'); + } + + + // Automatically split a fleet + function run($fid, $newName, $count) { + // Get fleet data + $f = $this->lib->call('get', $fid, true); + if (is_null($f) || $f['can_move'] != 'Y' || !is_null($f['sale_info'])) { + return 1; + } + + // Get player rules + $rules = $this->rules->call('get', $f['owner']); + + // Generate new ship counts + $sg = floor($f['gaships'] / $count); $sf = floor($f['fighters'] / $count); + $sc = floor($f['cruisers'] / $count); $sb = floor($f['bcruisers'] / $count); + $count --; + $mg = $count * $sg; $mf = $count * $sf; + $mc = $count * $sc; $mb = $count * $sb; + + // If we're moving or standing by in Hyperspace, we need to make sure both + // the new and old fleets are HS-capable + if ((!is_null($f['move']) && $f['move']['hyperspace'] == 't') || !is_null($f['wait'])) { + $nu = $rules['gaship_space'] * $sg + $rules['fighter_space'] * $sf; + $na = $rules['cruiser_haul'] * $sc + $rules['bcruiser_haul'] * $sb; + $ou = $rules['gaship_space'] * ($f['gaships'] - $mg) + $rules['fighter_space'] * ($f['fighters'] - $mf); + $oa = $rules['cruiser_haul'] * ($f['cruisers'] - $mc) + $rules['bcruiser_haul'] * ($f['bcruisers'] - $mb); + if ($nu > $na || $ou > $oa) { + return 3; + } + } + + // Generate code that will set the new fleets' orders + if (is_null($f['location'])) { + $location = "NULL"; + if (is_null($f['moving'])) { + $moving = "NULL"; + $oCode = '$waiting = $this->standby->call("create",'.$f['wait']['time_left'].",".$f['wait']['drop_point'] + . ','.$f['wait']['origin'].','.$f['wait']['time_spent'].');'; + } else { + $waiting = "NULL"; + $oCode = '$moving = $this->moving->call("cloneObject", '.$f['moving'].');'; + } + } else { + $location = $f['location']; + $moving = $waiting = 'NULL'; + $oCode = null; + } + + // Generate new fleets + $nn = addslashes($newName == "" ? preg_replace('/ [0-9]+$/', '', $f['name']) : $newName); + for ($i=0;$i<$count;$i++) { + if ($oCode != "") { + eval($oCode); + } + $nnb = $count > 1 ? (" " . ($i + 1)) : ""; + $this->db->query("INSERT INTO fleet(owner,name,location,gaships,fighters,cruisers,bcruisers,attacking,moving,waiting,time_spent) VALUES(" + .$f['owner'].",'$nn$nnb',$location,$sg,$sf,$sc,$sb," + .dbBool($f['attacking'] == 't').",$moving,$waiting,{$f['time_spent']})"); + } + + // Update original fleet + $this->db->query("UPDATE fleet SET gaships=gaships-$mg,fighters=fighters-$mf,cruisers=cruisers-$mc,bcruisers=bcruisers-$mb " + ."WHERE id=$fid"); + $this->db->query("DELETE FROM beacon_detection WHERE fleet = $fid"); + + $this->lib->call('invCache', $fid); + return 0; + } +} + +?> diff --git a/scripts/game/beta5/fleet/library/disband.inc b/scripts/game/beta5/fleet/library/disband.inc new file mode 100644 index 0000000..5ec9cea --- /dev/null +++ b/scripts/game/beta5/fleet/library/disband.inc @@ -0,0 +1,58 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->planets = $this->lib->game->getLib('beta5/planet'); + $this->sales = $this->lib->game->getLib('beta5/sale'); + } + + + // Disbands a fleet and removes any sales / movement / stand-by + // entries associated with it + function run($fId, $final = false) { + // Get fleet data + $f = $this->lib->call('get', $fId); + if (is_null($f)) { + return 1; + } + + if (!is_null($f['sale_info'])) { + // It is for sale + $r = $this->sales->call('cancel', $f['owner'], $f['sale_info']['sale']['id']); + if (!($r || $final)) { + return 2; + } elseif (!$r) { + // Sale was finalized and we can't cancel; assume no planet is for sale. + // FIXME: send message + $this->db->query("UPDATE fleet SET sale=NULL,owner=".$f['sale_info']['sale']['sold_to']." WHERE id=$fId"); + // FIXME: add history + $this->db->query("DELETE FROM sale WHERE id=".$f['sale_info']['sale']['id']); + } + } elseif (!is_null($f['waiting'])) { + // It is standing by + $this->db->query("DELETE FROM hs_wait WHERE id=".$f['waiting']); + } elseif (!is_null($f['moving'])) { + // It's moving? + $this->db->query("DELETE FROM moving_object WHERE id=".$f['moving']); + if (!is_null($f['move']['wait_order'])) { + $this->db->query("DELETE FROM hs_wait WHERE id=".$f['move']['wait_order']); + } + } + + // Remove this fleet + $this->db->query("DELETE FROM fleet WHERE id=$fId"); + + // Update planet status where the fleet was + if (!is_null($f['location'])) { + $this->planets->call('updateMilStatus', $f['location']); + $this->planets->call('updateHappiness', $f['location']); + } + + return 0; + } +} + +?> diff --git a/scripts/game/beta5/fleet/library/get.inc b/scripts/game/beta5/fleet/library/get.inc new file mode 100644 index 0000000..e4c44ff --- /dev/null +++ b/scripts/game/beta5/fleet/library/get.inc @@ -0,0 +1,69 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns data regarding a fleet + function run($id, $update = false) { + if ($update) { + $uqs = " FOR UPDATE"; + } else { + $uqs = ""; + } + + // Return the fleet cache's contents if the fleet is in there + if (!is_null($this->lib->mainClass->fleets[$id])) { + return $this->lib->mainClass->fleets[$id]; + } + + // Get the complete row + $q = $this->db->query("SELECT * FROM fleet WHERE id = $id $uqs"); + if (!($q && dbCount($q) == 1)) { + return null; + } + $fdata = dbFetchHash($q); + + // Extract movement data + if (!is_null($fdata['moving'])) { + $q = $this->db->query("SELECT * FROM moving_object WHERE id=" . $fdata['moving'] . $uqs); + if ($q && dbCount($q) == 1) { + $fdata['move'] = dbFetchHash($q); + if (!is_null($fdata['move']['wait_order'])) { + $fdata['waiting'] = $fdata['move']['wait_order']; + } + } + } + + // Extract HS standby orders + if (!is_null($fdata['waiting'])) { + $q = $this->db->query("SELECT * FROM hs_wait WHERE id=" . $fdata['waiting'] . $uqs); + if ($q && dbCount($q) == 1) { + $fdata['wait'] = dbFetchHash($q); + } + } + + // Extract sales data + $q = $this->db->query("SELECT * FROM sale WHERE fleet=" . $fdata['id'] . $uqs); + if (dbCount($q)) { + $a = array('sale' => dbFetchHash($q)); + $q = $this->db->query("SELECT * FROM public_offer WHERE offer=" . $a['sale']['id'] . $uqs); + if ($q && dbCount($q) == 1) { + $a['public'] = dbFetchHash($q); + } + $q = $this->db->query("SELECT * FROM private_offer WHERE offer=" . $a['sale']['id'] . $uqs); + if ($q && dbCount($q) == 1) { + $a['private'] = dbFetchHash($q); + } + $fdata['sale_info'] = $a; + } + + return ($this->lib->mainClass->fleets[$id] = $fdata); + } +} + +?> diff --git a/scripts/game/beta5/fleet/library/getLocation.inc b/scripts/game/beta5/fleet/library/getLocation.inc new file mode 100644 index 0000000..ab5352d --- /dev/null +++ b/scripts/game/beta5/fleet/library/getLocation.inc @@ -0,0 +1,22 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns the list of fleets in orbit around a planet + function run($pid, $pl = null) { + $q = $this->db->query("SELECT id,name FROM fleet WHERE location = $pid" . (is_null($pl) ? "" : " AND owner=$pl")); + $a = array(); + while ($r = dbFetchArray($q)) { + $a[$r[0]] = $r[1]; + } + return $a; + } +} + +?> diff --git a/scripts/game/beta5/fleet/library/getPlayerLocations.inc b/scripts/game/beta5/fleet/library/getPlayerLocations.inc new file mode 100644 index 0000000..2e70ca3 --- /dev/null +++ b/scripts/game/beta5/fleet/library/getPlayerLocations.inc @@ -0,0 +1,22 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns all of the locations at which a player has stationned fleets + function run($pid) { + $q = $this->db->query("SELECT DISTINCT location FROM fleet WHERE location IS NOT NULL AND owner=$pid"); + $a = array(); + while ($r = dbFetchArray($q)) { + array_push($a, $r[0]); + } + return $a; + } +} + +?> diff --git a/scripts/game/beta5/fleet/library/getPower.inc b/scripts/game/beta5/fleet/library/getPower.inc new file mode 100644 index 0000000..285e826 --- /dev/null +++ b/scripts/game/beta5/fleet/library/getPower.inc @@ -0,0 +1,28 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->rules = $this->lib->game->getLib('beta5/rules'); + } + + + // Computes a fleet's power + function run($pl, $g, $f, $c, $b) { + if (!is_array($this->ePower[$pl])) { + $r = $this->rules->call('get', $pl); + $a = array('gaship','fighter','cruiser','bcruiser'); + $this->ePower[$pl] = array(); + foreach ($a as $st) { + $this->ePower[$pl][$st] = floor($r[$st."_power"] * $r['effective_fleet_power'] / 100); + } + } + $r = $this->ePower[$pl]; + return $g * $r['gaship'] + $f * $r['fighter'] + $c * $r['cruiser'] + $b * $r['bcruiser']; + } +} + +?> diff --git a/scripts/game/beta5/fleet/library/getStats.inc b/scripts/game/beta5/fleet/library/getStats.inc new file mode 100644 index 0000000..98b7e14 --- /dev/null +++ b/scripts/game/beta5/fleet/library/getStats.inc @@ -0,0 +1,85 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns data regarding current fleets + function run($pid) { + // Get count and ship types + $q = $this->db->query( + "SELECT COUNT(*), SUM(gaships), SUM(fighters), SUM(cruisers), SUM(bcruisers)" + . " FROM fleet WHERE owner = $pid GROUP BY owner" + ); + $cnt = dbFetchArray($q); + if (!$cnt) { + $cnt = array(0, 0, 0, 0, 0); + } + + // Fleets at home + $q = $this->db->query( + "SELECT COUNT(*) FROM fleet f, planet p " + . "WHERE f.owner = $pid AND f.location = p.id AND p.owner = $pid" + ); + list($fah) = dbFetchArray($q); + // Fleets at home, in battle + $q = $this->db->query( + "SELECT COUNT(*) FROM fleet f, planet p, fleet f2 " + . "WHERE f.owner = $pid AND f.location = p.id AND p.owner = $pid " + . "AND f2.location = p.id AND f2.attacking" + ); + list($fahb) = dbFetchArray($q); + + // Fleets on foreign planets + $q = $this->db->query( + "SELECT COUNT(*) FROM fleet f,planet p " + . "WHERE f.owner=$pid AND f.location=p.id" + . " AND (p.owner IS NULL OR p.owner<>$pid)" + ); + list($af) = dbFetchArray($q); + // Fleets on foreign planets, in battle + $q = $this->db->query( + "SELECT COUNT(*) FROM fleet f,planet p,fleet f2 " + . "WHERE f.owner=$pid AND f.location=p.id AND (p.owner IS NULL OR p.owner<>$pid) " + . "AND f2.location=p.id AND (f2.attacking AND NOT f.attacking)" + ); + list($afb1) = dbFetchArray($q); + $q = $this->db->query( + "SELECT COUNT(*) FROM fleet f, planet p " + . "WHERE f.owner=$pid AND f.location=p.id AND (p.owner IS NULL OR p.owner<>$pid) " + . "AND f.attacking" + ); + list($afb2) = dbFetchArray($q); + $afb = $afb1 + $afb2; + + // Moving fleets + $q = $this->db->query("SELECT COUNT(*) FROM fleet WHERE owner = $pid AND moving IS NOT NULL"); + list($mf) = dbFetchArray($q); + // Waiting fleets + $q = $this->db->query("SELECT COUNT(*) FROM fleet WHERE owner = $pid AND waiting IS NOT NULL"); + list($wf) = dbFetchArray($q); + + return array( + "fleets" => $cnt[0], + "battle" => $fahb+$afb, + "upkeep" => $this->lib->call('getUpkeep', $pid, $cnt[1], $cnt[2], $cnt[3], $cnt[4]), + "power" => $this->lib->call('getPower', $pid, $cnt[1], $cnt[2], $cnt[3], $cnt[4]), + "at_home" => $fah, + "home_battle" => $fahb, + "foreign" => $af, + "foreign_battle" => $afb, + "moving" => $mf, + "waiting" => $wf, + "gaships" => $cnt[1], + "fighters" => $cnt[2], + "cruisers" => $cnt[3], + "bcruisers" => $cnt[4] + ); + } +} + +?> diff --git a/scripts/game/beta5/fleet/library/getUpkeep.inc b/scripts/game/beta5/fleet/library/getUpkeep.inc new file mode 100644 index 0000000..4a78c18 --- /dev/null +++ b/scripts/game/beta5/fleet/library/getUpkeep.inc @@ -0,0 +1,23 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->rules = $this->lib->game->getLib('beta5/rules'); + } + + + // Computes a fleet's upkeep + function run($pl, $g, $f, $c, $b) { + $r = $this->rules->call('get', $pl); + $fu = $g * $r['gaship_upkeep']; + $fu += $f * $r['fighter_upkeep']; + $fu += $c * $r['cruiser_upkeep']; + $fu += $b * $r['bcruiser_upkeep']; + return $fu; + } +} + +?> diff --git a/scripts/game/beta5/fleet/library/merge.inc b/scripts/game/beta5/fleet/library/merge.inc new file mode 100644 index 0000000..c28083d --- /dev/null +++ b/scripts/game/beta5/fleet/library/merge.inc @@ -0,0 +1,163 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->moving = $this->lib->game->getLib('beta5/moving'); + } + + + // Merge fleets + function run($fIds, $okOwners, $newName) { + // Did we get fleet IDs? + if (!count($fIds)) { + return array(); + } + + // Get fleets to merge + $q = $this->db->query( + "SELECT * FROM fleet " + ."WHERE id IN (".join(',',$fIds).") AND owner IN (".join(',',$okOwners) + .") AND can_move='Y' AND sale IS NULL " + ."ORDER BY location,owner" + ); + $q2 = $this->db->query("SELECT id FROM sale WHERE fleet IN (".join(',',$fIds).")"); + if (!$q || !$q2 || dbCount($q) != count($fIds) || dbCount($q2)) { + return array(); + } + + // Generate an array from the fleets read and extract + // movement / stand-by information + $fleets = array(); $mIds = array(); $wIds = array(); + while ($r = dbFetchHash($q)) { + $fleets[$r['id']] = $r; + if (!is_null($r['moving'])) { + array_push($mIds, $r['moving']); + } elseif (!is_null($r['waiting'])) { + array_push($wIds, $r['waiting']); + } + } + + // Extract movement information + $move = array(); + if (count($mIds)) { + $q = $this->db->query("SELECT id,m_to,time_left,wait_order,changed FROM moving_object " + ."WHERE id IN (".join(',',$mIds).")"); + + while ($r = dbFetchHash($q)) { + $move[$r['id']] = $r; + if (!is_null($r['wait_order'])) + array_push($wIds, $r['wait_order']); + } + } + + // Extract stand-by information + $wait = array(); + if (count($wIds)) { + $q = $this->db->query("SELECT id,drop_point,time_left FROM hs_wait " + ."WHERE id IN (".join(',',$wIds).")"); + while ($r = dbFetchHash($q)) { + $wait[$r['id']] = $r; + } + } + + // Group fleets by location / status / owner + $gFleets = array(); + foreach ($fIds as $i) { + // Generate identifier and time to wait + $lid = $fleets[$i]['owner'].":"; + $am = ($fleets[$i]['attacking'] == 't'); + if (!is_null($fleets[$i]['waiting'])) { + $lid .= "W:".$wait[$fleets[$i]['waiting']]['drop_point']; + $tl = $wait[$fleets[$i]['waiting']]['time_left']; + } elseif (!is_null($moid = $fleets[$i]['moving'])) { + $mwo = $move[$moid]['wait_order']; + $lid .= "M:".$move[$moid]['m_to'].':'.$move[$moid]['time_left'].':' + .$move[$moid]['changed'].":".$this->moving->call('getLocation', $moid); + $tl = is_null($mwo) ? 0 : $wait[$mwo]['time_left']; + } else { + $tl = 0; + $lid .= "L:".$fleets[$i]['location']; + } + + // Generate / update container + if (!is_array($gFleets[$lid])) { + $gFleets[$lid] = array('t' => $tl, 'a' => $am, 'l' => array()); + } else { + if ($tl > $gFleets[$lid]['t']) { + $gFleets[$lid]['t'] = $tl; + } + $gFleets[$lid]['a'] |= $am; + } + + // Add fleet + array_push($gFleets[$lid]['l'], $i); + } + + // Merge groups into single fleets + $nfl = array(); + foreach ($gFleets as $fg) { + // Compute total amount of ships + $sums = array(0,0,0,0); + $minSpent = null; + foreach ($fg['l'] as $i) { + $sums[0] += $fleets[$i]['gaships']; + $sums[1] += $fleets[$i]['fighters']; + $sums[2] += $fleets[$i]['cruisers']; + $sums[3] += $fleets[$i]['bcruisers']; + + if (is_null($minSpent) || $minSpent > $fleets[$i]['time_spent']) { + $minSpent = $fleets[$i]['time_spent']; + } + } + + // Update merged fleet + $nId = array_shift($fg['l']); + $name = addslashes(($newName == "") ? $fleets[$nId]['name'] : $newName); + $this->db->query("UPDATE fleet SET name='$name',gaships=".$sums[0].",fighters=".$sums[1] + . ",cruisers=".$sums[2].",bcruisers=".$sums[3].",attacking=" + . dbBool($fg['a']) . ",time_spent=$minSpent WHERE id=$nId"); + + // Delete unneeded data + if (count($fg['l'])) { + $dMids = array(); $dWids = array(); + foreach ($fg['l'] as $i) { + if (!is_null($fleets[$i]['waiting'])) { + array_push($dWids, $fleets[$i]['waiting']); + } elseif (!is_null($fleets[$i]['moving'])) { + array_push($dMids, $fleets[$i]['moving']); + } + } + foreach ($dMids as $i) { + if (!is_null($move[$i]['wait_order'])) { + array_push($dWids, $move[$i]['wait_order']); + } + } + $this->db->query("DELETE FROM fleet WHERE id IN (".join(',',$fg['l']).")"); + if (count($dMids)) { + $this->db->query("DELETE FROM moving_object WHERE id IN (".join(',',$dMids).")"); + } + if (count($dWids)) { + $this->db->query("DELETE FROM hs_wait WHERE id IN (".join(',',$dWids).")"); + } + } + + // Update detection status + $this->db->query("DELETE FROM beacon_detection WHERE fleet = $nId"); + + // Make sure orders are up-to-date + $mwo = $fleets[$nId]['moving']; + $dest = is_null($mwo) ? null : $move[$mwo]['m_to']; + $wait = $fg['t'] ? $fg['t'] : null; + $this->lib->call('setOrders', $nId, $dest, $wait); + + array_push($nfl, $nId); + } + + return $nfl; + } +} + +?> diff --git a/scripts/game/beta5/fleet/library/sendMoveMessages.inc b/scripts/game/beta5/fleet/library/sendMoveMessages.inc new file mode 100644 index 0000000..1df4847 --- /dev/null +++ b/scripts/game/beta5/fleet/library/sendMoveMessages.inc @@ -0,0 +1,195 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->msg = $this->lib->game->getLib('beta5/msg'); + } + + + // Sends messages related to fleet movements + function run() { + $locs = array_unique(array_merge(array_keys($this->lib->mainClass->fleetDepartures), array_keys($this->lib->mainClass->fleetArrivals))); + if (count($locs) == 0) { + return; + } + + // Get all fleet or planet owners for the affected locations and their status + $q = $this->db->query( + "SELECT owner AS id,FALSE AS att,id AS loc FROM planet WHERE owner IS NOT NULL AND id IN (".join(',',$locs).") " + . "UNION SELECT owner AS id,attacking AS att,location AS loc FROM fleet WHERE location IN (".join(',',$locs).")" + ); + + $fMove = array(); + $ownArrivals = array(); + while ($r = dbFetchArray($q)) { + list($player,$status,$location) = $r; + $oa = $ha = $hd = $fa = $fd = array(); + + // Generate the list of fleet departures + if (is_array($this->lib->mainClass->fleetDepartures[$location])) { + foreach ($this->lib->mainClass->fleetDepartures[$location][(int)$status] as $fId) { + $f = $this->lib->call('get', $fId); + if ($f['owner'] == $player) { + continue; + } + array_push($fd, $f); + } + foreach ($this->lib->mainClass->fleetDepartures[$location][1-$status] as $fId) { + $f = $this->lib->call('get', $fId); + if ($f['owner'] == $player) { + continue; + } + array_push($hd, $f); + } + } + + // Generate the list of fleet arrivals + if (is_array($this->lib->mainClass->fleetArrivals[$location])) { + foreach ($this->lib->mainClass->fleetArrivals[$location][(int)$status] as $fdt) { + list($fId, $from) = $fdt; + $f = $this->lib->call('get', $fId); + $f['from'] = $from; + if ($f['owner'] == $player) { + if (!is_array($ownArrivals[$player])) { + $ownArrivals[$player] = array(); + } + if (!is_array($ownArrivals[$player][$location])) { + $ownArrivals[$player][$location] = array(); + } + array_push($ownArrivals[$player][$location], $f); + } else { + array_push($fa, $f); + } + } + foreach ($this->lib->mainClass->fleetArrivals[$location][1-$status] as $fdt) { + list($fId, $from) = $fdt; + $f = $this->lib->call('get', $fId); + $f['from'] = $from; + if ($f['owner'] == $player) { + l::warn("beta5/sendFleetMoveMessages(): fleet $fId owned by player $player hostile to its owner"); + continue; + } + array_push($ha, $f); + } + } + + // Add the data to the list of fleet movements + if (!(count($fa)||count($fd)||count($ha)||count($hd))) { + continue; + } + if (!is_array($fMove[$player])) { + $fMove[$player] = array($location => array($fa, $fd, $ha, $hd)); + } else { + $fMove[$player][$location] = array($fa, $fd, $ha, $hd); + } + } + + $pnames = array(); + $fpowers = array(); + + // Send messages for own fleets arrivals + $tm = time() - 1; + foreach ($ownArrivals as $player => $locs) { + foreach ($locs as $loc => $flist) { + // Get planet name + if (is_null($pnames[$loc])) { + $q = $this->db->query("SELECT name FROM planet WHERE id=$loc"); + list($pnames[$loc]) = dbFetchArray($q); + } + $pname = $pnames[$loc]; + + // Generate a message + $mid = $this->msg->call('send', $player, 'flmove', array( + 'p_id' => $loc, + 'p_name' => $pname + )); + + // Insert fleet data + foreach ($flist as $fleet) { + // Get origin planet name + if (is_null($pnames[$fleet['from']])) { + $q = $this->db->query("SELECT name FROM planet WHERE id={$fleet['from']}"); + list($pnames[$fleet['from']]) = dbFetchArray($q); + } + $pname = $pnames[$fleet['from']]; + + $fpowers[$fleet['id']] = $fPower = $this->lib->call('getPower', + $player, $fleet['gaships'], $fleet['fighters'], $fleet['cruisers'], $fleet['bcruisers']); + $this->db->query("INSERT INTO flmove_data VALUES ($mid,'".addslashes($fleet['name'])."',$player," + . "{$fleet['gaships']},{$fleet['fighters']},{$fleet['cruisers']},{$fleet['bcruisers']},$fPower," + . "FALSE,TRUE,{$fleet['from']},'".addslashes($pname)."')"); + } + } + } + + // Send messages for other fleets + $tm++; + foreach ($fMove as $player => $locs) { + foreach ($locs as $loc => $flists) { + $flist = array(); + foreach ($flists[0] as $f) { + $f['hostile'] = 0; + array_push($flist, $f); + } + foreach ($flists[1] as $f) { + $f['hostile'] = 0; + array_push($flist, $f); + } + foreach ($flists[2] as $f) { + $f['hostile'] = 1; + array_push($flist, $f); + } + foreach ($flists[3] as $f) { + $f['hostile'] = 1; + array_push($flist, $f); + } + + // Get planet name + if (is_null($pnames[$loc])) { + $q = $this->db->query("SELECT name FROM planet WHERE id=$loc"); + list($pnames[$loc]) = dbFetchArray($q); + } + $pname = $pnames[$loc]; + + // Generate a message + $mid = $this->msg->call('send', $player, 'flmove', array( + 'p_id' => $loc, + 'p_name' => $pname + )); + + // Insert fleet data + foreach ($flist as $fleet) { + if (is_null($fleet['from'])) { + $arrived = 0; + } else { + // Get origin planet name + if (is_null($pnames[$fleet['from']])) { + $q = $this->db->query("SELECT name FROM planet WHERE id={$fleet['from']}"); + list($pnames[$fleet['from']]) = dbFetchArray($q); + } + $pname = $pnames[$fleet['from']]; + $arrived = 1; + } + + if (is_null($fpowers[$fleet['id']])) { + $fpowers[$fleet['id']] = $this->lib->call('getPower', + $fleet['owner'], $fleet['gaships'], $fleet['fighters'], $fleet['cruisers'], $fleet['bcruisers']); + } + $fPower = $fpowers[$fleet['id']]; + l::trace("beta5/sendFleetMoveMessages: inserting message for player $player, location $loc, fleet {$fleet['id']}"); + $this->db->query("INSERT INTO flmove_data VALUES ($mid,'".addslashes($fleet['name'])."',{$fleet['owner']}," + . "{$fleet['gaships']},{$fleet['fighters']},{$fleet['cruisers']},{$fleet['bcruisers']},$fPower," + . ($fleet['hostile'] ? "TRUE" : "FALSE") . "," . ($arrived ? "TRUE" : "FALSE") . "," + . ($arrived ? ("{$fleet['from']},'".addslashes($pname)."'") : "NULL,NULL") . ")"); + } + } + } + + $this->lib->mainClass->fleetArrivals = $this->lib->mainClass->fleetDepartures = array(); + } +} + +?> diff --git a/scripts/game/beta5/fleet/library/setOrders.inc b/scripts/game/beta5/fleet/library/setOrders.inc new file mode 100644 index 0000000..fba12dc --- /dev/null +++ b/scripts/game/beta5/fleet/library/setOrders.inc @@ -0,0 +1,245 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->moving = $this->lib->game->getLib('beta5/moving'); + $this->planets = $this->lib->game->getLib('beta5/planet'); + $this->players = $this->lib->game->getLib('beta5/player'); + $this->rules = $this->lib->game->getLib('beta5/rules'); + $this->standby = $this->lib->game->getLib('beta5/standby'); + } + + + // Changes a fleet's orders + function run($fid, $newDest, $newDelay, $attack = null) { + // Get fleet data + $fleet = $this->lib->call('get', $fid); + if (is_null($fleet) || !is_null($fleet['sale_info']) || $fleet['can_move'] != 'Y') { + return false; + } + if (is_null($attack)) { + $attack = ($fleet['attacking'] == 't'); + } + + // Check for hyperspace capabilities + $r = $this->rules->call('get', $fleet['owner']); + $used = $r['gaship_space'] * $fleet['gaships'] + $r['fighter_space'] * $fleet['fighters']; + $avail = $r['cruiser_haul'] * $fleet['cruisers'] + $r['bcruiser_haul'] * $fleet['bcruisers']; + $hsOk = ($used <= $avail); + + // Get cruisers status / Capital ships speed + $cru = ($fleet['cruisers'] > 0); + $csp = $r['capital_ship_speed']; + + // Check for an implicit "null" move order + $cLoc = is_null($fleet['move']) + ? (is_null($fleet['wait']) + ? $fleet['location'] + : $fleet['wait']['drop_point']) + : $this->moving->call('getLocation', $fleet['move']['id']); + if ($cLoc == $newDest) + $newDest = null; + + // Some checks must be performed if the fleet is going out of system + if (!(is_null($cLoc) || is_null($newDest))) { + $cLocInfo = $this->planets->call('byId', $cLoc); + $nDestInfo = $this->planets->call('byId', $newDest); + if ($cLocInfo['x'] != $nDestInfo['x'] || $cLocInfo['y'] != $nDestInfo['y']) { + // Fleet is not hyperspace-capable + if (!$hsOk) { + return false; + } + // Check protection + if ($this->lib->game->params['victory'] == 0) { + $protected = $this->players->call('getProtectionLevel', $fleet['owner']); + if ($protected) { + $this->players->call('breakProtection', $fleet['owner'], 'ACT'); + } + } + } + } + + // Identify movement-related actions + $newMove = is_null($fleet['move']) && !is_null($newDest); + $rmMove = !is_null($fleet['move']) && is_null($newDest); + $chgMove = !is_null($fleet['move']) && !is_null($newDest) && ($newDest != $fleet['move']['m_to']); + $mNoAct = !($newMove||$rmMove||$chgMove); + + // Identify HS standby-related actions + $newWait = $hsOk && is_null($fleet['wait']) && !is_null($newDelay); + $rmWait = $hsOk && !is_null($fleet['wait']) && is_null($newDelay); + $chgWait = $hsOk && !is_null($fleet['wait']) && !is_null($newDelay) && ($newDelay != $fleet['wait']['time_left']); + $wNoAct = !($newWait||$rmWait||$chgWait); + + + logText("newMove $newMove; rmMove $rmMove; chgMove $chgMove; mNoAct $mNoAct"); + logText("newWait $newWait; rmWait $rmWait; chgWait $chgWait; wNoAct $wNoAct"); + + // No actions are to be taken, return + if ($mNoAct && $wNoAct) { + return true; + } + + // Start moving + $fleetArrived = false; + if ($newMove) { + $departure = false; + + // HS orders haven't changed. + if ($wNoAct) { + // Are we already waiting? + if (is_null($fleet['wait'])) { + $wo = null; + $sl = $fleet['location']; + $departure = true; + } else { + $wo = $fleet['wait']['id']; + $sl = $fleet['wait']['drop_point']; + logText("Adding new move orders, initial location: $sl"); + $this->db->query("UPDATE hs_wait SET time_left=$newDelay,time_spent=0,origin=NULL WHERE id=$wo"); + } + } elseif ($newWait) { + // New HS stand-by order + $wo = $this->standby->call('create', $newDelay, $newDest); + $sl = $fleet['location']; + $departure = true; + } elseif ($rmWait) { + // Delete current HS stand-by order + $wo = null; + $sl = $fleet['wait']['drop_point']; + $this->db->query("DELETE FROM hs_wait WHERE id=".$fleet['wait']['id']); + } elseif ($chgWait) { + // Change HS stand-by order + $wo = $fleet['wait']['id']; + $sl = $fleet['wait']['drop_point']; + $this->db->query("UPDATE hs_wait SET time_left=$newDelay,time_spent=0,origin=NULL,drop_point=$newDest WHERE id=".$fleet['wait']['id']); + } + + // Create movement entry + $this->db->query("DELETE FROM beacon_detection WHERE fleet = $fid"); + $mo = $this->moving->call('newObject', $sl, $newDest, $csp, $cru, $wo); + if (is_null($mo)) { + logText("beta5/setFleetOrders($fid,$newDest,$newDelay,".($attack?1:0)."): unable to create a new moving_object entry", LOG_ERR); + return false; + } + $this->db->query("UPDATE fleet SET location=NULL,moving=$mo,waiting=NULL WHERE id=$fid"); + if ($departure) { + $this->planets->call('updateMilStatus', $sl); + $this->planets->call('updateHappiness', $sl); + $this->addDeparture($sl, $fid, $fleet['attacking'] == 't'); + } + } elseif ($rmMove) { + // Fleet stop requested + $mo = $fleet['move']['id']; + $nloc = $this->moving->call('getLocation', $mo); + + if ($wNoAct) { + // HS orders haven't changed. + // Do we have stand-by orders? + if (is_null($fleet['wait'])) { + $wo = null; + } else { + $wo = $fleet['wait']['id']; + $this->db->query("UPDATE hs_wait SET drop_point=$nloc WHERE id=$wo"); + } + } + elseif ($newWait) { + // New HS stand-by order + $wo = $this->standby->call('create', $newDelay, $nloc); + } elseif ($rmWait) { + // Delete current HS stand-by order + $wo = null; + $this->db->query("DELETE FROM hs_wait WHERE id=".$fleet['wait']['id']); + } elseif ($chgWait) { + // Change HS stand-by order + $wo = $fleet['wait']['id']; + $this->db->query("UPDATE hs_wait SET time_left=$newDelay,time_spent=0,drop_point=$nloc WHERE id=".$fleet['wait']['id']); + } + + // Stop movement + $this->moving->call('stop', $mo, $wo); + } elseif ($chgMove) { + // Fleet destination changed + $mo = $fleet['move']['id']; + + if ($wNoAct) { + // HS orders haven't changed. + // Do we have stand-by orders? + if (is_null($fleet['wait'])) { + $wo = null; + } else { + $wo = $fleet['wait']['id']; + $this->db->query("UPDATE hs_wait SET drop_point=$newDest WHERE id=$wo"); + } + } + elseif ($newWait) { + // New HS stand-by order + $wo = $this->standby->call('create', $newDelay, $newDest); + } elseif ($rmWait) { + // Delete current HS stand-by order + $wo = null; + $this->db->query("DELETE FROM hs_wait WHERE id=".$fleet['wait']['id']); + } elseif ($chgWait) { + // Change HS stand-by order + $wo = $fleet['wait']['id']; + $this->db->query("UPDATE hs_wait SET time_left=$newDelay,time_spent=0,drop_point=$newDest WHERE id=$wo"); + } + + // Redirect fleet + $this->moving->call('redirect', $mo, $newDest, $csp, $cru, $wo); + } elseif ($newWait) { + // No destination change, but stand-by orders changed + // New HS stand-by order + if (is_null($fleet['move'])) { + $loc = $fleet['location']; + $wo = $this->standby->call('create', $newDelay, $loc, $loc, null); + + $this->db->query("UPDATE fleet SET waiting=$wo,location=NULL WHERE id=$fid"); + $this->db->query("UPDATE hs_wait SET origin=$loc WHERE id=$wo"); + + $this->planets->call('updateMilStatus', $loc); + $this->planets->call('updateHappiness', $loc); + $this->addDeparture($loc, $fid, $fleet['attacking'] == 't'); + $this->planets->call('detectFleets', $loc); + } else { + $wo = $this->standby->call('create', $newDelay, $fleet['move']['m_to']); + $this->db->query("UPDATE moving_object SET wait_order=$wo WHERE id=".$fleet['move']['id']); + } + } elseif ($rmWait) { + // Delete current HS stand-by order + $this->db->query("DELETE FROM hs_wait WHERE id=".$fleet['wait']['id']); + if (is_null($fleet['move'])) { + $fleetArrived = true; + $this->lib->call('arrival', $fid, $fleet['wait']['drop_point'], $fleet['wait']['origin'], $attack); + $this->planets->call('updateMilStatus', $fleet['wait']['drop_point']); + $this->planets->call('updateHappiness', $fleet['wait']['drop_point']); + $this->db->query("DELETE FROM beacon_detection WHERE fleet = $fid"); + } + } elseif ($chgWait) { + // Change HS stand-by order + $this->db->query("UPDATE hs_wait SET time_left=$newDelay WHERE id=".$fleet['wait']['id']); + } + + // If the fleet hasn't arrived, set its status + if (!$fleetArrived) { + $this->db->query("UPDATE fleet SET attacking=".dbBool($attack)." WHERE id=$fid"); + } + + $this->lib->call('invCache', $fid); + return true; + } + + + // Adds an entry to the list of fleet departures + function addDeparture($location, $fid, $status) { + if (!is_array($this->lib->mainClass->fleetDepartures[$location])) { + $this->lib->mainClass->fleetDepartures[$location] = array(array(), array()); + } + array_push($this->lib->mainClass->fleetDepartures[$location][$status?1:0], $fid); + } +} + +?> diff --git a/scripts/game/beta5/fleet/library/split.inc b/scripts/game/beta5/fleet/library/split.inc new file mode 100644 index 0000000..1b659c0 --- /dev/null +++ b/scripts/game/beta5/fleet/library/split.inc @@ -0,0 +1,82 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->rules = $this->lib->game->getLib('beta5/rules'); + $this->standby = $this->lib->game->getLib('beta5/standby'); + $this->moving = $this->lib->game->getLib('beta5/moving'); + } + + + // Manually split a fleet in player-specified fleets + function run($fid, $newName, $count, $sg, $sf, $sc, $sb) { + // Get fleet data + $f = $this->lib->call('get', $fid, true); + if (is_null($f) || $f['can_move'] != 'Y' || !is_null($f['sale_info'])) { + return 1; + } + + // Check for enough ships in the original fleet + $mg = $count * $sg; $mf = $count * $sf; + $mc = $count * $sc; $mb = $count * $sb; + if ($f['gaships'] < $mg || $f['fighters'] < $mf || $f['cruisers'] < $mc || $f['bcruisers'] < $mb + || $f['gaships'] + $f['fighters'] + $f['cruisers'] + $f['bcruisers'] - ($mg+$mf+$mc+$mb) == 0) { + return 2; + } + + // If we're moving or standing by in Hyperspace, we need to make sure both + // the new and old fleets are HS-capable + if ((!is_null($f['move']) && $f['move']['hyperspace'] == 't') || !is_null($f['wait'])) { + $r = $this->rules->call('get', $f['owner']); + $nu = $r['gaship_space'] * $sg + $r['fighter_space'] * $sf; + $na = $r['cruiser_haul'] * $sc + $r['bcruiser_haul'] * $sb; + $ou = $r['gaship_space'] * ($f['gaships'] - $mg) + $r['fighter_space'] * ($f['fighters'] - $mf); + $oa = $r['cruiser_haul'] * ($f['cruisers'] - $mc) + $r['bcruiser_haul'] * ($f['bcruisers'] - $mb); + if ($nu > $na || $ou > $oa) { + return 3; + } + } + + // Generate code that will set the new fleets' orders + if (is_null($f['location'])) { + $location = "NULL"; + if (is_null($f['moving'])) { + $moving = "NULL"; + $oCode = '$waiting = $this->standby->call("create", '.$f['wait']['time_left'].",".$f['wait']['drop_point'] + . ','.$f['wait']['origin'].','.$f['wait']['time_spent'].');'; + } else { + $waiting = "NULL"; + $oCode = '$moving = $this->moving->call("cloneObject", '.$f['moving'].');'; + } + } else { + $location = $f['location']; + $moving = $waiting = 'NULL'; + $oCode = null; + } + + // Generate new fleets + $nn = addslashes($newName == "" ? preg_replace('/ [0-9]+$/', '', $f['name']) : $newName); + for ($i=0;$i<$count;$i++) { + if ($oCode != "") { + eval($oCode); + } + $nnb = $count > 1 ? (" " . ($i + 1)) : ""; + $this->db->query("INSERT INTO fleet(owner,name,location,gaships,fighters,cruisers,bcruisers,attacking,moving,waiting,time_spent) VALUES(" + .$f['owner'].",'$nn$nnb',$location,$sg,$sf,$sc,$sb," + .dbBool($f['attacking'] == 't').",$moving,$waiting,{$f['time_spent']})"); + } + + // Update original fleet + $this->db->query("UPDATE fleet SET gaships=gaships-$mg,fighters=fighters-$mf,cruisers=cruisers-$mc,bcruisers=bcruisers-$mb " + ."WHERE id=$fid"); + $this->db->query("DELETE FROM beacon_detection WHERE fleet = $fid"); + + $this->lib->call('invCache', $fid); + return 0; + } +} + +?> diff --git a/scripts/game/beta5/fleet/library/switchStatus.inc b/scripts/game/beta5/fleet/library/switchStatus.inc new file mode 100644 index 0000000..6f20a1b --- /dev/null +++ b/scripts/game/beta5/fleet/library/switchStatus.inc @@ -0,0 +1,31 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Switches a fleet's status + function run($id) { + $f = $this->lib->call('get', $id); + if ($f['attacking'] == 't') { + $this->db->query("UPDATE fleet SET attacking=".dbBool(0)." WHERE id=$id"); + } else { + $this->db->query("UPDATE fleet SET attacking=".dbBool(1) + .(is_null($f['location'])?"":",can_move='B',time_spent=0")." WHERE id=$id"); + } + + // FIXME: messages + + $this->lib->mainClass->fleets[$id]['attacking'] = ($this->lib->mainClass->fleets[$id]['attacking'] == 't') ? 'f' : 't'; + if ($this->lib->mainClass->fleets[$id]['attacking'] == 't' && !is_null($f['location'])) { + $this->lib->mainClass->fleets[$id]['can_move'] = 'B'; + } + logText("beta5/fleet/switchStatus($id): fleet owner {$f['owner']}, switched to " . ($f['attacking'] == 't' ? "def" : "att"), LOG_DEBUG); + } +} + +?> diff --git a/scripts/game/beta5/forums/library.inc b/scripts/game/beta5/forums/library.inc new file mode 100644 index 0000000..60c95db --- /dev/null +++ b/scripts/game/beta5/forums/library.inc @@ -0,0 +1,65 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Get the amount of read topics in an alliance forum + function getRead($fid, $pid) { + $q = $this->db->query("SELECT COUNT(*) FROM af_read r,af_topic t WHERE t.id=r.topic AND t.forum=$fid AND r.reader=$pid"); + list($nr) = dbFetchArray($q); + return $nr; + } + + function isRead($topic, $player) { + $q = $this->db->query("SELECT * FROM af_read WHERE topic=$topic AND reader=$player"); + return $q && dbCount($q); + } + + function markRead($topic, $player) { + if ($this->isRead($topic,$player)) { + return false; + } + $this->db->query("DELETE FROM af_read WHERE topic=$topic AND reader=$player"); + $this->db->query("INSERT INTO af_read(topic,reader)VALUES($topic,$player)"); + return true; + } + + function markUnread($topic, $player) { + $this->db->query("DELETE FROM af_read WHERE topic=$topic AND reader<>$player"); + } + + function switchSticky($forum, $topic) { + $this->db->query("UPDATE af_topic SET sticky=NOT sticky WHERE id=$topic AND forum=$forum"); + } + + function markForumRead($fid, $pid) { + $q = $this->db->query("SELECT id FROM af_topic WHERE forum=$fid"); + while ($r = dbFetchArray($q)) { + $this->markRead($r[0], $pid); + } + } +} + +?> diff --git a/scripts/game/beta5/forums/library/deletePost.inc b/scripts/game/beta5/forums/library/deletePost.inc new file mode 100644 index 0000000..824d92b --- /dev/null +++ b/scripts/game/beta5/forums/library/deletePost.inc @@ -0,0 +1,31 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($postId) { + $q = $this->db->query( + "SELECT f.id AS forum, t.id AS topic, reply_to FROM af_post p, af_forum f, af_topic t " + . "WHERE p.id = $postId AND t.id = p.topic AND f.id = p.forum " + . "FOR UPDATE OF p, f, t" + ); + if (!($q && dbCount($q))) { + return; + } + list($fid,$tid,$rtid) = dbFetchArray($q); + $this->db->query("UPDATE af_post SET reply_to=$rtid WHERE reply_to=$postId"); + $this->db->query("UPDATE af_forum SET posts=posts-1 WHERE id=$fid"); + $this->db->query("DELETE FROM af_post WHERE id=$postId"); + $q = $this->db->query("SELECT id FROM af_post WHERE topic=$tid ORDER BY moment DESC LIMIT 1"); + list($lastid) = dbFetchArray($q); + $this->db->query("UPDATE af_topic SET last_post=$lastid WHERE id=$tid"); + $this->lib->call('updateLast', $fid); + } +} + +?> diff --git a/scripts/game/beta5/forums/library/deleteTopic.inc b/scripts/game/beta5/forums/library/deleteTopic.inc new file mode 100644 index 0000000..f53a3c0 --- /dev/null +++ b/scripts/game/beta5/forums/library/deleteTopic.inc @@ -0,0 +1,26 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($forum, $topic) { + $q = $this->db->query( + "SELECT t.id, f.id, p.id FROM af_post p, af_topic t, af_forum f " + . "WHERE p.forum = $forum AND p.topic = $topic AND t.id = p.topic AND f.id = p.forum " + . "FOR UPDATE OF p, t, f" + ); + if (!($q && dbCount($q))) { + return; + } + $np = dbCount($q); + $this->db->query("DELETE FROM af_post WHERE topic=$topic AND forum=$forum"); + $this->db->query("UPDATE af_forum SET posts=posts-$np,topics=topics-1 WHERE id=$forum"); + $this->lib->call('updateLast', $forum); + } +} + +?> diff --git a/scripts/game/beta5/forums/library/edit.inc b/scripts/game/beta5/forums/library/edit.inc new file mode 100644 index 0000000..1b306e3 --- /dev/null +++ b/scripts/game/beta5/forums/library/edit.inc @@ -0,0 +1,23 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($a, $pid, $sub, $txt, $ec, $es) { + $tm = time(); + $q = $this->db->query("SELECT topic FROM af_post WHERE id=$pid"); + list($tid) = dbFetchArray($q); + $this->lib->call('markUnread', $tid,$a); + $qs = "UPDATE af_post SET edited=$tm,edited_by=$a,title='".addslashes($sub)."',contents='" + .addslashes($txt)."',enable_code=".dbBool($ec).",enable_smileys=" + . dbBool($es) . " WHERE id=$pid"; + return !is_null($this->db->query($qs)); + } +} + +?> diff --git a/scripts/game/beta5/forums/library/getLatest.inc b/scripts/game/beta5/forums/library/getLatest.inc new file mode 100644 index 0000000..0de560f --- /dev/null +++ b/scripts/game/beta5/forums/library/getLatest.inc @@ -0,0 +1,43 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->mForums = $this->lib->game->getLib('main/forums'); + } + + + function run($aForums, $gForums, $nb, $first) { + if (count($aForums) && count($gForums)) { + $qs = "SELECT 'A',id,moment FROM af_post WHERE forum IN (".join(',',$aForums).")"; + $qs .= "UNION SELECT 'G',id,moment FROM f_post WHERE forum IN (".join(',',$gForums).") AND deleted IS NULL "; + } elseif (count($aForums)) { + $qs = "SELECT 'A',id,moment FROM af_post WHERE forum IN (".join(',',$aForums).") "; + } elseif (count($gForums)) { + $qs = "SELECT 'G',id,moment FROM f_post WHERE forum IN (".join(',',$gForums).") AND deleted IS NULL "; + } else { + return array(); + } + $qs .= "ORDER BY moment DESC LIMIT $nb OFFSET $first"; + + $q = $this->db->query($qs); + $posts = array(); + while ($r = dbFetchArray($q)) { + if ($r[0] == 'A') { + $p = $this->lib->call('getPost', $r[1]); + $p['contents'] = $p['html']; + } else { + $p = $this->mForums->call('getPost',$r[1]); + $p['contents'] = $p['html']; + } + $p['ctype'] = $r[0]; + array_push($posts, $p); + } + + return $posts; + } +} + +?> diff --git a/scripts/game/beta5/forums/library/getPost.inc b/scripts/game/beta5/forums/library/getPost.inc new file mode 100644 index 0000000..8e1fd92 --- /dev/null +++ b/scripts/game/beta5/forums/library/getPost.inc @@ -0,0 +1,46 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->players = $this->lib->game->getLib('beta5/player'); + $this->mForums = $this->lib->game->getLib('main/forums'); + } + + + function run($pid) { + // Get post data + $q = $this->db->query( + "SELECT p.id AS id,p.title AS title," + . "t.id AS tid,p2.title AS tname," + . "f.id AS fid,f.title AS fname," + . "c.id AS cid,c.name AS cname," + . "p.author AS pid,p.reply_to as reply_to," + . "p.moment AS moment,p.title AS title," + . "p.contents AS contents,p.enable_code AS ec," + . "p.enable_smileys AS es,p.edited AS edited," + . "p.edited_by AS edit_id " + . "FROM af_topic t,af_post p,af_post p2,af_forum f,alliance c " + . "WHERE p.id=$pid AND t.id=p.topic AND p2.id=t.first_post " + . "AND f.id=p.forum AND c.id=f.alliance" + ); + if (!$q || dbCount($q) != 1) { + return null; + } + $rv = dbFetchHash($q); + $rv['html'] = $this->mForums->call('substitute', + $rv['contents'], $rv['ec'], $rv['es'] + ); + $pinf = $this->players->call('get', $rv['pid'], true); + $rv['html'] .= $this->mForums->call('signature', $pinf['uid']); + $rv['author'] = $pinf['name']; + if (!is_null($rv['edit_id'])) { + $rv['edited_by'] = $this->players->call('getName', $rv['edit_id'], true); + } + return $rv; + } +} + +?> diff --git a/scripts/game/beta5/forums/library/getPosts.inc b/scripts/game/beta5/forums/library/getPosts.inc new file mode 100644 index 0000000..b66f7dd --- /dev/null +++ b/scripts/game/beta5/forums/library/getPosts.inc @@ -0,0 +1,113 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->players = $this->lib->game->getLib('beta5/player'); + $this->mForums = $this->lib->game->getLib('main/forums'); + } + + + function run($tid, $thr, $o, $cnt, $fst) { + $os = $o?"DESC":"ASC"; + $posts = array(); + + // FIXME: this is the wrong way to access player ID + $myId = $_SESSION[game::sessName()]['player']; + if ($thr) { + // Read list of IDs + $q = $this->db->query( + "SELECT id,reply_to FROM af_post WHERE topic=$tid ORDER BY moment $os" + ); + $ids = array(); + while ($qr = dbFetchArray($q)) { + array_push($ids, $qr); + } + + // Get first post + if ($o) { + $mp = array_pop($ids); + } else { + $mp = array_shift($ids); + } + + // Initialize IDs and depths + $sids = array($mp[0]); + $dpth = array(0); + + // Create lists + $ist = array($mp[0]); + $cd = 0; + while (count($ids)) { + $od = $cd; + for ($i=0;$idb->query( + "SELECT id,author AS pid,moment,title,contents,enable_code AS ec," + . "enable_smileys AS es,edited,edited_by AS edit_id " + . "FROM af_post WHERE id IN (".join(',',$rsids).")" + ); + while ($qr = dbFetchHash($q)) { + $pinf = $this->players->call('get', $qr['pid'], true); + $qr['author'] = $pinf['name']; + $qr['mine'] = ($qr['pid'] == $myId); + $qr['contents'] = $this->mForums->call('substitute', + $qr['contents'], $qr['ec'], $qr['es'] + ); + $qr['contents'] .= $this->mForums->call('signature', $pinf['uid']); + $i = array_search($qr['id'], $rsids); + $qr['depth'] = $dpth[$i+$fst]; + if ($qr['depth'] > 9) { + $qr['depth'] = 9; + } + if (!is_null($qr['edit_id'])) { + $qr['edited_by'] = $this->players->call('getName', $qr['edit_id']); + } + $posts[$i] = $qr; + $i++; + } + } else { + $q = $this->db->query( + "SELECT id,author AS pid,moment,title,contents,enable_code AS ec," + . "enable_smileys AS es,edited,edited_by AS edit_id " + . "FROM af_post WHERE topic=$tid " + . "ORDER BY moment $os LIMIT $cnt OFFSET $fst" + ); + while ($qr = dbFetchHash($q)) { + $pinf = $this->players->call('get', $qr['pid'], true); + $qr['mine'] = ($qr['pid'] == $myId); + $qr['author'] = $pinf['name']; + $qr['contents'] = $this->mForums->call('substitute', + $qr['contents'], $qr['ec'], $qr['es'] + ); + $qr['contents'] .= $this->mForums->call('signature', $pinf['uid']); + $qr['depth'] = 0; + if (!is_null($qr['edit_id'])) { + $qr['edited_by'] = $this->players->call('getName', $qr['edit_id']); + } + array_push($posts, $qr); + } + } + + return $posts; + } +} + +?> diff --git a/scripts/game/beta5/forums/library/getStructure.inc b/scripts/game/beta5/forums/library/getStructure.inc new file mode 100644 index 0000000..63a4eb1 --- /dev/null +++ b/scripts/game/beta5/forums/library/getStructure.inc @@ -0,0 +1,98 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->players = $this->lib->game->getLib('beta5/player'); + $this->alliance = $this->lib->game->getLib('beta5/alliance'); + $this->account = $this->lib->game->getLib('main/account'); + $this->mForums = $this->lib->game->getLib('main/forums'); + } + + + function run($player) { + $rv = array(); + $ord = 0; + $pi = $this->players->call('get', $player); + $isAdm = $this->account->call('isAdmin', $pi['uid']); + $gAdm = $this->mForums->call('getAdministrator', $pi['uid']); + $gMod = $this->mForums->call('getModerator', $pi['uid']); + + // Get categories for general forums + $gCats = $this->mForums->call('getCategories'); + foreach ($gCats as $gc) { + $rv['G#'.$gc['id']] = array( + "id" => $gc['id'], + "type" => "G", + "title" => $gc['title'], + "desc" => $gc['description'], + "order" => $ord ++, + "forums" => array() + ); + } + + // Get version-specific general category + $vCat = $this->mForums->call('getVersionCategory', 'beta5'); + if ($vCat) { + $rv['G#'.$vCat['id']] = array( + "id" => $vCat['id'], + "type" => "V", + "title" => "Legacy Worlds - Beta 5", + "desc" => $vCat['description'], + "order" => $ord ++, + "forums" => array() + ); + } + + // Get general forums + $gCats = array_keys($rv); + foreach ($gCats as $cid) { + $rcid = substr($cid,2); + $rv[$cid]['forums'] = $this->mForums->call('getForums', $rcid); + $foDiff = 0; + for ($i=0;$imForums->call('getRead', $id, $pi['uid']); + $rv[$cid]['forums'][$i]['mod'] = in_array($rv[$cid]['id'], $gAdm) || in_array($id, $gMod); + $rv[$cid]['forums'][$i]['forder'] -= $foDiff; + } + } + + // Get alliance forums + $ap = $this->alliance->call('getPrivileges', $player); + if (count($ap['f_read']) || count($ap['f_mod'])) { + $rv['A#'.$pi['aid']] = array( + "id" => $pi['aid'], + "type" => "A", + "title" => $pi['aname'], + "order" => $ord ++, + "forums" => array() + ); + + $fl = $this->alliance->call('getForumsComplete', $pi['aid']); + $rcid = 'A#'.$pi['aid']; + foreach ($fl as $f) { + if (!(in_array($f['id'],$ap['f_read']) || in_array($f['id'],$ap['f_mod']))) { + continue; + } + $f['mod'] = in_array($f['id'],$ap['f_mod']); + $f['unread'] = $f['topics'] - $this->lib->call('getRead', $f['id'], $player); + $f['rcid'] = $rcid; + array_push($rv[$rcid]['forums'],$f); + } + } + + return $rv; + } +} + +?> diff --git a/scripts/game/beta5/forums/library/getTopic.inc b/scripts/game/beta5/forums/library/getTopic.inc new file mode 100644 index 0000000..3018872 --- /dev/null +++ b/scripts/game/beta5/forums/library/getTopic.inc @@ -0,0 +1,32 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($tid) { + // Get main topic data + $q = $this->db->query( + "SELECT t.id AS id,p.title AS title," + . "f.id AS fid,f.title AS fname," + . "p.id AS fpid,t.last_post AS lpid " + . "FROM af_topic t,af_post p,af_forum f " + . "WHERE t.id=$tid AND p.id=t.first_post AND f.id=t.forum" + ); + if (!$q || dbCount($q) != 1) { + return null; + } + $rv = dbFetchHash($q); + + // Get post count + $q = $this->db->query("SELECT COUNT(*) FROM af_post WHERE topic=$tid"); + list($rv["nitems"]) = dbFetchArray($q); + + return $rv; + } +} + +?> diff --git a/scripts/game/beta5/forums/library/getTopics.inc b/scripts/game/beta5/forums/library/getTopics.inc new file mode 100644 index 0000000..98103bc --- /dev/null +++ b/scripts/game/beta5/forums/library/getTopics.inc @@ -0,0 +1,37 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->players = $this->lib->game->getLib('beta5/player'); + } + + + function run($f, $first, $count) { + $q = $this->db->query( + "SELECT t.id AS id,p.title AS title,p.moment AS moment," + . "p.author AS author_id,p2.moment AS last_moment," + . "p2.author AS last_author_id,t.sticky AS sticky " + . "FROM af_topic t,af_post p,af_post p2 " + . "WHERE t.forum=$f AND p.id=t.first_post AND p2.id=t.last_post " + . "ORDER BY sticky DESC,last_moment DESC LIMIT $count OFFSET $first" + ); + $a = array(); + if (!$q) { + return $a; + } + while ($rs = dbFetchHash($q)) { + $q2 = $this->db->query("SELECT COUNT(*)-1 FROM af_post WHERE topic=".$rs["id"]); + list($rs['posts']) = dbFetchArray($q2); + $rs['sticky'] = ($rs['sticky'] == 't'); + $rs['author'] = $this->players->call('getName', $rs['author_id']); + $rs['last_author'] = $this->players->call('getName', $rs['last_author_id']); + array_push($a, $rs); + } + return $a; + } +} + +?> diff --git a/scripts/game/beta5/forums/library/moveTopic.inc b/scripts/game/beta5/forums/library/moveTopic.inc new file mode 100644 index 0000000..a4306aa --- /dev/null +++ b/scripts/game/beta5/forums/library/moveTopic.inc @@ -0,0 +1,29 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($forum, $topic, $dest, $user) { + $this->db->query("SELECT * FROM af_forum WHERE id IN ($forum,$dest) FOR UPDATE"); + + $q = $this->db->query("SELECT COUNT(*) FROM af_post WHERE forum=$forum AND topic=$topic"); + if (!($q && dbCount($q))) { + return; + } + list($np) = dbFetchArray($q); + $this->db->query("UPDATE af_post SET forum=$dest WHERE topic=$topic AND forum=$forum"); + $this->db->query("UPDATE af_topic SET forum=$dest WHERE id=$topic AND forum=$forum"); + $this->db->query("UPDATE af_forum SET posts=posts-$np,topics=topics-1 WHERE id=$forum"); + $this->db->query("UPDATE af_forum SET posts=posts+$np,topics=topics+1 WHERE id=$dest"); + $this->lib->call('markUnread', $topic, $user); + $this->lib->call('updateLast', $forum); + $this->lib->call('updateLast', $dest); + } +} + +?> diff --git a/scripts/game/beta5/forums/library/newTopic.inc b/scripts/game/beta5/forums/library/newTopic.inc new file mode 100644 index 0000000..469f9c3 --- /dev/null +++ b/scripts/game/beta5/forums/library/newTopic.inc @@ -0,0 +1,39 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($a, $fid, $sub, $txt, $ec, $es, $st) { + $tm = time(); + $qs = "INSERT INTO af_post(forum,author,moment,title,contents,enable_code,enable_smileys) VALUES (" + . "$fid,$a,$tm,'".addslashes($sub)."','".addslashes($txt)."'," + . dbBool($ec) . "," . dbBool($es) . ")"; + if (!$this->db->query($qs)) { + return false; + } + + $q = $this->db->query("SELECT id FROM af_post WHERE forum=$fid AND topic IS NULL AND author=$a AND moment=$tm"); + if (!$q || dbCount($q) != 1) { + return false; + } + list($pid) = dbFetchArray($q); + + $this->db->query("INSERT INTO af_topic(forum,first_post,last_post,sticky) VALUES($fid,$pid,$pid," . ($st?"TRUE":"FALSE") . ")"); + $q = $this->db->query("SELECT id FROM af_topic WHERE forum=$fid AND first_post=$pid"); + if (!$q || dbCount($q) != 1) { + return false; + } + list($tid) = dbFetchArray($q); + + $this->db->query("UPDATE af_post SET topic=$tid WHERE id=$pid"); + $this->db->query("UPDATE af_forum SET topics=topics+1,posts=posts+1,last_post=$pid WHERE id=$fid"); + $this->lib->call('markRead', $tid, $a); + return $pid; + } +} + +?> diff --git a/scripts/game/beta5/forums/library/reply.inc b/scripts/game/beta5/forums/library/reply.inc new file mode 100644 index 0000000..cbd2584 --- /dev/null +++ b/scripts/game/beta5/forums/library/reply.inc @@ -0,0 +1,34 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($a, $post, $sub, $txt, $ec, $es) { + $tm = time(); + $fid = $post['fid']; $tid = $post['tid']; $pid = $post['id']; + $qs = "INSERT INTO af_post(forum,topic,reply_to,author,moment,title,contents,enable_code,enable_smileys) VALUES (" + . "$fid,$tid,$pid,$a,$tm,'".addslashes($sub)."','".addslashes($txt)."'," + . dbBool($ec) . "," . dbBool($es) . ")"; + if (!$this->db->query($qs)) { + return false; + } + + $q = $this->db->query("SELECT id FROM af_post WHERE topic=$tid AND reply_to=$pid AND author=$a AND moment=$tm"); + if (!$q || dbCount($q) != 1) { + return false; + } + list($pid) = dbFetchArray($q); + + $this->db->query("UPDATE af_topic SET last_post=$pid WHERE id=$tid"); + $this->db->query("UPDATE af_forum SET posts=posts+1,last_post=$pid WHERE id=$fid"); + $this->lib->call('markUnread', $tid,$a); + return $pid; + } +} + +?> diff --git a/scripts/game/beta5/forums/library/searchPosts.inc b/scripts/game/beta5/forums/library/searchPosts.inc new file mode 100644 index 0000000..a783eef --- /dev/null +++ b/scripts/game/beta5/forums/library/searchPosts.inc @@ -0,0 +1,41 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->mForums = $this->lib->game->getLib('main/forums'); + } + + function run($aForums, $gForums, $nb, $first, $text, $complete, $sField, $sOrder) { + $tQs = " AND (title ILIKE '$text'" . ($complete ? " OR contents ILIKE '$text'" : "") . ")"; + if (count($aForums) && count($gForums)) { + $qs = "SELECT 'A',id,$sField FROM af_post WHERE forum IN (".join(',',$aForums).") $tQs "; + $qs .= "UNION SELECT 'G',id,$sField FROM f_post WHERE forum IN (".join(',',$gForums).") AND deleted IS NULL $tQs "; + } else if (count($aForums)) { + $qs = "SELECT 'A',id,$sField FROM af_post WHERE forum IN (".join(',',$aForums).") $tQs "; + } else { + $qs = "SELECT 'G',id,$sField FROM f_post WHERE forum IN (".join(',',$gForums).") AND deleted IS NULL $tQs "; + } + $qs .= "ORDER BY $sField $sOrder LIMIT $nb OFFSET $first"; + + $q = $this->db->query($qs); + $posts = array(); + while ($r = dbFetchArray($q)) { + if ($r[0] == 'A') { + $p = $this->lib->call('getPost', $r[1]); + $p['contents'] = $p['html']; + } else { + $p = $this->mForums->call('getPost',$r[1]); + $p['contents'] = $p['html']; + } + $p['ctype'] = $r[0]; + array_push($posts, $p); + } + + return $posts; + } +} + +?> diff --git a/scripts/game/beta5/forums/library/searchTopics.inc b/scripts/game/beta5/forums/library/searchTopics.inc new file mode 100644 index 0000000..4cbe45f --- /dev/null +++ b/scripts/game/beta5/forums/library/searchTopics.inc @@ -0,0 +1,41 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->mForums = $this->lib->game->getLib('main/forums'); + } + + function run($aForums, $gForums, $nb, $first, $text, $complete, $sField, $sOrder) { + $tQs = "AND (title ILIKE '$text'" . ($complete ? " OR contents ILIKE '$text'" : "") . ")"; + if (count($aForums) && count($gForums)) { + $qs = "SELECT 'A',topic,MAX($sField) AS $sField FROM af_post WHERE forum IN (".join(',',$aForums).") $tQs GROUP BY topic "; + $qs .= "UNION SELECT 'G',topic,MAX($sField) AS $sField FROM f_post WHERE forum IN (".join(',',$gForums).") $tQs GROUP BY topic "; + } else if (count($aForums)) { + $qs = "SELECT 'A',topic,MAX($sField) AS $sField FROM af_post WHERE forum IN (".join(',',$aForums).") $tQs GROUP BY topic "; + } else { + $qs = "SELECT 'G',topic,MAX($sField) AS $sField FROM f_post WHERE forum IN (".join(',',$gForums).") $tQs GROUP BY topic "; + } + $qs .= " ORDER BY $sField $sOrder LIMIT $nb OFFSET $first"; + + $q = $this->db->query($qs); + $topics = array(); + while ($r = dbFetchArray($q)) { + if ($r[0] == 'A') { + $p = $this->lib->call('getTopic', $r[1]); + $p['contents'] = $p['html']; + } else { + $p = $this->mForums->call('getTopic',$r[1]); + $p['contents'] = $p['html']; + } + $p['ctype'] = $r[0]; + array_push($topics, $p); + } + + return $topics; + } +} + +?> diff --git a/scripts/game/beta5/forums/library/updateLast.inc b/scripts/game/beta5/forums/library/updateLast.inc new file mode 100644 index 0000000..2a0dfc9 --- /dev/null +++ b/scripts/game/beta5/forums/library/updateLast.inc @@ -0,0 +1,21 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($forum) { + $q = $this->db->query("SELECT id FROM af_post WHERE forum=$forum ORDER BY moment DESC LIMIT 1"); + if (!($q && dbCount($q))) { + return; + } + list($id) = dbFetchArray($q); + $this->db->query("UPDATE af_forum SET last_post=$id WHERE id=$forum"); + } +} + +?> diff --git a/scripts/game/beta5/library.inc b/scripts/game/beta5/library.inc new file mode 100644 index 0000000..c101031 --- /dev/null +++ b/scripts/game/beta5/library.inc @@ -0,0 +1,58 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + /** Checks whether a user plays this version; returns null if he + * doesn't or his player ID otherwise. + */ + function doesUserPlay($u) { + $r = $this->db->query("SELECT id FROM player WHERE userid='" . addslashes($u) + . "' AND (quit IS NULL OR UNIX_TIMESTAMP(NOW())-quit<86400)"); + if (!($r && dbCount($r))) { + return null; + } + list($pid) = dbFetchArray($r); + return $pid; + } + + + /** Checks whether a user has played this version. */ + function hasPlayed($u) { + $r = $this->db->query("SELECT id FROM player WHERE userid='$u'"); + return ($r && dbCount($r)); + } + + + /** Checks whether it's possible to join this game. */ + function canJoin() { + $mPlayers = $this->lib->game->params['maxplayers']; + if ($mPlayers == -1) { + $q = $this->db->query("SELECT COUNT(*) FROM system WHERE nebula = 0"); + list($mPlayers) = dbFetchArray($q); + } + return ( !$mPlayers ) || ( $this->lib->call('getPlayerCount') < $mPlayers); + } +} + +?> diff --git a/scripts/game/beta5/library/checkPlanetName.inc b/scripts/game/beta5/library/checkPlanetName.inc new file mode 100644 index 0000000..1fb5d28 --- /dev/null +++ b/scripts/game/beta5/library/checkPlanetName.inc @@ -0,0 +1,46 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->planets = $this->lib->game->getLib('beta5/planet'); + } + + public function run($name) { + if (trim($name) != $name) { + $rv = 7; + } elseif (strlen($name) > 15) { + $rv = 1; + } elseif (preg_match('/[^A-Za-z0-9_\.\-\+@\/'."'".' ]/', $name)) { + $rv = 2; + } elseif (preg_match('/\s\s+/', $name)) { + $rv = 3; + } elseif (strlen($name) < 2) { + $rv = 4; + } elseif (!preg_match('/[A-Za-z]/', $name)) { + $rv = 5; + } elseif ($this->planets->call('nameExists', $name)) { + $rv = 6; + } else { + $rv = 0; + } + return $rv; + } + +} + +?> diff --git a/scripts/game/beta5/library/getPlayerCount.inc b/scripts/game/beta5/library/getPlayerCount.inc new file mode 100644 index 0000000..17d7e9c --- /dev/null +++ b/scripts/game/beta5/library/getPlayerCount.inc @@ -0,0 +1,23 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + /** Get player count. */ + function run() { + $r = $this->db->query("SELECT COUNT(*) FROM player " + . "WHERE (quit IS NULL OR UNIX_TIMESTAMP(NOW())-quit<86400) AND NOT hidden"); + if (!$r) { + return 0; + } + list($c) = dbFetchArray($r); + return $c; + } +} + +?> diff --git a/scripts/game/beta5/library/getPlayerStatus.inc b/scripts/game/beta5/library/getPlayerStatus.inc new file mode 100644 index 0000000..e3519a7 --- /dev/null +++ b/scripts/game/beta5/library/getPlayerStatus.inc @@ -0,0 +1,30 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($pid) { + $q = $this->db->query("SELECT cash FROM player WHERE id=$pid"); + if ($q && dbCount($q)) { + list($cash) = dbFetchArray($q); + } else { + $cash = 0; + } + + $q = $this->db->query("SELECT COUNT(*) FROM planet WHERE owner=$pid"); + if ($q && dbCount($q)) { + list($planets) = dbFetchArray($q); + } else { + $planets = 0; + } + + return array($planets, $cash, 0); + } +} + +?> diff --git a/scripts/game/beta5/library/investigate.inc b/scripts/game/beta5/library/investigate.inc new file mode 100644 index 0000000..f12537f --- /dev/null +++ b/scripts/game/beta5/library/investigate.inc @@ -0,0 +1,119 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + public function run($player1, $player2, $now, $delay) { + // Create the events record, generate timing part of the queries + $events = array( + "LSE" => 0, + "SE" => 0, + "HSE" => 0, + "VHSE" => 0 + ); + $tQuery = "BETWEEN " . (1 + $now - $delay) . " AND $now"; + + // Get the amount of donations in the interval + $q = $this->db->query( + "SELECT amount FROM donation_log " + . "WHERE \"time\" $tQuery " + . "AND (source = $player1 AND target = $player2 " + . "OR source = $player2 AND target = $player1)" + ); + while ($r = dbFetchArray($q)) { + list($amount) = $r; + if ($amount > 10000000) { + $et = "VHSE"; + } elseif ($amount > 1000000) { + $et = "HSE"; + } elseif ($amount > 100000) { + $et = "SE"; + } else { + $et = "LSE"; + } + $events[$et] ++; + } + + // Get the amount of sales in the interval + $q = $this->db->query( + "SELECT sell_price FROM sale_history " + . "WHERE ended $tQuery " + . "AND (from_player = $player1 AND to_player = $player2 " + . "OR from_player = $player2 AND to_player = $player1)" + ); + while ($r = dbFetchArray($q)) { + list($price) = $r; + if (is_null($price)) { + continue; + } + + if ($price == 0) { + $et = "HSE"; + } else { + $et = "SE"; + } + $events[$et] ++; + } + + // Get planets that were abandonned by one and retaken by the other + $q = $this->db->query( + "SELECT planet FROM abandon_log " + . "WHERE retake_time $tQuery AND retake_time - abandon_time < 5 * 86400 " + . "AND (former_owner = $player1 AND retake_owner = $player2 " + . "OR former_owner = $player2 AND retake_owner = $player1)" + ); + while ($r = dbFetchArray($q)) { + $events["SE"] ++; + } + + // Get tech exchanges + $q = $this->db->query( + "SELECT accepted, price FROM research_assistance " + . "WHERE moment $tQuery " + . "AND (player = $player1 AND offer_to = $player2 " + . "OR player = $player2 AND offer_to = $player1)" + ); + while ($r = dbFetchArray($q)) { + list($accepted, $price) = $r; + if ($accepted == 'f') { + $et = "LSE"; + } elseif ($price > 1000 || is_null($accepted)) { + $et = "SE"; + } elseif ($price < 1000) { + $et = "HSE"; + } + $events[$et] ++; + } + + // Generate the actual array of events + $actualEvents = array("CHECK"); + foreach ($events as $eType => $eCount) { + if ($eCount > 0) { + array_push($actualEvents, "$eType-$eCount"); + } + } + return $actualEvents; + } +} + + +?> diff --git a/scripts/game/beta5/library/isFinished.inc b/scripts/game/beta5/library/isFinished.inc new file mode 100644 index 0000000..ce84c7b --- /dev/null +++ b/scripts/game/beta5/library/isFinished.inc @@ -0,0 +1,31 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->getDBAccess(); + } + + function run() { + if ($this->game->params['victory'] == 0) { + return false; + } + + // Check for victory on normal matches + if ($this->game->params['victory'] == 1) { + $q = $this->db->query("SELECT COUNT(*) FROM alliance_victory " + . "WHERE UNIX_TIMESTAMP(NOW()) >= time_of_victory"); + list($c) = dbFetchArray($q); + return ($c > 0); + } + + // Check for victory on CTF matches + $q = $this->db->query("SELECT * FROM ctf_points WHERE points = 100"); + return (dbCount($q) == 1); + } + +} + +?> diff --git a/scripts/game/beta5/library/leaveGame.inc b/scripts/game/beta5/library/leaveGame.inc new file mode 100644 index 0000000..01c073b --- /dev/null +++ b/scripts/game/beta5/library/leaveGame.inc @@ -0,0 +1,142 @@ +lib = $lib; + $this->game = $lib->game; + $this->db = $this->lib->game->db; + $this->rankings = $this->lib->game->getLib('main/rankings'); + $this->alliance = $this->lib->game->getLib('beta5/alliance'); + $this->planets = $this->lib->game->getLib('beta5/planet'); + $this->players = $this->lib->game->getLib('beta5/player'); + $this->sales = $this->lib->game->getLib('beta5/sale'); + } + + + function run($id, $reason) { + if ($this->game->params['victory'] != 0) { + return; + } + logText("Player #$id is LEAVING game ($reason)!"); + if ($reason == 'KICKED') { + $reason = 'KICK'; + } + + $pinf = $this->players->call('get', $id); + $name = $pinf['name']; + $msgs = array(); + + // Leave alliance + if (!is_null($pinf['aid'])) { + $this->alliance->call('leave', $id, true); + $a = "'" . addslashes($pinf['alliance']) . "'"; + $q = $this->db->query("SELECT id FROM player WHERE alliance={$pinf['aid']} AND a_status='IN'"); + while ($r = dbFetchArray($q)) { + $msgs[$r[0]] = array($a, dbBool(0), 0, 0); + } + } + + // Marketplace + $q = $this->db->query("SELECT id,player,finalized,sold_to FROM sale WHERE player=$id OR sold_to=$id"); + while ($r = dbFetchArray($q)) { + list($sid,$seller,$fin,$buyer) = $r; + if (is_null($fin)) { + $ga = 'cancel'; + } else { + $ga = 'cancelTransfer'; + + if ($seller == $id) { + $tInc = 0; + $fInc = 1; + $t = $buyer; + } else { + $tInc = 1; + $fInc = 0; + $t = $seller; + } + + if (is_array($msgs[$t])) { + $msgs[$t][2] += $tInc; + $msgs[$t][3] += $fInc; + } else { + $msgs[$t] = array('NULL', dbBool(0), $tInc, $fInc); + } + } + $this->sales->call($ga, $seller, $sid); + } + + // FIXME: probes + $this->db->query("DELETE FROM fleet WHERE owner=$id"); + + // Remove planets and build queues + $q = $this->db->query( + "SELECT p.id, s.id FROM planet p, system s " + . "WHERE p.owner = $id AND s.id = p.system " + . "FOR UPDATE OF p, s" + ); + $systems = array(); + while ($r = dbFetchArray($q)) { + if (!in_array($r[1], $systems)) { + $systems[] = $r[1]; + } + $this->db->query("DELETE FROM buildqueue WHERE planet=".$r[0]); + $this->db->query("DELETE FROM planet_abandon_time WHERE id = {$r[0]}"); +// $this->db->query("DELETE FROM built_probes WHERE planet={$r[0]}"); + $this->planets->call('updateMaxPopulation', $r[0], $pid, null); + } + foreach ($systems as $systemID) { + $this->db->query("UPDATE system SET prot = 0 WHERE id = $systemID"); + } + $this->db->query("UPDATE planet SET owner=NULL,sale=NULL,bh_prep=NULL,abandon=NULL,beacon=0," + . "vacation='NO',built_probe=FALSE,probe_policy=NULL WHERE owner=$id"); + + // Remove research data + $this->db->query("UPDATE player SET res_assistance=NULL WHERE id=$id OR res_assistance=$id"); + $this->db->query("DELETE FROM research_player WHERE player=$id"); + // FIXME: doesn't work for some reason - investigate + //$this->db->query("DELETE FROM rule WHERE player=$id"); + + // Remove player from TA lists and enemy lists + $this->db->query("DELETE FROM trusted WHERE player=$id"); + $q = $this->db->query("SELECT player,level FROM trusted WHERE friend=$id"); + while ($r = dbFetchArray($q)) { + list($pl,$l) = $r; + + if (!is_array($msgs[$pl])) { + $msgs[$pl] = array('NULL', dbBool(1), 0, 0); + } else { + $msgs[$pl][1] = dbBool(1); + } + + $this->db->query("DELETE FROM trusted WHERE friend=$id AND player=$pl"); + $q2 = $this->db->query("SELECT level FROM trusted WHERE level > $l AND player=$pl " + . "ORDER BY level FOR UPDATE"); + while ($r2 = dbFetchArray($q2)) { + $this->db->query("UPDATE trusted SET level=level-1 WHERE player=$pl AND level={$r2[0]}"); + } + } + $this->db->query("DELETE FROM enemy_alliance WHERE player=$id"); + $this->db->query("DELETE FROM enemy_player WHERE player=$id"); + + // Remove player from the rankings + $types = array('general','financial','military','civ','round','idr'); + foreach ($types as $t) { + $rt = $this->rankings->call('getType', "p_$t"); + $this->rankings->call('delete', $rt, $name); + } + + // Inform other players + $tm = time(); + foreach ($msgs as $pid => $data) { + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($pid,$tm,'leave','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=$pid AND sent_on=$tm AND mtype='leave' ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_leave VALUES($mid,$id,'$reason'," . join(',', $data) . ")"); + } + + $this->db->query("UPDATE player SET quit=UNIX_TIMESTAMP(NOW()) - 86401 WHERE id=$id"); + } +} + +?> diff --git a/scripts/game/beta5/library/leaveVacation.inc b/scripts/game/beta5/library/leaveVacation.inc new file mode 100644 index 0000000..9602682 --- /dev/null +++ b/scripts/game/beta5/library/leaveVacation.inc @@ -0,0 +1,23 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Called when a player leaves vacation mode + function run($player) { + if ($this->lib->game->params['novacation'] == 1) { + return; + } + + $this->db->query("UPDATE planet SET vacation='NO' WHERE owner=$player"); + + // FIXME: Send messages + } +} + +?> diff --git a/scripts/game/beta5/library/listing.inc b/scripts/game/beta5/library/listing.inc new file mode 100644 index 0000000..79aaf74 --- /dev/null +++ b/scripts/game/beta5/library/listing.inc @@ -0,0 +1,118 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + // Generate a listing for the generic Listing JS component + function run($data, $conf, $param, $md5) { + // Extract parameters + $pArray = explode('#', $param); + $page = (int)array_shift($pArray); + $perPage = (int)array_shift($pArray); + $sField = array_shift($pArray); + $sDir = (array_shift($pArray) == 1); + $search = join('#', $pArray); + $pArray = explode('!', $search); + $srMode = (int)array_shift($pArray); + $srText = join('!', $pArray); + + // Apply search parameters + if (is_array($conf['searchModes']) && count($conf['searchModes']) + && $srText != "" && count($data)) { + if (is_null($conf['searchModes'][$srMode])) { + $srMode = 0; + } + $field = $conf['searchModes'][$srMode]; + + $nRes = array(); + foreach ($data as $e) { + if (stristr($e[$field], $srText) === false) { + continue; + } + array_push($nRes, $e); + } + $data = $nRes; + } + + // Sort it + if (is_array($conf['sortable']) && count($conf['sortable']) && count($data)) { + $sFields = array_keys($conf['sortable']); + if (!in_array($sField, $sFields)) { + $sField = $sFields[0]; + } + $sType = $conf['sortable'][$sField]; + + $sArr = array(); + foreach ($data as $i => $e) { + $v = $e[$sField]; + if (is_null($sArr[$v])) { + $sArr[$v] = array(); + } + array_push($sArr[$v], $i); + } + + $sVals = array_keys($sArr); + sort($sVals, $sType); + if ($sDir) + $sVals = array_reverse($sVals); + + $nRes = array(); + foreach ($sVals as $v) { + foreach ($sArr[$v] as $i) { + array_push($nRes, $data[$i]); + } + } + $data = $nRes; + } + + // Apply paging + if (is_array($conf['perPage']) && count($conf['perPage'])) { + if (!in_array($perPage, $conf['perPage'])) { + $perPage = $conf['perPage'][0]; + } + + $c = count($data); + $m = $c % $perPage; + $nPages = ($c - $m) / $perPage + ($m ? 1 : 0); + + if (count($data)) { + if ($page < 0) { + $page = 0; + } else if ($page >= $nPages) { + $page = $nPages - 1; + } + + $nRes = array(); + $fi = $page * $perPage; + for ($i=$fi;$i diff --git a/scripts/game/beta5/library/preJoin.inc b/scripts/game/beta5/library/preJoin.inc new file mode 100644 index 0000000..fb2187b --- /dev/null +++ b/scripts/game/beta5/library/preJoin.inc @@ -0,0 +1,45 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + public function run($account) { + // Get the planet name from the queue + $q = $this->db->query("SELECT p_name FROM planet_reg_queue WHERE account = $account"); + if (!($q && dbCount($q))) { + $this->db->end(false); + return false; + } + list($pName) = dbFetchArray($q); + + // Delete the registration entry + $this->db->query("DELETE FROM planet_reg_queue WHERE account = $account"); + + // Register + if ($this->lib->call('register', $account, $pName, null)) { + $this->db->end(false); + return false; + } + + return true; + } +} + + +?> diff --git a/scripts/game/beta5/library/preRegister.inc b/scripts/game/beta5/library/preRegister.inc new file mode 100644 index 0000000..ee3d835 --- /dev/null +++ b/scripts/game/beta5/library/preRegister.inc @@ -0,0 +1,31 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + public function run($account, $planet) { + $this->db->query( + "INSERT INTO planet_reg_queue (account, p_name) " + . "VALUES ($account, '" . addslashes($planet) . "')" + ); + } +} + + +?> diff --git a/scripts/game/beta5/library/register.inc b/scripts/game/beta5/library/register.inc new file mode 100644 index 0000000..2c63fa0 --- /dev/null +++ b/scripts/game/beta5/library/register.inc @@ -0,0 +1,167 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($uid, $planet, $nick) { + // Player mustn't be playing already + if ($this->lib->call('doesUserPlay', $uid)) { + return 1; + } + + // Extract the player's name + $q = $this->db->query("SELECT name FROM account WHERE id=$uid"); + list($pn) = dbFetchArray($q); + if (is_null($nick)) { + $nick = $pn; + } + $n = addslashes($nick); + + // Nickname must be unique + $q = $this->db->query("SELECT name FROM account WHERE (LOWER(name)=LOWER('$n') AND id <> $uid)"); + if (!$q || dbCount($q)) { + return 2; + } + $q = $this->db->query("SELECT name FROM player WHERE LOWER(name)=LOWER('$n')"); + if (!$q || dbCount($q) || ($nick == $pn && $this->lib->call('hasPlayed', $uid))) { + return 2; + } + + // Planet name must be unique + $planetLib = $this->lib->game->getLib('beta5/planet'); + if ($planetLib->call('nameExists', $planet)) { + return 3; + } + + // Create player record + $ni = ($nick == $pn) ? "null" : "'$n'"; + $initCash = $this->lib->game->params['initialcash']; + $cf = $initCash ? ",cash" : ""; + $cv = $initCash ? ",$initCash" : ""; + $pid = $this->db->query("INSERT INTO player(userid,name$cf) VALUES($uid,$ni$cv)"); + if (!$pid) { + return 4; + } + + // Initialize rules + $this->db->query("INSERT INTO rule (name,player,value) SELECT name,$pid,value FROM rule_def"); + + // Initialize rankings + $rLib = $this->lib->game->getLib('main/rankings'); + $types = array('general','civ','military','financial','idr'); + foreach ($types as $t) { + $rt = $rLib->call('getType', "p_$t"); + $rLib->call('append', $rt, $nick); + } + + // Handle locked alliances if required + if ((int) $this->lib->game->params['lockalliances'] > 1 && ! ($team = $this->lockedAlliances($pid))) { + return 4; + } + + if ((int) $this->lib->game->params['usemap'] == 0) { + // Assign planet random planets for matches and rounds + $pLib = $this->lib->game->getLib('beta5/player'); + $plid = $pLib->call('assign', $pid, $planet); + } else { + // Assign planet according to map specifications for CTF games + $ctfLib = $this->lib->game->getLib('beta5/ctf'); + $plid = $ctfLib->call('assign', $pid, $planet, $team); + + // Send welcoming message to the player + $ctfLib->call('message', $pid, 0, $team); + $ctfLib->call('joinMessage', $team, $pid); + } + if (!$plid) { + return 4; + } + $this->db->query("UPDATE player SET first_planet=$plid WHERE id=$pid"); + + // Initialize session + $_SESSION[game::sessName()] = array("player" => $pid); + + return 0; + } + + + private function lockedAlliances($player) { + + // Lock the table + $this->db->query("LOCK TABLE alliance IN ACCESS EXCLUSIVE MODE"); + + // Get the amount of alliances in the database + $q = $this->db->query("SELECT COUNT(*) FROM alliance"); + if (!($q && dbCount($q))) { + return 0; + } + list($nAlliances) = dbFetchArray($q); + + // Check if there are enough + $nWanted = (int) $this->lib->game->params['lockalliances']; + if ($nWanted > $nAlliances) { + // No, we need to create one + $rv = $this->createDefaultAlliance($player); + } else { + // Yes, have the player join the smallest alliance + $rv = $this->joinDefaultAlliance($player); + } + + return $rv; + } + + + private function createDefaultAlliance($player) { + + // Fetch default alliance records that haven't been used yet + $q = $this->db->query("SELECT tag,name FROM default_alliance WHERE tag NOT IN (" + . "SELECT tag FROM alliance)"); + if (!($q && dbCount($q))) { + return false; + } + $defaults = array(); + while ($r = dbFetchHash($q)) { + array_push($defaults, $r); + } + + // Choose a random alliance tag from the list + $toCreate = $defaults[rand(0, count($defaults) - 1)]; + + // Create the alliance + $aLib = $this->lib->game->getLib('beta5/alliance'); + $aID = $aLib->call('create', $toCreate['tag'], $toCreate['name'], $player); + if (is_null($aID)) { + return false; + } + + // Make the alliance a democracy + $aLib->call('setDemocratic', $aID, true); + + return $aID; + } + + + private function joinDefaultAlliance($player) { + + // Get the ID of the smallest alliance + $q = $this->db->query("SELECT alliance,COUNT(*) AS members FROM player " + . "WHERE alliance IS NOT NULL GROUP BY alliance " + . "ORDER BY members,alliance LIMIT 1"); + if (!($q && dbCount($q))) { + return false; + } + list($aID, $junk) = dbFetchArray($q); + + // Have the player join the alliance + $this->db->query("UPDATE player SET alliance = $aID, a_status = 'IN', a_vote = NULL, a_grade = NULL " + . "WHERE id = $player"); + return $aID; + } + +} + +?> diff --git a/scripts/game/beta5/library/startVacation.inc b/scripts/game/beta5/library/startVacation.inc new file mode 100644 index 0000000..00ade78 --- /dev/null +++ b/scripts/game/beta5/library/startVacation.inc @@ -0,0 +1,33 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Called when a player enters vacation mode + function run($player) { + if ($this->lib->game->params['novacation'] == 1) { + return; + } + + $q = $this->db->query("SELECT id FROM planet WHERE owner=$player"); + while ($r = dbFetchArray($q)) { + $qa = $this->db->query("SELECT COUNT(*) FROM fleet WHERE location={$r[0]} AND attacking"); + if (!$qa) { + $mode = 'YES'; + } else { + list($c) = dbFetchArray($qa); + $mode = $c ? 'PEND' : 'YES'; + } + $this->db->query("UPDATE planet SET vacation='$mode' WHERE id={$r[0]}"); + } + + // FIXME: Send messages + } +} + +?> diff --git a/scripts/game/beta5/library/updateRankings.inc b/scripts/game/beta5/library/updateRankings.inc new file mode 100644 index 0000000..16e79a8 --- /dev/null +++ b/scripts/game/beta5/library/updateRankings.inc @@ -0,0 +1,243 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + $this->fleets = $lib->game->getLib('beta5/fleet'); + $this->planets = $this->game->getLib('beta5/planet'); + $this->players = $this->game->getLib('beta5/player'); + $this->rankings = $lib->game->getLib('main/rankings'); + } + + + public function run($dryRun = false) { + // Compute and update player rankings + list($players, $playersById, $genRankings) = $this->computePlayerRankings($dryRun); + + // Round and alliance rankings + $this->computeRoundRankings($players, $dryRun); + $this->computeAllianceRankings($genRankings, $dryRun); + + return array($players, $playersById); + } + + + private function computePlayerRankings($dryRun) { + // Compute player rankings + $this->at = time(); + $q = $this->db->query("SELECT id FROM player " + . "WHERE (quit IS NULL OR UNIX_TIMESTAMP(NOW()) - quit < 86400) AND NOT hidden"); + $genR = $civR = $milR = $finR = array(); + $genRankings = array(); + $players = array(); + $playersById = array(); + while ($r = dbFetchArray($q)) { + + // Get player name + $pn = $this->players->call('getName',$r[0]); + $players[$pn] = array( + "player" => $r[0], + "tick_at" => $this->at, + ); + $playersById[$r[0]] = $pn; + + // Get planet data + $q2 = $this->db->query( + "SELECT SUM(pop), SUM(ifact), SUM(mfact), SUM(turrets), " + . "AVG(happiness), SUM(beacon), AVG(corruption) " + . "FROM planet WHERE owner={$r[0]}" + ); + $pld = dbFetchArray($q2); + + // Get fleet data + $q2 = $this->db->query( + "SELECT SUM(gaships), SUM(fighters), SUM(cruisers), SUM(bcruisers) " + . "FROM fleet WHERE owner={$r[0]}"); + $fld = dbFetchArray($q2); + $players[$pn]['gaships'] = (int) $fld[0]; + $players[$pn]['fighters'] = (int) $fld[1]; + $players[$pn]['cruisers'] = (int) $fld[2]; + $players[$pn]['bcruisers'] = (int) $fld[3]; + + // Financial ranking + $q2 = $this->db->query("SELECT cash FROM player WHERE id={$r[0]}"); + list($cash) = dbFetchArray($q2); + $players[$pn]['cash'] = $cash; + + $q2 = $this->db->query( + "SELECT pop, ifact, mfact, turrets, corruption, happiness " + . "FROM planet WHERE owner={$r[0]}" + ); + $income = 0; + while ($r2 = dbFetchArray($q2)) { + $ir = $this->planets->call('getIncome', $r[0], $r2[0], $r2[5], + $r2[1], $r2[2], $r2[3], $r2[4]); + $income += $ir[0]; + } + $upkeep = $this->fleets->call('getUpkeep', $r[0], $fld[0], $fld[1], $fld[2], $fld[3]); + $profit = max(0, $income[0] - $upkeep); + $fr = round($cash / 2000); + $fr += round($profit / 2) + round($income / 1.5); + $fr += round($pld[1] * 201); + if (!is_array($finR[$fr])) { + $finR[$fr] = array(); + } + array_push($finR[$fr], $pn); + $players[$pn]['f_rank'] = $fr; + + // Military ranking + $fpower = $this->fleets->call('getPower', $r[0], $fld[0], $fld[1], $fld[2], $fld[3]); + $players[$pn]['fleet'] = $fpower; + $mr = $pld[3] * 8 + $fpower * 7 + $pld[2] * 40 + $pld[5] * 100; + if (!is_array($milR[$mr])) { + $milR[$mr] = array(); + } + array_push($milR[$mr], $pn); + $players[$pn]['m_rank'] = $mr; + + // Civilian ranking + $q2 = $this->db->query("SELECT SUM(points) FROM research_player WHERE player={$r[0]}"); + list($rpoints) = dbFetchArray($q2); + $players[$pn]['tech_points'] = $rpoints; + $cr = round(pow(1 + $pld[4] / 100, 4) * $pld[0] * 3); + $cr -= round($cr * $pld[6] / 48000); + $cr += round(sqrt($rpoints*1000) * 3); + if (!is_array($civR[$cr])) { + $civR[$cr] = array(); + } + array_push($civR[$cr], $pn); + $players[$pn]['c_rank'] = $cr; + + // General ranking + $gr = $cr + $mr + $fr; + if (!is_array($genR[$gr])) { + $genR[$gr] = array(); + } + array_push($genR[$gr], $pn); + $genRankings[$r[0]] = $gr; + $players[$pn]['g_rank'] = $gr; + } + + // Update player rankings if this is not a dry run + if (! $dryRun) { + $rankings = array($finR, $milR, $civR, $genR); + $rTypes = array('p_financial', 'p_military', 'p_civ', 'p_general'); + for ($i = 0; $i < count($rTypes); $i ++) { + $rt = $this->rankings->call('getType', $rTypes[$i]); + $this->rankings->call('update', $rt, $rankings[$i]); + } + } + + return array($players, $playersById, $genRankings); + } + + + private function computeRoundRankings(&$players, $dryRun) { + // Get the general top 15 + $rt = $this->rankings->call('getType', 'p_general'); + $rr = $this->rankings->call('getAll', $rt, 15); + $top15 = array(); + foreach ($rr as $r) { + $top15[$r['id']] = array($r['ranking'], $r['points']); + } + + // Generate the new values + $rt = $this->rankings->call('getType', 'p_round'); + $o = $this->rankings->call('getAll', $rt); + $round = array(); + foreach ($o as $r) { + $round[$r['id']] = $r['points']; + } + foreach ($top15 as $n => $r) { + $round[$n] += round((16-$r[0]) * $r[1] / 24000); + } + + // Update the rankings + $rndR = array(); + foreach ($round as $n => $p) { + if (!is_array($rndR[$p])) { + $rndR[$p] = array(); + } + array_push($rndR[$p], $n); + $players[$n]['o_rank'] = $p; + } + if (! $dryRun) { + $this->rankings->call('update', $rt, $rndR); + } + } + + + private function computeAllianceRankings(&$genRankings, $dryRun) { + // Sum up the values + $q = $this->db->query( + "SELECT p.id, a.tag FROM player p,alliance a " + . "WHERE p.alliance=a.id AND p.a_status='IN' " + . "AND (p.quit IS NULL OR UNIX_TIMESTAMP(NOW()) - p.quit < 86400)" + ); + $alliances = array(); + while ($r = dbFetchArray($q)) { + if (is_null($alliances[$r[1]])) { + $alliances[$r[1]] = $genRankings[$r[0]]; + } else { + $alliances[$r[1]] += $genRankings[$r[0]]; + } + } + + // If there are victory conditions, add that to the alliances' score + if ($this->lib->game->params['victory'] == 1) { + $q = $this->db->query("SELECT id,tag FROM alliance"); + $aLib = $this->lib->game->getLib('beta5/alliance'); + while ($r = dbFetchArray($q)) { + $vVal = (100 + $aLib->call('updateVictory', $r[0])) / 100; + $alliances[$r[1]] = round($alliances[$r[1]] * $vVal); + } + } elseif ($this->lib->game->params['victory'] == 2) { + $q = $this->db->query("SELECT id,tag FROM alliance"); + $aLib = $this->lib->game->getLib('beta5/ctf'); + while ($r = dbFetchArray($q)) { + $vVal = (100 + $aLib->call('getTeamPoints', $r[0])) / 100; + $alliances[$r[1]] = round($alliances[$r[1]] * $vVal); + } + } + + // Update the rankings + $allR = array(); + foreach ($alliances as $n => $p) { + if (!is_array($allR[$p])) { + $allR[$p] = array(); + } + array_push($allR[$p], $n); + } + if (! $dryRun) { + $rt = $this->rankings->call('getType', 'a_general'); + $this->rankings->call('update', $rt, $allR); + } + } +} + + +?> diff --git a/scripts/game/beta5/map/library.inc b/scripts/game/beta5/map/library.inc new file mode 100644 index 0000000..28a7857 --- /dev/null +++ b/scripts/game/beta5/map/library.inc @@ -0,0 +1,111 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns statistics about the whole universe + function getUniverse() { + $q = $this->db->query("SELECT COUNT(*) FROM planet WHERE status=0"); + list($np) = dbFetchArray($q); + $q = $this->db->query("SELECT COUNT(*) FROM planet WHERE status=0 AND owner IS NULL"); + list($nnp) = dbFetchArray($q); + $q = $this->db->query("SELECT COUNT(*) FROM system WHERE nebula>0"); + list($nn) = dbFetchArray($q); + $q = $this->db->query("SELECT ROUND(AVG(mfact+ifact)),ROUND(AVG(turrets)) FROM planet WHERE status=0"); + list($af,$at) = dbFetchArray($q); + return array($np,$nnp,$nn,$af,$at); + } + + + // Returns statistics about a protection zone + function getProtectionZone($pz) { + $q = $this->db->query("SELECT COUNT(*) FROM planet p,system s WHERE p.status=0 AND s.id=p.system AND s.prot=$pz"); + list($np) = dbFetchArray($q); + $q = $this->db->query("SELECT COUNT(*) FROM planet p,system s WHERE p.status=0 AND p.owner IS NULL AND s.id=p.system AND s.prot=$pz"); + list($nnp) = dbFetchArray($q); + $q = $this->db->query("SELECT COUNT(*) FROM system WHERE nebula>0 AND prot=$pz"); + list($nn) = dbFetchArray($q); + $q = $this->db->query("SELECT ROUND(AVG(p.mfact+p.ifact)),ROUND(AVG(p.turrets)) FROM " + . "planet p,system s WHERE p.status=0 AND s.id=p.system AND s.prot=$pz"); + list($af,$at) = dbFetchArray($q); + + // Protection time left + $t = "FIXME"; // FIXME + + return array($np,$nnp,$nn,$af,$at,$t); + } + + + // Returns data about a system at specified coordinates + function at($x, $y) { + $q = $this->db->query("SELECT * FROM system WHERE x=$x AND y=$y"); + if (!($q && dbCount($q))) { + return null; + } + return dbFetchHash($q); + } + + + // Returns map data for planets in a specified system + function getSystem($sid) { + $q = $this->db->query( + "SELECT id,name,owner,status FROM planet WHERE system = $sid ORDER BY orbit" + ); + $pl = array(); + $o = array(); + while ($r = dbFetchHash($q)) { + array_push($pl, $r); + if ($r['owner'] != "" && !in_array($r['owner'], $o)) { + array_push($o,$r['owner']); + } + } + + if (!count($o)) { + return $pl; + } + + $q = $this->db->query( + "SELECT p.id,a.tag " + . "FROM player p,alliance a " + . "WHERE p.a_status = 'IN' AND p.alliance=a.id" + . " AND p.id IN (".join(',',$o).")" + ); + $o = array(); + while ($r = dbFetchArray($q)) { + $o[$r[0]] = $r[1]; + } + + for ($i=0;$idb->query( + "SELECT p.id FROM planet p,system s " + . "WHERE p.system=s.id AND SQRT((s.x-($x))*(s.x-($x))+(s.y-($y))*(s.y-($y)))<=$d" + ); + $rl = array(); + while ($r = dbFetchArray($q)) { + array_push($rl,$r[0]); + } + return $rl; + } +} + +?> diff --git a/scripts/game/beta5/moving/library.inc b/scripts/game/beta5/moving/library.inc new file mode 100644 index 0000000..53780f0 --- /dev/null +++ b/scripts/game/beta5/moving/library.inc @@ -0,0 +1,20 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } +} + +?> diff --git a/scripts/game/beta5/moving/library/cloneObject.inc b/scripts/game/beta5/moving/library/cloneObject.inc new file mode 100644 index 0000000..76b7dac --- /dev/null +++ b/scripts/game/beta5/moving/library/cloneObject.inc @@ -0,0 +1,60 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->standby = $this->lib->game->getLib('beta5/standby'); + } + + // Clone a moving object + function run($oid) { + // Get object record + $q = $this->db->query("SELECT * FROM moving_object WHERE id=$oid"); + if (!($q && dbCount($q) == 1)) { + return null; + } + $mo = dbFetchHash($q); + + // Duplicate stand-by order if any + if (!is_null($mo['wait_order'])) { + $q = $this->db->query("SELECT * FROM hs_wait WHERE id=".$mo['wait_order']); + $r = dbFetchHash($q); + $wo = $this->standby->call('create', $r['time_left'],$r['drop_point'],$r['origin'],$r['time_spent']); + } else { + $wo = 'NULL'; + } + + // Insert clone with a random value for time_left + $rnd = rand($mo['time_left'],999999); + $this->db->query("INSERT INTO moving_object(m_from,m_to,changed,time_left,hyperspace,wait_order) VALUES (" + . $mo['m_from'] . "," . $mo['m_to'] . "," . $mo['changed'] . ",$rnd," + .dbBool($mo['hyperspace'] == 't').",$wo)"); + + // Get new entry's ID + $q = $this->db->query("SELECT id FROM moving_object WHERE m_from=".$mo['m_from']." AND m_to=".$mo['m_to']." AND time_left=$rnd"); + list($moid) = dbFetchArray($q); + + // Reset time_left to its real value + $this->db->query("UPDATE moving_object SET time_left=".$mo['time_left']." WHERE id=$moid"); + + // Get trajectory data + $q = $this->db->query("SELECT * FROM waypoint WHERE move_id=$oid"); + $qsl = array(); + while ($r = dbFetchArray($q)) { + array_push($qsl, "($moid,{$r[1]},{$r[2]})"); + } + logText(count($qsl) . " waypoint(s) to be copied from object #$oid to object #$moid", LOG_DEBUG); + foreach ($qsl as $qs) { + if (!$this->db->query("INSERT INTO waypoint VALUES $qs")) { + $this->db->query("DELETE FROM moving_object WHERE id=$moid"); + return null; + } + } + + return $moid; + } +} + +?> diff --git a/scripts/game/beta5/moving/library/computeTrajectory.inc b/scripts/game/beta5/moving/library/computeTrajectory.inc new file mode 100644 index 0000000..8e6d0b8 --- /dev/null +++ b/scripts/game/beta5/moving/library/computeTrajectory.inc @@ -0,0 +1,183 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->planets = $this->lib->game->getLib('beta5/planet'); + } + + // Get a fleet's trajectory (using the trajectory cache) + function run($srcId, $dstId, $speed, $cruisers) { + $s = "$srcId:$dstId:$speed:".($cruisers?1:0); + if (is_null($this->trajCache[$s])) { + $this->trajCache[$s] = $this->computeTrajectory($srcId, $dstId, $speed, $cruisers); + } + return $this->trajCache[$s]; + } + + // Computes a fleet's trajectory + function computeTrajectory($srcId, $dstId, $speed, $cruisers) { + $src = $this->planets->call('byId', $srcId); + $dst = $this->planets->call('byId', $dstId); + + // Get planet list and base travel time + $pList = array(); + if ($src['x'] == $dst['x'] && $src['y'] == $dst['y']) { + // Planets are in the same system + $minId = min($srcId,$dstId); $maxId = max($srcId,$dstId); + logText("min/max id = $minId / $maxId"); + $q = $this->db->query("SELECT id,status,system FROM planet WHERE id>=$minId AND id<=$maxId ORDER BY id " + . ($srcId==$minId ? 'ASC' : 'DESC')); + $bTTime = 0; + while ($r = dbFetchArray($q)) { + array_push($pList, array($r[0], $r[1], $r[2])); + $bTTime += ($r[0] == $minId || $r[0] == $maxId) ? 6 : 12; + } + } else { + // Planets are in different systems + if ($src['x'] != $dst['x']) { + // Trajectory looks like y = ax + b + $slope = ($dst['y'] - $src['y']) / ($dst['x'] - $src['x']); + $displ = $src['y'] - $src['x'] * $slope; + $vert = false; + + $minX = min($dst['x'],$src['x']); $maxX = max($dst['x'],$src['x']); + $minY = min($dst['y'],$src['y']); $maxY = max($dst['y'],$src['y']); + $qs = "SELECT id,x,y FROM system WHERE x>=$minX AND x<=$maxX AND y>=$minY AND y<=$maxY AND "; + if ($slope > 0) + $qs .= "y+0.5>$slope*(x-0.5)+$displ AND y-0.5<$slope*(x+0.5)+$displ"; + else + $qs .= "y+0.5>$slope*(x+0.5)+$displ AND y-0.5<$slope*(x-0.5)+$displ"; + $qs .= " ORDER BY x " .($minX == $dst['x'] ? 'DESC' : 'ASC') . ", y " . ($src['y'] < $dst['y'] ? "ASC" : "DESC"); + } else { + // Trajectory looks like x = c + $vert = true; + $minY = min($dst['y'],$src['y']); $maxY = max($dst['y'],$src['y']); + $qs = "SELECT id,x,y FROM system WHERE y>=$minY AND y<=$maxY AND x=".$src['x']; + $qs .= " ORDER BY y " .($minY == $dst['y'] ? 'DESC' : 'ASC'); + } + + $dX = $src['x'] - $dst['x']; $dY = $src['y'] - $dst['y']; + $distance = sqrt($dX*$dX+$dY*$dY); + $bTTime = round($distance * (70 - $speed * 10) * ($cruisers ? 2 : 1)); + + // List orbits + $q = $this->db->query($qs); + while ($r = dbFetchArray($q)) { + $dir = "DESC"; + + // If we are in source or destination system, get all planets with orbits >= source/dest planet + if ($r[1] == $src['x'] && $r[2] == $src['y']) { + $dir = "ASC"; + $minOrbit = $src['orbit']; + } elseif ($r[1] == $dst['x'] && $r[2] == $dst['y']) { + $minOrbit = $dst['orbit']; + } elseif ($vert) { + // Vertical trajectories: 4 orbits out of 6 get intersected (1 / sqrt(2)) + $minOrbit = 2; + } else { + // If we are intersecting another system, 6 different cases: + // (1) passing through x = xs - 0.5 and y = ys - 0.5 + // (2) passing through x = xs - 0.5 and y = ys + 0.5 + // (3) passing through x = xs - 0.5 and x = xs + 0.5 + // (4) passing through x = xs + 0.5 and y = ys - 0.5 + // (5) passing through x = xs + 0.5 and y = ys + 0.5 + // (6) passing through y = ys - 0.5 and y = ys + 0.5 + $y1 = ($r[1] - 0.5) * $slope + $displ; + if ($y1 < $r[2] - 0.5 || $y1 > $r[2] + 0.5) { + // Case (4), (5) or (6) + $y1 = ($r[1] + 0.5) * $slope + $displ; + if ($y1 < $r[2] - 0.5 || $y1 > $r[2] + 0.5) { + // Case (6) + $y1 = $r[2] - 0.5; + $x1 = ($y1 - $displ) / $slope; + $y2 = $r[2] + 0.5; + $x2 = ($y2 - $displ) / $slope; + } else { + // Case (4) or (5) + $x1 = $r[1] + 0.5; + $x2 = ($r[2] - 0.5 - $displ) / $slope; + if ($x2 < $r[1] - 0.5 || $x2 > $r[1] + 0.5) { + // Case (5) + $y2 = $r[2] + 0.5; + $x2 = ($y2 - $displ) / $slope; + } else { + // Case (4) + $y2 = $r[2] - 0.5; + } + } + } else { + // Case (1), (2) or (3) + $x1 = $r[1] - 0.5; + $y2 = ($r[1] + 0.5) * $slope + $displ; + if ($y2 < $r[2] - 0.5 || $y2 > $r[2] + 0.5) { + // Case (1) or (2) + $x2 = ($r[2] - 0.5 - $displ) / $slope; + if ($x2 < $r[1] - 0.5 || $x2 > $r[1] + 0.5) { + // Case (2) + $y2 = $r[2] + 0.5; + $x2 = ($y2 - $displ) / $slope; + } else { + // Case (1) + $y2 = $r[2] + 0.5; + } + } else { + // Case (3) + $x2 = $r[1] + 0.5; + } + } + + $dX = $x1 - $x2; $dY = $y1 - $y2; $d = sqrt($dX*$dX+$dY*$dY); + $no = round(1 + 5 * $d / sqrt(2)); + $minOrbit = 6 - $no; + } + + // List orbits for this system + $q2 = $this->db->query("SELECT id,status FROM planet WHERE system=".$r[0]." AND orbit>=$minOrbit ORDER BY orbit $dir"); + while ($r2 = dbFetchArray($q2)) { + array_push($pList, array($r2[0], $r2[1], $r[0])); + } + } + } + + // Generate trajectory + $trajectory = array( + "hs" => ($src['x'] != $dst['x'] || $src['y'] != $dst['y']), + "list" => array() + ); + + $opDelay = array(0,26,26,38,56,80); + $tTime = $bTTime; + $nPlanets = count($pList); + $perPlanet = floor($bTTime / ($nPlanets - 1)); + $perEndpoint = ($bTTime - ($perPlanet * ($nPlanets - 2))) / 2; + + for ($i=0;$i<$nPlanets;$i++) { + $otime = $opDelay[$pList[$i][1]]; + + if ($i == 0) { + $trtime = floor($perEndpoint); + $otime /= 2; + } elseif ($i == $nPlanets - 1) { + $trtime = ceil($perEndpoint); + $otime /= 2; + } else { + $trtime = $perPlanet; + } + + $tTime += $otime; + array_push($trajectory['list'], array( + "pid" => $pList[$i][0], + "time" => $trtime + $otime + )); + } + $trajectory['time'] = $tTime; + + return $trajectory; + } +} + +?> diff --git a/scripts/game/beta5/moving/library/getLocation.inc b/scripts/game/beta5/moving/library/getLocation.inc new file mode 100644 index 0000000..3f43537 --- /dev/null +++ b/scripts/game/beta5/moving/library/getLocation.inc @@ -0,0 +1,26 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Get a moving object's current location + function run($oid) { + $q = $this->db->query( + "SELECT w.location FROM waypoint w,moving_object o " + . "WHERE o.id=$oid AND w.move_id=$oid AND w.until_eta<=o.time_left " + . "ORDER BY w.until_eta DESC LIMIT 1" + ); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($loc) = dbFetchArray($q); + return $loc; + } +} + +?> diff --git a/scripts/game/beta5/moving/library/getTrajectory.inc b/scripts/game/beta5/moving/library/getTrajectory.inc new file mode 100644 index 0000000..f4f4447 --- /dev/null +++ b/scripts/game/beta5/moving/library/getTrajectory.inc @@ -0,0 +1,30 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Get an object's current trajectory + function run($oid) { + $q = $this->db->query("SELECT w.location AS id,w.until_eta AS eta,s.x AS x,s.y AS y,p.orbit AS orbit,p.status AS opacity,p.name AS name " + . "FROM waypoint w,planet p,system s " + . "WHERE w.move_id=$oid AND p.id=w.location AND s.id=p.system " + . "ORDER BY w.until_eta DESC"); + if (!($q && dbCount($q))) { + return array(); + } + + $rv = array(); + while ($r = dbFetchHash($q)) { + array_push($rv, $r); + } + + return $rv; + } +} + +?> diff --git a/scripts/game/beta5/moving/library/newObject.inc b/scripts/game/beta5/moving/library/newObject.inc new file mode 100644 index 0000000..f869cbd --- /dev/null +++ b/scripts/game/beta5/moving/library/newObject.inc @@ -0,0 +1,53 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Generate a new moving object entry + function run($srcId, $dstId, $speed, $cruisers, $wait) { + // Compute trajectory + $t = $this->lib->call('computeTrajectory', $srcId, $dstId, $speed, $cruisers); + + // Insert object entry + $qs = "INSERT INTO moving_object(m_from,m_to,changed,time_left,hyperspace"; + if (!is_null($wait)) { + $qs .= ",wait_order"; + } + $isHS = ($t['hs'] || !is_null($wait)) ? 1 : 0; + $qs .= ") VALUES ($srcId,$dstId,0,".$t['time']."," . dbBool($isHS); + if (!is_null($wait)) { + $qs .= ",$wait"; + } + $qs .= ")"; + $mid = $this->db->query($qs); + if (!$mid) { + logText("New object: mid was null :s", LOG_WARNING); + return null; + } + + // Create trajectory data + $qsl = array(); $sum = 0; + $tl = array_reverse($t['list']); + foreach ($tl as $w) { + array_push($qsl, "($mid,".$w['pid'].",$sum)"); + $sum += $w['time']; + } + logText(count($qsl) . " waypoint(s) to be added for new object #$mid", LOG_DEBUG); + + // Insert trajectory data + foreach ($qsl as $qs) { + if (!$this->db->query("INSERT INTO waypoint VALUES $qs")) { + $this->db->query("DELETE FROM moving_object WHERE id=$mid"); + return null; + } + } + return $mid; + } +} + +?> diff --git a/scripts/game/beta5/moving/library/redirect.inc b/scripts/game/beta5/moving/library/redirect.inc new file mode 100644 index 0000000..4b335d3 --- /dev/null +++ b/scripts/game/beta5/moving/library/redirect.inc @@ -0,0 +1,49 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Redirects a moving object to a new destination + function run($oid, $newDest, $speed, $cruisers, $newWait) { + $loc = $this->lib->call('getLocation', $oid); + if (is_null($loc)) { + return false; + } elseif ($loc == $newDest) { + return $this->lib->call('stop', $oid, $newWait); + } + + $t = $this->lib->call('computeTrajectory', $loc, $newDest, $speed, $cruisers); + + $this->db->query("UPDATE moving_object SET m_to=$newDest,changed=changed+10," + . "hyperspace=" . dbBool($t['hs'] || !is_null($newWait)) + . ",wait_order=".(is_null($newWait)?"NULL":$newWait) + . ",time_left=".$t['time']." WHERE id=$oid"); + logText("Deleting waypoints for moving object #$oid", LOG_DEBUG); + $this->db->query("DELETE FROM waypoint WHERE move_id=$oid"); + + // Create trajectory data + $qsl = array(); $sum = 0; + $tl = array_reverse($t['list']); + foreach ($tl as $w) { + array_push($qsl, "($oid,".$w['pid'].",$sum)"); + $sum += $w['time']; + } + logText(count($qsl) . " new waypoint(s) reinserted for moving object #$oid", LOG_DEBUG); + + // Insert trajectory data + foreach ($qsl as $qs) { + if (!$this->db->query("INSERT INTO waypoint VALUES $qs")) { + return false; + } + } + + return true; + } +} + +?> diff --git a/scripts/game/beta5/moving/library/stop.inc b/scripts/game/beta5/moving/library/stop.inc new file mode 100644 index 0000000..3afe053 --- /dev/null +++ b/scripts/game/beta5/moving/library/stop.inc @@ -0,0 +1,42 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + // Stop a moving object + function run($oid, $newWait) { + $q = $this->db->query( + "SELECT w.location,w.until_eta,o.time_left " + . "FROM waypoint w,moving_object o " + . "WHERE o.id=$oid AND w.move_id=$oid AND w.until_eta<=o.time_left " + . "ORDER BY w.until_eta DESC LIMIT 1" + ); + if (!($q && dbCount($q) == 1)) { + return null; + } + + list($loc,$ue,$tl) = dbFetchArray($q); + if ($ue == 0) { + $this->db->query("UPDATE moving_object SET wait_order=" + . (is_null($newWait)?"NULL":$newWait) + . " WHERE id=$oid"); + return false; + } + + $newDelay = 1 + $tl - $ue; + $this->db->query("UPDATE moving_object SET m_to=$loc,changed=changed+10," + . "wait_order=".(is_null($newWait)?"NULL":$newWait)."," + . "time_left=$newDelay WHERE id=$oid"); + logText("Deleting waypoints for moving object #$oid", LOG_DEBUG); + $this->db->query("DELETE FROM waypoint WHERE move_id=$oid"); + logText("Inserting single waypoint moving object #$oid", LOG_DEBUG); + $this->db->query("INSERT INTO waypoint VALUES($oid,$loc,0)"); + return true; + } +} + +?> diff --git a/scripts/game/beta5/msg/library.inc b/scripts/game/beta5/msg/library.inc new file mode 100644 index 0000000..420d7d4 --- /dev/null +++ b/scripts/game/beta5/msg/library.inc @@ -0,0 +1,119 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + // Lists custom folders for a player + function getCustomFolders($pid) { + $q = $this->db->query("SELECT id,name FROM custom_folder WHERE player = $pid ORDER BY name"); + $a = array(); + while ($r = dbFetchArray($q)) { + $a[$r[0]] = $r[1]; + } + return $a; + } + + // Creates a custom folder + function createFolder($pid, $name) { + $this->db->query("INSERT INTO custom_folder(name,player) VALUES('".addslashes($name)."',$pid)"); + } + + // Renames a custom folder + function renameFolder($fid, $name) { + $this->db->query("UPDATE custom_folder SET name = '".addslashes($name)."' WHERE id=$fid"); + } + + // Flushes messages from a folder + function flushFolder($pid, $ft, $fid) { + $qs = "UPDATE message SET deleted=TRUE WHERE player=$pid AND ftype='$ft'"; + if ($ft == 'CUS') { + $qs .= " AND fcustom=$fid"; + } + $this->db->query($qs); + } + + // Deletes a custom folder + function deleteFolder($pid, $fid) { + $this->db->query("UPDATE message SET deleted=TRUE WHERE player=$pid AND ftype='CUS' AND fcustom=$fid"); + $this->db->query("DELETE FROM custom_folder WHERE player=$pid AND id=$fid"); + } + + // Get count of all messages in a player's folder + function getAll($pid, $ft, $cfid = null) { + $qs = "SELECT COUNT(*) FROM message WHERE player=$pid AND ftype='$ft' AND NOT deleted"; + if (!is_null($cfid)) { + $qs .= " AND fcustom = $cfid"; + } + $q = $this->db->query($qs); + if (!($q && count($q) == 1)) { + return 0; + } + list($r) = dbFetchArray($q); + return $r; + } + + // Get count of new messages in a player's folder + function getNew($pid, $ft = null, $cfid = null) { + $qs = "SELECT COUNT(*) FROM message WHERE player=$pid AND is_new AND NOT deleted"; + if (!is_null($ft)) { + $qs .= " AND ftype='$ft'"; + if (!is_null($cfid)) { + $qs .= " AND fcustom = $cfid"; + } + } + $q = $this->db->query($qs); + if (!($q && count($q) == 1)) { + return 0; + } + list($r) = dbFetchArray($q); + return $r; + } + + // Sets a message status to "read" + function setRead($mId) { + $this->db->query("UPDATE message SET is_new = FALSE WHERE id = $mId"); + } + + // Sets a message as deleted + function delete($mId, $pId) { + $this->db->query("UPDATE message SET deleted = TRUE WHERE id = $mId AND player = $pId"); + } + + // Moves a message + function move($mId, $pId, $tTp, $tId) { + $qs = "ftype='$tTp',fcustom=" . ($tTp == "CUS" ? $tId : "NULL"); + $this->db->query("UPDATE message SET $qs WHERE id = $mId AND player = $pId"); + } + + + // Loads message formatting scripts + function loadFormat($type, $lang, $player) { + if (!is_null($this->formats[$type])) { + return $this->formats[$type]; + } + + $path = config::$main['scriptdir'] . "/game/beta5/msgformat/$lang/$type.inc"; + if (!(file_exists($path) && is_readable($path))) { + return null; + } + require_once($path); + eval('$this->formats[$type] = new msgformat_'.$type.'($this->lib->game);'); + $this->formats[$type]->game = $this->game; + $this->formats[$type]->player = $player; + return $this->formats[$type]; + } +} + +?> diff --git a/scripts/game/beta5/msg/library/get.inc b/scripts/game/beta5/msg/library/get.inc new file mode 100644 index 0000000..4b460a7 --- /dev/null +++ b/scripts/game/beta5/msg/library/get.inc @@ -0,0 +1,63 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns complete information about a message + function run($mId, $pid) { + $q = $this->db->query("SELECT * FROM message WHERE id=$mId AND NOT deleted"); + if (!($q && dbCount($q) == 1)) { + return null; + } + + $a1 = dbFetchHash($q); + $a = array( + "id" => $a1['id'], + "player" => $a1['player'], + "ftype" => trim($a1['ftype']), + "cfid" => $a1['fcustom'], + "replyto" => $al['reply_to'], + "received" => $a1['sent_on'] + ); + $new = ($a1['is_new'] == 't'); + if (!$new) { + $q2 = $this->db->query("SELECT COUNT(*) <> 0 FROM message WHERE reply_to=$mId AND player=$pid"); + list($replied) = dbFetchArray($q2); + } + + $q2 = $this->db->query("SELECT * FROM msg_".$a1['mtype']." WHERE id=$mId"); + if (!($q2 && dbCount($q2))) { + return $a; + } + + $lg = getLanguage(); + $f = $this->lib->mainClass->loadFormat($a1['mtype'], $lg, $pid); + if (is_null($f)) { + return $a; + } + + if (dbCount($q2) == 1) { + $f->data = dbFetchHash($q2); + } else { + $f->data = array(); + while ($r2 = dbFetchHash($q2)) + array_push($f->data, $r2); + } + $a["from"] = $f->getSender(); + $a["to"] = $f->getRecipient(); + $a["subject"] = $f->getSubject(); + $a["slink"] = $f->getSLink(); + $a["rlink"] = $f->getRLink(); + $a["replink"] = $f->getReplyLink(); + $a["text"] = $f->getContents(); + $a["status"] = $new ? "N" : ($replied ? "R" : "r"); + return $a; + } +} + +?> diff --git a/scripts/game/beta5/msg/library/getHeaders.inc b/scripts/game/beta5/msg/library/getHeaders.inc new file mode 100644 index 0000000..7943cf4 --- /dev/null +++ b/scripts/game/beta5/msg/library/getHeaders.inc @@ -0,0 +1,64 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Get headers for all messages inside a folder + function run($pid, $fld, $cfid) { + $lg = getLanguage(); + $qs = "player=$pid AND NOT deleted AND ftype='$fld'"; + if ($fld == "CUS") { + $qs .= " AND fcustom=$cfid"; + } + $q = $this->db->query("SELECT id,sent_on,mtype,is_new,reply_to FROM message WHERE $qs"); + + $a = array(); + while ($r = dbFetchArray($q)) { + list($id,$mom,$type,$new,$rt) = $r; + $new = ($new == 't'); + + if (!$new) { + $q2 = $this->db->query("SELECT COUNT(*) FROM message WHERE reply_to=$id AND player=$pid"); + list($replied) = dbFetchArray($q2); + } + + $q2 = $this->db->query("SELECT * FROM msg_$type WHERE id=$id"); + if (!($q2 || dbCount($q2))) { + continue; + } + + $f = $this->lib->mainClass->loadFormat($type, $lg, $pid); + if (is_null($f)) { + continue; + } + + if (dbCount($q2) == 1) { + $f->data = dbFetchHash($q2); + } else { + $f->data = array(); + while ($r2 = dbFetchHash($q2)) + array_push($f->data, $r2); + } + $a[$id] = array( + "received" => $mom, + "from" => $f->getSender(), + "to" => $f->getRecipient(), + "subject" => $f->getSubject(), + "slink" => $f->getSLink(), + "rlink" => $f->getRLink(), + "replink" => $f->getReplyLink(), + "replyTo" => $rt, + "status" => $new ? "N" : ($replied ? "R" : "r"), + ); + } + + return $a; + } +} + +?> diff --git a/scripts/game/beta5/msg/library/send.inc b/scripts/game/beta5/msg/library/send.inc new file mode 100644 index 0000000..0ef84de --- /dev/null +++ b/scripts/game/beta5/msg/library/send.inc @@ -0,0 +1,51 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Sends a message + function run($player, $mType, $mData, $folder = 'INT', $replyTo = null, $isNew = true) { + $moment = time(); + + // Insert the message header + $qs = "INSERT INTO message(player,sent_on,mtype,ftype,is_new" . (is_null($replyTo) ? "" : ",reply_to") + . ") VALUES ($player,$moment,'$mType','$folder'," . ($isNew ? 'TRUE' : 'FALSE') . (is_null($replyTo) ? "" : ",$replyTo") . ")"; + $id = $this->db->query($qs); + if (!$id) { + logText("beta5/msg/send: failed to send message to player $player", LOG_WARNING); + return false; + } + + if (is_array($mData[0])) { + // It's an array of arrays, insert multiple entries + foreach ($mData as $msg) { + $this->insertData($mType, $id, $msg); + } + } else { + $this->insertData($mType, $id, $mData); + } + + return $id; + } + + function insertData($mType, $id, $mData) { + $fields = array_keys($mData); + $qs = "INSERT INTO msg_$mType (id," . join(',', $fields) . ") VALUES ($id"; + foreach ($fields as $f) { + if (is_null($mData[$f])) { + $qs .= ",NULL"; + } else { + $qs .= ",'" . addslashes($mData[$f]) . "'"; + } + } + $qs .= ")"; + $this->db->query($qs); + } +} + +?> diff --git a/scripts/game/beta5/msg/library/sendInAlliance.inc b/scripts/game/beta5/msg/library/sendInAlliance.inc new file mode 100644 index 0000000..7f2ada6 --- /dev/null +++ b/scripts/game/beta5/msg/library/sendInAlliance.inc @@ -0,0 +1,63 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->players = $this->lib->game->getLib('beta5/player'); + } + + + // Sends a message from a player to his own alliance + function run($src, $dst, $sub, $msg, $rep = null) { + $moment = time(); + $pinf = $this->players->call('get', $src); + if ($pinf['aid'] != $dst) { + return false; + } + $tag = addslashes($pinf['alliance']); + + if (!is_null($rep)) { + $q = $this->db->query("SELECT original FROM message WHERE player=$src AND id=$rep"); + if ($q && dbCount($q)) { + list($oriMsgId) = dbFetchArray($q); + } else { + $rep = null; + } + } + + $qs = "INSERT INTO message(player,sent_on,mtype,ftype,is_new"; + $qs .= (!is_null($rep)?",reply_to":"") . ") VALUES ("; + $qs .= "$src,$moment,'alliance','OUT',FALSE" . (!is_null($rep)?",$rep":"") . ")"; + $this->db->query($qs); + $q = $this->db->query("SELECT id FROM message WHERE player=$src AND sent_on=$moment AND ftype='OUT'"); + list($id) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_alliance VALUES($id,$src,$dst,'$tag','".addslashes($sub)."','".addslashes($msg)."')"); + + $pl = $this->db->query("SELECT id FROM player WHERE alliance=$dst AND a_status='IN' AND id <> $src"); + while ($r = dbFetchArray($pl)) { + list($pid) = $r; + + if (!(is_null($rep) || is_null($oriMsgId))) { + $q = $this->db->query("SELECT id FROM message WHERE id=$oriMsgId OR original=$oriMsgId"); + if ($q && dbCount($q)) { + list($plMsgId) = dbFetchArray($q); + } else { + $plMsgId = $oriMsgId; + } + } + + $qs = "INSERT INTO message(player,sent_on,mtype,ftype,original"; + $qs .= (!is_null($rep)?",reply_to":"") . ") VALUES ("; + $qs .= "$pid,$moment,'alliance','IN',$id".(!is_null($rep)?",$plMsgId":"").")"; + $this->db->query($qs); + + $q = $this->db->query("SELECT id FROM message WHERE player=$pid AND sent_on=$moment AND ftype='IN'"); + list($id2) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_alliance VALUES($id2,$src,$dst,'$tag','".addslashes($sub)."','".addslashes($msg)."')"); + } + } +} + +?> diff --git a/scripts/game/beta5/msg/library/sendToPlanet.inc b/scripts/game/beta5/msg/library/sendToPlanet.inc new file mode 100644 index 0000000..56ffe69 --- /dev/null +++ b/scripts/game/beta5/msg/library/sendToPlanet.inc @@ -0,0 +1,51 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->planets = $this->lib->game->getLib('beta5/planet'); + } + + + // Sends a message from player to planet + function run($src, $dst, $sub, $msg, $rep = null) { + $moment = time(); + $pinf = $this->planets->call('byId', $dst); + if (!is_null($rep)) { + $q = $this->db->query("SELECT original FROM message WHERE player=$src AND id=$rep"); + if ($q && dbCount($q)) { + list($oriMsgId) = dbFetchArray($q); + } else { + $rep = null; + } + } + + $qs = "INSERT INTO message(player,sent_on,mtype,ftype,is_new"; + $qs .= (!is_null($rep)?",reply_to":"") . ") VALUES ("; + $qs .= "$src,$moment,'planet','OUT',FALSE" . (!is_null($rep)?",$rep":"") . ")"; + $this->db->query($qs); + $q = $this->db->query("SELECT id FROM message WHERE player=$src AND sent_on=$moment AND ftype='OUT'"); + list($id) = dbFetchArray($q); + $this->db->query( + "INSERT INTO msg_planet VALUES($id,$dst,'".addslashes($pinf['name'])."',$src," + . "'".addslashes($sub)."','".addslashes($msg)."')" + ); + + if (!is_null($pinf['owner'])) { + $qs = "INSERT INTO message(player,sent_on,mtype,ftype,original"; + $qs .= (!is_null($rep)?",reply_to":"") . ") VALUES ("; + $qs .= $pinf['owner'].",$moment,'planet','IN',$id".(!is_null($rep)?",$oriMsgId":"").")"; + $this->db->query($qs); + $q = $this->db->query("SELECT id FROM message WHERE player=".$pinf['owner']." AND sent_on=$moment AND ftype='IN'"); + list($id2) = dbFetchArray($q); + $this->db->query( + "INSERT INTO msg_planet VALUES($id2,$dst,'".addslashes($pinf['name'])."',$src," + . "'".addslashes($sub)."','".addslashes($msg)."')" + ); + } + } +} + +?> diff --git a/scripts/game/beta5/msg/library/sendToPlayer.inc b/scripts/game/beta5/msg/library/sendToPlayer.inc new file mode 100644 index 0000000..6b1b821 --- /dev/null +++ b/scripts/game/beta5/msg/library/sendToPlayer.inc @@ -0,0 +1,36 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Sends a message from player to player + function run($src, $dst, $sub, $msg, $rep = null) { + $moment = time(); + if (is_null($rep)) { + $oriMsgId = null; + } else { + $q = $this->db->query("SELECT original FROM message WHERE player=$src AND id=$rep"); + if ($q && dbCount($q)) { + list($oriMsgId) = dbFetchArray($q); + } else { + $oriMsgId = $rep = null; + } + } + + $message = array( + "sender" => $src, + "recipient" => $dst, + "subject" => $sub, + "message" => $msg + ); + $this->lib->call('send', $src, 'std', $message, 'OUT', $rep, false); + $this->lib->call('send', $dst, 'std', $message, 'IN', $oriMsgId); + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/abandon.inc b/scripts/game/beta5/msgformat/en/abandon.inc new file mode 100644 index 0000000..47197b8 --- /dev/null +++ b/scripts/game/beta5/msgformat/en/abandon.inc @@ -0,0 +1,69 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + return 'Military Advisor'; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + $name = $this->players->call('getName', $this->player); + return utf8entities($name); + } + + function getRLink() { + return ""; + } + + function getSubject() { + if (is_array($this->data[0])) { + $c = count($this->data); + } else { + $c = 1; + } + return "$c planet" . ($c>1?"s":"") . " abandoned"; + } + + function getReplyLink() { + return ""; + } + + function getContents() { + if (is_array($this->data[0])) { + $data = $this->data; + } else { + $data = array($this->data); + } + $c = count($data); + + $str = "Sir! Our forces have completed the evacuation of planet" . ($c>1?"s":"") . " "; + + for ($i=0;$i<$c;$i++) { + if ($i > 0) { + if ($i == $c - 1) { + $str .= ' and '; + } else { + $str .= ', '; + } + } + $str .= "" + . utf8entities($data[$i]['p_name']) . ""; + } + + $str .= ". The citizens of th" . ($c>1?"ese":"is") . " world" . ($c>1?"s":"") + . " are left to fend for themselves."; + + return $str; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/admin.inc b/scripts/game/beta5/msgformat/en/admin.inc new file mode 100644 index 0000000..ff66d71 --- /dev/null +++ b/scripts/game/beta5/msgformat/en/admin.inc @@ -0,0 +1,47 @@ +game = $game; + $this->db = $game->db; + $this->players = $game->getLib('beta5/player'); + $this->forums = $game->getLib('main/forums'); + } + + public function getRecipient() { + return utf8entities($this->players->call('getName', $this->player),ENT_COMPAT); + } + public function getSender() { return 'LegacyWorlds Administrators'; } + + public function getSLink() { return ""; } + public function getRLink() { return ""; } + public function getReplyLink() { return ""; } + + public function getSubject() { + $c = $this->readContents(); + return $c['subject']; + } + + public function getContents() { + $c = $this->readContents(); + return $this->forums->call('substitute', $c['contents'], 't', 'f'); + } + + private function readContents() { + if (is_null($this->msgContents[$this->data['spam']])) { + $q = $this->db->query("SELECT * FROM admin_spam WHERE id = {$this->data['spam']}"); + if (!($q && dbCount($q))) { + return array(); + } + $this->msgContents[$this->data['spam']] = dbFetchHash($q); + } + + return $this->msgContents[$this->data['spam']]; + } +} + +?> + diff --git a/scripts/game/beta5/msgformat/en/alint.inc b/scripts/game/beta5/msgformat/en/alint.inc new file mode 100644 index 0000000..ed12466 --- /dev/null +++ b/scripts/game/beta5/msgformat/en/alint.inc @@ -0,0 +1,146 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getPlayer() { + if (is_null($this->data['player'])) + return null; + return $this->players->call('get', $this->data['player']); + } + + function getSender() { + return "Alliance [".utf8entities($this->data['tag']) . "]"; + } + + function getSLink() { + if (is_null($this->data['alliance'])) { + return ""; + } + return "2,".$this->data['alliance']; + } + + function getRecipient() { + $name = $this->players->call('getName', $this->player); + return utf8entities($name); + } + + function getRLink() { + return ""; + } + + function getSubject() { + $p = $this->getPlayer(); + switch ($this->data['msg_type']) : + case 0: $s = "A player has requested to join the alliance!"; break; + case 1: $s = "A player has cancelled his request to join!"; break; + case 2: $s = "You have been removed as " . utf8entities($p['name']) ."'s successor"; break; + case 3: $s = "You have been made " . utf8entities($p['name']) . "'s successor"; break; + case 4: $s = "[".utf8entities($this->data['tag'])."] has become a democracy"; break; + case 5: $s = "[".utf8entities($this->data['tag'])."] has become a dictature"; break; + case 6: $s = "You have stepped down from the head of [".utf8entities($this->data['tag'])."]"; break; + case 7: $s = "You are the new leader of [".utf8entities($this->data['tag'])."]"; break; + case 8: $s = "Leadership change in [".utf8entities($this->data['tag'])."]"; break; + case 9: $s = "A player has left the alliance"; break; + case 10: $s = "Request granted!"; break; + case 11: $s = "A player has been accepted into the alliance"; break; + case 12: $s = "Request rejected!"; break; + case 13: $s = "A request to join has been denied"; break; + case 14: $s = "Leadership change in [".utf8entities($this->data['tag'])."]"; break; + case 15: $s = "[".utf8entities($this->data['tag'])."] no longer has a leader!"; break; + case 16: $s = "New candidate for presidency"; break; + case 17: $s = "A candidate is no longer running for the alliance's presidency"; break; + case 18: $s = "You have taken the alliance's presidency"; break; + case 19: $s = "Leadership change in [".utf8entities($this->data['tag'])."]"; break; + case 20: $s = "New technology trading orders issued"; break; + endswitch; + return $s; + } + + function getReplyLink() { + return ""; + } + + function getContents() { + $p = $this->getPlayer(); + switch ($this->data['msg_type']) : + case 0: + $s = "Player ".utf8entities($p['name'])." has sent a request to join the [".utf8entities($this->data['tag'])."] alliance."; + // FIXME: link to admission page + break; + case 1: + $s = "Player ".utf8entities($p['name'])." has cancelled his request to join the [".utf8entities($this->data['tag'])."] alliance."; + break; + case 2: + $s = "You are no longer ".utf8entities($p['name'])."'s successor at the head of [".utf8entities($this->data['tag'])."]."; + break; + case 3: + $s = "You have been designated by ".utf8entities($p['name'])." to be his successor at the head of [".utf8entities($this->data['tag'])."]. "; + $s .= "If ".utf8entities($p['name'])." leaves the alliance, you will automatically be in charge."; + break; + case 4: + $s = "By orders of ".utf8entities($p['name']).", the [".utf8entities($this->data['tag'])."] alliance has become a democracy."; + break; + case 5: + $s = "By orders of ".utf8entities($p['name']).", the [".utf8entities($this->data['tag'])."] alliance has become a dictature."; + break; + case 6: + $s = "You have stepped down from the head of the [".utf8entities($this->data['tag'])."] alliance.
    ".utf8entities($p['name']).","; + $s .= "your successor, has assumed control of the alliance."; + break; + case 7: + $s = "".utf8entities($p['name']).", who was the leader of the [".utf8entities($this->data['tag'])."] alliance, has stepped down from "; + $s .= "power.
    As his successor, you are the new leader of the alliance."; + break; + case 8: + $s = "The leader of the [".utf8entities($this->data['tag'])."] alliance has stepped down from power.
    "; + $s .= "".utf8entities($p['name']).", his successor, is the new leader of the alliance."; + break; + case 9: + $s = "".utf8entities($p['name'])." has left the [".utf8entities($this->data['tag'])."] alliance."; + break; + case 10: + $s = "You have been accepted into the [".utf8entities($this->data['tag'])."] alliance."; + break; + case 11: + $s = "Player ".utf8entities($p['name'])." has been accepted into the [".utf8entities($this->data['tag'])."] alliance."; + break; + case 12: + $s = "Your request to join the [".utf8entities($this->data['tag'])."] alliance has been rejected."; + break; + case 13: + $s = "The request from player ".utf8entities($p['name'])." to join the [".utf8entities($this->data['tag'])."] alliance has been rejected."; + break; + case 14: + $s = "[".utf8entities($this->data['tag'])."]'s leader has left the alliance. His successor, ".utf8entities($p['name']).","; + $s .= " has taken control of the alliance."; + break; + case 15: + $s = "[".utf8entities($this->data['tag'])."]'s leader, ".utf8entities($p['name']).", has left the alliance. Since "; + $s .= "[".utf8entities($this->data['tag'])."] no longer has a leader, all members can now vote to elect his successor."; + break; + case 16: + $s = "".utf8entities($p['name'])." is now running for [".utf8entities($this->data['tag'])."]'s presidency."; + break; + case 17: + $s = "".utf8entities($p['name'])." has cancelled his candidacy to [".utf8entities($this->data['tag'])."]'s presidency."; + break; + case 18: + $s = "You have taken presidency of the [".utf8entities($this->data['tag'])."] alliance."; + break; + case 19: + $s = "".utf8entities($p['name'])." has taken presidency of the [".utf8entities($this->data['tag'])."] alliance."; + break; + case 20: + $s = "New technology trading orders have been issued!
    You should check the alliance's technology trading page to find out how they affect you."; + break; + endswitch; + return $s; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/alliance.inc b/scripts/game/beta5/msgformat/en/alliance.inc new file mode 100644 index 0000000..30a0df5 --- /dev/null +++ b/scripts/game/beta5/msgformat/en/alliance.inc @@ -0,0 +1,51 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + return utf8entities($this->players->call('getName', $this->data['sender'])); + } + + function getSLink() { + if ($this->data['sender'] == $this->player) { + return ""; + } + $pinf = $this->players->call('get', $this->data['sender']); + if (is_null($pinf)) { + return ""; + } + return "0,".$this->data['sender']; + } + + function getRecipient() { + return "Alliance [".utf8entities($this->data['tag']) . "]"; + } + + function getRLink() { + if (is_null($this->data['alliance'])) + return ""; + return "2,".$this->data['alliance']; + } + + function getSubject() { + return utf8entities($this->data['subject']); + } + + function getReplyLink() { + if ($this->data['sender'] == $this->player || is_null($this->data['alliance'])) { + return ""; + } + return "2,".$this->data['alliance'].",".$this->data['id']; + } + + function getContents() { + return preg_replace('/\n/', '
    ', utf8entities($this->data['message'])); + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/battle.inc b/scripts/game/beta5/msgformat/en/battle.inc new file mode 100644 index 0000000..182db49 --- /dev/null +++ b/scripts/game/beta5/msgformat/en/battle.inc @@ -0,0 +1,139 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + return 'Military Advisor'; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + $pinf = $this->players->call('getName', $this->player); + return utf8entities($pinf); + } + + function getRLink() { + return ""; + } + + function getSubject() { + return "Battle report for " . utf8entities($this->data['planet']); + } + + function getReplyLink() { + return ""; + } + + function getContents() { + $vacation = ($this->data['o_power'] == 0); + + $str = 'Sir! ' . ($vacation ? "Forces " : "Our forces ") + . 'have been fighting in orbit around ' + . utf8entities($this->data['planet']) . ". " + . ($this->data['heroic_def'] == -1 ? 'The defending forces, overwhelmed, resisted to the last man. Their heroic actions have caused us unexpected losses. ' : '') + . ($this->data['heroic_def'] == 1 ? 'While our forces were greatly outnumbered, they opposed the attacking forces heroically. ' : '') + . "We have received an update on the latest casualties:

    " + . ""; + if ($this->data['a_power'] > 0) { + $str .= ""; + } + $str .= ""; + if ($this->data['a_power'] > 0) { + $str .= ""; + } + $str .= ""; + + for ($i=0;$i<4;$i++) { + $n = $this->sfn[$i]; + if ($this->data["o_$n"] + $this->data["a_$n"] + $this->data["e_$n"] == 0) { + continue; + } + $str .= ""; + if ($this->data['a_power'] > 0) + $str .= ""; + $str .= ""; + $str .= ""; + } + + $tmode = $this->data['tmode']; + if ($tmode != 0) { + $str .= ""; + } + + $str .= ""; + if ($this->data['a_power'] > 0) + $str .= ""; + $str .= "
     Own TroopsAllied TroopsEnemy Troops
    StartLostStartLostStartLost
    " . $this->sdn[$i] . "" . number_format($this->data["o_$n"]) + . "" . number_format($this->data["ol_$n"]) . "" . number_format($this->data["a_$n"]) . "" . number_format($this->data["al_$n"]) . "" . number_format($this->data["e_$n"]) . "" . number_format($this->data["el_$n"]) . "
    Turrets"; + if ($tmode == 1) + $str .= number_format($this->data["turrets"]) . "" . number_format($this->data["l_turrets"]); + else + $str .= "--"; + + $str .= ""; + if ($tmode == 2) + $str .= number_format($this->data["turrets"]) . "" . number_format($this->data["l_turrets"]) . ""; + elseif ($this->data['a_power'] > 0) + $str .= "--"; + + if ($tmode == 3) + $str .= number_format($this->data["turrets"]) . "" . number_format($this->data["l_turrets"]); + else + $str .= "--"; + $str .= "
    Fleet Power" . number_format($this->data['o_power']) . "" . number_format($this->data['ol_power']) . "" . number_format($this->data['a_power']) . "" . number_format($this->data['al_power']) . "" . number_format($this->data['e_power']) . "" . number_format($this->data['el_power']) . "

    "; + + $ePower = $this->data['e_power']; $eLoss = $this->data['el_power']; + $aPower = $this->data['a_power'] + $this->data['o_power']; + $aLoss = $this->data['al_power'] + $this->data['ol_power']; + + if ($aPower == $aLoss && $ePower == $eLoss) { + $str .= "The fleets annihilated each other."; + } elseif ($ePower == $eLoss) { + $str .= "Victory is ours! The enemy has been annihilated!"; + } elseif ($aPower == $aLoss) { + $str .= "Our forces were annihilated!"; + } else { + $ePLoss = $eLoss / $ePower; + $aPLoss = $aLoss / $aPower; + if ($ePower - $eLoss > $aPower - $aLoss) { + $ratio = $ePLoss ? ($aPLoss / $ePLoss) : 11; + if ($ratio > 10) { + $str .= "Sir, our forces have to retreat from this losing battle!"; + } elseif ($ratio > 3) { + $str .= "We are getting whacked. We need reinforcements!"; + } elseif ($ratio > 1.2) { + $str .= "The situation doesn't look too good for us."; + } else { + $str .= "Our forces and the enemy's forces are a close match."; + } + } elseif ($ePower - $eLoss < $aPower - $aLoss) { + $ratio = $aPLoss ? ($ePLoss / $aPLoss) : 11; + if ($ratio > 10) { + $str .= "The enemy will soon be crushed."; + } else if ($ratio > 3) { + $str .= "We are clearly winning this fight."; + } else if ($ratio > 1.2) { + $str .= "The enemy is bound to lose this fight in the long run."; + } else { + $str .= "Our forces and the enemy's forces are a close match."; + } + } else { + $str .= "Our forces and the enemy's forces are a close match."; + } + } + + return $str; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/bid.inc b/scripts/game/beta5/msgformat/en/bid.inc new file mode 100644 index 0000000..b0228fe --- /dev/null +++ b/scripts/game/beta5/msgformat/en/bid.inc @@ -0,0 +1,100 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + return 'Foreign Minister'; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + return utf8entities($this->players->call('getName', $this->player)); + } + + function getRLink() { + return ""; + } + + function getSubject() { + $s = $this->data['is_planet'] == 't' ? "planet" : "fleet at"; + return "New bid on $s " . utf8entities($this->data['pname']); + } + + function getReplyLink() { + return ""; + } + + function getFleetText() { + $a = array(); + $ids = array( + 'gas' => "G.A. Ship", + 'fighters' => "Fighter", + 'cruisers' => "Cruiser", + 'bcruisers' => "Battle Cruiser", + ); + foreach ($ids as $i => $n) { + if ($this->data["f_$i"] == 0) { + continue; + } + $nb = $this->data["f_$i"]; + array_push($a, "$nb $n" . ($nb > 1 ? "s" : "")); + } + return join(', ', $a); + } + + function getContents() { + if (is_null($this->data['last_bidder'])) { + $str = "Sir, we had placed a bid on " . ($this->data['is_planet'] == 't' ? "planet" : "a fleet in orbit around") . " "; + if (!is_null($this->data['planet'])) { + $str .= ""; + } + $str .= "" . utf8entities($this->data['pname']) . "" . (is_null($this->data['planet']) ? "" : ""); + $fs = $this->getFleetText(); + if ($this->data['is_planet'] == 'f') { + $str .= " ($fs)"; + } elseif ($fs != '') { + $str .= " (along with the following fleet: $fs)"; + } + $str .= ".
    We have been informed that someone just placed a higher bid. The new price is €"; + $str .= number_format($this->data['new_price']) . "."; + if (!is_null($this->data['offer'])) { + $str .= '
    We should place a higher bid.'; + } + } else { + $str = "Sir, we have received an offer for " . ($this->data['is_planet'] == 't' ? "planet" : "the fleet in orbit around") . " "; + if (!is_null($this->data['planet'])) { + $str .= ""; + } + $str .= "" . utf8entities($this->data['pname']) . "" . (is_null($this->data['planet']) ? "" : ""); + $fs = $this->getFleetText(); + if ($this->data['is_planet'] == 'f') { + $str .= " ($fs)"; + } elseif ($fs != '') { + $str .= " (being sold with the following fleet: $fs)"; + } + $str .= ".
    "; + + $pinf = $this->players->call('get', $this->data['last_bidder'], true); + if (!$pinf['quit']) { + $str .= ''; + } + $str .= utf8entities($pinf['name']); + if (!$pinf['quit']) { + $str .= ''; + } + $str .= " is offering to buy the " . ($this->data['is_planet'] == 'f' ? "fleet" : "planet") . " for €"; + $str .= number_format($this->data['new_price']) . "."; + } + return $str; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/cash.inc b/scripts/game/beta5/msgformat/en/cash.inc new file mode 100644 index 0000000..ce9b33f --- /dev/null +++ b/scripts/game/beta5/msgformat/en/cash.inc @@ -0,0 +1,43 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + return 'Foreign Minister'; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + return utf8entities($this->players->call('getName', $this->player),ENT_COMPAT); + } + + function getRLink() { + return ""; + } + + function getSubject() { + return "Cash donation received!"; + } + + function getReplyLink() { + return ""; + } + + function getContents() { + $pinf = $this->players->call('get', $this->data['p_id'], true); + return "Sir! We just received a cash donation amounting to €" + . number_format($this->data['amount']) . " from player " + . ($pinf['quit'] == 1 ? '' : "") + . "" . utf8entities($pinf['name'],ENT_COMPAT) . "" . ($pinf['quit'] == 1 ? "" : "") . "!"; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/ctf.inc b/scripts/game/beta5/msgformat/en/ctf.inc new file mode 100644 index 0000000..f22951c --- /dev/null +++ b/scripts/game/beta5/msgformat/en/ctf.inc @@ -0,0 +1,241 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + $this->alliance = $game->getLib('beta5/alliance'); + } + + public function getSLink() { return ""; } + public function getRLink() { return ""; } + public function getReplyLink() { return ""; } + + public function getSender() { return "LegacyWorlds"; } + public function getRecipient() { + $name = $this->players->call('getName', $this->player); + return utf8entities($name); + } + + + public function getSubject() { + // Get the team's data if needed + if (!is_null($this->data['team'])) { + $this->team = $this->alliance->call('get', $this->data['team']); + } + + // Create the subject + switch ($this->data['msg_type']) : + default: + case 0: $rv = "Welcome!"; break; + case 1: $rv = "A player joined the team"; break; + case 2: $rv = "We are holding the targets"; break; + case 3: $rv = $this->team['name'] . " has the targets!"; break; + case 4: $rv = "Targets lost - we must retake"; break; + case 5: + case 6: $rv = "Targets lost"; break; + case 7: $rv = $this->team['name'] . " could lose the targets"; break; + case 8: + case 9: $rv = $this->team['name'] . " lost the targets"; break; + case 10: $rv = "We are still holding!"; break; + case 11: $rv = $this->team['name'] . " is still holding!"; break; + case 12: $rv = "Victory!... sort of."; break; + case 13: $rv = $this->team['name'] . " owned us"; break; + case 14: $rv = "WE HAVE WON THE GAME!"; break; + case 15: $rv = "We were defeated :'("; break; + endswitch; + return $rv; + } + + public function getContents() { + // Get the team's data if needed + if (!is_null($this->data['team']) && is_null($this->team)) { + $this->team = $this->alliance->call('get', $this->data['team']); + } + + // Generate the contents + $generators = array( + "Welcome", "Join", + "Hold", "OthersHold", + "LostGrace", "Lost", "GraceExpired", + "OthersLostGrace", "OthersLost", "OthersGraceExpired", + "Half", "OthersHalf", + "Win", "Lose", + "WonGame", "LostGame" + ); + + $v = "msg" . $generators[$this->data['msg_type']]; + ob_start(); + $this->$v(); + $rv = ob_get_contents(); + ob_end_clean(); + + return $rv; + } + + + private function msgWelcome() { + $game = $this->players->game; +?> +Welcome to this special King of the Hills LegacyWorlds game!
    +
    +You have been assigned to [team['tag']?>], the +team['name'])?>.
    +
    +Before you start playing, a few explanations on this special minigame may be required.
    +
    +Here, the goal is for your team to control special star systems called targets for +params['v2time']?> hours in a row. If you succeed, the team will earn +params['v2points']?> points.
    +params['v2grace']) { +?> +
    +However, if you lose control on the targets, you will still have +params['v2grace']?> hours to recover them without restarting the +countdown to victory. Of course, the time during which your team no longer holds the +targets does not count in the race towards victory.
    + +
    +When a team wins a round, the targets are made neutral again, and their factories and turrets +are reset. All planets which had been destroyed by Wormhole Supernovas are restored, and all +planets a team owns in another team's territory are neutralized. Players who no longer had +planets are respawned at their original location. In addition, fleets are equalized so that +no-one starts the new round with more fleets than the others.
    +
    +The first team to reach 100 points will win the game. +players->call('getName', $this->data['time_stamp']); +?> +A new player, '>, +has been assigned to the team['name'])?>. + +Our team is now holding all the targets! We must hold them until +data['time_stamp'] + 60)?> (data['time_stamp'])?>) +to win this round. + +team['tag']?>, the team['name'])?>, is now holding all the targets!
    +We must retake them at all cost, otherwise the team['name'])?> will win this round at +data['time_stamp'] + 60)?> (data['time_stamp'])?>). + +Our team is no longer holding the targets... If we do not retake them before +data['time_stamp'] - 60)?> (data['time_stamp'])?>), +the countdown to victory will be reset and we'll need to do it all over again... + +Our team is no longer holding the targets :-(
    +The countdown to victory has been reset and we need to start all over again... + +Our team is no longer holding the targets, and we lost our chance to retake them in time...
    +The countdown to victory has been reset and we need to start all over again... + +team['tag']?>, the team['name'])?>, is no longer holding the targets!
    +However, if they retake the targets before data['time_stamp'] - 60)?> +(data['time_stamp'])?>), +the countdown to their victory will start again ... We must prevent them from retaking! + +team['tag']?>, the team['name'])?>, is no longer holding the targets! +The countdown has been cancelled! + +team['tag']?>, the team['name'])?>, is no longer holding the targets and +didn't manage to retake them in time! The countdown has been cancelled! + +Our team is now holding all the targets! We must hold them until +data['time_stamp'] + 60)?> (data['time_stamp'])?>) +to win this round. + +team['tag']?>, the team['name'])?>, is still holding all the targets!
    +We must retake them at all cost, otherwise the team['name'])?> will win this round at +data['time_stamp'] + 60)?> (data['time_stamp'])?>). + +Our team has just won a round of the game!
    +
    +As a consequence, the map has been reset and everyone has been respawned...
    +Keep sharp, the battle isn't over yet. + +We just lost a round of the game to [team['tag']?>], the team['name'])?>.
    +
    +As a consequence, the map has been reset and everyone has been respawned...
    +We still stand a chance of winning! + +The game is finished - AND WE WON! + +The game is finished - but we lost. [team['tag']?>], the team['name'])?>, +overpowered us. + diff --git a/scripts/game/beta5/msgformat/en/detect.inc b/scripts/game/beta5/msgformat/en/detect.inc new file mode 100644 index 0000000..ce2703f --- /dev/null +++ b/scripts/game/beta5/msgformat/en/detect.inc @@ -0,0 +1,104 @@ +game = $game; + $this->db = $game->db; + $this->players = $game->getLib('beta5/player'); + $this->forums = $game->getLib('main/forums'); + } + + public function getRecipient() { + return utf8entities($this->players->call('getName', $this->player),ENT_COMPAT); + } + public function getSender() { + return ($this->data['is_owner'] == 't' ? 'Military Advisor' : ('Governor of ' . $this->data['p_name'])); + } + + public function getSLink() { return ""; } + public function getRLink() { return ""; } + public function getReplyLink() { return ""; } + + public function getSubject() { + if ($this->data['is_owner'] == 't') { + return "Fleet detected at {$this->data['p_name']}"; + } + return "Fleet detected in hyperspace"; + } + + public function getContents() { + $func = ($this->data['is_owner'] == 't') ? "ownFleet" : "otherFleet"; + + ob_start(); + $this->$func(); + $x = ob_get_contents(); + ob_end_clean(); + + return $x; + } + + private function ownFleet() { +?> +One of our fleets in Hyperspace stand-by around planet data['p_name'])?> +has been detected by the planet's hyperspace beacon.
    +
    +data['i_level']) : + case 0: + echo "The beacon didn't manage to gather any information regarding the fleet, tho."; + break; + case 1: + echo "The beacon managed to obtain a very rough estimate of our fleet's size."; + break; + case 2: + echo "The beacon managed to obtain a rough estimate of our fleet's size."; + break; + case 3: + echo "The beacon managed to obtain our fleet's size."; + break; + case 4: + echo "The beacon identified our fleet!"; + break; + endswitch; + } + + private function otherFleet() { +?> +Our hyperspace beacon around planet data['p_name'])?> has detected a fleet +in Hyperspace.
    +
    +data['i_level']) : + case 0: + echo "However, no additional information could be gathered due to heavy jamming."; + break; + case 1: + echo "Jamming prevented us from obtaining much information, however the beacon " + . "transmitted a very rough estimate of the fleet's size. Its power is " + . "estimated at " . number_format($this->data['fl_size']) + . " (+/- 25%)."; + break; + case 2: + echo "Jamming prevented us from obtaining much information, however the beacon " + . "transmitted a rough estimate of the fleet's size. Its power is " + . "estimated at " . number_format($this->data['fl_size']) + . " (+/- 7.5%)."; + break; + case 3: + echo "The beacon was able to compute the fleet's size; its power is " + . number_format($this->data['fl_size']) . ". However, because of jamming, " + . "the beacon could not identify the fleet."; + break; + case 4: + echo "The beacon completely identified the fleet! Its power is " + . number_format($this->data['fl_size']) . ", and it is owned by " + . utf8entities($this->data['flo_name']) . "."; + break; + endswitch; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/endprotection.inc b/scripts/game/beta5/msgformat/en/endprotection.inc new file mode 100644 index 0000000..7ce801e --- /dev/null +++ b/scripts/game/beta5/msgformat/en/endprotection.inc @@ -0,0 +1,74 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + public function getSender() { + return 'Peacekeeper Headquarters'; + } + + public function getSLink() { + return ''; + } + + public function getRecipient() { + $name = $this->players->call('getName', $this->player); + return utf8entities($name); + } + + public function getRLink() { + return ''; + } + + public function getSubject() { + return 'You are no longer under our protection'; + } + + public function getReplyLink() { + return ''; + } + + public function getContents() { + ob_start(); + switch ($this->data['end_type']) { + case 'EXP': +?> +The period of 20 days during which we were meant to protect you has expired.
    +We are confident that you are now strong enough to survive, and wish you the best of luck.
    +
    +Kind regards,
    +Peacekeeper Commander Dapkor + +You have decided that you no longer require our services.
    +We wish you the best of luck in your future endeavours, and remain at your disposal +should you need our help again.
    +
    +Kind regards,
    +Peacekeeper Commander Multair + +You have sent a ship outside your system, therefore revoking our agreement.
    +We wish you the best of luck in your future endeavours, and remain at your disposal +should you need our services again.
    +
    +Kind regards,
    +Peacekeeper Commander Hestaks + diff --git a/scripts/game/beta5/msgformat/en/flmove.inc b/scripts/game/beta5/msgformat/en/flmove.inc new file mode 100644 index 0000000..923bd10 --- /dev/null +++ b/scripts/game/beta5/msgformat/en/flmove.inc @@ -0,0 +1,114 @@ +game = $game; + $this->db = $game->db; + $this->players = $game->getLib('beta5/player'); + } + + function getMovementData() { + if (!is_null($this->fmData) && $this->fmId == $this->data['id']) { + return; + } + + $q = $this->db->query("SELECT * FROM flmove_data WHERE id={$this->data['id']}"); + $this->fmData = array(); + $isMe = true; + while ($r = dbFetchHash($q)) { + $isMe = $isMe && ($r['f_owner'] == $this->player); + array_push($this->fmData, $r); + } + $this->isOwnArrival = $isMe; + $this->fmId = $this->data['id']; + } + + function getSender() { + return 'Military Advisor'; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + $pinf = $this->players->call('getName', $this->player); + return utf8entities($pinf); + } + + function getRLink() { + return ""; + } + + function getSubject() { + $this->getMovementData(); + if ($this->isOwnArrival) { + if (count($this->fmData) > 1) { + $str = "Our fleets have"; + } else { + $str = "Our fleet has"; + } + $str .= " arrived at "; + } else { + $str .= "Fleet movement at "; + } + $str .= utf8entities($this->data['p_name']); + return $str; + } + + function getReplyLink() { + return ""; + } + + function getComposition($r) { + $rv = array(); + for ($i=0;$i<4;$i++) { + $n = $r['f_' . $this->compi[$i]]; + if ($n == 0) { + continue; + } + array_push($rv, "" . number_format($n) . " " . $this->compn[$i*2 + ($n>1 ? 1 : 0)]); + } + return join(', ', $rv); + } + + function getContents() { + $this->getMovementData(); + $pl = (count($this->fmData) > 1); + if ($this->isOwnArrival) { + $str = "Sir! The following fleet" . ($pl ? "s have" : " has") . " arrived at " . ($pl ? "their" : "its") + . " destination, " . utf8entities($this->data['p_name']) + . ":
    "; + foreach ($this->fmData as $r) { + $str .= "
    Fleet " . utf8entities($r['f_name']) . " coming from " + . utf8entities($r['from_name']) . " (composition: "; + $str .= $this->getComposition($r); + $str .= "; power: " . number_format($r['f_power']) . ")"; + } + } else { + $str = "Sir! The fleets stationed in orbit around " . utf8entities($this->data['p_name']) + . " report the following event" . ($pl ? "s" : "") . ":
    "; + foreach ($this->fmData as $r) { + $str .= "
    A " . ($r['hostile'] == 'f' ? "friendly" : "hostile") . " fleet, " . utf8entities($r['f_name']) . ", owned by " + . "" . utf8entities($this->players->call('getName', $r['f_owner'])) + . ", has "; + if ($r['arrived'] == 'f') { + $str .= "left orbit"; + } else { + $str .= "entered orbit, coming from " . utf8entities($r['from_name']) . ""; + } + $str .= " (composition: "; + $str .= $this->getComposition($r); + $str .= "; power: " . number_format($r['f_power']) . ")"; + } + } + return $str; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/flswitch.inc b/scripts/game/beta5/msgformat/en/flswitch.inc new file mode 100644 index 0000000..a630d31 --- /dev/null +++ b/scripts/game/beta5/msgformat/en/flswitch.inc @@ -0,0 +1,53 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + return 'Military Advisor'; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + $pinf = $this->players->call('getName', $this->player); + return utf8entities($pinf); + } + + function getRLink() { + return ""; + } + + function getSubject() { + return "Fleets forced to attack on " . utf8entities($this->data['pname']); + } + + function getReplyLink() { + return ""; + } + + function getContents() { + if (!is_null($this->data['planet'])) { + $a1 = ''; + $a2 = ''; + } else { + $a1="";$a2=""; + } + $str = "Sir, the owner of planet $a1".utf8entities($this->data['pname'])."$a2 has added us to his enemy list.
    "; + $str .= "Our fleets have been forced to attack the planet as a consequence and it will not be possible to switch them back "; + $str .= "unless the owner removes us from his enemy list. Our fleets are stuck there until a Battle Tick happens.
    "; + + $nf = $this->data['n_fleets']; + $str .= "$nf of our fleets " . ($nf>1?'are':'is') . " in orbit around the planet, with a total fleet power of "; + $str .= number_format($this->data['fpower']) . "."; + return $str; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/hsloss.inc b/scripts/game/beta5/msgformat/en/hsloss.inc new file mode 100644 index 0000000..7af897a --- /dev/null +++ b/scripts/game/beta5/msgformat/en/hsloss.inc @@ -0,0 +1,76 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + return 'Military Advisor'; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + $pinf = $this->players->call('getName', $this->player); + return utf8entities($pinf); + } + + function getRLink() { + return ""; + } + + function getSubject() { + if (is_array($this->data[0])) { + $data = $this->data[0]; + } else { + $data = $this->data; + } + return "Ships lost in Hyperspace around " . utf8entities($data['p_name']); + } + + function getReplyLink() { + return ""; + } + + function getFleetComposition($r) { + $rv = array(); + for ($i=0;$i<4;$i++) { + $n = $r[$this->compi[$i]]; + if ($n == 0) { + continue; + } + array_push($rv, "" . number_format($n) . " " . $this->compn[$i*2 + ($n>1 ? 1 : 0)]); + } + return join(', ', $rv); + } + + function getContents() { + if (is_array($this->data[0])) { + $data = $this->data; + } else { + $data = array($this->data); + } + + $str = "Sir! Some of our ships standing by in Hyperspace in the vicinity of " . utf8entities($data['p_name']) + . " were lost; a ceremony will be held today in their honor while these " + . "poor guys drift in Hyperspace until they starve to death.

    " + . "The following report indicates the amount of ships lost.
    "; + + foreach ($data as $f) { + $str .= "
    Fleet " . utf8entities($f['f_name']) . " lost " + . $this->getFleetComposition($f) . " (lost fleet power: " + . number_format($f['power']) . ")"; + } + return $str; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/kfleet.inc b/scripts/game/beta5/msgformat/en/kfleet.inc new file mode 100644 index 0000000..ba74fa6 --- /dev/null +++ b/scripts/game/beta5/msgformat/en/kfleet.inc @@ -0,0 +1,67 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + return 'Military Advisor'; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + $pinf = $this->players->call('getName', $this->player); + return utf8entities($pinf); + } + + function getRLink() { + return ""; + } + + function getSubject() { + return "Fleets lost due to insufficient funds"; + } + + function getReplyLink() { + return ""; + } + + function getContents() { + $str = "Sir! Due to insufficient funds, we have been unable to pay for our fleets' upkeep. "; + + $types = array('gaships','fighters','cruisers','bcruisers'); + $names = array('G.A. ship', 'fighter', 'cruiser', 'battle cruiser'); + $sum = 0; + for ($i=0;$i<4;$i++) { + $sum += $this->data[$types[$i]]; + } + + $add = 0; + for ($i=0;$i<4;$i++) { + $n = $this->data[$types[$i]]; + if ($n == 0) { + continue; + } + $sum -= $n; + if ($sum == 0 && $add != 0) { + $str .= ' and '; + } elseif ($add != 0) { + $str .= ', '; + } + $add += $n; + + $str .= "".number_format($n)." " . $names[$i] . ($n > 1 ? 's' : ''); + } + $str .= " ha" . ($add > 1 ? "ve" : "s") . " been lost."; + + return $str; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/kimpr.inc b/scripts/game/beta5/msgformat/en/kimpr.inc new file mode 100644 index 0000000..3b01ea1 --- /dev/null +++ b/scripts/game/beta5/msgformat/en/kimpr.inc @@ -0,0 +1,53 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + return 'Civilian Advisor'; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + $pinf = $this->players->call('getName', $this->player); + return utf8entities($pinf); + } + + function getRLink() { + return ""; + } + + function getSubject() { + return "Improvements lost due to insufficient funds"; + } + + function getReplyLink() { + return ""; + } + + function getContents() { + $str = "Sir! Due to insufficient funds, we have been unable to pay for our planetary improvements' upkeep. "; + $tl = $this->data['turrets']; $ml = $this->data['factories']; + if ($tl > 0) { + $str .= "".number_format($tl)." turret" . ($tl>1?'s':''); + if ($ml > 0) { + $str .= " and "; + } + } + if ($ml > 0) { + $str .= "".number_format($ml)." military factor" . ($ml>1?"ies":"y"); + } + $str .= " ha" . ($ml+$tl>1?"ve":"s") . " been destroyed!"; + + return $str; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/leave.inc b/scripts/game/beta5/msgformat/en/leave.inc new file mode 100644 index 0000000..b5708bf --- /dev/null +++ b/scripts/game/beta5/msgformat/en/leave.inc @@ -0,0 +1,78 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + return 'Legacy Worlds'; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + $pinf = $this->players->call('getName', $this->player); + return utf8entities($pinf); + } + + function getRLink() { + return ""; + } + + function getSubject() { + $pinf = $this->players->call('get', $this->data['player'], true); + $str = utf8entities($pinf['name']) . " has "; + switch ($this->data['reason']) : + case 'QUIT': $str .= "quit the game"; break; + case 'INAC': $str .= "become inactive"; break; + case 'KICK': $str .= 'been kicked from the game'; break; + endswitch; + return $str; + } + + function getReplyLink() { + return ""; + } + + function getContents() { + $pinf = $this->players->call('get', $this->data['player'], true); + $str = "" . utf8entities($pinf['name']) . " "; + switch ($this->data['reason']) : + case 'QUIT': $str .= "has decided he had enough of this world and has just left the game."; break; + case 'INAC': $str .= "has disappeared, it would seem. He hasn't logged on for 30 days."; break; + case 'KICK': $str .= 'probably did something very, very bad and got kicked from the game.'; break; + endswitch; + + if (!is_null($this->data['tag'])) + $str .= " He has been removed from the members of the " . utf8entities($this->data['tag']) + . " alliance."; + if ($this->data['ally'] == 't') + $str .= " Since you had him in your Trusted Allies list, he has been removed from it."; + $str .= "
    "; + + if ($this->data['sales_from'] + $this->data['sales_to'] > 0) { + $str .= "
    "; + if ($this->data['sales_from'] > 0) + $str .= "" . number_format($this->data['sales_from']) . " sale" + . ($this->data['sales_from']>1?"s":"") . " from this player ha" + . ($this->data['sales_from']>1?"ve":"s") . " been cancelled.
    "; + if ($this->data['sales_to'] > 0) + $str .= "" . number_format($this->data['sales_to']) . " sale" + . ($this->data['sales_to']>1?"s":"") . " to this player ha" + . ($this->data['sales_to']>1?"ve":"s") . " been cancelled.
    "; + } + + $str .= "
    " . utf8entities($pinf['name']) . " will" + . ($this->data['reason'] == 'KICK' ? " NOT" : "") + . " be missed."; + + return $str; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/ownerch.inc b/scripts/game/beta5/msgformat/en/ownerch.inc new file mode 100644 index 0000000..cd0dc97 --- /dev/null +++ b/scripts/game/beta5/msgformat/en/ownerch.inc @@ -0,0 +1,74 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + return 'Military Advisor'; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + $pinf = $this->players->call('getName', $this->player); + return utf8entities($pinf); + } + + function getRLink() { + return ""; + } + + function getSubject() { + switch ($this->data['status']) : + case 'TAKE': $s = "We have taken control of "; break; + case 'LOSE': $s = "We have lost control of "; break; + case 'VIEW': $s = "Owner change on "; break; + endswitch; + $s .= utf8entities($this->data['p_name']); + return $s; + } + + function getReplyLink() { + return ""; + } + + function getContents() { + $planet = ''.utf8entities($this->data['p_name']).""; + switch ($this->data['status']) : + case 'TAKE': + $s = "Sir! We have seized control of planet $planet"; + if (!is_null($this->data['owner'])) { + $pid = $this->data['owner']; + $pn = utf8entities($this->players->call('getName', $pid)); + $s .= " from the clutches of $pn, " + . "its former owner"; + } else { + $s .= " from the local (lack of) government."; + } + $s .= "."; + break; + case 'LOSE': + $pid = $this->data['owner']; + $pn = utf8entities($this->players->call('getName', $pid)); + $s = "We have lost control of planet $planet, which was taken from us by " + . "$pn."; + break; + case 'VIEW': + $pid = $this->data['owner']; + $pn = utf8entities($this->players->call('getName', $pid)); + $s = "Sir! Our forces orbitting $planet report that " + . "$pn has taken " + . "control of the planet."; + break; + endswitch; + return $s; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/pkwarning.inc b/scripts/game/beta5/msgformat/en/pkwarning.inc new file mode 100644 index 0000000..dc003ef --- /dev/null +++ b/scripts/game/beta5/msgformat/en/pkwarning.inc @@ -0,0 +1,97 @@ +game = $game; + $this->db = $game->getDBAccess(); + $this->players = $game->getLib('beta5/player'); + } + + public function getSender() { + return 'Peacekeeper Headquarters'; + } + + public function getSLink() { + return ''; + } + + public function getRecipient() { + $name = $this->players->call('getName', $this->player); + return utf8entities($name); + } + + public function getRLink() { + return ''; + } + + public function getSubject() { + switch ($this->data['msg_type']) { + case 'W': + return 'Your fleets have entered protected territory'; + case 'D': + return 'Your fleets will be destroyed'; + case 'E': + return 'You have been declared an ennemy of the Peacekeepers'; + } + return 'Huh? wrong message type'; + } + + public function getReplyLink() { + return ''; + } + + public function getContents() { + $planets = "

    • " . join('
    • ', $this->getPlanets()) . "
    "; + + ob_start(); + switch ($this->data['msg_type']) { + case 'W': +?> +This is a warning from Peacekeeper Headquarters. Your fleets have entered Peacekeeper-protected territories. If +you do not retreat before data['delay'])?>, we will be forced to take action.
    +This warning applies to the following planets:

    + +

    Best regards,
    +Peacekeeper Commander Dapkor + +Your hostile actions will not go unpunished! The Peacekeepers will see to it that your fleets are destroyed on the +following planets:

    + +

    Regards,
    +Peacekeeper Commander Multair + +Your continued hostile behaviour has forced us to declare you an enemy of the Peacekeepers. Your fleets will be destroyed +on sight whenever you enter protected territory, and we will not protect you if you need it.
    +In addition, your fleets will be destroyed on the following planets: +

    + +

    Sanctions against you will end at on %d/%m/%Y", $this->data['delay'])?>. +
    +Peacekeeper Commander Hestaks +db->query("SELECT planet, p_name FROM pkwarning_planet WHERE id = {$this->data['id']}"); + $planets = array(); + while ($r = dbFetchArray($q)) { + array_push($planets, "" . utf8entities($r[1]) . ""); + } + return $planets; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/planet.inc b/scripts/game/beta5/msgformat/en/planet.inc new file mode 100644 index 0000000..1f1ea58 --- /dev/null +++ b/scripts/game/beta5/msgformat/en/planet.inc @@ -0,0 +1,58 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + $this->planets = $game->getLib('beta5/planet'); + } + + function getSender() { + $pinf = $this->players->call('getName', $this->data['sender']); + return utf8entities($pinf); + } + + function getSLink() { + if ($this->data['sender'] == $this->player) { + return ""; + } + $pinf = $this->players->call('get', $this->data['sender']); + if (is_null($pinf)) { + return ""; + } + return "0,".$this->data['sender']; + } + + function getRecipient() { + return "Planet ".utf8entities($this->data['pname']); + } + + function getRLink() { + if (is_null($this->data['planet'])) { + return ""; + } + $pinf = $this->planets->call('byId', $this->data['planet']); + if ($pinf['owner'] == $this->player) { + return ""; + } + return "1,".$this->data['planet']; + } + + function getSubject() { + return utf8entities($this->data['subject']); + } + + function getReplyLink() { + if ( $this->data['sender'] == $this->player || is_null($this->data['planet'])) { + return ""; + } + return "0,".$this->data['sender'].",".$this->data['id']; + } + + function getContents() { + return preg_replace('/\n/', '
    ', utf8entities($this->data['message'])); + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/plsc.inc b/scripts/game/beta5/msgformat/en/plsc.inc new file mode 100644 index 0000000..05711a3 --- /dev/null +++ b/scripts/game/beta5/msgformat/en/plsc.inc @@ -0,0 +1,49 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + return 'Foreign Minister'; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + return utf8entities($this->players->call('getName', $this->player)); + } + + function getRLink() { + return ""; + } + + function getSubject() { + return "Transfer of planet " . utf8entities($this->data['p_name']) . " cancelled!"; + } + + function getReplyLink() { + return ""; + } + + function getContents() { + $p1 = $this->players->call('get', $this->data['seller'], true); + $p1s = ($p1['quit'] == 0 ? "" : "") + . "" . utf8entities($p1['name']) . "" . ($p1['quit'] == 0 ? "" : ""); + + $p2 = $this->players->call('get', $this->data['taker'], true); + $p2s = ($p2['quit'] == 0 ? "" : "") + . "" . utf8entities($p2['name']) . "" . ($p2['quit'] == 0 ? "" : ""); + + return "The sale of planet " + . utf8entities($this->data['p_name']) . " has been cancelled.
    " + . "The seller, $p1s, lost the planet to the forces of $p2s."; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/rename.inc b/scripts/game/beta5/msgformat/en/rename.inc new file mode 100644 index 0000000..327bc06 --- /dev/null +++ b/scripts/game/beta5/msgformat/en/rename.inc @@ -0,0 +1,50 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + return 'Head of Intelligence'; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + $pinf = $this->players->call('getName', $this->player); + return utf8entities($pinf,ENT_COMPAT); + } + + function getRLink() { + return ""; + } + + function getSubject() { + return "Planet " . utf8entities($this->data['old_name'],ENT_COMPAT) . " has been renamed"; + } + + function getReplyLink() { + return ""; + } + + function getContents() { + $str = "Sir! A planet we were "; + switch ($this->data['status']) : + case 'ORBIT': $str .= "orbitting"; break; + case 'MOVE': $str .= "moving towards"; break; + case 'PROBE': $str .= "shamelessly spying on with our probes"; break; + endswitch; + $str .= ", " . utf8entities($this->data['old_name'],ENT_COMPAT) . ", has been renamed " + . "by its owner to " + . utf8entities($this->data['new_name'],ENT_COMPAT) . "."; + + return $str; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/resdipl.inc b/scripts/game/beta5/msgformat/en/resdipl.inc new file mode 100644 index 0000000..eccdec8 --- /dev/null +++ b/scripts/game/beta5/msgformat/en/resdipl.inc @@ -0,0 +1,76 @@ +game = $game; + $this->db = $game->db; + $this->players = $game->getLib('beta5/player'); + } + + function getOffer() { + $q = $this->db->query("SELECT * FROM research_assistance WHERE id=".$this->data['offer']); + return dbFetchHash($q); + } + + function getSender() { + return "Science Minister"; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + $pinf = $this->players->call('getName', $this->player); + return utf8entities($pinf); + } + + function getRLink() { + return ""; + } + + function getSubject() { + $o = $this->getOffer(); + $p1 = $this->players->call('get', $o['player'], true); + $p2 = $this->players->call('get', $o['offer_to'], true); + switch ($this->data['msg_id']) : + case 0: + $s = "Scientific assistance offer from " . utf8entities($p1['name']); + break; + case 1: case 2: + $s = "[UPDATE] Scientific assistance to " . utf8entities($p2['name']); + break; + endswitch; + return $s; + } + + function getReplyLink() { + return ""; + } + + function getContents() { + $o = $this->getOffer(); + $p1 = $this->players->call('get', $o['player'], true); + $p2 = $this->players->call('get', $o['offer_to'], true); + switch ($this->data['msg_id']) : + case 0: + $s = "We have received an offer of scientific assistance from " . utf8entities($p1['name']) . "."; + break; + case 1: + $s = "Our offer to " . utf8entities($p2['name']) . " has been accepted"; + if ($o['price'] > 0) { + $s .= " and you have therefore received €" . number_format($o['price']) . ""; + } + $s .= "."; + break; + case 2: + $s = "Our offer of scientific assistance to " . utf8entities($p2['name']) . " has been declined."; + break; + endswitch; + $s .= " More details ..."; + return $s; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/revdmg.inc b/scripts/game/beta5/msgformat/en/revdmg.inc new file mode 100644 index 0000000..ae5a7ba --- /dev/null +++ b/scripts/game/beta5/msgformat/en/revdmg.inc @@ -0,0 +1,95 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + return 'Damage Control Squad'; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + $pinf = $this->players->call('getName', $this->player); + return utf8entities($pinf); + } + + function getRLink() { + return ""; + } + + function getSubject() { + return "Damage to infrastructure due to riots"; + } + + function getReplyLink() { + return ""; + } + + function getContents() { + if (is_array($this->data[0])) { + $data = $this->data; + } else { + $data = array($this->data); + } + + $str = "Sir! Citizens in the empire are riotting; we have been able to contain most of the acts of " + . "violence. Here is our report.

    "; + + $n = 0; + foreach ($data as $p) { + $str .= 'Planet '.utf8entities($p['pname']).": "; + if ($p['mfact'] + $p['ifact'] + $p['turrets'] == 0) { + $str .= "no damage reported."; + } else { + $nd = 0; + if ($p['ifact'] != 0) { + $str .= "" . $p['ifact'] . " industrial factor"; + if ($p['ifact'] > 1) { + $str .= "ies"; + } else { + $str .= "y"; + } + $nd += $p['ifact']; + } + if ($p['mfact'] != 0) { + if ($nd) { + if ($p['turrets'] > 0) { + $str .= ", "; + } else { + $str .= " and "; + } + } + $str .= "" . $p['mfact'] . " military factor"; + if ($p['mfact'] > 1) { + $str .= "ies"; + } else { + $str .= "y"; + } + $nd += $p['mfact']; + } + if ($p['turrets'] != 0) { + if ($nd) { + $str .= " and "; + } + $str .= "" . $p['turrets'] . " turret" . ($p['turrets'] > 1 ? "s" : ""); + } + $str .= " ha" . ($nd > 1 ? "ve" : "s") . " been destroyed."; + } + + $n ++; + if ($n < count($data)) { + $str .= "
    "; + } + } + return $str; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/revolt.inc b/scripts/game/beta5/msgformat/en/revolt.inc new file mode 100644 index 0000000..df64e67 --- /dev/null +++ b/scripts/game/beta5/msgformat/en/revolt.inc @@ -0,0 +1,52 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + return 'Governor of ' . utf8entities($this->data['pname']); + } + + function getSLink() { + return ""; + } + + function getRecipient() { + $pinf = $this->players->call('getName', $this->player); + return utf8entities($pinf); + } + + function getRLink() { + return ""; + } + + function getSubject() { + if ($this->data['started'] == 't') { + return "Civil unrest on " . utf8entities($this->data['pname']); + } + return "End of the uprising on " . utf8entities($this->data['pname']); + } + + function getReplyLink() { + return ""; + } + + function getContents() { + $planet = ''.utf8entities($this->data['pname']).""; + if ($this->data['started'] == 't') { + $str = "Sir! The citizens of $planet are most displeased with their treatment! " + . "They are protesting in the streets at being so unhappy! The riots are intensifying, " + . "and we suspect that they will soon start destroying our infrastructure if no action " + . "is taken!"; + } else { + $str = "Sir, the riots on planet $planet have ended."; + } + return $str; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/sale.inc b/scripts/game/beta5/msgformat/en/sale.inc new file mode 100644 index 0000000..5f7b1ce --- /dev/null +++ b/scripts/game/beta5/msgformat/en/sale.inc @@ -0,0 +1,85 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + return "Economic Advisor"; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + return utf8entities($this->players->call('getName', $this->player)); + } + + function getRLink() { + return ""; + } + + function getSubject() { + $v = ($this->data['is_sale'] == 'f') ? "bought" : "sold"; + if (is_null($this->data['p_name'])) { + return "Fleet '" . utf8entities($this->data['f_name']) . " has been $v"; + } + return "Planet " . utf8entities($this->data['p_name']) . " has been $v"; + } + + function getReplyLink() { + return ""; + } + + function getContents() { + $p = $this->players->call('get', $this->data['pl_id'], true); + $sale = ($this->data['is_sale'] == 'f'); + $pq = ($p['quit'] == 0); + $fp = ($this->data['f_power'] != "") ? number_format($this->data['f_power']) : ""; + + if (is_null($this->data['p_name'])) { + if ($this->data['is_sale'] == 't') { + $s = "We just lost control of our fleet " . utf8entities($this->data['f_name']) + . " (fleet power: $fp).
    It has been sold to " + . ($pq ? "" : "") + . "" . utf8entities($this->data['pl_name']) . "" . ($pq?"":"") . "."; + } else { + $s = "We just gained control of the fleet " . utf8entities($this->data['f_name']) + . " (fleet power: $fp).
    It has been obtained from " + . ($pq ? "" : "") + . "" . utf8entities($this->data['pl_name']) . "" . ($pq?"":"") . "."; + } + } elseif ($this->data['is_sale'] == 't') { + $s = "We just lost control of our planet " . utf8entities($this->data['p_name']) + . ""; + if (!is_null($this->data['f_name'])) { + $s .= " along with the " . utf8entities($this->data['f_name']) + . " fleet (power: $fp)"; + } + + $s .= ".
    It has been sold to " + . ($pq ? "" : "") + . "" . utf8entities($this->data['pl_name']) . "" . ($pq?"":"") . "."; + } else { + $s = "We just gained control on planet " . utf8entities($this->data['p_name']) + . ""; + if (!is_null($this->data['f_name'])) { + $s .= " and on the " . utf8entities($this->data['f_name']) + . " fleet (power: $fp)"; + } + + $s .= ".
    We bought it from " + . ($pq ? "" : "") + . "" . utf8entities($this->data['pl_name']) . "" . ($pq?"":"") . "."; + } + return $s; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/std.inc b/scripts/game/beta5/msgformat/en/std.inc new file mode 100644 index 0000000..300fdeb --- /dev/null +++ b/scripts/game/beta5/msgformat/en/std.inc @@ -0,0 +1,63 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + $pinf = $this->players->call('get', $this->data['sender'], true); + return utf8entities($pinf['name']); + } + + function getSLink() { + if ($this->data['sender'] == $this->player) { + return ""; + } + + $pinf = $this->players->call('get', $this->data['sender']); + if (is_null($pinf)) { + return ""; + } + return "0,".$this->data['sender']; + } + + function getRecipient() { + $pinf = $this->players->call('get', $this->data['recipient'], true); + return utf8entities($pinf['name']); + } + + function getRLink() { + if ($this->data['recipient'] == $this->player) { + return ""; + } + $pinf = $this->players->call('get', $this->data['recipient']); + if (is_null($pinf)) { + return ""; + } + return "0,".$this->data['recipient']; + } + + function getSubject() { + return utf8entities($this->data['subject']); + } + + function getReplyLink() { + if ($this->data['sender'] == $this->player) { + return ""; + } + $pinf = $this->players->call('get', $this->data['sender']); + if (is_null($pinf)) { + return ""; + } + return "0,".$this->data['sender'].",".$this->data['id']; + } + + function getContents() { + return preg_replace('/\n/', '
    ', utf8entities($this->data['message'])); + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/warnname.inc b/scripts/game/beta5/msgformat/en/warnname.inc new file mode 100644 index 0000000..7804399 --- /dev/null +++ b/scripts/game/beta5/msgformat/en/warnname.inc @@ -0,0 +1,60 @@ +game = $game; + $this->players = $game->getLib('beta5/player'); + } + + function getSender() { + return 'LW Moderators'; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + return utf8entities($this->players->call('getName', $this->player),ENT_COMPAT); + } + + function getRLink() { + return ""; + } + + function getSubject() { + switch ($this->data['event']) : + case 'WARN ': $s = '/!\\ PLANET NAME WARNING /!\\'; break; + default: $s = '/!\\ PLANET RESET /!\\'; break; + endswitch; + return $s; + } + + function getReplyLink() { + return ""; + } + + function getContents() { + if ($this->data['event'] == 'WARN ') { + $t = "This is an official warning from the Legacy Worlds moderation team.
    " + . "One of the planets you own, " + . utf8entities($this->data['p_name'],ENT_COMPAT) . ", has a name that is considered either " + . "vulgar, disrespectful or discriminating (please check the manual for more details).
    " + . "The planet will be made neutral and reset to its initial state (2k population, 3 factories " + . "of each type and 3 turrets) if you don't rename it within 24h or if the new name is " + . "found to be offensive as well."; + } elseif ($this->data['event'] == 'NEUT ') { + $t = "Your planet " . utf8entities($this->data['p_name'],ENT_COMPAT) . " has been neutralized and reset " + . "by the Legacy Worlds moderation team; you had been warned regarding its name and renamed it " + . "to something offensive."; + } else { + $t = "Your planet " . utf8entities($this->data['p_name'],ENT_COMPAT) . " has been automatically neutralized " + . "and reset; you had been warned regarding its name and did not rename it within 24h."; + } + return $t; + } +} + +?> diff --git a/scripts/game/beta5/msgformat/en/whsn.inc b/scripts/game/beta5/msgformat/en/whsn.inc new file mode 100644 index 0000000..30ef21b --- /dev/null +++ b/scripts/game/beta5/msgformat/en/whsn.inc @@ -0,0 +1,118 @@ +game = $game; + $this->db = $game->getDBAccess(); + $this->players = $game->getLib('beta5/player'); + $this->planets = $game->getLib('beta5/planet'); + } + + function getSender() { + return 'Military Advisor'; + } + + function getSLink() { + return ""; + } + + function getRecipient() { + $pinf = $this->players->call('getName', $this->player); + return utf8entities($pinf); + } + + function getRLink() { + return ""; + } + + function getSubject() { + return "Planet " . utf8entities($this->data['p_name']) . " has been destroyed!"; + } + + function getReplyLink() { + return ""; + } + + function getFleetComposition($r) { + $rv = array(); + for ($i=0;$i<4;$i++) { + $n = $r[$this->compi[$i]]; + if ($n == 0) { + continue; + } + array_push($rv, "" . number_format($n) . " " . $this->compn[$i*2 + ($n>1 ? 1 : 0)]); + } + return join(', ', $rv); + } + + function getContents() { + $q = $this->db->query("SELECT * FROM whsn_fleet WHERE id={$this->data['id']}"); + $fList = array(); + $fPower = 0; + while ($r = dbFetchHash($q)) { + $fPower += $r['power']; + array_push($fList, $r); + } + + if ($this->data['was_owner'] == 'f') { + $str = "Sir! A planet we were orbitting, " . utf8entities($this->data['p_name']) + . ", has self-destructed using a wormhole supernova!
    "; + + $ffp = $this->data['f_power']; $efp = $this->data['e_power']; + if ($ffp != 0) { + $str .= "Allied forces lost a total of " . number_format($ffp) + . " fleet power"; + if ($efp != 0) { + $str .= ", while enemy forces lost a total of " + . number_format($efp) . " fleet power"; + } + $str .= "."; + } else if ($efp != 0) { + $str .= "A total fleet power of " . number_format($efp) + . " was lost by the enemy when the planet exploded."; + } + if (count($fList)) { + $str .= " We lost the following fleet" . ((count($fList)>1) ? "s" : "") + . " (representing a total fleet power of " . number_format($fPower) + . "):

    "; + } + } else { + $str = "Sir! Our planet " . utf8entities($this->data['p_name']) + . ", has successfully self-destructed using a wormhole supernova!
    "; + + $ffp = $this->data['f_power']; $efp = $this->data['e_power']; + if ($ffp != 0) { + $str .= "Allied forces lost a total of " . number_format($ffp) + . " fleet power"; + if ($efp != 0) { + $str .= ", while enemy forces lost a total of " . number_format($efp) . " fleet power"; + } + $str .= "."; + } else if ($efp != 0) { + $str .= "A total fleet power of " . number_format($efp) . " was lost by the enemy when the planet exploded."; + } + if (count($fList)) { + $str .= " We lost the following fleet" . ((count($fList)>1) ? "s" : "") + . " (representing a total fleet power of " . number_format($fPower) + . ") during the event:

    "; + } + } + + foreach ($fList as $f) { + $str .= "Fleet " . utf8entities($f['name']) . ": " + . $this->getFleetComposition($f) . "; fleet power: " + . number_format($f['power']) . "
    "; + } + + $pinfo = $this->planets->call('byId', $this->data['p_id']); + $str .= "
    The remains of the planet have been named " + . utf8entities($pinfo['name']) . "."; + + return $str; + } +} + +?> diff --git a/scripts/game/beta5/planet/library.inc b/scripts/game/beta5/planet/library.inc new file mode 100644 index 0000000..ebfa128 --- /dev/null +++ b/scripts/game/beta5/planet/library.inc @@ -0,0 +1,176 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + } + + + // Returns data regarding a planet with a specified identifier + function byId($id) { + if (is_null($id)) { + logText("****** BUG: planet::byId(null) called", LOG_ERR); + l::backtrace(); + return null; + } + $q = $this->db->query( + "SELECT p.id AS id,p.name AS name,s.x AS x,s.y AS y," + . "p.orbit AS orbit,s.prot AS prot,p.status AS status,p.owner AS owner," + . "p.pop AS pop,p.max_pop AS max_pop,p.happiness AS happiness,p.ifact AS ifact," + . "p.mfact AS mfact,p.turrets AS turrets,p.abandon AS abandon," + . "p.beacon AS beacon,p.bh_prep AS bh_prep,p.beacon AS beacon," + . "p.sale AS sale,p.bh_unhappiness AS bh_unhappiness,p.vacation AS vacation," + . "s.id AS system,p.built_probe AS built_probe,p.corruption AS corruption," + . "(UNIX_TIMESTAMP(NOW()) - p.renamed > 1296000) AS rename " + . "FROM planet p, system s " + . "WHERE p.id=$id AND s.id=p.system" + ); + if (!($q && dbCount($q) == 1)) { + logText("****** BUG: planet::byId($id) called (orbit not found)"); + return null; + } + $r = dbFetchHash($q); + $r["rename"] = ($r['rename'] == 't') ? 1 : 0; + + if (is_null($this->pOwner[$r['id']])) { + $this->pOwner[$r['id']] = $r['owner']; + } + + return $r; + } + + + // Returns data regarding a planet with a specified name + function byName($n) { + $q = $this->db->query("SELECT id FROM planet WHERE LOWER(name)='".strtolower(addslashes($n))."'"); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($id) = dbFetchArray($q); + + return $this->byId($id); + } + + + // Checks whether a planet name exists, either in the game or in the + // registration queue + public function nameExists($name) { + $okName = strtolower(addslashes($name)); + $q = $this->db->query( + "SELECT id FROM planet WHERE LOWER(name) = '$okName' " + . "UNION SELECT account AS id FROM planet_reg_queue " + . "WHERE LOWER(p_name) = '$okName'" + ); + return (dbCount($q) > 0); + } + + + // Gets a planet's owner + function getOwner($pid) { + if (!is_null($this->pOwner[$pid])) { + return $this->pOwner[$pid]; + } + $q = $this->db->query("SELECT owner FROM planet WHERE id=$pid"); + list($r) = dbFetchArray($q); + return $r; + } + + + // Destroy factories on a planet + function destroyFactories($id, $nb, $t) { + $this->db->query("UPDATE planet SET ${t}fact = ${t}fact - $nb WHERE id = $id"); + $this->lib->call('updateFactHistory', $id, $t == "m", -$nb); + $this->lib->call('updateHappiness', $id); + } + + + // Updates a planet's factory history and delete old history records + function updateFactHistory($pid, $mil, $nb) { + $this->db->query("INSERT INTO facthist (planet, is_military, change) VALUES($pid, " . ($mil?'TRUE':'FALSE') . ", $nb)"); + $this->db->query("DELETE FROM facthist WHERE UNIX_TIMESTAMP(NOW()) - moment > 86400"); + } + + + // Abandon a planet + function setAbandon($pid, $start = true) { + if ($start) { + $q = $this->db->query("SELECT time_required FROM planet_abandon_time WHERE id = $pid"); + list($time) = dbFetchArray($q); + if (is_null($time)) { + l::warn("**** BUG: Planet #{$pid} has no abandon record, creating one with 24h"); + $this->db->query( + "INSERT INTO planet_abandon_time (id, time_required) " + . "VALUES ($pid, 24)" + ); + $time = 24; + } + } else { + $time = "NULL"; + } + $this->db->query( + "UPDATE planet SET abandon = $time WHERE id = $pid" + ); + } + + + // Destroy a planet + function setBoom($pid, $start = true) { + $this->db->query("UPDATE planet SET bh_prep=".($start?"3":"NULL")." WHERE id=$pid"); + } + + // Checks if a planet can be allocated + function canAllocate($planetID) { + // Get the planet + $planet = $this->lib->call('byId', $planetID); + + // Compute optimal factories and turrets + $x = ($planet['pop'] - 2000) / 48000; + $optFact = ($planet['pop'] / 30) - 754 * $x * $x; + $optTurrets = ($planet['pop'] / 22) - 510 * $x * $x; + + // Check if the planet is OK already + $facts = $planet['ifact'] + $planet['mfact']; + $turrets = $planet['turrets']; + return ($facts <= $optFact * 1.1 && $turrets <= $optTurrets && $planet['corruption'] < 3200); + } + + // Gets the list of fleets a beacon detects + function getDetectedFleets($planetID) { + $detected = array(); + + $q = $this->db->query("SELECT i_level,fl_size,fl_owner FROM beacon_detection WHERE planet = $planetID"); + while ($r = dbFetchHash($q)) { + if ($r['i_level'] == 4) { + $r['owner'] = $this->game->getLib('beta5/player')->call('getName', $r['fl_owner']); + } + array_push($detected, $r); + } + + return $detected; + } +} + +?> diff --git a/scripts/game/beta5/planet/library/buildFactories.inc b/scripts/game/beta5/planet/library/buildFactories.inc new file mode 100644 index 0000000..3ecefd0 --- /dev/null +++ b/scripts/game/beta5/planet/library/buildFactories.inc @@ -0,0 +1,27 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->rules = $this->lib->game->getLib('beta5/rules'); + } + + + // Build factories on a planet + function run($id, $nb, $t) { + $q = $this->db->query("SELECT owner FROM planet WHERE id = $id"); + list($uid) = dbFetchArray($q); + $ru = $this->rules->call('get', $uid); + + $this->db->query("UPDATE planet SET ${t}fact = ${t}fact + $nb WHERE id = $id"); + $cost = $ru[$t.'f_cost'] * $nb; + $this->db->query("UPDATE player SET cash = cash - $cost WHERE id = $uid"); + + $this->lib->call('updateFactHistory', $id, $t == "m", $nb); + $this->lib->call('updateHappiness', $id); + } +} + +?> diff --git a/scripts/game/beta5/planet/library/checkBuildFactories.inc b/scripts/game/beta5/planet/library/checkBuildFactories.inc new file mode 100644 index 0000000..f79ccd5 --- /dev/null +++ b/scripts/game/beta5/planet/library/checkBuildFactories.inc @@ -0,0 +1,60 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + $this->players = $this->game->getLib('beta5/player'); + $this->rules = $this->game->getLib('beta5/rules'); + } + + + // Check a planet to see whether it's possible to build factories + function run($id, $nb, $t) { + // Get planet data + $q = $this->db->query("SELECT ifact+mfact,sale,pop,owner FROM planet WHERE id=$id"); + list($nf, $sale, $pop, $owner) = dbFetchArray($q); + logText("checkBuildFact($id, $nb, $t) : nf=$nf, sale=$sale owner=$owner"); + + // Get owner rules + $ru = $this->rules->call('get', $owner); + $cost = $ru["{$t}f_cost"] * $nb; + $pinf = $this->players->call('get', $owner); + if ($pinf['cash'] < $cost) { + return 1; + } + + // Prevent building more factories if the planet's for sale + if (!is_null($sale)) { + return 2; + } + + // Get the amount of factories built / destroyed in the past "24h" (actually, the interval between + // day ticks) + $interval = $this->game->ticks['day']->interval; + $q = $this->db->query( + "SELECT SUM(change) FROM facthist " + . "WHERE planet=$id AND UNIX_TIMESTAMP(NOW()) - moment <= $interval" + ); + if (dbCount($q)) { + list($ch24) = dbFetchArray($q); + } else { + $ch24 = 0; + } + $onf = $nf - $ch24; + + // We can't build more than optFact*1.5 factories every 24h + $x = ($pop - 2000) / 48000; $allowed = round(1.5 * (($pop / 30) - 754 * $x * $x)); + if ($nf + $nb > $onf + $allowed) { + return 3; + } + + // FIXME : siege + + return 0; + } +} + +?> diff --git a/scripts/game/beta5/planet/library/checkDestroyFactories.inc b/scripts/game/beta5/planet/library/checkDestroyFactories.inc new file mode 100644 index 0000000..bb2df7c --- /dev/null +++ b/scripts/game/beta5/planet/library/checkDestroyFactories.inc @@ -0,0 +1,74 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + } + + + // Check a planet to see whether it's possible to destroy factories + function run($id, $nb, $t) { + $q = $this->db->query("SELECT ${t}fact,sale FROM planet WHERE id = $id"); + list($nf,$sale) = dbFetchArray($q); + logText("checkDestFact($id, $nb, $t) : nf=$nf, sale=$sale"); + if ($nf < $nb) { + return 1; + } + if (!is_null($sale)) { + return 4; + } + if ($t == 'm' && $nf == $nb) { + return 2; + } + + // Get changes for this type of factories within the past "2h" (actually, two hour ticks) + $interval = $this->game->ticks['hour']->interval * 2; + $q = $this->db->query( + "SELECT SUM(change) FROM facthist " + . "WHERE planet=$id AND UNIX_TIMESTAMP(NOW()) - moment <= $interval " + . "AND " . ($t == 'm' ? "" : "NOT ") . "is_military" + ); + if (dbCount($q)) { + list($ch2) = dbFetchArray($q); + if (is_null($ch2)) { + $ch2 = 0; + } + } else { + $ch2 = 0; + } + if ($ch2 > 0) { + return 30; + } + + // Get changes for this type of factories within the past 24h + $interval = $this->game->ticks['day']->interval; + $q = $this->db->query( + "SELECT SUM(change) FROM facthist " + . "WHERE planet=$id AND UNIX_TIMESTAMP(NOW()) - moment <= $interval " + . "AND " . ($t == 'm' ? "" : "NOT ") . "is_military" + ); + if (dbCount($q)) { + list($ch24) = dbFetchArray($q); + if (is_null($ch24)) { + $ch24 = 0; + } + } else { + $ch24 = 0; + } + + logText("checkDestFact($id, $nb, $t) : ch24=$ch24 ch2=$ch2"); + $onf = $nf - $ch24; + if (($onf > 100 && $nf - $nb < floor($onf * 0.9)) || ($onf < 100 && ($nf - $nb < 0 || $nf - $nb < $onf - 10)) ) { + return 3; + } + + // FIXME : siege + + return 0; + } +} + +?> diff --git a/scripts/game/beta5/planet/library/destroyTurrets.inc b/scripts/game/beta5/planet/library/destroyTurrets.inc new file mode 100644 index 0000000..b384dd6 --- /dev/null +++ b/scripts/game/beta5/planet/library/destroyTurrets.inc @@ -0,0 +1,52 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + } + + + // Try to destroy turrets on a planet + function run($id, $nb) { + $q = $this->db->query("SELECT turrets,sale FROM planet WHERE id = $id"); + list($nt,$sale) = dbFetchArray($q); + if ($nt < $nb) { + return 1; + } + if (!is_null($sale)) { + return 3; + } + + $interval = $this->game->ticks['day']->interval; + $q = $this->db->query( + "SELECT SUM(change) FROM turhist " + . "WHERE planet = $id AND UNIX_TIMESTAMP(NOW()) - moment < $interval" + ); + if (dbCount($q)) { + list($ch) = dbFetchArray($q); + if (is_null($ch)) { + $ch = 0; + } + } else { + $ch = 0; + } + $ont = $nt - $ch; + if ($nt - $nb < floor($ont * 0.8)) { + return 2; + } + + // FIXME : siege + + $tm = time(); + $this->db->query("UPDATE planet SET turrets = turrets - $nb WHERE id = $id"); + $this->db->query("INSERT INTO turhist VALUES ($id,$tm,-$nb)"); + $this->lib->call('updateHappiness', $id); + + return 0; + } +} + +?> diff --git a/scripts/game/beta5/planet/library/detectFleets.inc b/scripts/game/beta5/planet/library/detectFleets.inc new file mode 100644 index 0000000..e9cc10e --- /dev/null +++ b/scripts/game/beta5/planet/library/detectFleets.inc @@ -0,0 +1,241 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + + $this->ecm = $this->game->getLib('beta5/ecm'); + $this->msgs = $this->game->getLib('beta5/msg'); + $this->players = $this->game->getLib('beta5/player'); + $this->fleets = $this->game->getLib('beta5/fleet'); + } + + + public function run($planet) { + // Get information about the planet + $q = $this->db->query("SELECT owner, beacon, name FROM planet WHERE id = $planet"); + list($owner, $beacon, $name) = dbFetchArray($q); + + // If there's no beacon or no owner, we're done + $reqLevel = $this->game->params['fakebeacons'] ? 1 : 2; + if (is_null($owner) || $beacon < $reqLevel) { + return; + } + + // Get fleets that are in Hyperspace stand-by above the planet + $hsFleets = $this->getHSFleets($planet); + if (empty($hsFleets)) { + return; + } + + // Store planet name + if (is_null($this->pNames)) { + $this->pNames = array(); + } + if (is_null($this->pNames[$planet])) { + $this->pNames[$planet] = $name; + } + + // Get the planet owner's trusted allies and alliance + $allies = $this->getAllies($owner); + $alliance = $this->getAlliance($owner); + + // Check each fleet for detection + foreach ($hsFleets as $fleet) { + if ($fleet['owner'] == $owner || in_array($fleet['owner'], $allies)) { + continue; + } + + $this->tryDetect($planet, $owner, $alliance, $fleet); + } + } + + + private function getHSFleets($planet) { + $q = $this->db->query( + "SELECT f.id, f.owner, w.time_spent FROM fleet f, hs_wait w " + . "WHERE f.location IS NULL AND f.moving IS NULL " + . "AND w.id = f.waiting AND w.drop_point = $planet " + . "AND f.id NOT IN (SELECT fleet FROM beacon_detection WHERE planet = $planet)" + ); + + $fleets = array(); + while ($r = dbFetchHash($q)) { + array_push($fleets, $r); + } + + return $fleets; + } + + + private function getAllies($player) { + $rawAllies = $this->players->call('getAllies', $player); + $allies = array(); + foreach ($rawAllies as $ally) { + array_push($allies, $ally['id']); + } + + return $allies; + } + + private function getAlliance($player) { + if (is_null($this->pAlliances)) { + $this->pAlliances = array(); + } + if (is_null($this->pAlliances[$player])) { + $q = $this->db->query("SELECT alliance FROM player WHERE id = $player AND a_status = 'IN'"); + if (dbCount($q)) { + list($alliance) = dbFetchArray($q); + } else { + $alliance = null; + } + $this->pAlliances[$player] = $alliance; + } + return $this->pAlliances[$player]; + } + + + private function tryDetect($planet, $owner, $alliance, $fleet) { + // Compute probability of detection according to time spent + $tsProb = ($fleet['time_spent'] >= 3) ? 1 : (0.5 + $fleet['time_spent'] / 6); + + // Get ECM/ECCM levels + $ecm = $this->getECMLevel($fleet['owner']); + $eccm = $this->getECCMLevel($owner); + + // Compute probability of detection based on ECM/ECCM + $ecmProb = (1 + $this->ecm->call('getInformationLevel', $ecm, $eccm)) * 0.2; + + // Get fleet owner's alliance + $fAlliance = $this->getAlliance($fleet['owner']); + + // Actual probability + $prob = $tsProb * $ecmProb * 0.8; + if (!is_null($alliance) && $fAlliance === $alliance) { + $prob += 1/3; + } + $prob = min(1, $prob); + + // Log it + logText("Planet #$planet: probability of detecting fleet #{$fleet['id']} is $prob " + . "(tsProb = $tsProb, ecmProb = $ecmProb)"); + + // Try detecting it + $rnd = rand(0, 100000) / 100000; + if ($rnd < $prob) { + $this->fleetDetected($planet, $owner, $fleet, $ecm, $eccm); + } + } + + private function getECCMLevel($player) { + if (is_null($this->eccmLevel)) { + $q = $this->db->query("SELECT value FROM rule WHERE player = $player AND name = 'eccm_level'"); + list($this->eccmLevel) = dbFetchArray($q); + } + return $this->eccmLevel; + } + + private function getECMLevel($player) { + if (is_null($this->ecmLevel)) { + $this->ecmLevel = array(); + } + if (is_null($this->ecmLevel[$player])) { + $q = $this->db->query("SELECT value FROM rule WHERE player = $player AND name = 'ecm_level'"); + list($this->ecmLevel[$player]) = dbFetchArray($q); + } + return $this->ecmLevel[$player]; + } + + + private function fleetDetected($planet, $owner, $fleet, $ecm, $eccm) { + // Get the information level + $iLevel = $this->ecm->call('getInformationLevel', $ecm, $eccm); + + // Detected fleet size + if ($iLevel > 0) { + $fleetSize = $this->computeDetectedSize($iLevel, $fleet['id']); + } else { + $fleetSize = null; + } + logText("Planet #$planet: fleet #{$fleet['id']} detected at level $iLevel" + . ($iLevel == 0 ? "" : " (size: $fleetSize)")); + + // Did we detect the owner? + if ($iLevel == 4) { + $fleetOwner = $fleet['owner']; + $fleetOwnerName = $this->players->call('getName', $fleetOwner); + } else { + $fleetOwner = $fleetOwnerName = null; + } + + // Insert into the status table + $sQuery = "INSERT INTO beacon_detection(planet, fleet, i_level"; + if ($iLevel > 0) { + $sQuery .= ", fl_size"; + if ($iLevel == 4) { + $sQuery .= ", fl_owner"; + } + } + $sQuery .= ") VALUES ($planet, {$fleet['id']}, $iLevel"; + if ($iLevel > 0) { + $sQuery .= ", $fleetSize"; + if ($iLevel == 4) { + $sQuery .= ", $fleetOwner"; + } + } + $this->db->query("$sQuery)"); + + // Send messages + $this->msgs->call('send', $owner, 'detect', array( + "planet" => $planet, + "p_name" => $this->pNames[$planet], + "is_owner" => 'f', + "i_level" => $iLevel, + "fl_size" => $fleetSize, + "flo_id" => $fleetOwner, + "flo_name" => $fleetOwnerName + )); + $this->msgs->call('send', $fleet['owner'], 'detect', array( + "planet" => $planet, + "p_name" => $this->pNames[$planet], + "is_owner" => 't', + "i_level" => $iLevel + )); + } + + + private function computeDetectedSize($iLevel, $fleet) { + // Compute the actual fleet size + $fData = $this->fleets->call('get', $fleet); + $actualFleetSize = $this->fleets->call('getPower', $fData['owner'], $fData['gaships'], + $fData['fighters'], $fData['cruisers'], $fData['bcruisers']); + + // Compute the detected fleet size + if ($iLevel >= 3) { + $variation = 0; + } elseif ($iLevel == 2) { + $variation = rand(5, 10) / 100; + } elseif ($iLevel == 1) { + $variation = rand(20, 30) / 100; + } + $variation = rand(0,1) ? $variation : (-$variation); + return round($actualFleetSize * ($variation + 1)); + } +} + +?> diff --git a/scripts/game/beta5/planet/library/getIncome.inc b/scripts/game/beta5/planet/library/getIncome.inc new file mode 100644 index 0000000..cfbf5fc --- /dev/null +++ b/scripts/game/beta5/planet/library/getIncome.inc @@ -0,0 +1,52 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->rules = $this->lib->game->getLib('beta5/rules'); + } + + + // Computes a planet's income + function run($owner, $pop = null, $happ = null, $ifact = null, $mfact = null, + $turrets = null, $corruption = null) { + + if (is_null($pop)) { + $info = $owner; + $owner = $info['owner']; + $pop = $info['pop']; + $happ = $info['happiness']; + $ifact = $info['ifact']; + $mfact = $info['mfact']; + $turrets = $info['turrets']; + $corruption = $info['corruption']; + } + + $r = $this->rules->call('get', $owner); + if (is_null($r)) { + return array(); + } + $biFact = ($happ >= 20) ? 1 : ($happ / 20); + $bi = floor($pop * $r['base_income'] * $biFact); + $ii = $ifact * $r['if_productivity'] * $r['if_productivity_factor']; + $fc = ($ifact + $mfact) * $r['factory_cost']; + $tc = $turrets * $r['turret_cost']; + + $ti = $bi + $ii; + $cf = $corruption / 32000; + if ($cf > .1) { + $cf = $cf - .1; + $cl = ceil($ti * $cf); + } else { + $cl = 0; + } + + $tot = $ti - $fc - $tc - $cl; + + return array($tot, $bi, $ii, $fc, $tc, $cl); + } +} + +?> diff --git a/scripts/game/beta5/planet/library/getPower.inc b/scripts/game/beta5/planet/library/getPower.inc new file mode 100644 index 0000000..a70138d --- /dev/null +++ b/scripts/game/beta5/planet/library/getPower.inc @@ -0,0 +1,36 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->rules = $this->lib->game->getLib('beta5/rules'); + } + + + function run($pid, $turrets = null) { + if (is_null($turrets)) { + $q = $this->db->query("SELECT owner,turrets FROM planet WHERE id=$pid"); + if (!($q && dbCount($q) == 1)) { + return 0; + } + list($owner,$turrets) = dbFetchArray($q); + } else { + $owner = $pid; + } + + if (is_null($this->ePower[$owner])) { + $rules = $this->rules->call('get', $owner); + if (is_null($rules)) { + return 0; + } + $this->ePower[$owner] = floor($rules['turret_power'] * $rules['effective_fleet_power'] / 100); + } + + return $turrets * $this->ePower[$owner]; + } +} + +?> diff --git a/scripts/game/beta5/planet/library/getStats.inc b/scripts/game/beta5/planet/library/getStats.inc new file mode 100644 index 0000000..10c8a1a --- /dev/null +++ b/scripts/game/beta5/planet/library/getStats.inc @@ -0,0 +1,37 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns data regarding current planets + function run($pid) { + $q = $this->db->query( + "SELECT COUNT(*), SUM(pop), SUM(ifact) + SUM(mfact), SUM(turrets), SUM(happiness), SUM(corruption) " + . " FROM planet WHERE owner = $pid GROUP BY owner" + ); + $row = dbFetchArray($q); + if (!$row) { + return array(0, 'N/A', 0, 'N/A', 0, 'N/A', 0, 'N/A', 0, 'N/A'); + } + $c = $row[0]; + $pa = floor($row[1] / $c); + $fa = floor($row[2] / $c); + $ta = floor($row[3] / $c); + $ha = floor($row[4] / $c); + $ca = round($row[5] / ($c*320)); + $q = $this->db->query( + "SELECT p.id FROM planet p, fleet f " + . "WHERE p.owner = $pid AND f.location = p.id AND f.attacking " + . "GROUP BY p.id" + ); + $ua = dbCount($q); + return array($c, $ha, $row[1], $pa, $row[2], $fa, $row[3], $ta, $ua, $ca); + } +} + +?> diff --git a/scripts/game/beta5/planet/library/ownerChange.inc b/scripts/game/beta5/planet/library/ownerChange.inc new file mode 100644 index 0000000..7d9374f --- /dev/null +++ b/scripts/game/beta5/planet/library/ownerChange.inc @@ -0,0 +1,44 @@ +lib = $lib; + $this->db = $lib->game->db; + $this->bq = $lib->game->getLib('beta5/bq'); + } + + function run($planet, $newOwner = null, $noVacation = 'NO') { + $info = $this->lib->call('byId', $planet); + if (is_null($info)) { + logText("beta5::planet::ownerChange(): planet '$planet' not found", LOG_WARNING); + return; + } + + $this->bq->call('flush', $planet); + $rqs = is_null($info['owner']) ? "" : (", renamed = " . time() . " "); + $this->db->query( + "UPDATE planet " + . "SET beacon = 0, built_probe = FALSE, probe_policy = NULL, abandon = NULL, " + . "vacation = '$noVacation', bh_prep = NULL, sale = NULL, " + . "owner = " . (is_null($newOwner) ? "NULL" : $newOwner) . $rqs + . "WHERE id = $planet" + ); + + if (is_null($newOwner)) { + $this->db->query("DELETE FROM planet_abandon_time WHERE id = $planet"); + } elseif (is_null($info['owner'])) { + $this->db->query( + "INSERT INTO planet_abandon_time (id, time_required) " + . "VALUES ($planet, 6)" + ); + } else { + $this->db->query( + "UPDATE planet_abandon_time SET time_required = 6 WHERE id = $planet" + ); + } + + $this->lib->call('updateHappiness', $planet); + return $this->lib->call('updateMaxPopulation', $planet, $info['owner'], $newOwner); + } +} diff --git a/scripts/game/beta5/planet/library/rename.inc b/scripts/game/beta5/planet/library/rename.inc new file mode 100644 index 0000000..d0c1432 --- /dev/null +++ b/scripts/game/beta5/planet/library/rename.inc @@ -0,0 +1,53 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Renames a planet + function run($pid, $name) { + // Check the old name + $n = addslashes($name); + $q = $this->db->query("SELECT name,owner FROM planet WHERE id=$pid"); + list($oName,$oid) = dbFetchArray($q); + if ($oName == $name) { + return; + } + + // Get the list of players who have fleets or probes + // orbitting or moving towards the planet + $pl = array(); + $q = $this->db->query("SELECT DISTINCT owner FROM fleet WHERE location=$pid" . (is_null($oid) ? "" : " AND owner<>$oid")); + while ($r = dbFetchArray($q)) { + $pl[$r[0]] = 'ORBIT'; + } + $q = $this->db->query("SELECT DISTINCT f.owner FROM fleet f,moving_object o " + . "WHERE f.location IS NULL AND f.moving=o.id AND o.m_to=$pid" + . (is_null($oid) ? "" : " AND f.owner<>$oid")); + while ($r = dbFetchArray($q)) { + $pl[$r[0]] = 'MOVE'; + } + // FIXME: probes + + // Send messages to the players + $on = addslashes($oName); + $tm = time(); + foreach ($pl as $plId => $st) { + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new)" + ." VALUES($plId,$tm,'rename','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message " + . "WHERE player=$plId AND sent_on=$tm AND mtype='rename' " + . "ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_rename VALUES($mid,$pid,'$st','$on','$n')"); + } + + $this->db->query("UPDATE planet SET name='$n',renamed=$tm,mod_check=FALSE WHERE id=$pid"); + } +} + +?> diff --git a/scripts/game/beta5/planet/library/restoreNeutral.inc b/scripts/game/beta5/planet/library/restoreNeutral.inc new file mode 100644 index 0000000..7da3a0d --- /dev/null +++ b/scripts/game/beta5/planet/library/restoreNeutral.inc @@ -0,0 +1,114 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + } + + + public function run($planetID) { + // Get the planet + $planet = $this->lib->call('byId', $planetID); + + // Compute optimal factories and turrets + $x = ($planet['pop'] - 2000) / 48000; + $optFact = ($planet['pop'] / 30) - 754 * $x * $x; + $optTurrets = ($planet['pop'] / 22) - 510 * $x * $x; + + // Check if the planet is OK already + $facts = $planet['ifact'] + $planet['mfact']; + $turrets = $planet['turrets']; + if ($facts >= $optFact * 0.9 && $facts <= $optFact * 1.1 && $turrets <= 0.6 * $optTurrets) { + return true; + } + + // If the planet's factories are outside the range, improve that + if ($facts < $optFact * 0.9) { + $facts = $this->buildFactories($planet, $optFact); + } else if ($facts > $optFact * 1.1) { + $facts = $this->destroyFactories($planet, $optFact); + } + + // If the planet's turrets are outside the range, improve that + if ($turrets > 0.5 * $optTurrets) { + $turrets = $this->destroyTurrets($planet, $optTurrets * 0.5); + } + + // Recompute happiness + $this->lib->call('updateHappiness', $planet['id']); + + return ($facts >= $optFact * 0.9 && $facts <= $optFact * 1.1 && $turrets <= 0.6 * $optTurrets); + } + + + private function buildFactories($planet, $optimal) { + // Compute how many factories we should build + $total = $planet['ifact'] + $planet['mfact']; + $toBuild = ($optimal - $total) * 0.02; + + // Use ratios to determine which types to build + $mFacts = ceil($toBuild * $planet['ifact'] / $total); + $iFacts = ceil($toBuild * $planet['mfact'] / $total); + + // Update the planet + $this->db->query("UPDATE planet SET mfact = mfact + $mFacts, ifact = ifact + $iFacts " + . "WHERE id = {$planet['id']}"); + + // Log it + logText("NEUTRAL RESTORE (#{$planet['id']}): +$mFacts MFs/$iFacts IFs ($total => $optimal)"); + + return $total + $mFacts + $iFacts; + } + + + private function destroyFactories($planet, $optimal) { + // Compute how many factories we should destroy + $total = $planet['ifact'] + $planet['mfact']; + $toDestroy = ($total - $optimal) * 0.05; + + // Use ratios to determine which types to destroy + $mFacts = ceil($toDestroy * $planet['mfact'] / $total); + $iFacts = ceil($toDestroy * $planet['ifact'] / $total); + + // Update the planet + $this->db->query("UPDATE planet SET mfact = mfact - $mFacts, ifact = ifact - $iFacts " + . "WHERE id = {$planet['id']}"); + + // Log it + logText("NEUTRAL RESTORE (#{$planet['id']}): -$mFacts MFs/$iFacts IFs ($total => $optimal)"); + + return $total - ( $mFacts + $iFacts ); + } + + + private function destroyTurrets($planet, $target) { + // Compute the amount of turrets to destroy + $turrets = $planet['turrets']; + $toDestroy = ceil(($turrets - $target) * 0.1); + + // Update the planet + $this->db->query("UPDATE planet SET turrets = turrets - $toDestroy WHERE id = {$planet['id']}"); + + // Log it + logText("NEUTRAL RESTORE (#{$planet['id']}): -$toDestroy turrets ($turrets => $target)"); + + return $total - $toDestroy; + } +} + +?> diff --git a/scripts/game/beta5/planet/library/updateHappiness.inc b/scripts/game/beta5/planet/library/updateHappiness.inc new file mode 100644 index 0000000..d1efce8 --- /dev/null +++ b/scripts/game/beta5/planet/library/updateHappiness.inc @@ -0,0 +1,150 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->players = $this->lib->game->getLib('beta5/player'); + $this->fleets = $this->lib->game->getLib('beta5/fleet'); + $this->rules = $this->lib->game->getLib('beta5/rules'); + $this->msg = $this->lib->game->getLib('beta5/msg'); + $this->main = $this->lib->game->getLib(); + } + + // Updates happiness for a planet + function run($id) { + $pinf = $this->lib->call('byId', $id); + if ($pinf['status'] != 0) { + return 0; + } + $oid = $pinf['owner']; + $rules = $this->rules->call('get', $oid); + + if (!is_null($oid)) { + $q = $this->db->query("SELECT bh_unhappiness FROM player WHERE id=$oid"); + list($bhu) = dbFetchArray($q); + } else { + $bhu = 0; + } + $uf = ($bhu * 7 + $rules['unhappiness_factor']) / 100; + + // Factory happiness + $x = ($pinf['pop'] - 2000) / 48000; + $optFact = ($pinf['pop'] / 30) - 754 * $x * $x; $bFHap = 0.9; + $x = $pinf['mfact'] + $pinf['ifact']; + if ($x <= $optFact) { + $tmp = ($bFHap - 1) / $optFact; + $hapFact = $x * $x * $tmp / $optFact - 2 * $x * $tmp + $bFHap; + } elseif ($x <= 3 * $optFact) { + $tmp = 4 * $optFact; + $hapFact = - $x * $x / (2 * $tmp * $optFact) + $x / $tmp + 7 / 8; + } else { + $tmp = 2 * ($x - 3 * $optFact) / $optFact; + if ($tmp < 500) { + $hapFact = 1 - exp($tmp) / (exp($tmp) + 1); + } else { + $hapFact = 0; + } + } + + // Turret happiness + $x = ($pinf['pop'] - 2000) / 48000; + $optTurrets = ($pinf['pop'] / 22) - 510 * $x * $x; $bTHap = 0.7; + $x = $pinf['turrets']; + if ($x <= $optTurrets) { + $tmp = ($bTHap - 1) / $optTurrets; + $hapTurrets = $x * $x * $tmp / $optTurrets - 2 * $x * $tmp + $bTHap; + } elseif ($x <= 4 * $optTurrets) { + $tmp = 9 * $optTurrets; + $hapTurrets = - $x * $x / (2 * $tmp * $optTurrets) + $x / $tmp + 17 / 18; + } else { + $tmp = 2 * ($x - 4 * $optTurrets) / $optTurrets; + if ($tmp < 500) { + $hapTurrets = 1 - exp($tmp) / (exp($tmp) + 1); + } else { + $hapTurrets = 0; + } + } + $hapTurrets = (1 + $hapTurrets * 2) / 3; + + // Get average fleet power + if (is_null($this->avgFPower)) { + $q = $this->db->query("SELECT SUM(gaships),SUM(fighters),SUM(cruisers),SUM(bcruisers) FROM fleet"); + list($g,$f,$c,$b) = dbFetchArray($q); + $np = $this->main->call('getPlayerCount'); + if ($np) { + $g /= $np; $f /= $np; $c /= $np; $b /= $np; + $fpAvg = $g * 5 + $f * 10 + $c * 40 + $b * 80; + } else { + $fpAvg = 0; + } + $this->avgFPower = $fpAvg; + } else { + $fpAvg = $this->avgFPower; + } + + // Get total and local fleet powers + if (!is_null($oid)) { + $q = $this->db->query("SELECT SUM(gaships),SUM(fighters),SUM(cruisers),SUM(bcruisers) FROM fleet WHERE location=$id AND owner=$oid"); + list($g,$f,$c,$b) = dbFetchArray($q); + $fpLoc = $this->fleets->call('getPower', $oid, $g, $f, $c, $b); + $fpTot = $this->players->call('getPower', $oid); + } else { + $fpLoc = $fpTot = 0; + } + + // Fleet happiness + if ($fpAvg) { + $hapFTot = exp($fpTot / $fpAvg) / (exp($fpTot / $fpAvg) + 1); + if ($fpTot > 0) { + $hapFLoc = $hapFTot * $fpLoc / $fpTot; + } else { + $hapFLoc = 0; + } + } else { + $hapFLoc = 0; + } + $hapFLoc = (9 + $hapFLoc) / 10; + + // Compute local happiness + $hapTot = 100 * $hapFact * $hapTurrets * $hapFLoc; + $hapTot -= $hapTot * $pinf['bh_unhappiness'] / 30; + $hapTot = max(0,min(100,round($hapTot))); + + // Compute modifier from planet count and unhappiness factor + $x = min($this->players->call('getPlanetCount', $oid), 40); + $pcMod = pow(1 - 0.63 * exp(($x-18)/2) / (exp(($x-18)/2)+1) - log($x) / 10, $uf*$uf); + $hapTot = round($hapTot * $pcMod); + if ($hapTot == 'NAN') { + $hapTot = 0; + } + + // Check for revolt + if (!is_null($pinf['owner'])) { + $send = false; + if ($pinf['happiness'] >= 20 && $hapTot < 20) { + $rStat = 'TRUE'; + $send = true; + } elseif ($pinf['happiness'] < 20 && $hapTot >= 20) { + $rStat = 'FALSE'; + $send = true; + } + if ($send) { + $tm = time(); + $player = $pinf['owner']; + $this->msg->call('send', $player, 'revolt', array( + 'planet' => $id, + 'pname' => $pinf['name'], + 'started' => $rStat + )); + } + } + + $this->db->query("UPDATE planet SET happiness=$hapTot WHERE id=$id"); + return $hapTot; + } +} + +?> diff --git a/scripts/game/beta5/planet/library/updateMaxPopulation.inc b/scripts/game/beta5/planet/library/updateMaxPopulation.inc new file mode 100644 index 0000000..d7c92e8 --- /dev/null +++ b/scripts/game/beta5/planet/library/updateMaxPopulation.inc @@ -0,0 +1,66 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->rules = $this->lib->game->getLib('beta5/rules'); + } + + + // Updates a planet's maximum population + function run($id, $formerOwner, $newOwner) { + // Get the current technological levels for both players + $r = $this->rules->call('get', $formerOwner); + $oldLevel = ($r['planet_max_pop'] - 10000) / 10000; + if ($formerOwner == $newOwner) { + $oldLevel --; + } + $r = $this->rules->call('get', $newOwner); + $newLevel = ($r['planet_max_pop'] - 10000) / 10000; + + // If the levels are equivalent, return + if ($oldLevel == $newLevel) { + return -1; + } + + // Get the planet's maximum populations for each tech level + $maxPops = $this->getMaxPopulations($id); + + // Get the planet's current population + $pinf = $this->lib->call('byId', $id); + $pop = $pinf['pop']; + + // If the population's greater than the new max. pop., fix the "real" max pop + $maxPop = $maxPops[$newLevel]; + if ($pop > $maxPop) { + $maxPop = $pop + rand(0, min(500, $maxPops[3] - $pop)); + } + + // Update the planet + $this->db->query("UPDATE planet SET max_pop=$maxPop WHERE id=$id"); + + return $maxPop; + } + + + // Get a planet's maximum populations + function getMaxPopulations($id) { + if (is_null($id)) { + return null; + } + if (!is_array($this->maxPops[$id])) { + $this->maxPops[$id] = array(); + $q = $this->db->query("SELECT max_pop FROM planet_max_pop WHERE planet=$id ORDER BY tech_level ASC"); + $i = 0; + while ($r = dbFetchArray($q)) { + $this->maxPops[$id][$i++] = $r[0]; + } + } + return $this->maxPops[$id]; + } +} + +?> diff --git a/scripts/game/beta5/planet/library/updateMilStatus.inc b/scripts/game/beta5/planet/library/updateMilStatus.inc new file mode 100644 index 0000000..a941456 --- /dev/null +++ b/scripts/game/beta5/planet/library/updateMilStatus.inc @@ -0,0 +1,107 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->ecm = $this->lib->game->getLib('beta5/ecm'); + $this->fleets = $this->lib->game->getLib('beta5/fleet'); + } + + function run($planet) { + // Get previous status + $q = $this->db->query("SELECT * FROM attacks WHERE planet=$planet"); + if ($q && dbCount($q)) { + $attack = dbFetchHash($q); + } else { + $attack = null; + } + + // Get current planet owner + $ownId = $this->lib->call('getOwner', $planet); + + // If it's neutral, we're done + if (is_null($ownId)) { + if (is_array($attack)) { + // No attack status for neutrals + $this->db->query("DELETE FROM attacks WHERE planet=$planet"); + } + return; + } + + // Select all fleets on the planet + $q = $this->db->query("SELECT owner,attacking,gaships,fighters,cruisers,bcruisers FROM fleet WHERE location=$planet"); + $fList = array(); $attPlayers = array(); $defPlayers = array(); + while ($r = dbFetchHash($q)) { + array_push($fList, $r); + if ($r['attacking'] == 't' && !in_array($r['owner'], $attPlayers)) { + array_push($attPlayers, $r['owner']); + } elseif ($r['attacking'] == 'f' && !in_array($r['owner'], $defPlayers)) { + array_push($defPlayers, $r['owner']); + } + } + + // Not under attack + if (!count($attPlayers)) { + // ... but were we before? + if (is_array($attack)) { + $this->db->query("DELETE FROM attacks WHERE planet=$planet"); + } + return; + } + + // Add planet owner to defending players if he's not there already + if (!in_array($ownId, $defPlayers)) { + array_push($defPlayers, $ownId); + } + + // Get max. ECM and ECCM levels + $q = $this->db->query("SELECT MAX(value) FROM rule WHERE name='ecm_level' AND player IN (".join(',',$attPlayers).")"); + list($ecm) = dbFetchArray($q); + $q = $this->db->query("SELECT MAX(value) FROM rule WHERE name='eccm_level' AND player IN (".join(',',$defPlayers).")"); + list($eccm) = dbFetchArray($q); + + // Compute fleet powers + $attPower = $defPower = 0; + foreach ($fList as $fl) { + $pow = $this->fleets->call('getPower', $fl['owner'], $fl['gaships'], $fl['fighters'], $fl['cruisers'], $fl['bcruisers']); + if ($fl['attacking'] == 'f') { + $defPower += $pow; + } else { + $attPower += $pow; + } + } + $defPower += $this->lib->call('getPower', $planet); + + // If situation hasn't changed, we're done + if (is_array($attack) && $attack['ecm_level'] == $ecm && $attack['eccm_level'] == $eccm && $attack['friendly'] == $defPower && $attack['enemy'] == $attPower) { + return; + } + + // Generate new information level + $ifl = $this->ecm->call('getInformationLevel', $ecm, $eccm); + + // If we weren't under attack before, we must add an entry + if (is_null($attack)) { + $qs = "INSERT INTO attacks VALUES($planet,$ecm,$eccm,$defPower,$attPower,"; + $qs .= dbBool($ifl == 4); + $vDP = $this->ecm->call('scrambleFleetPower', $ifl, $defPower); + $vAP = $this->ecm->call('scrambleFleetPower', $ifl, $attPower); + $qs .= ",$vDP,$vAP"; + $this->db->query("$qs)"); + return; + } + + // Update attack status + $qs = "UPDATE attacks SET v_players=" . dbBool($ifl == 4) . ",friendly=$defPower,enemy=$attPower,"; + $vDP = $this->ecm->call('scrambleFleetPower', $ifl, $defPower); + $vAP = $this->ecm->call('scrambleFleetPower', $ifl, $attPower); + $qs .= "v_friendly=$vDP,v_enemy=$vAP,ecm_level=$ecm,eccm_level=$eccm WHERE planet=$planet"; + $this->db->query($qs); + } + + +} + +?> diff --git a/scripts/game/beta5/player/library.inc b/scripts/game/beta5/player/library.inc new file mode 100644 index 0000000..0809cb3 --- /dev/null +++ b/scripts/game/beta5/player/library.inc @@ -0,0 +1,206 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns the name of a player's first planet + function getFirstPlanet($id) { + list($name) = dbFetchArray($this->db->query("SELECT l.name FROM player p,planet l WHERE p.id=$id AND l.id=p.first_planet")); + return $name; + } + + // Checks whether a player is in another's enemy list + function isEnemy($pid, $aid) { + $q = $this->db->query("SELECT COUNT(*) FROM enemy_player WHERE player=$pid AND enemy=$aid"); + list($c) = dbFetchArray($q); + return ($c > 0); + } + + // Checks whether an alliance is in a player's enemy list + function isAllianceEnemy($pid, $aid) { + $q = $this->db->query("SELECT COUNT(*) FROM enemy_alliance WHERE player=$pid AND alliance=$aid"); + list($c) = dbFetchArray($q); + return ($c > 0); + } + + // Remove enemy players from a player's enemy list + function removeEnemy($pid, $eid) { + $this->db->query("DELETE FROM enemy_player WHERE player=$pid AND enemy=$eid"); + } + + // Remove enemy alliances from a player's enemy list + function removeEnemyAlliance($pid, $eid) { + $this->db->query("DELETE FROM enemy_alliance WHERE player=$pid AND alliance=$eid"); + } + + // Adds a player to the enemy list + function addEnemy($pid, $eid) { + $this->db->query("INSERT INTO enemy_player VALUES($pid,$eid)"); + $this->lib->call('makeEnemies', $pid, array($eid)); + } + + // Checks whether a player is in another's ally list + function isAlly($pid, $aid) { + $q = $this->db->query("SELECT COUNT(*) FROM trusted WHERE player=$pid AND friend=$aid"); + list($c) = dbFetchArray($q); + return ($c > 0); + } + + // Removes an ally from the list + function removeAlly($pid, $it) { + $this->db->query("DELETE FROM trusted WHERE player=$pid AND level=$it"); + } + + // Adds an ally to the list + function addAlly($pid, $aid) { + $q = $this->db->query("SELECT COUNT(*) FROM trusted WHERE player=$pid"); + list($all) = dbFetchArray($q); + $this->db->query("INSERT INTO trusted VALUES($pid,$all,$aid)"); + } + + // Adds a player to another's T.A. blacklist + function addTAListBan($pid, $bpid) { + $this->db->query("INSERT INTO trusted_ban VALUES($pid, $bpid)"); + } + + // Checks a player's T.A. blacklist for another player + function checkTAListBan($pid, $bpid) { + $q = $this->db->query("SELECT COUNT(*) FROM trusted_ban WHERE player=$pid AND ban_player=$bpid"); + if (!$q) { + return true; + } + list($n) = dbFetchArray($q); + return ($n == 1); + } + + // Removes a player from another's T.A. blacklist + function delTAListBan($pid, $bpid) { + $this->db->query("DELETE FROM trusted_ban WHERE player=$pid AND ban_player=$bpid"); + } + + // Marks a player as quitting + function setQuit($pid) { + logText("BETA5/QUIT: player #$pid set to abandon the game"); + $this->db->query("UPDATE player SET quit=".time()." WHERE id=$pid"); + if (is_array($this->lib->mainClass->players[$pid])) { + $this->lib->mainClass->players[$pid] = null; + } + } + + // Marks a player as no longer quitting + function cancelQuit($pid) { + logText("BETA5/QUIT: player #$pid no longer set to abandon the game"); + $this->db->query("UPDATE player SET quit=NULL WHERE id=$pid"); + if (is_array($this->lib->mainClass->players[$pid])) { + $this->lib->mainClass->players[$pid] = null; + } + } + + + // Updates happiness on all of a player's planets + function updateHappiness($id) { + $lib = $this->lib->game->getLib('beta5/planet'); + $pl = $this->lib->call('getPlanets', $id); + foreach (array_keys($pl) as $pid) { + $lib->call('updateHappiness', $pid); + } + } + + + // Update the player's first_planet if needed when he loses a planet + function losePlanet($player, $planet) { + if (is_null($player) || is_null($planet)) { + return; + } + + // Fetch the first_planet value + $q = $this->db->query("SELECT first_planet FROM player WHERE id = $player"); + if (!($q && dbCount($q))) { + return; + } + list($fpID) = dbFetchArray($q); + + // Is it the planet that has just been lost? + if ($fpID != $planet) { + return; + } + + // Does the player has other planets? + $q = $this->db->query("SELECT id FROM planet WHERE owner = $player AND id <> $planet LIMIT 1"); + if (!($q && dbCount($q))) { + return; + } + list($nFpID) = dbFetchArray($q); + + // Update first_planet + $this->db->query("UPDATE player SET first_planet = $nFpID WHERE id = $player"); + } + + // Update the player's first_planet if needed when he takes a planet + function takePlanet($player, $planet) { + if (is_null($player) || is_null($planet)) { + return; + } + + // Fetch the first_planet value + $q = $this->db->query("SELECT first_planet FROM player WHERE id = $player"); + if (!($q && dbCount($q))) { + return; + } + list($fpID) = dbFetchArray($q); + + // Is it the planet that has just been taken? + if ($fpID != $planet) { + return; + } + + // Does the player still have control on his first planet? + $q = $this->db->query("SELECT owner FROM planet WHERE id = $fpID AND owner <> $player"); + if (!($q && dbCount($q))) { + return; + } + + // Update first planet + $this->db->query("UPDATE player SET first_planet = $planet WHERE id = $player"); + } +} + +?> diff --git a/scripts/game/beta5/player/library/addEnemyAlliance.inc b/scripts/game/beta5/player/library/addEnemyAlliance.inc new file mode 100644 index 0000000..17991fa --- /dev/null +++ b/scripts/game/beta5/player/library/addEnemyAlliance.inc @@ -0,0 +1,23 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Adds an alliance to the enemy list + function run($pid, $eid) { + $this->db->query("INSERT INTO enemy_alliance VALUES($pid,$eid)"); + $q = $this->db->query("SELECT id FROM player WHERE alliance=$eid AND a_status='IN'"); + $al = array(); + while ($r = dbFetchArray($q)) { + array_push($al, $r[0]); + } + $this->lib->call('makeEnemies', $pid, $al); + } +} + +?> diff --git a/scripts/game/beta5/player/library/assign.inc b/scripts/game/beta5/player/library/assign.inc new file mode 100644 index 0000000..6419e42 --- /dev/null +++ b/scripts/game/beta5/player/library/assign.inc @@ -0,0 +1,53 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + $this->planets = $this->game->getLib('beta5/planet'); + } + + + // Assign a planet to a player + function run($pid, $planet) { + $q = $this->db->query("SELECT id FROM system WHERE NOT assigned ORDER BY RANDOM() LIMIT 1"); + list($sid) = dbFetchArray($q); + + $npl = 6; + $porb = rand(0, $npl - 1); + + $tm = time(); + $pQuery = ""; + if ($this->game->params['victory'] == 0) { + $fTick = $this->game->ticks['day']->first; + $this->db->query("DELETE FROM pk_enemy WHERE until < UNIX_TIMESTAMP(NOW())"); + $q = $this->db->query("SELECT * FROM pk_enemy WHERE player = $pid"); + if ($tm - $fTick >= $this->game->params['prot_after'] * 86400 && dbCount($q) == 0) { + $pQuery = ",prot = " . $this->game->params['prot_duration']; + } + } + + $p = addslashes($planet); + $this->db->query( + "UPDATE planet SET name = '$p', owner = $pid, renamed = $tm,mod_check = FALSE " + . "WHERE system = $sid AND orbit = $porb" + ); + $this->db->query("UPDATE system SET assigned = TRUE$pQuery WHERE id=$sid"); + + $q = $this->db->query("SELECT id,pop FROM planet WHERE system=$sid AND orbit=$porb FOR UPDATE"); + list($plid, $cPop) = dbFetchArray($q); + + $this->planets->call('updateHappiness', $plid); + $this->planets->call('updateMaxPopulation', $plid, null, $pid); + $this->db->query( + "INSERT INTO planet_abandon_time (id, time_required) " + . "VALUES ($plid, 6)" + ); + + return $plid; + } +} + +?> diff --git a/scripts/game/beta5/player/library/breakProtection.inc b/scripts/game/beta5/player/library/breakProtection.inc new file mode 100644 index 0000000..cd7027b --- /dev/null +++ b/scripts/game/beta5/player/library/breakProtection.inc @@ -0,0 +1,54 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->msgs = $this->lib->game->getLib('beta5/msg'); + } + + public function run($playerID, $breakType) { + // Get protection level and system ID + $q = $this->db->query( + "SELECT s.id, s.prot FROM system s " + . "WHERE s.id IN (SELECT DISTINCT p.system FROM planet p WHERE p.owner = $playerID) " + . "FOR UPDATE" + ); + if (dbCount($q) != 1) { + return 0; + } + list($systemID, $protLevel) = dbFetchArray($q); + if ($protLevel == 0) { + return; + } + + // Set the system's protection level to 0 + $this->db->query("UPDATE system SET prot = 0 WHERE id = $systemID"); + $this->db->query("DELETE FROM pk_sys_status WHERE system = $systemID"); + + // Send message + $this->msgs->call('send', $playerID, 'endprotection', array( + 'end_type' => $breakType + )); + } + +} + +?> diff --git a/scripts/game/beta5/player/library/checkAllies.inc b/scripts/game/beta5/player/library/checkAllies.inc new file mode 100644 index 0000000..f407318 --- /dev/null +++ b/scripts/game/beta5/player/library/checkAllies.inc @@ -0,0 +1,35 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + // Checks players that have set a player as trusted allies in order to know whether the player can control their fleets + function run($pid) { + $allies = $this->lib->call('isAllyOf', $pid); + $res = array(); + foreach ($allies as $ally => $crap) { + if ($this->lib->call('isOnline', $ally) || $this->lib->call('isOnVacation', $ally)) { + continue; + } + + $aList = $this->lib->call('getAllies', $ally); + foreach ($aList as $aa) { + if ($aa['id'] == $pid) { + array_push($res, $ally); + break; + } + if ($this->lib->call('isOnline', $aa['id'])) { + break; + } + } + } + + return $res; + } +} + +?> diff --git a/scripts/game/beta5/player/library/get.inc b/scripts/game/beta5/player/library/get.inc new file mode 100644 index 0000000..6ba4dd3 --- /dev/null +++ b/scripts/game/beta5/player/library/get.inc @@ -0,0 +1,73 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($id, $quitOk = false) { + if (is_null($id)) { + l::warn("****** BUG: beta5::player::get(null)"); + if (config::$main['debug'] == 2) { + l::backtrace(); + } + return null; + } + + if (is_array($this->lib->mainClass->players[$id])) { + if (!$quitOk && $this->lib->mainClass->players[$id]["quit"] == "1") { + return null; + } + return $this->lib->mainClass->players[$id]; + } + + $s = $quitOk ? "" : "AND (p.quit IS NULL OR UNIX_TIMESTAMP(NOW()) - p.quit < 86400)"; + $q = $this->db->query( + "SELECT u.id,u.name,p.name,p.cash,p.alliance,TRIM(p.a_status),p.a_vote,p.a_grade," + . "(p.quit IS NOT NULL AND UNIX_TIMESTAMP(NOW()) - p.quit >= 86400),p.quit," + . "(u.status='VAC') " + . "FROM account u, player p " + . "WHERE p.id=$id $s AND u.id=p.userid" + ); + if (!($q && dbCount($q))) { + return null; + } + $a = dbFetchArray($q); + $pinf = array( + "uid" => $a[0], + "pid" => $id, + "name" => ($a[2] != "")?$a[2]:$a[1], + "cash" => $a[3], + "quit" => $a[8] == 't', + "qts" => $a[9], + "vac" => ($a[10] == 't'), + ); + + if (is_null($this->lib->mainClass->pNames[$id])) { + $this->lib->mainClass->pNames[$id] = $pinf['name']; + } + + if ($a[5] == "IN") { + $q = $this->db->query("SELECT tag, name FROM alliance WHERE id=".$a[4]); + $pinf['aid'] = $a[4]; + $pinf['vote'] = $a[6]; + $pinf['a_grade'] = $a[7]; + list($pinf['alliance'], $pinf['aname']) = dbFetchArray($q); + } elseif ($a[5] == "REQ") { + $q = $this->db->query("SELECT tag, name FROM alliance WHERE id=".$a[4]); + $pinf['arid'] = $a[4]; + list($pinf['alliance_req'], $pinf['aname']) = dbFetchArray($q); + } + + $q = $this->db->query("SELECT COUNT(*) FROM planet WHERE owner=$id"); + list($pinf["planets"]) = dbFetchArray($q); + + $this->lib->mainClass->players[$id] = $pinf; + return $pinf; + } +} + +?> diff --git a/scripts/game/beta5/player/library/getAllies.inc b/scripts/game/beta5/player/library/getAllies.inc new file mode 100644 index 0000000..1c27375 --- /dev/null +++ b/scripts/game/beta5/player/library/getAllies.inc @@ -0,0 +1,25 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + // Get the list of a player's trusted allies + function run($pid) { + $q = $this->db->query( + "SELECT l.friend,p.name,u.name FROM trusted l,player p,account u " + . "WHERE l.player=$pid AND p.id=l.friend AND u.id=p.userid " + . "ORDER BY level ASC" + ); + $al = array(); + while ($r = dbFetchArray($q)) { + array_push($al, array('id' => $r[0], 'name' => is_null($r[1]) ? $r[2] : $r[1])); + } + return $al; + } +} + +?> diff --git a/scripts/game/beta5/player/library/getDiploSummary.inc b/scripts/game/beta5/player/library/getDiploSummary.inc new file mode 100644 index 0000000..69aa2c9 --- /dev/null +++ b/scripts/game/beta5/player/library/getDiploSummary.inc @@ -0,0 +1,25 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns a summary of the player's diplomatic relations + function run($pid) { + $q = $this->db->query("SELECT COUNT(*) FROM enemy_player WHERE player=$pid"); + list($c0) = dbFetchArray($q); + $q = $this->db->query("SELECT COUNT(*) FROM enemy_alliance WHERE player=$pid"); + list($c1) = dbFetchArray($q); + $q = $this->db->query("SELECT COUNT(*) FROM trusted WHERE player=$pid"); + list($c2) = dbFetchArray($q); + $q = $this->db->query("SELECT COUNT(*) FROM trusted WHERE friend=$pid"); + list($c3) = dbFetchArray($q); + return array($c0,$c1,$c2,$c3); + } +} + +?> diff --git a/scripts/game/beta5/player/library/getEnemies.inc b/scripts/game/beta5/player/library/getEnemies.inc new file mode 100644 index 0000000..d52e310 --- /dev/null +++ b/scripts/game/beta5/player/library/getEnemies.inc @@ -0,0 +1,25 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Get the list of enemy players + function run($pid) { + $q = $this->db->query( + "SELECT p.id,p.name,u.name FROM enemy_player e,player p,account u " + . "WHERE e.player=$pid AND e.enemy=p.id AND u.id=p.userid" + ); + $rs = array(); + while ($r = dbFetchArray($q)) { + $rs[$r[0]] = is_null($r[1]) ? $r[2] : $r[1]; + } + return $rs; + } +} + +?> diff --git a/scripts/game/beta5/player/library/getEnemyAlliances.inc b/scripts/game/beta5/player/library/getEnemyAlliances.inc new file mode 100644 index 0000000..39de2ee --- /dev/null +++ b/scripts/game/beta5/player/library/getEnemyAlliances.inc @@ -0,0 +1,25 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Get the list of enemy alliances + function run($pid) { + $q = $this->db->query( + "SELECT a.id,a.tag FROM enemy_alliance e,alliance a " + . "WHERE e.player=$pid AND e.alliance=a.id" + ); + $rs = array(); + while ($r = dbFetchArray($q)) { + $rs[$r[0]] = is_null($r[1]) ? $r[2] : $r[1]; + } + return $rs; + } +} + +?> diff --git a/scripts/game/beta5/player/library/getFleets.inc b/scripts/game/beta5/player/library/getFleets.inc new file mode 100644 index 0000000..6329b7a --- /dev/null +++ b/scripts/game/beta5/player/library/getFleets.inc @@ -0,0 +1,22 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns the list of a player's fleets + function run($pid) { + $q = $this->db->query("SELECT id,name FROM fleet WHERE owner = $pid"); + $a = array(); + while ($r = dbFetchArray($q)) { + $a[$r[0]] = $r[1]; + } + return $a; + } +} + +?> diff --git a/scripts/game/beta5/player/library/getName.inc b/scripts/game/beta5/player/library/getName.inc new file mode 100644 index 0000000..fd8f791 --- /dev/null +++ b/scripts/game/beta5/player/library/getName.inc @@ -0,0 +1,29 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns the name of a player + function run($id) { + if (is_null($id)) { + logText("****** BUG: beta5::player::getName(null) called"); + l::backtrace(); + return null; + } + if (!is_null($this->lib->mainClass->pNames[$id])) { + return $this->lib->mainClass->pNames[$id]; + } + $q = $this->db->query( + "SELECT u.name,p.name FROM account u, player p WHERE p.id=$id AND u.id=p.userid" + ); + list($an,$pn) = dbFetchArray($q); + return ($this->lib->mainClass->pNames[$id] = is_null($pn) ? $an : $pn); + } +} + +?> diff --git a/scripts/game/beta5/player/library/getPlanetCount.inc b/scripts/game/beta5/player/library/getPlanetCount.inc new file mode 100644 index 0000000..4fba164 --- /dev/null +++ b/scripts/game/beta5/player/library/getPlanetCount.inc @@ -0,0 +1,26 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns the amount of planets a player controls + function run($pl) { + if (is_null($pl)) { + return 1; + } + if (!is_null($this->pPlanets[$pl])) { + return $this->pPlanets[$pl]; + } + $q = $this->db->query("SELECT COUNT(*) FROM planet WHERE owner=$pl"); + list($this->pPlanets[$pl]) = dbFetchArray($q); + return $this->pPlanets[$pl]; + } +} + +?> diff --git a/scripts/game/beta5/player/library/getPlanets.inc b/scripts/game/beta5/player/library/getPlanets.inc new file mode 100644 index 0000000..e5f75e4 --- /dev/null +++ b/scripts/game/beta5/player/library/getPlanets.inc @@ -0,0 +1,25 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns the list of a player's planets + function run($pid) { + $q = $this->db->query( + "SELECT p.id, p.name FROM planet p, system s " + . "WHERE p.owner = $pid AND s.id = p.system " + . "ORDER BY s.x, s.y, p.orbit"); + $a = array(); + while ($r = dbFetchArray($q)) { + $a[$r[0]] = $r[1]; + } + return $a; + } +} + +?> diff --git a/scripts/game/beta5/player/library/getPlayerId.inc b/scripts/game/beta5/player/library/getPlayerId.inc new file mode 100644 index 0000000..5160e29 --- /dev/null +++ b/scripts/game/beta5/player/library/getPlayerId.inc @@ -0,0 +1,31 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + /** Returns a player's ID using his name to locate him. */ + function run($name) { + $n = strtolower($name); + if (is_null($this->players[$n])) { + $n2 = addslashes($n); + $q = $this->db->query( + "SELECT p.id FROM account u, player p " + . "WHERE (p.quit IS NULL OR UNIX_TIMESTAMP(NOW())-p.quit<86400)" + . " AND u.id=p.userid AND ((p.name IS NULL AND LOWER(u.name)='$n2') OR LOWER(p.name)='$n2')" + ); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($this->players[$n]) = dbFetchArray($q); + } + return $this->players[$n]; + } +} + +?> diff --git a/scripts/game/beta5/player/library/getPower.inc b/scripts/game/beta5/player/library/getPower.inc new file mode 100644 index 0000000..f19dae1 --- /dev/null +++ b/scripts/game/beta5/player/library/getPower.inc @@ -0,0 +1,24 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->fleets = $this->lib->game->getLib('beta5/fleet'); + } + + + // Returns a player's total fleet power + function run($pid) { + if (!is_null($this->pFleets[$pid])) { + return $this->pFleets[$pid]; + } + $q = $this->db->query("SELECT SUM(gaships),SUM(fighters),SUM(cruisers),SUM(bcruisers) FROM fleet WHERE owner = $pid"); + list($g,$f,$c,$b) = dbFetchArray($q); + return ($this->pFleets[$pid] = $this->fleets->call('getPower', $pid, $g, $f, $c, $b)); + } +} + +?> diff --git a/scripts/game/beta5/player/library/getProtectionLevel.inc b/scripts/game/beta5/player/library/getProtectionLevel.inc new file mode 100644 index 0000000..e538773 --- /dev/null +++ b/scripts/game/beta5/player/library/getProtectionLevel.inc @@ -0,0 +1,25 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns a player's protection level + function run($pid) { + $q = $this->db->query( + "SELECT s.prot FROM system s " + . "WHERE s.id IN (SELECT DISTINCT p.system FROM planet p WHERE p.owner = $pid)" + ); + if (dbCount($q) != 1) { + return 0; + } + list($prot) = dbFetchArray($q); + return $prot; + } +} + +?> diff --git a/scripts/game/beta5/player/library/getRealPlanetCount.inc b/scripts/game/beta5/player/library/getRealPlanetCount.inc new file mode 100644 index 0000000..50b395a --- /dev/null +++ b/scripts/game/beta5/player/library/getRealPlanetCount.inc @@ -0,0 +1,21 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($id) { + $c = $this->lib->call('getPlanetCount', $id); + $q = $this->db->query("SELECT COUNT(*) FROM sale WHERE player=$id AND planet IS NOT NULL"); + list($scount) = dbFetchArray($q); + $q = $this->db->query("SELECT COUNT(*) FROM planet WHERE owner=$id AND (abandon<>0 OR bh_prep<>0)"); + list($acount) = dbFetchArray($q); + return $c - ($scount + $acount); + } +} + +?> diff --git a/scripts/game/beta5/player/library/getTAListBans.inc b/scripts/game/beta5/player/library/getTAListBans.inc new file mode 100644 index 0000000..f8590f2 --- /dev/null +++ b/scripts/game/beta5/player/library/getTAListBans.inc @@ -0,0 +1,27 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Get the list of players in a player's T.A. blacklist + function run($pid) { + $q = $this->db->query( + "SELECT l.ban_player,p.name,u.name FROM trusted_ban l,player p,account u " + . "WHERE l.player=$pid AND p.id=l.ban_player AND u.id=p.userid" + . " AND (p.quit IS NULL OR UNIX_TIMESTAMP(NOW())-p.quit<86400)" + ); + $al = array(); + while ($r = dbFetchArray($q)) { + logText(join(' - ', $r)); + $al[$r[0]] = is_null($r[1]) ? $r[2] : $r[1]; + } + return $al; + } +} + +?> diff --git a/scripts/game/beta5/player/library/isAllyOf.inc b/scripts/game/beta5/player/library/isAllyOf.inc new file mode 100644 index 0000000..f07e28a --- /dev/null +++ b/scripts/game/beta5/player/library/isAllyOf.inc @@ -0,0 +1,25 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Get the list of players who have a player as a trusted ally + function run($pid) { + $q = $this->db->query( + "SELECT l.player,l.level,p.name,u.name FROM trusted l,player p,account u " + . "WHERE l.friend=$pid AND p.id=l.player AND u.id=p.userid" + ); + $al = array(); + while ($r = dbFetchArray($q)) { + $al[$r[0]] = array('name' => is_null($r[2]) ? $r[3] : $r[2], "level" => $r[1]); + } + return $al; + } +} + +?> diff --git a/scripts/game/beta5/player/library/isOnVacation.inc b/scripts/game/beta5/player/library/isOnVacation.inc new file mode 100644 index 0000000..79b542d --- /dev/null +++ b/scripts/game/beta5/player/library/isOnVacation.inc @@ -0,0 +1,32 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->getDBAccess(); + $this->main = $this->game->getLib(); + $this->vac = $this->game->getLib('main/vacation'); + } + + + // Checks whether a player is currently on vacation + function run($pid) { + if ($this->main->call('isFinished')) { + return true; + } + + if ($this->lib->game->params['novacation'] == 1) { + return false; + } + + $p = $this->lib->call('get', $pid); + if (is_null($p)) { + return false; + } + return $this->vac->call('isOnVacation', $p['uid']); + } +} + +?> diff --git a/scripts/game/beta5/player/library/isOnline.inc b/scripts/game/beta5/player/library/isOnline.inc new file mode 100644 index 0000000..d9a9ebd --- /dev/null +++ b/scripts/game/beta5/player/library/isOnline.inc @@ -0,0 +1,22 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->main = $this->lib->game->getLib('main/account'); + } + + + // Checks whether a player is currently online + function run($pid) { + $p = $this->lib->call('get', $pid); + if (is_null($p)) { + return false; + } + return $this->main->call('isOnline', $p['uid']); + } +} + +?> diff --git a/scripts/game/beta5/player/library/isRestrained.inc b/scripts/game/beta5/player/library/isRestrained.inc new file mode 100644 index 0000000..d6ec24f --- /dev/null +++ b/scripts/game/beta5/player/library/isRestrained.inc @@ -0,0 +1,22 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Checks whether a player is restrained or not + function run($pid) { + $q = $this->db->query("SELECT restrain FROM player WHERE id = $pid"); + if (!($q || dbCount($q))) { + return 666; + } + list($r) = dbFetchArray($q); + return $r; + } +} + +?> diff --git a/scripts/game/beta5/player/library/lastOnline.inc b/scripts/game/beta5/player/library/lastOnline.inc new file mode 100644 index 0000000..1a793bb --- /dev/null +++ b/scripts/game/beta5/player/library/lastOnline.inc @@ -0,0 +1,24 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->main = $this->lib->game->getLib('main/account'); + } + + + // Returns the timestamp at which the player last logged out, + // or 0 if the player is online at the moment + function run($playerID) { + $player = $this->lib->call('get', $playerID, true); + if (is_null($player)) { + return 0; + } + + return $this->main->call('lastOnline', $player['uid']); + } +} + +?> diff --git a/scripts/game/beta5/player/library/makeEnemies.inc b/scripts/game/beta5/player/library/makeEnemies.inc new file mode 100644 index 0000000..492fa10 --- /dev/null +++ b/scripts/game/beta5/player/library/makeEnemies.inc @@ -0,0 +1,53 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->planets = $this->lib->game->getLib('beta5/planet'); + $this->fleets = $this->lib->game->getLib('beta5/fleet'); + } + + + // Sets players' fleets to attack mode when they're added to an enemy list + function run($pid, $elist) { + $lmsg = array(); + $q = $this->db->query( + "SELECT f.id,p.id,f.owner FROM fleet f,planet p " + . "WHERE p.owner=$pid AND f.location=p.id AND NOT f.attacking AND f.owner IN (".join(',',$elist).")" + ); + while ($r = dbFetchArray($q)) { + $this->db->query("UPDATE fleet SET attacking=".dbBool(1).",can_move='B' WHERE id=".$r[0]); + if (!is_array($lmsg[$r[1]])) { + $lmsg[$r[1]] = array(); + } + if (!is_array($lmsg[$r[1]][$r[2]])) { + $lmsg[$r[1]][$r[2]] = array(); + } + array_push($lmsg[$r[1]][$r[2]], $r[0]); + } + + $pids = array_keys($lmsg); + $tm = time(); + foreach ($pids as $pl) { + $this->planets->call('updateMilStatus', $pl); + $pinf = $this->planets->call('byId', $pl); + $pn = addslashes($pinf['name']); + + foreach ($lmsg[$pl] AS $plid => $fl) { + $q = $this->db->query("SELECT COUNT(*),SUM(gaships),SUM(fighters),SUM(cruisers),SUM(bcruisers) FROM fleet" + . " WHERE id IN (".join(',',$fl).")"); + list($nf,$g,$f,$c,$b) = dbFetchArray($q); + $fp = $this->fleets->call('getPower', $plid, $g,$f,$c,$b); + + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($plid,$tm,'flswitch','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=$plid AND sent_on=$tm AND mtype='flswitch' ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_flswitch VALUES($mid,$pl,'$pn',$nf,$fp)"); + } + } + } +} + +?> diff --git a/scripts/game/beta5/player/library/moveAllyDown.inc b/scripts/game/beta5/player/library/moveAllyDown.inc new file mode 100644 index 0000000..890e96f --- /dev/null +++ b/scripts/game/beta5/player/library/moveAllyDown.inc @@ -0,0 +1,22 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Move an ally down the list + function run($pid, $it) { + $q = $this->db->query("SELECT COUNT(*) FROM trusted WHERE player=$pid"); + list($all) = dbFetchArray($q); + $np = $it + 1; + $this->db->query("UPDATE trusted SET level=$all WHERE player=$pid AND level=$it"); + $this->db->query("UPDATE trusted SET level=$it WHERE player=$pid AND level=$np"); + $this->db->query("UPDATE trusted SET level=$np WHERE player=$pid AND level=$all"); + } +} + +?> diff --git a/scripts/game/beta5/player/library/moveAllyUp.inc b/scripts/game/beta5/player/library/moveAllyUp.inc new file mode 100644 index 0000000..25a5934 --- /dev/null +++ b/scripts/game/beta5/player/library/moveAllyUp.inc @@ -0,0 +1,22 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Move an ally up the list + function run($pid, $it) { + $q = $this->db->query("SELECT COUNT(*) FROM trusted WHERE player=$pid"); + list($all) = dbFetchArray($q); + $np = $it - 1; + $this->db->query("UPDATE trusted SET level=$all WHERE player=$pid AND level=$it"); + $this->db->query("UPDATE trusted SET level=$it WHERE player=$pid AND level=$np"); + $this->db->query("UPDATE trusted SET level=$np WHERE player=$pid AND level=$all"); + } +} + +?> diff --git a/scripts/game/beta5/player/library/reassign.inc b/scripts/game/beta5/player/library/reassign.inc new file mode 100644 index 0000000..b6d8102 --- /dev/null +++ b/scripts/game/beta5/player/library/reassign.inc @@ -0,0 +1,54 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->fleets = $this->lib->game->getLib('beta5/fleet'); + $this->sales = $this->lib->game->getLib('beta5/sale'); + } + + + // Assigns the player a new planet after he loses all of his + function run($player, $name) { + // Delete fleets + $list = array_keys($this->lib->call('getFleets', $player)); + foreach ($list as $fId) { + $this->fleets->call('disband', $fId, true); + } + + // Cancel all sales from this player + $q = $this->db->query("SELECT id,player,finalized,sold_to FROM sale WHERE player=$player OR sold_to=$player"); + while ($r = dbFetchArray($q)) { + list($sid,$seller,$fin,$buyer) = $r; + if (is_null($fin)) { + $ga = 'cancel'; + } else { + $ga = 'cancelTransfer'; + + if ($seller == $id) { + $tInc = 0; + $fInc = 1; + $t = $buyer; + } else { + $tInc = 1; + $fInc = 0; + $t = $seller; + } + } + $this->sales->call($ga, $seller, $sid); + } + + // Delete probes + // FIXME + + // Assign new planet + $n = addslashes($name); + $plid = $this->lib->call('assign', $player, $n); + $this->db->query("UPDATE player SET first_planet=$plid WHERE id=$player"); + return $plid; + } +} + +?> diff --git a/scripts/game/beta5/player/library/reorderAllies.inc b/scripts/game/beta5/player/library/reorderAllies.inc new file mode 100644 index 0000000..d180afa --- /dev/null +++ b/scripts/game/beta5/player/library/reorderAllies.inc @@ -0,0 +1,24 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Reorders the list of allies after it's been modified + function run($pid) { + $q = $this->db->query("SELECT level FROM trusted WHERE player=$pid ORDER BY level ASC"); + $i = 0; + while ($r = dbFetchArray($q)) { + if ($r[0] != $i) { + $this->db->query("UPDATE trusted SET level=$i WHERE player=$pid AND level=".$r[0]); + } + $i ++; + } + } +} + +?> diff --git a/scripts/game/beta5/player/library/transferFunds.inc b/scripts/game/beta5/player/library/transferFunds.inc new file mode 100644 index 0000000..3463711 --- /dev/null +++ b/scripts/game/beta5/player/library/transferFunds.inc @@ -0,0 +1,27 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Transfers cash from an account to another + function run($s, $d, $a) { + // Transfer cash + $this->db->query("UPDATE player SET cash=cash-$a WHERE id=$s"); + $this->db->query("UPDATE player SET cash=cash+$a WHERE id=$d"); + $this->db->query("INSERT INTO donation_log VALUES(" . time() . ",$s,$d,$a)"); + + // Send a message to the recipient + $tm = time(); + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($d,$tm,'cash','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=$d AND sent_on=$tm AND mtype='cash' ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_cash VALUES($mid,$s,$a)"); + } +} + +?> diff --git a/scripts/game/beta5/prot/library.inc b/scripts/game/beta5/prot/library.inc new file mode 100644 index 0000000..ecc5a1a --- /dev/null +++ b/scripts/game/beta5/prot/library.inc @@ -0,0 +1,224 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + $this->msgs = $this->game->getLib('beta5/msg'); + $this->now = time(); + } + + + /** This function returns the identifier of the Peacekeepers player. + */ + public function getPeacekeepers() { + if (is_null($this->pkPlayerID)) { + $q = $this->db->query( + "SELECT p.id FROM player p " + . "LEFT JOIN account a ON a.id = p.userid " + . "WHERE a.name = 'AI>Peacekeepers'" + ); + list($this->pkPlayerID) = dbFetchArray($q); + } + return $this->pkPlayerID; + } + + + /** This function adds actions to the list of actions to perform. + */ + public function addActions($actions) { + array_push($this->actions, $actions); + } + + + /** This function executes all actions listed during the latest checks. + */ + public function flushStatus() { + if (!empty($this->actions)) { + $this->flushActions(); + } + $this->lib->call('updateFleets'); + } + + private function flushActions() { + $actions = $this->buildActualActions(); + foreach ($actions['enemy'] as $enemy) { + $this->declareEnemy($enemy); + } + + $dMessages = $wMessages = $dSystems = array(); + $wDelay = $this->now + $this->game->ticks['battle']->interval; + foreach ($actions['other'] as $action) { + switch ($action['type']) { + case 'A': + $delay = 'NULL'; + break; + case 'O': + $dMessages[$action['player']][] = $action['system']; + $delay = 'NULL'; + break; + case 'W': + $wMessages[$action['player']][] = $action['system']; + $delay = $wDelay; + break; + } + if ($action['mustInsert']) { + $qString = 'INSERT INTO pk_sys_status (system, player, status, switch_at) VALUES (' + . "{$action['system']}, {$action['player']}, '{$action['type']}', $delay)"; + } else { + $qString = "UPDATE pk_sys_status SET status = '{$action['type']}', switch_at = $delay " + . "WHERE system = {$action['system']} AND player = {$action['player']}"; + } + $this->db->query($qString); + } + + foreach ($dMessages as $player => $systems) { + $this->sendMessages('D', $player, $systems); + } + foreach ($wMessages as $player => $systems) { + $this->sendMessages('W', $player, $systems, $wDelay); + } + + $this->actions = array(); + } + + + /** This method merges all records received from system checks. + */ + private function buildActualActions() { + // First merge the lists of players to add as enemies + $enemies = array(); + foreach ($this->actions as $action) { + $enemies = array_merge($enemies, $action['enemy']); + } + $enemies = array_unique($enemies); + + // List all actions to be executed, unless the player + // has been declared an enemy + $actions = array(); + foreach ($this->actions as $action) { + foreach ($action['other'] as $aRecord) { + if (!in_array($aRecord['player'], $enemies)) { + array_push($actions, $aRecord); + } + } + } + + return array( + "enemy" => $enemies, + "other" => $actions + ); + } + + + /** This method declares a player as an enemy of the peacekeepers. + */ + private function declareEnemy($playerID) { + $howLong = $this->now + 20 * $this->game->ticks['day']->interval; + + $q = $this->db->query( + "SELECT DISTINCT p.id, p.name, p.system FROM planet p, fleet f, system s " + . "WHERE f.location = p.id AND p.system = s.id AND f.owner = $playerID AND s.prot > 0" + ); + $planets = array(); + $systems = array(); + while ($r = dbFetchArray($q)) { + $planets[$r[0]] = $r[1]; + if (!in_array($r[2], $systems)) { + array_push($systems, $r[2]); + } + } + + $this->db->query("INSERT INTO pk_enemy (player, until) VALUES ($playerID, $howLong)"); + + $q = $this->db->query("SELECT system FROM pk_sys_status WHERE player = $playerID"); + $inSystems = array(); + while ($r = dbFetchArray($q)) { + $inSystems[] = $r[0]; + } + foreach ($systems as $systemID) { + if (in_array($systemID, $inSystems)) { + continue; + } + $this->db->query( + "INSERT INTO pk_sys_status (player, system, status) " + . "VALUES ($playerID, $systemID, 'O')" + ); + } + $this->db->query("UPDATE pk_sys_status SET status = 'O' WHERE player = $playerID"); + + $msgID = $this->msgs->call('send', $playerID, "pkwarning", array( + 'msg_type' => 'E', + 'delay' => $howLong + )); + + $pInsert = new db_copy('pkwarning_planet', db_copy::copyTo); + $pInsert->setAccessor($this->db); + foreach ($planets as $id => $name) { + $pInsert->appendRow(array($msgID, $id, $name)); + } + $pInsert->execute(); + } + + + /** This method sends messages to players. + */ + private function sendMessages($type, $player, $systems, $delay = null) { + $q = $this->db->query( + "SELECT DISTINCT p.id, p.name FROM planet p, fleet f " + . "WHERE f.location = p.id AND p.system IN (" . join(',', $systems) . ") " + . "AND f.owner = $player" + ); + $planets = array(); + while ($r = dbFetchArray($q)) { + $planets[$r[0]] = $r[1]; + } + + $msgID = $this->msgs->call('send', $player, "pkwarning", array( + 'msg_type' => $type, + 'delay' => $delay + )); + + $pInsert = new db_copy('pkwarning_planet', db_copy::copyTo); + $pInsert->setAccessor($this->db); + foreach ($planets as $id => $name) { + $pInsert->appendRow(array($msgID, $id, $name)); + } + $pInsert->execute(); + } +} + +?> diff --git a/scripts/game/beta5/prot/library/checkSystem.inc b/scripts/game/beta5/prot/library/checkSystem.inc new file mode 100644 index 0000000..d278e32 --- /dev/null +++ b/scripts/game/beta5/prot/library/checkSystem.inc @@ -0,0 +1,349 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + public function run($planetID) { + // Get the system's ID + $systemID = $this->getSystem($planetID); + if ($systemID == -1) { + return; + } + $this->systemID = $systemID; + l::trace("Checking protection status in system #$systemID"); + $this->now = time(); + + // Get the protected player's ID + $this->fetchProtectedPlayerID(); + if (is_null($this->ownerID)) { + l::error("No owner found in system #$systemID!"); + $this->db->query("UPDATE system SET prot = 0 WHERE id = $systemID"); + return; + } + + // Get the ID of the Peacekeepers + $this->fetchPeacekeepersID(); + + // Get the list of all players who have fleets in the system + $this->fetchFleetOwners(); + + // Get the Peacekeeper's records for the system, as well + // as fleet owners status (offenses and enemy status) + $this->fetchPKRecords(); + $this->fetchPKOffenders(); + $this->fetchPKEnemies(); + + // Update status records using the current list of fleets + $this->updateRecords(); + } + + + private function getSystem($planetID) { + $q = $this->db->query( + "SELECT s.id, s.prot FROM planet p " + . "LEFT JOIN system s ON s.id = p.system " + . "WHERE p.id = $planetID" + ); + + list($systemID, $protection) = dbFetchArray($q); + if ($protection == 0 || in_array($systemID, $this->checkedSystems)) { + return -1; + } + + array_push($this->checkedSystems, $systemID); + return $systemID; + } + + + private function fetchProtectedPlayerID() { + $q = $this->db->query( + "SELECT owner FROM planet WHERE system = {$this->systemID} AND owner IS NOT NULL LIMIT 1" + ); + list($this->ownerID) = dbFetchArray($q); + $this->ownerAllies = null; + } + + + private function fetchPeacekeepersID() { + if (is_null($this->peacekeepersID)) { + $this->peacekeepersID = $this->lib->call('getPeacekeepers'); + } + } + + + private function fetchFleetOwners() { + $q = $this->db->query( + "SELECT DISTINCT f.owner, f.attacking FROM fleet f " + . "LEFT JOIN planet p ON p.id = f.location " + . "WHERE p.system = {$this->systemID} " + . "AND f.owner NOT IN ({$this->ownerID}, {$this->peacekeepersID})" + ); + + $result = array(); + while ($r = dbFetchArray($q)) { + $attack = ($r[1] == 't'); + if (array_key_exists($r[0], $result)) { + $result[$r[0]] = $result[$r[0]] || $attack; + } else { + $result[$r[0]] = $attack; + } + } + + $this->fleetOwners = $result; + } + + + private function fetchPKRecords() { + $q = $this->db->query( + "SELECT player, status, switch_at FROM pk_sys_status " + . "WHERE system = {$this->systemID} " + . "FOR UPDATE" + ); + + $result = array(); + while ($r = dbFetchArray($q)) { + $result[$r[0]] = array( + "status" => $r[1], + "switch_at" => $r[2] + ); + } + + $this->pkRecords = $result; + } + + + private function fetchPKOffenders() { + $this->pkOffenders = array(); + if (empty($this->fleetOwners)) { + return; + } + + $q = $this->db->query( + "SELECT player, nb_offenses FROM pk_offenses " + . "WHERE player IN (" . join(",", array_keys($this->fleetOwners)) . ") " + . "FOR UPDATE" + ); + + while ($r = dbFetchArray($q)) { + $this->pkOffenders[$r[0]] = $r[1]; + } + } + + + private function fetchPKEnemies() { + $this->pkEnemies = array(); + if (empty($this->fleetOwners)) { + return; + } + + $q = $this->db->query( + "SELECT player FROM pk_enemy " + . "WHERE player IN (" . join(",", array_keys($this->fleetOwners)) . ") " + . "AND until > UNIX_TIMESTAMP(NOW()) " + . "FOR UPDATE" + ); + + while ($r = dbFetchArray($q)) { + array_push($this->pkEnemies, $r[0]); + } + } + + + private function updateRecords() { + $this->deleteRecords = array(); + $this->actions = array( + "enemy" => array(), + "other" => array() + ); + + // Check all existing records and update them + foreach ($this->pkRecords as $player => $record) { + if (array_key_exists($player, $this->fleetOwners)) { + $this->handleExistingRecord($player, $record); + } else { + array_push($this->deleteRecords, $player); + } + } + + // Add records for fleets that weren't here before + foreach ($this->fleetOwners as $player => $attacking) { + if (! array_key_exists($player, $this->pkRecords)) { + $this->addRecord($player, $attacking); + } + } + + // Destroy records that are no longer necessary + $this->purgeOldRecords(); + + // Send the actions we generated to the main library + $this->lib->call('addActions', $this->actions); + } + + + private function handleExistingRecord($player, $record) { + l::trace(" Updating record for player #$player with status {$record['status']}"); + + if ($record['status'] == 'E' || $record['status'] == 'O') { + l::trace(" -> already attacking or enemy"); + return; + } + + if ($record['status'] == 'W' && !$this->fleetOwners[$player]) { + l::trace(" => warned player (switch_at = {$record['switch_at']}, now = {$this->now})"); + if ($this->isAlly($player)) { + l::trace(" -> player is now an allied of the owner"); + $this->addAction($player, 'A', false); + } elseif ($record['switch_at'] <= $this->now) { + l::trace(" -> player has been around for too long"); + $this->addAction($player, 'O', false); + } + return; + } + + if ($this->fleetOwners[$player]) { + l::trace(" -> player has switched to attack"); + $this->increaseOffense($player, $record['status'] == 'W' ? 1 : 2, 'O', false); + return; + } + + l::trace(" -> allied player"); + if (! $this->isAlly($player)) { + l::trace(" => player is no longer an ally of the owner"); + $this->addAction($player, 'W', false); + } + } + + + private function addRecord($player, $attacking) { + l::trace(" Adding record for " . ($attacking ? "attacking " : "") . "player #$player"); + + if (in_array($player, $this->pkEnemies)) { + l::trace(" -> player is an enemy"); + $this->addAction($player, 'O', true); + return; + } + + if ($attacking) { + l::trace(" -> player is attacking"); + $this->increaseOffense($player, 2, 'O', true); + return; + } + + if ($this->isAlly($player)) { + l::trace(" -> player is an ally of the owner"); + $this->addAction($player, 'A', true); + } else { + l::trace(" -> player must be warned"); + $this->increaseOffense($player, 1, 'W', true); + } + } + + + private function purgeOldRecords() { + if (!empty($this->deleteRecords)) { + $this->db->query( + "DELETE FROM pk_sys_status " + . "WHERE system = {$this->systemID} " + . "AND player IN (" . join(',', $this->deleteRecords) . ")" + ); + } + } + + + private function isAlly($player) { + if (is_null($this->ownerAllies)) { + $this->fetchOwnerAllies(); + } + + return in_array($player, $this->ownerAllies); + } + + + private function fetchOwnerAllies() { + $q = $this->db->query( + "SELECT alliance, a_status FROM player WHERE id = {$this->ownerID}" + ); + list($alliance, $aStatus) = dbFetchArray($q); + + $qString = "SELECT friend AS ally FROM trusted WHERE player = {$this->ownerID}"; + if (!is_null($alliance) && $aStatus == 'IN') { + $qString .= " UNION SELECT id AS trusted FROM player " + . "WHERE a_status = 'IN' AND alliance = $alliance"; + } + + $q = $this->db->query($qString); + $this->ownerAllies = array(); + + while ($q = dbFetchArray($q)) { + array_push($this->ownerAllies, $r[0]); + } + } + + + private function increaseOffense($player, $points, $aType, $mustInsert) { + if (is_null($this->pkOffenders[$player])) { + $tot = $this->pkOffenders[$player] = $points; + $this->db->query("INSERT INTO pk_offenses (player, nb_offenses) VALUES ($player, $points)"); + } else { + $tot = ($this->pkOffenders[$player] + $points); + $this->db->query( + "UPDATE pk_offenses SET nb_offenses = nb_offenses + $points WHERE player = $player" + ); + } + + if ($tot > 6) { + l::trace(" => PLAYER #$player HAS BEEN DECLARED AN ENEMY"); + $this->declareEnemy($player); + } else { + $this->addAction($player, $aType, $mustInsert); + } + + return ($tot > 6); + } + + + private function addAction($player, $aType, $mustInsert) { + array_push($this->actions['other'], array( + 'player' => $player, + 'system' => $this->systemID, + 'type' => $aType, + 'mustInsert' => $mustInsert + )); + } + + + private function declareEnemy($player) { + if (!in_array($player, $this->actions['enemy'])) { + array_push($this->actions['enemy'], $player); + } + } +} + +?> diff --git a/scripts/game/beta5/prot/library/isPlayerMarked.inc b/scripts/game/beta5/prot/library/isPlayerMarked.inc new file mode 100644 index 0000000..e634769 --- /dev/null +++ b/scripts/game/beta5/prot/library/isPlayerMarked.inc @@ -0,0 +1,32 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + public function run($playerID, $planetID) { + $q = $this->db->query( + "SELECT p.id FROM planet p, pk_sys_status s " + . "WHERE p.id = $planetID AND s.system = p.system " + . "AND s.status = 'O' AND s.player = $playerID" + ); + return dbCount($q) == 1; + } +} + +?> diff --git a/scripts/game/beta5/prot/library/updateFleets.inc b/scripts/game/beta5/prot/library/updateFleets.inc new file mode 100644 index 0000000..f4e139e --- /dev/null +++ b/scripts/game/beta5/prot/library/updateFleets.inc @@ -0,0 +1,299 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + $this->fleets = $this->game->getLib('beta5/fleet'); + $this->msgs = $this->game->getLib('beta5/msg'); + } + + + public function run() { + // Get Peacekeepers player ID + $this->peacekeepers = $this->lib->call('getPeacekeepers'); + + // Check for systems with enemy fleets + $rPlanets = $this->checkBattleSystems(); + + // Check for fleets to evacuate + $ePlanets = $this->checkEvacuations($rPlanets); + } + + + private function checkBattleSystems() { + $reinforce = array(); + + $enemyFleets = $this->getEnemyFleets(); + if (empty($enemyFleets)) { + return $reinforce; + } + + $sysOwners = $this->getSystemOwners(array_keys($enemyFleets)); + foreach ($enemyFleets as $systemID => $planets) { + $ownerID = $sysOwners[$systemID]['owner']; + $ownPlanets = $sysOwners[$systemID]['planets']; + + foreach ($planets as $planetID => $eFleets) { + // Check enemy fleet size + $q = $this->db->query( + "SELECT SUM(fighters + gaships / 2), SUM(cruisers), SUM(bcruisers) " + . "FROM fleet " + . "WHERE id IN (" . join(',', $eFleets) . ")" + ); + list($eFighters, $eCruisers, $eBattleCruisers) = dbFetchArray($q); + + // Check Peacekeeper fleet size + $q = $this->db->query( + "SELECT SUM(fighters), SUM(cruisers), SUM(bcruisers) FROM fleet " + . "WHERE location = $planetID AND owner = {$this->peacekeepers}" + ); + list($pkFighters, $pkCruisers, $pkBattleCruisers) = dbFetchArray($q); + + // Compute fleet size requirements for Peacekeepers + $rFighters = ceil($eFighters * 2 - (int) $pkFighters); + $rCruisers = $eCruisers * 2 - (int) $pkCruisers; + $rBattleCruisers = $eBattleCruisers * 2 - (int) $pkBattleCruisers; + + // Do we need to reinforce ? + if ($rFighters > 0 || $rCruisers > 0 || $rBattleCruisers > 0) { + // If we need fighters, do we have enough haul space? + $haul = $rCruisers * 20 + $rBattleCruisers * 15; + if ($haul * 0.9 < $rFighters) { + // Add cruisers to match haul size + $rCruisers += ceil(($rFighters - ($haul * 0.9)) / 20); + } + + $reinforce[$planetID] = array(true, $rFighters, $rCruisers, $rBattleCruisers); + } else { + $reinforce[$planetID] = array(false); + } + + // Make sure all enemy fleets are attacking and battle-ready + $this->db->query( + "UPDATE fleet SET attacking = 't', time_spent = (CASE " + . "WHEN time_spent <= 15 THEN 16 " + . "ELSE time_spent " + . "END) WHERE id IN (" . join(',', $eFleets) . ")" + ); + + // If we're on a planet not owned by the system's owner, + // make sure his fleets are defending + if (!in_array($planetID, $ownPlanets)) { + $this->db->query( + "UPDATE fleet SET attacking = 'f' " + . "WHERE location = $planetID AND owner = $ownerID" + ); + } + } + } + + foreach ($reinforce as $planetID => $reinforcements) { + if (!$reinforcements[0]) { + continue; + } + array_shift($reinforcements); + $this->reinforce($planetID, $reinforcements); + } + + return array_keys($reinforce); + } + + + private function getEnemyFleets() { + $q = $this->db->query( + "SELECT f.id, p.id, p.system FROM fleet f, planet p, pk_sys_status s " + . "WHERE s.status = 'O' AND p.system = s.system " + . "AND f.owner = s.player AND f.location = p.id " + . "ORDER BY p.id, f.owner " + . "FOR UPDATE OF f, p" + ); + + $result = array(); + while ($r = dbFetchArray($q)) { + if (!is_array($result[$r[2]])) { + $result[$r[2]] = array(); + } + if (!is_array($result[$r[2]][$r[1]])) { + $result[$r[2]][$r[1]] = array(); + } + array_push($result[$r[2]][$r[1]], $r[0]); + } + + return $result; + } + + + private function getSystemOwners($systems) { + $q = $this->db->query( + "SELECT system, id, owner FROM planet " + . "WHERE system IN (" . join(',', $systems) . ") " + . "ORDER BY system, (owner IS NOT NULL) DESC" + ); + + $result = array(); + while ($r = dbFetchArray($q)) { + if (!is_array($result[$r[0]])) { + $result[$r[0]] = array( + 'owner' => $r[2], + 'planets' => array($r[1]) + ); + } elseif (!is_null($r[2])) { + array_push($result[$r[0]]['planets'], $r[1]); + } + } + + return $result; + } + + + private function reinforce($planetID, $fSize) { + // Generate fleet + $name = addslashes(self::$fleetName); + $this->db->query( + "INSERT INTO fleet (location, owner, fighters, cruisers, bcruisers, time_spent, name) " + . "VALUES ($planetID, {$this->peacekeepers}, {$fSize[0]}, {$fSize[1]}, " + . "{$fSize[2]}, 16, '$name')" + ); + $fPower = $this->fleets->call('getPower', $this->peacekeepers, 0, $fSize[0], $fSize[1], $fSize[2]); + + // Get planet name + $q = $this->db->query("SELECT name FROM planet WHERE id = $planetID"); + list($pName) = dbFetchArray($q); + + // Get random origin (nebula or planetary remains) + $q = $this->db->query( + "SELECT id, name FROM planet WHERE status > 0 ORDER BY RANDOM() LIMIT 1" + ); + list($originID, $origin) = dbFetchArray($q); + $origin = addslashes($origin); + + // Get list of players and status + $q = $this->db->query( + "SELECT owner AS player, FALSE AS attacking FROM planet " + . "WHERE id = $planetID AND owner IS NOT NULL " + . "UNION SELECT DISTINCT owner AS player, attacking FROM fleet " + . "WHERE location = $planetID AND owner <> {$this->peacekeepers}" + ); + + // Send messages + while ($r = dbFetchArray($q)) { + $mid = $this->msgs->call('send', $r[0], 'flmove', array( + 'p_id' => $planetID, + 'p_name' => $pName + )); + $this->db->query( + "INSERT INTO flmove_data VALUES ($mid, '$name', {$this->peacekeepers}, 0, " + . "{$fSize[0]}, {$fSize[1]}, {$fSize[2]}, $fPower, '{$r[1]}', 't', " + . "$originID, '$origin')" + ); + } + } + + + private function checkEvacuations($noCheck) { + if (empty($noCheck)) { + $ncQuery = ""; + } else { + $ncQuery = "AND location NOT IN (" . join(',', $noCheck) . ")"; + } + + // Get PK fleets on planets where no battle is taking place + $q = $this->db->query( + "SELECT id FROM fleet " + . "WHERE owner = {$this->peacekeepers} AND location IS NOT NULL $ncQuery " + . "FOR UPDATE" + ); + if (dbCount($q) == 0) { + return; + } + + // Merge PK fleets + $fleets = array(); + while ($r = dbFetchArray($q)) { + array_push($fleets, $r[0]); + } + $fleets = $this->fleets->call('merge', $fleets, array($this->peacekeepers), ""); + + // Remove extra fighters and delete empty fleets if required + $this->db->query( + "UPDATE fleet SET fighters = (CASE " + . "WHEN fighters < cruisers * 20 + bcruisers * 10 THEN fighters " + . "ELSE cruisers * 20 + bcruisers * 10 " + . "END) WHERE id IN (" . join(',', $fleets) . ")" + ); + $this->db->query( + "DELETE FROM fleet WHERE cruisers = 0 AND bcruisers = 0 AND id IN (" . join(',', $fleets) . ")" + ); + + // Fetch all remaining fleets and evacuate them + $q = $this->db->query( + "SELECT p.id, p.name, f.id, f.fighters, f.cruisers, f.bcruisers FROM fleet f, planet p " + . "WHERE p.id = f.location AND f.id IN (" . join(',', $fleets) . ")" + ); + $locations = array(); + $fleets = array(); + while ($r = dbFetchArray($q)) { + $this->evacuate($r); + array_push($locations, $r[0]); + array_push($fleets, $r[2]); + } + + // Delete fleets + if (! empty($fleets)) { + $this->db->query("DELETE FROM fleet WHERE id IN (" . join(',', $fleets) . ")"); + } + + return $locations; + } + + + private function evacuate($fleetRecord) { + list($planetID, $planet, $fleet, $fighters, $cruisers, $bCruisers) = $fleetRecord; + l::trace("Peacekeeper fleet #$fleet leaving planet $planet (#$planetID)"); + $planet = addslashes($planet); + $fName = addslashes(self::$fleetName); + $fPower = $this->fleets->call('getPower', $this->peacekeepers, 0, $fighters, $cruisers, $bCruisers); + + // Get list of players and status + $q = $this->db->query( + "SELECT owner AS player, FALSE AS attacking FROM planet " + . "WHERE id = $planetID AND owner IS NOT NULL " + . "UNION SELECT DISTINCT owner AS player, attacking FROM fleet " + . "WHERE location = $planetID AND owner <> {$this->peacekeepers}" + ); + + // Send messages + while ($r = dbFetchArray($q)) { + $mid = $this->msgs->call('send', $r[0], 'flmove', array( + 'p_id' => $planetID, + 'p_name' => $planet + )); + $this->db->query( + "INSERT INTO flmove_data VALUES ($mid, '$fName', {$this->peacekeepers}, 0, " + . "$fighters, $cruisers, $bCruisers, $fPower, '{$r[1]}', 'f', NULL, NULL)" + ); + } + } +} + +?> diff --git a/scripts/game/beta5/rules/library.inc b/scripts/game/beta5/rules/library.inc new file mode 100644 index 0000000..8b38cf1 --- /dev/null +++ b/scripts/game/beta5/rules/library.inc @@ -0,0 +1,52 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->planets = $this->lib->game->getLib('beta5/planet'); + } + + + //-------------------------------------------------------------------------------------------------------------------------------- + // RULE HANDLERS + //-------------------------------------------------------------------------------------------------------------------------------- + + // Handler for modifications on planet_max_pop + function rhUpdateMaxPopulation($pid, $maxPop) { + $q = dbQuery("SELECT id FROM planet WHERE owner=$pid AND max_pop<".($maxPop-1500)); + while ($r = dbFetchArray($q)) { + $this->planets->call('updateMaxPopulation', $r[0], $pid, $pid); + } + } + + // Handler for modifications on the unhappiness factor + function rhUpdateHappiness($pid, $junk) { + $q = dbQuery("SELECT id FROM planet WHERE owner=$pid"); + while ($r = dbFetchArray($q)) { + $this->planets->call('updateHappiness', $r[0]); + } + } + + // Handler for modifications on ECM or ECCM techs + function rhUpdateCommTech($pid, $junk) { + $q = dbQuery("SELECT location FROM fleet WHERE owner=$pid AND location IS NOT NULL GROUP BY location"); + while ($r = dbFetchArray($q)) { + $this->planets->call('updateMilStatus', $r[0]); + } + } + + // Handler for modifications on fleet power rules + function rhUpdateFleetPower($pid, $junk) { + $this->rhUpdateHappiness($pid, ''); + $this->rhUpdateCommTech($pid, ''); + } +} + +?> diff --git a/scripts/game/beta5/rules/library/change.inc b/scripts/game/beta5/rules/library/change.inc new file mode 100644 index 0000000..3c2de83 --- /dev/null +++ b/scripts/game/beta5/rules/library/change.inc @@ -0,0 +1,29 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($pid, $rules) { + if (!count($rules)) { + return; + } + + foreach ($rules as $r => $v) { + $this->db->query("UPDATE rule SET value=value+($v) WHERE player=$pid AND name='$r'"); + } + + $this->lib->mainClass->rules[$pl] = null; + + $q = $this->db->query("SELECT h.handler,r.value FROM rule_handler h,rule r " + . "WHERE r.name IN ('" . join("','", array_keys($rules)) . "') AND r.player=$pid AND h.rule=r.name"); + while ($r = dbFetchArray($q)) { + $this->lib->call("rh{$r[0]}", $pid, $r[1]); + } + } +} + +?> diff --git a/scripts/game/beta5/rules/library/get.inc b/scripts/game/beta5/rules/library/get.inc new file mode 100644 index 0000000..9dc9f83 --- /dev/null +++ b/scripts/game/beta5/rules/library/get.inc @@ -0,0 +1,32 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Loads rules for a player + function run($pid) { + $apid = is_null($pid) ? ' ' : $pid; + if (is_array($this->lib->mainClass->rules[$apid])) { + return $this->lib->mainClass->rules[$apid]; + } + + $q = $this->db->query("SELECT name,value FROM rule" + . (is_null($pid) ? "_def" : " WHERE player=$pid")); + if (!($q && dbCount($q))) { + return null; + } + + $this->lib->mainClass->rules[$apid] = array(); + while ($r = dbFetchArray($q)) { + $this->lib->mainClass->rules[$apid][$r[0]] = $r[1]; + } + + return $this->lib->mainClass->rules[$apid]; + } +} + +?> diff --git a/scripts/game/beta5/sale/library.inc b/scripts/game/beta5/sale/library.inc new file mode 100644 index 0000000..db558ff --- /dev/null +++ b/scripts/game/beta5/sale/library.inc @@ -0,0 +1,54 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function getHistoryFrom($player) { + $q = $this->db->query("SELECT * FROM sale_history WHERE from_player=$player AND end_mode IS NOT NULL ORDER BY ended DESC,started DESC"); + $rs = array(); + while ($r = dbFetchHash($q)) { + array_push($rs,$r); + } + return $rs; + } + + + function getHistoryTo($player) { + $q = $this->db->query("SELECT * FROM sale_history WHERE to_player=$player AND mode<2 AND end_mode IS NOT NULL ORDER BY ended DESC,started DESC"); + $rs = array(); + while ($r = dbFetchHash($q)) { + array_push($rs,$r); + } + return $rs; + } + + + function isDirectOffer($pid, $oid) { + $q = $this->db->query( + "SELECT id FROM sale,private_offer WHERE offer=id AND to_player=$pid AND id=$oid " + . "AND (expires IS NULL OR UNIX_TIMESTAMP(NOW())-expires<0) AND finalized IS NULL" + ); + return $q && (dbCount($q)==1); + } +} + +?> diff --git a/scripts/game/beta5/sale/library/bid.inc b/scripts/game/beta5/sale/library/bid.inc new file mode 100644 index 0000000..6986b11 --- /dev/null +++ b/scripts/game/beta5/sale/library/bid.inc @@ -0,0 +1,119 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->players = $this->lib->game->getLib('beta5/player'); + $this->planets = $this->lib->game->getLib('beta5/planet'); + $this->fleets = $this->lib->game->getLib('beta5/fleet'); + } + + + function run($pid, $oid, $price) { + $q = $this->db->query( + "SELECT id,price,player FROM sale,public_offer" + ." WHERE id=$oid AND offer=$oid AND player<>$pid AND auction" + . " AND finalized IS NULL AND expires>UNIX_TIMESTAMP(NOW())" + ); + if (!($q && dbCount($q))) { + return "0"; + } + $r = dbFetchArray($q); + $q = $this->db->query("SELECT price FROM auction WHERE offer=$oid ORDER BY price DESC LIMIT 1"); + if ($q && dbCount($q)) { + list($mp) = dbFetchArray($q); + } else { + $mp = $r[1]; + } + if ($mp >= $price) { + return "2#$oid#$mp"; + } + + $q = $this->db->query("SELECT price FROM auction WHERE offer=$oid AND player=$pid ORDER BY price DESC LIMIT 1"); + if ($q && dbCount($q)) { + list($lBid) = dbFetchArray($q); + } else { + $lBid = 0; + } + $rPrice = $price - $lBid; + + $pi = $this->players->call('get', $pid); + if ($pi['cash'] < $rPrice) { + return "1"; + } + $this->db->query("UPDATE player SET cash=cash-$rPrice WHERE id=$pid"); + $this->db->query("INSERT INTO auction VALUES ($oid,$pid,".time().",$price)"); + $this->sendOwnerBidMessage($r[2], $oid, $price, $pid); + + $q = $this->db->query("SELECT player FROM auction WHERE offer=$oid AND price<$price ORDER BY price DESC LIMIT 1"); + if (!($q && dbCount($q))) { + return ""; + } + list($oldPId) = dbFetchArray($q); + if ($oldPId == $pid) { + return ""; + } + $this->sendNewBidMessage($oldPId, $oid, $price); + return ""; + } + + + function sendOwnerBidMessage($player, $offer, $newPrice, $bidder) { + $q = $this->db->query("SELECT fleet,planet FROM sale WHERE id=$offer"); + list($fid,$pid) = dbFetchArray($q); + $qs = "$offer," . (is_null($pid)?0:1); + + if (!is_null($fid)) { + $fleet = $this->fleets->call('get', $fid); + $g = $fleet['gaships']; $f = $fleet['fighters']; $c = $fleet['cruisers']; $b = $fleet['bcruisers']; + if (is_null($pid)) { + $pid = $fleet['location']; + } + } else { + $g=$f=$c=$b=0; + } + $pinf = $this->planets->call('byId', $pid); + $pname = $pinf['name']; + + $qs .= ",'".addslashes($pname)."',$pid,$g,$f,$c,$b,$newPrice"; + + $tm = time(); + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($player,$tm,'bid','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=$player AND sent_on=$tm AND mtype='bid' ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_bid VALUES($mid,$qs,$bidder)"); + } + + + function sendNewBidMessage($player, $offer, $newPrice) { + $q = $this->db->query("SELECT fleet,planet FROM sale WHERE id=$offer"); + list($fid,$pid) = dbFetchArray($q); + $qs = "$offer," . (is_null($pid)?0:1); + + if (!is_null($fid)) { + $fleet = $this->fleets->call('get', $fid); + $g = $fleet['gaships']; $f = $fleet['fighters']; $c = $fleet['cruisers']; $b = $fleet['bcruisers']; + if (is_null($pid)) { + $pid = $fleet['location']; + } + } else { + $g=$f=$c=$b=0; + } + $pinf = $this->planets->call('byId', $pid); + $pname = $pinf['name']; + + $qs .= ",'".addslashes($pname)."',$pid,$g,$f,$c,$b,$newPrice"; + + $tm = time(); + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($player,$tm,'bid','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=$player AND sent_on=$tm AND mtype='bid' ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_bid VALUES($mid,$qs,NULL)"); + } +} + +?> diff --git a/scripts/game/beta5/sale/library/buy.inc b/scripts/game/beta5/sale/library/buy.inc new file mode 100644 index 0000000..5b47cdf --- /dev/null +++ b/scripts/game/beta5/sale/library/buy.inc @@ -0,0 +1,61 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->players = $this->lib->game->getLib('beta5/player'); + } + + + function run($pid, $offer) { + $q = $this->db->query( + "SELECT player,fleet,planet FROM sale " + . "WHERE id=$offer AND player<>$pid AND finalized IS NULL AND " + . "(expires IS NULL OR expires>UNIX_TIMESTAMP(NOW()))" + ); + if (!($q && dbCount($q))) { + return "0"; + } + list($seller,$flId,$plId) = dbFetchArray($q); + + $q = $this->db->query("SELECT price FROM public_offer WHERE offer=$offer AND NOT auction UNION SELECT price FROM private_offer WHERE offer=$offer"); + if (!($q && dbCount($q))) { + return "0"; + } + list($price) = dbFetchArray($q); + + $pi = $this->players->call('get', $pid); + if ($pi['cash'] < $price) { + return "1"; + } + + $tm = time(); + $this->db->query("UPDATE player SET cash=cash-$price WHERE id=$pid"); + $this->db->query("UPDATE player SET cash=cash+$price WHERE id=$seller"); + $this->db->query("UPDATE sale SET finalized=$tm,sold_to=$pid WHERE id=$offer"); + + // Start owner transfer + if (is_null($plId)) { + $qs = ""; + } else { + $this->db->query("UPDATE planet SET sale=3 WHERE id=$plId"); + $q = $this->db->query("SELECT pop,turrets,ifact+mfact FROM planet WHERE id=$plId"); + list($pop,$turrets,$fact) = dbFetchArray($q); + $qs = ",p_pop=$pop,p_turrets=$turrets,p_factories=$fact"; + } + if (!is_null($flId)) { + $this->db->query("UPDATE fleet SET sale=3 WHERE id=$flId"); + } + + // Update history + $this->db->query("UPDATE sale_history SET ended=$tm,end_mode=2,sell_price=$price,to_player=$pid$qs WHERE offer=$offer"); + + // FIXME: send message + } +} + +?> diff --git a/scripts/game/beta5/sale/library/cancel.inc b/scripts/game/beta5/sale/library/cancel.inc new file mode 100644 index 0000000..3e16b3d --- /dev/null +++ b/scripts/game/beta5/sale/library/cancel.inc @@ -0,0 +1,45 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($pid,$id) { + $q = $this->db->query("SELECT planet FROM sale WHERE player=$pid AND id=$id AND finalized IS NULL"); + if (!($q && dbCount($q))) { + return false; + } + list($pl) = dbFetchArray($q); + + // Refund auctions + $q = $this->db->query("SELECT MAX(price),player FROM auction WHERE offer=$id GROUP BY player"); + while ($r = dbFetchArray($q)) { + $this->db->query("UPDATE player SET cash=cash+".$r[0]." WHERE id=".$r[1]); + // FIXME: send message + } + + // Insert history entry + if (is_null($pl)) { + $qs = ""; + } else { + $q = $this->db->query("SELECT pop,turrets,ifact+mfact FROM planet WHERE id=$pl"); + list($pop,$turrets,$fact) = dbFetchArray($q); + $qs = ",p_pop=$pop,p_turrets=$turrets,p_factories=$fact"; + } + $tm = time(); + $this->db->query("UPDATE sale_history SET end_mode=0,ended=$tm$qs WHERE offer=$id"); + + // Delete offer + $this->db->query("DELETE FROM sale WHERE id=$id"); + + return true; + } +} + +?> diff --git a/scripts/game/beta5/sale/library/cancelTransfer.inc b/scripts/game/beta5/sale/library/cancelTransfer.inc new file mode 100644 index 0000000..e60b9c3 --- /dev/null +++ b/scripts/game/beta5/sale/library/cancelTransfer.inc @@ -0,0 +1,61 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($pid,$id) { + // Get sale details + $q = $this->db->query("SELECT planet,fleet,sold_to FROM sale WHERE player=$pid AND id=$id AND finalized IS NOT NULL"); + if (!($q && dbCount($q))) { + return false; + } + list($pl,$fl,$stid) = dbFetchArray($q); + $q = $this->db->query("SELECT price FROM public_offer WHERE offer=$id UNION SELECT price FROM private_offer WHERE offer=$id"); + list($price) = dbFetchArray($q); + + // Get seller cash and find out if refund is possible + $q = $this->db->query("SELECT cash FROM player WHERE id=$pid"); + list($cSeller) = dbFetchArray($q); + if ($cSeller < $price) { + return false; + } + + // Refund + $this->db->query("UPDATE player SET cash=cash+$price WHERE id=$stid"); + $this->db->query("UPDATE player SET cash=cash-$price WHERE id=$pid"); + + // Cancel planet & fleet sale + if (is_null($pl)) { + $qs = ""; + } else { + $this->db->query("UPDATE planet SET sale=NULL WHERE id=$pl"); + $q = $this->db->query("SELECT pop,turrets,ifact+mfact FROM planet WHERE id=$pl"); + list($pop,$turrets,$fact) = dbFetchArray($q); + $qs = ",p_pop=$pop,p_turrets=$turrets,p_factories=$fact"; + } + if (!is_null($fl)) { + $this->db->query("UPDATE fleet SET sale=NULL WHERE id=$fl"); + } + + // Insert history entries + $tm = time(); + $this->db->query("UPDATE sale_history SET end_mode=1,ended=$tm,sell_price=NULL$qs WHERE offer=$id"); + + // Send message + // FIXME + + // Delete offer + $this->db->query("DELETE FROM sale WHERE id=$id"); + + return true; + } +} + +?> diff --git a/scripts/game/beta5/sale/library/decline.inc b/scripts/game/beta5/sale/library/decline.inc new file mode 100644 index 0000000..6a5ab41 --- /dev/null +++ b/scripts/game/beta5/sale/library/decline.inc @@ -0,0 +1,39 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($id) { + $q = $this->db->query("SELECT planet FROM sale WHERE id=$id"); + if (!($q&&dbCount($q))) { + return; + } + list($pl) = dbFetchArray($q); + + // Update history entry + if (is_null($pl)) { + $qs = ""; + } else { + $q = $this->db->query("SELECT pop,turrets,ifact+mfact FROM planet WHERE id=$pl"); + list($pop,$turrets,$fact) = dbFetchArray($q); + $qs = ",p_pop=$pop,p_turrets=$turrets,p_factories=$fact"; + } + $tm = time(); + $this->db->query("UPDATE sale_history SET end_mode=4,ended=$tm$qs WHERE offer=$id"); + + // Send message + // FIXME + + // Delete offer + $this->db->query("DELETE FROM sale WHERE id=$id"); + } +} + +?> diff --git a/scripts/game/beta5/sale/library/getDirectSales.inc b/scripts/game/beta5/sale/library/getDirectSales.inc new file mode 100644 index 0000000..a50fe9b --- /dev/null +++ b/scripts/game/beta5/sale/library/getDirectSales.inc @@ -0,0 +1,56 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($pid) { + $q = $this->db->query( + "SELECT * FROM sale,private_offer WHERE id=offer AND to_player=$pid AND finalized IS NULL AND offer=id " + . "AND (expires IS NULL OR expires>UNIX_TIMESTAMP(NOW())) ORDER BY started DESC" + ); + + $oList = $pList = $fList = array(); + while ($r = dbFetchHash($q)) { + $oList[$r['id']] = $r; + if (!is_null($r['planet'])) { + $pList[$r['planet']] = $r['id']; + } + if (!is_null($r['fleet'])) { + $fList[$r['fleet']] = $r['id']; + } + } + + $l1 = join(',',array_keys($pList)); + if ($l1 != '') { + $q = $this->db->query( + "SELECT p.id AS id,pop AS pop,p.turrets AS turrets,p.mfact+p.ifact AS fact,p.orbit AS orbit,s.x AS x,s.y AS y,p.name AS name " + . "FROM planet p,system s WHERE p.system=s.id AND p.id IN ($l1)" + ); + while ($r = dbFetchHash($q)) { + $oList[$pList[$r['id']]]['planet'] = $r; + } + } + + $l1 = join(',',array_keys($fList)); + if ($l1 != '') { + $q = $this->db->query( + "SELECT f.id AS id,f.gaships AS sg,f.fighters AS sf,f.cruisers AS sc,f.bcruisers AS sb," + ."p.id AS pid,p.name AS pname,s.x AS x,s.y AS y,p.orbit AS orbit " + . "FROM fleet f,planet p,system s WHERE f.id IN ($l1) AND p.id=f.location AND p.system=s.id" + ); + while ($r = dbFetchHash($q)) + $oList[$fList[$r['id']]]['fleet'] = $r; + } + + return array_values($oList); + } +} + +?> diff --git a/scripts/game/beta5/sale/library/getFleetSale.inc b/scripts/game/beta5/sale/library/getFleetSale.inc new file mode 100644 index 0000000..a5150bf --- /dev/null +++ b/scripts/game/beta5/sale/library/getFleetSale.inc @@ -0,0 +1,49 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($sId) { + $q = $this->db->query("SELECT id,player,expires,finalized,sold_to,fleet FROM sale WHERE id=$sId AND planet IS NULL AND fleet IS NOT NULL"); + if (!($q && dbCount($q))) { + return null; + } + $r = dbFetchHash($q); + + $q = $this->db->query("SELECT sale FROM fleet WHERE id={$r['fleet']}"); + if (!($q && dbCount($q))) + return null; + list($r['tx_time']) = dbFetchArray($q); + + $q = $this->db->query("SELECT price,auction FROM public_offer WHERE offer=".$r['id']); + if ($q && dbCount($q)) { + $r['public'] = true; + list($r['price'],$r['is_auction']) = dbFetchArray($q); + $r['is_auction'] = ($r['is_auction'] == 't'); + if ($r['is_auction']) { + $q = $this->db->query("SELECT MAX(price) FROM auction WHERE offer={$r['id']}"); + if ($q && dbCount($q)) { + list($r['max_bid']) = dbFetchArray($q); + if (!is_null($r['max_bid'])) { + $q = $this->db->query("SELECT player,moment FROM auction WHERE offer={$r['id']} AND price={$r['max_bid']}"); + list($r['bidder'],$r['last_bid']) = dbFetchArray($q); + } + } + } + return $r; + } + + $q = $this->db->query("SELECT price,to_player FROM private_offer WHERE offer=".$r['id']); + list($r['price'],$r['sold_to']) = dbFetchArray($q); + return $r; + } +} + +?> diff --git a/scripts/game/beta5/sale/library/getPlanetSale.inc b/scripts/game/beta5/sale/library/getPlanetSale.inc new file mode 100644 index 0000000..b56d3a0 --- /dev/null +++ b/scripts/game/beta5/sale/library/getPlanetSale.inc @@ -0,0 +1,33 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($id) { + $q = $this->db->query("SELECT id,expires,finalized,sold_to FROM sale WHERE planet=$id"); + if (!($q && dbCount($q))) { + return null; + } + $r = dbFetchHash($q); + + $q = $this->db->query("SELECT price FROM public_offer WHERE offer=".$r['id']); + if ($q && dbCount($q)) { + $r['public'] = true; + list($r['price']) = dbFetchArray($q); + return $r; + } + + $q = $this->db->query("SELECT price,to_player FROM private_offer WHERE offer=".$r['id']); + list($r['price'],$r['sold_to']) = dbFetchArray($q); + return $r; + } +} + +?> diff --git a/scripts/game/beta5/sale/library/getPublicSales.inc b/scripts/game/beta5/sale/library/getPublicSales.inc new file mode 100644 index 0000000..0a27f28 --- /dev/null +++ b/scripts/game/beta5/sale/library/getPublicSales.inc @@ -0,0 +1,77 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($pList) { + $l1 = join(',',$pList); + + $q = $this->db->query("SELECT id FROM fleet WHERE location IN ($l1)"); + $fList = array(); + while ($r = dbFetchArray($q)) { + array_push($fList, $r[0]); + } + $l2 = join(',',$fList); + + $q = $this->db->query( + "SELECT * FROM sale,public_offer " + . "WHERE finalized IS NULL AND offer=id AND (expires IS NULL OR expires>UNIX_TIMESTAMP(NOW())) " + . "AND (planet IN ($l1)" . ($l2 != '' ? " OR fleet IN ($l2)" : "") . ")" + ); + $aList = $oList = $pList = $fList = array(); + while ($r = dbFetchHash($q)) { + $oList[$r['id']] = $r; + if (!is_null($r['planet'])) { + $pList[$r['planet']] = $r['id']; + } + if (!is_null($r['fleet'])) { + $fList[$r['fleet']] = $r['id']; + } + if ($r['auction'] == 't') { + $aList[$r['id']] = $r['price']; + } + } + + $l1 = join(',',array_keys($aList)); + if ($l1 != '') { + $q = $this->db->query("SELECT offer,MAX(price) FROM auction WHERE offer IN ($l1) GROUP BY offer"); + while ($r = dbFetchArray($q)) { + $oList[$r[0]]['price'] = $r[1]; + } + } + + $l1 = join(',',array_keys($pList)); + if ($l1 != '') { + $q = $this->db->query( + "SELECT p.id AS id,pop AS pop,p.turrets AS turrets,p.mfact+p.ifact AS fact,p.orbit AS orbit,s.x AS x,s.y AS y,p.name AS name " + . "FROM planet p,system s WHERE p.system=s.id AND p.id IN ($l1)" + ); + while ($r = dbFetchHash($q)) { + $oList[$pList[$r['id']]]['planet'] = $r; + } + } + + $l1 = join(',',array_keys($fList)); + if ($l1 != '') { + $q = $this->db->query( + "SELECT f.id AS id,f.gaships AS sg,f.fighters AS sf,f.cruisers AS sc,f.bcruisers AS sb," + ."p.id AS pid,p.name AS pname,s.x AS x,s.y AS y,p.orbit AS orbit " + . "FROM fleet f,planet p,system s WHERE f.id IN ($l1) AND p.id=f.location AND p.system=s.id" + ); + while ($r = dbFetchHash($q)) { + $oList[$fList[$r['id']]]['fleet'] = $r; + } + } + + return array_values($oList); + } +} + +?> diff --git a/scripts/game/beta5/sale/library/getSentOffers.inc b/scripts/game/beta5/sale/library/getSentOffers.inc new file mode 100644 index 0000000..73ae3c7 --- /dev/null +++ b/scripts/game/beta5/sale/library/getSentOffers.inc @@ -0,0 +1,85 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($pid) { + $q = $this->db->query( + "SELECT * FROM sale WHERE player=$pid AND finalized IS NULL " + . "AND (expires IS NULL OR expires>UNIX_TIMESTAMP(NOW()))" + ); + + $oList = $pList = $fList = array(); + while ($r = dbFetchHash($q)) { + $oList[$r['id']] = $r; + if (!is_null($r['planet'])) { + $pList[$r['planet']] = $r['id']; + } + if (!is_null($r['fleet'])) { + $fList[$r['fleet']] = $r['id']; + } + } + + $l1 = join(',',array_keys($oList)); + $aList = array(); + if ($l1 != '') { + $q = $this->db->query("SELECT * FROM public_offer WHERE offer IN ($l1)"); + while ($r = dbFetchArray($q)) { + $oList[$r[0]]['mode'] = ($r[2] == 1 ? 3 : 2); + $oList[$r[0]]['price'] = $r[1]; + if ($r[2]) + array_push($aList, $r[0]); + // FIXME: to_player for auctions + } + + $q = $this->db->query("SELECT * FROM private_offer WHERE offer IN ($l1)"); + while ($r = dbFetchArray($q)) { + $oList[$r[0]]['mode'] = ($r[2] == 0 ? 0 : 1); + $oList[$r[0]]['to_player'] = $r[1]; + $oList[$r[0]]['price'] = $r[2]; + } + } + + $l1 = join(',', $aList); + if ($l1 != '') { + $q = $this->db->query("SELECT offer,MAX(price) FROM auction WHERE offer IN ($l1) GROUP BY offer"); + while ($r = dbFetchArray($q)) { + $oList[$r[0]]['price'] = $r[1]; + } + } + + $l1 = join(',',array_keys($pList)); + if ($l1 != '') { + $q = $this->db->query( + "SELECT p.id AS id,pop AS pop,p.turrets AS turrets,p.mfact+p.ifact AS fact,p.orbit AS orbit,s.x AS x,s.y AS y,p.name AS name " + . "FROM planet p,system s WHERE p.system=s.id AND p.id IN ($l1)" + ); + while ($r = dbFetchHash($q)) { + $oList[$pList[$r['id']]]['planet'] = $r; + } + } + + $l1 = join(',',array_keys($fList)); + if ($l1 != '') { + $q = $this->db->query( + "SELECT f.id AS id,f.gaships AS sg,f.fighters AS sf,f.cruisers AS sc,f.bcruisers AS sb," + ."p.id AS pid,p.name AS pname,s.x AS x,s.y AS y,p.orbit AS orbit " + . "FROM fleet f,planet p,system s WHERE f.id IN ($l1) AND p.id=f.location AND p.system=s.id" + ); + while ($r = dbFetchHash($q)) { + $oList[$fList[$r['id']]]['fleet'] = $r; + } + } + + return array_values($oList); + } +} + +?> diff --git a/scripts/game/beta5/sale/library/sell.inc b/scripts/game/beta5/sale/library/sell.inc new file mode 100644 index 0000000..3fdf65b --- /dev/null +++ b/scripts/game/beta5/sale/library/sell.inc @@ -0,0 +1,69 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->planets = $this->lib->game->getLib('beta5/planet'); + $this->fleets = $this->lib->game->getLib('beta5/fleet'); + } + + + function run($player, $public, $auction, $expires, $price, $target, $planet, $fleet) { + // Insert sale entry + $tm = time(); + $exp = ($expires > 0) ? ($tm + 3600 * $expires) : 'NULL'; + $pl = is_null($planet)?'NULL':$planet; + $plq = is_null($planet)?' IS NULL':"=$planet"; + $fl = is_null($fleet)?'NULL':$fleet; + $flq = is_null($fleet)?' IS NULL':"=$fleet"; + $this->db->query("INSERT INTO sale(player,started,expires,planet,fleet) VALUES ($player,$tm,$exp,$pl,$fl)"); + + // Insert offer + $q = $this->db->query("SELECT id FROM sale WHERE player=$player AND started=$tm AND planet$plq AND fleet$flq"); + list($sId) = dbFetchArray($q); + if ($public) { + $this->db->query("INSERT INTO public_offer VALUES($sId,$price,".dbBool($auction).")"); + } else { + $this->db->query("INSERT INTO private_offer VALUES($sId,$target,$price)"); + } + + // Insert history entry + $mode = $public ? ($auction ? 3 : 2) : ($price > 0 ? 1 : 0); + $toPl = $public ? "NULL" : $target; + if (is_null($planet)) { + $finf = $this->fleets->call('get', $fleet); + $pinf = $this->planets->call('byId', $finf['location']); + $pName = addslashes($pinf['name']); + $pid = $finf['location']; + } else { + $pinf = $this->planets->call('byId', $planet); + $pid = $planet; + $pName = addslashes($pinf['name']); + } + if (is_null($fleet)) { + $flFields = $flValues = ""; + } else { + if (is_null($finf)) { + $finf = $this->fleets->call('get', $fleet); + } + $flFields = ",f_gaships,f_fighters,f_cruisers,f_bcruisers"; + $flValues = ",".$finf['gaships'].",".$finf['fighters'].",".$finf['cruisers'].",".$finf['bcruisers']; + } + $this->db->query( + "INSERT INTO sale_history(offer,from_player,to_player,started,mode,price,p_id,p_name,is_planet$flFields) VALUES" + . "($sId,$player,$toPl,$tm,$mode,$price,$pid,'$pName',".dbBool(!is_null($planet))."$flValues)" + ); + + // FIXME: send messages + + if (!is_null($fleet)) { + $this->fleets->call('invCache', $fleet); + } + } +} + +?> diff --git a/scripts/game/beta5/standby/library.inc b/scripts/game/beta5/standby/library.inc new file mode 100644 index 0000000..e1e0d98 --- /dev/null +++ b/scripts/game/beta5/standby/library.inc @@ -0,0 +1,51 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->planets = $this->lib->game->getLib('beta5/planet'); + $this->players = $this->lib->game->getLib('beta5/player'); + } + + + // Generates a new Hyperspace stand-by order + function create($time, $location, $origin = null, $spent = null) { + if (is_null($origin)) { + $origin = "NULL"; + } + if (is_null($spent)) { + $spent = "0"; + } + return $this->db->query("INSERT INTO hs_wait (time_left,time_spent,drop_point,origin) VALUES ($time,$spent,$location,$origin)"); + } + + + // Checks whether fleets can be destroyed while waiting in hyperspace at a given location + function canDestroy($location, $owner) { + // Conditions for fleet destruction: no HS beacon OR (HS beacon AND not in alliance AND not trusted) + $p = $this->planets->call('byId', $location); + $canDestroy = ($p['beacon'] == 0); + if (!($canDestroy || is_null($p['owner']) || $p['owner'] == $owner)) { + $fo = $this->players->call('get', $owner); + $po = $this->players->call('get', $p['owner']); + $canDestroy = !$this->players->call('isAlly', $p['owner'], $owner) && (is_null($fo['aid']) || $fo['aid'] !== $po['aid']); + } + return $canDestroy; + } + + + // Computes the probability for fleet destruction when standing by in Hyperspace + function getLossProb($timeSpent) { + $fact = ($timeSpent+1) / 36; + $fact *= $fact; + if ($fact > 1) { + $fact = 1; + } + return floor($fact * 100); + } +} + +?> diff --git a/scripts/game/beta5/tech/library.inc b/scripts/game/beta5/tech/library.inc new file mode 100644 index 0000000..6c6991d --- /dev/null +++ b/scripts/game/beta5/tech/library.inc @@ -0,0 +1,98 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + } + + + // Modifies the research budget + function setBudget($pid, $ba) { + $s = join('!', $ba); + $this->db->query("UPDATE player SET research='$s' WHERE id = $pid"); + } + + + // Checks whether a player can make an offer or if he has already done so. + function checkOffer($pid) { + $delay = $this->game->ticks['day']->interval - $this->game->ticks['hour']->interval * 2; + $q = $this->db->query( + "SELECT id FROM research_assistance " + . "WHERE player = $pid AND (UNIX_TIMESTAMP(NOW()) - moment) < $delay" + ); + return (dbCount($q) > 0); + } + + + function getAllDependencies(&$list, $id) { + if (is_array($list[$id]['all_deps'])) { + return $list[$id]['all_deps']; + } + + $list[$id]['all_deps'] = array(); + foreach ($list[$id]['depends_on'] as $dep) { + if (in_array($dep, $list[$id]['all_deps'])) { + continue; + } + + array_push($list[$id]['all_deps'], $dep); + $ddeps = $this->getAllDependencies($list, $dep); + foreach ($ddeps as $ddep) { + if (in_array($ddep, $list[$id]['all_deps'])) { + continue; + } + array_push($list[$id]['all_deps'], $ddep); + } + } + return $list[$id]['all_deps']; + } + + // Returns the list of all technologies + public function getAll($lang) { + $q = $this->db->query( + "SELECT t.research, t.name, d.depends_on FROM research_txt t " + . "LEFT JOIN research_dep d ON d.research = t.research " + . "WHERE t.lang = '$lang' " + . "ORDER BY t.research, d.depends_on" + ); + + $list = array(); + while ($r = dbFetchArray($q)) { + if (!is_array($list[$r[0]])) { + $list[$r[0]] = array( + "name" => $r[1], + "deps" => array() + ); + } + if (!is_null($r[2])) { + array_push($list[$r[0]]["deps"], $r[2]); + } + } + + return $list; + } +} + +?> diff --git a/scripts/game/beta5/tech/library/acceptOffer.inc b/scripts/game/beta5/tech/library/acceptOffer.inc new file mode 100644 index 0000000..88c1d4e --- /dev/null +++ b/scripts/game/beta5/tech/library/acceptOffer.inc @@ -0,0 +1,63 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->players = $this->lib->game->getLib('beta5/player'); + } + + + // Accepts a research offer + function run($pid, $oid) { + $q = $this->db->query("SELECT * FROM research_assistance WHERE offer_to=$pid AND id=$oid AND (UNIX_TIMESTAMP(NOW())-moment<=86400)"); + if (!($q && dbCount($q)==1)) { + return 4; + } + $r = dbFetchHash($q); + if (!is_null($r['accepted'])) { + return $r['accepted'] == 't' ? 2 : 3; + } + if ($r['price'] > 0) { + $pinf = $this->players->call('get', $pid); + if ($r['price'] > $pinf['cash']) { + return 1; + } + } + + $this->db->query("UPDATE research_assistance SET accepted=TRUE WHERE id=$oid"); + if (is_null($r['technology'])) { + $this->db->query("UPDATE player SET res_assistance=" . $r['player'] . " WHERE id=$pid"); + } else { + $tid = $r['technology']; + if (!$this->lib->call('checkDependencies', $pid, $tid)) { + return 5; + } + + $q = $this->db->query("SELECT points FROM research WHERE id=$tid"); + list($points) = dbFetchArray($q); + $points = ceil(75 * $points / 100); + $q = $this->db->query("SELECT * FROM research_player WHERE player=$pid AND research=$tid"); + if (!dbCount($q)) { + $this->db->query("INSERT INTO research_player VALUES($pid,$tid,TRUE,0,$points," . $r['player'] . ")"); + } else { + $this->db->query("UPDATE research_player SET possible=TRUE,points=$points,given_by=".$r['player']." WHERE player=$pid AND research=$tid"); + } + } + if ($r['price'] > 0) { + $this->db->query("UPDATE player SET cash=cash-" . $r["price"] . " WHERE id=$pid"); + $this->db->query("UPDATE player SET cash=cash+" . $r["price"] . " WHERE id=".$r['player']); + } + + $tm = time(); + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES(".$r['player'].",$tm,'resdipl','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=".$r['player']." AND sent_on=$tm AND ftype='INT'"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_resdipl VALUES($mid,$oid,1)"); + + return 0; + } +} + +?> diff --git a/scripts/game/beta5/tech/library/checkDependencies.inc b/scripts/game/beta5/tech/library/checkDependencies.inc new file mode 100644 index 0000000..5d82cf7 --- /dev/null +++ b/scripts/game/beta5/tech/library/checkDependencies.inc @@ -0,0 +1,26 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Checks whether a player has already researched a technology's dependencies + function run($pid, $rid) { + $q = $this->db->query("SELECT depends_on FROM research_dep WHERE research=$rid"); + if (!dbCount($q)) { + return true; + } + + $rv = true; + while ($rv && $r = dbFetchArray($q)) { + $rv = $rv && $this->lib->call('has', $pid, $r[0]); + } + return $rv; + } +} + +?> diff --git a/scripts/game/beta5/tech/library/createTree.inc b/scripts/game/beta5/tech/library/createTree.inc new file mode 100644 index 0000000..8cec893 --- /dev/null +++ b/scripts/game/beta5/tech/library/createTree.inc @@ -0,0 +1,47 @@ +lib = $lib; + $this->db = $lib->game->db; + } + + + function run($player) { + list($res, $rLeaf) = $this->lib->call('getTree'); + + $pRes = array(); + foreach ($res as $r) { + if ($r['optional'] == 0) { + array_push($pRes, $r['id']); + } + } + + while (count($pRes) < 2 * count($res) / 5) { + $tried = array(); + do { + do { + $rId = $rLeaf[rand(0, count($rLeaf) - 1)]; + } while (in_array($rId, $pRes) || in_array($rId, $tried)); + + $npRes = $pRes; + array_push($tried, $rId); + array_push($npRes, $rId); + $mDeps = $this->lib->mainClass->getAllDependencies($res,$rId); + foreach ($mDeps as $d) { + if (!in_array($d, $npRes)) { + array_push($npRes, $d); + } + } + } while (count($npRes) > 3 * count($res) / 5); + $pRes = $npRes; + } + + foreach ($pRes as $rid) { + $this->db->query("INSERT INTO research_player(player,research,possible) VALUES($player,$rid,TRUE)"); + } + } +} + +?> diff --git a/scripts/game/beta5/tech/library/declineOffer.inc b/scripts/game/beta5/tech/library/declineOffer.inc new file mode 100644 index 0000000..4bb165d --- /dev/null +++ b/scripts/game/beta5/tech/library/declineOffer.inc @@ -0,0 +1,34 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Declines a research offer + function run($pid, $oid) { + $q = $this->db->query("SELECT * FROM research_assistance WHERE offer_to=$pid AND id=$oid AND (UNIX_TIMESTAMP(NOW())-moment<=86400)"); + if (!($q && dbCount($q)==1)) { + return 4; + } + + $r = dbFetchHash($q); + if (!is_null($r['accepted'])) { + return $r['accepted'] == 't' ? 2 : 3; + } + + $this->db->query("UPDATE research_assistance SET accepted=FALSE WHERE id=$oid"); + $tm = time(); + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES(".$r['player'].",$tm,'resdipl','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=".$r['player']." AND sent_on=$tm AND ftype='INT'"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_resdipl VALUES($mid,$oid,2)"); + + return 0; + } +} + +?> diff --git a/scripts/game/beta5/tech/library/getAvailableTechs.inc b/scripts/game/beta5/tech/library/getAvailableTechs.inc new file mode 100644 index 0000000..981f709 --- /dev/null +++ b/scripts/game/beta5/tech/library/getAvailableTechs.inc @@ -0,0 +1,82 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + public function run($player) { + // Get all techs the player has implemented + $q = $this->db->query( + "SELECT r.id FROM research r, research_player p " + . "WHERE r.id = p.research AND p.points = r.points AND p.player = $player " + . "AND (p.in_effect <> 0 OR r.is_law)" + ); + $gotTechs = array(); + while ($r = dbFetchArray($q)) { + array_push($gotTechs, $r[0]); + } + + // If we don't have techs, return + if (empty($gotTechs)) { + return $gotTechs; + } + + // Get all techs the player sees + $q = $this->db->query( + "SELECT r.id FROM research r, research_player p " + . "WHERE r.id = p.research AND p.points >= 75 * r.points / 100 AND p.player = $player" + ); + $seeTechs = array(); + while ($r = dbFetchArray($q)) { + array_push($seeTechs, $r[0]); + } + + // Get the amount of dependencies for each tech not in the list + $q = $this->db->query( + "SELECT research,COUNT(*) FROM research_dep " + . "WHERE research NOT IN (" . join(',',$seeTechs) . ") " + . "GROUP BY research" + ); + if (!dbCount($q)) { + return array(); + } + $depCount = array(); + while ($r = dbFetchArray($q)) { + $depCount[$r[0]] = $r[1]; + } + + // Now get all techs that depend on one of these techs + $q = $this->db->query( + "SELECT research,COUNT(*) FROM research_dep " + . "WHERE research NOT IN (" . join(',',$seeTechs) . ") " + . "AND depends_on IN (" . join(',',$gotTechs) . ")" + . "GROUP BY research" + ); + $okTechs = array(); + while ($r = dbFetchArray($q)) { + if ($depCount[$r[0]] == $r[1]) { + array_push($okTechs, $r[0]); + } + } + + return $okTechs; + } + +} + +?> diff --git a/scripts/game/beta5/tech/library/getBudget.inc b/scripts/game/beta5/tech/library/getBudget.inc new file mode 100644 index 0000000..a44ed20 --- /dev/null +++ b/scripts/game/beta5/tech/library/getBudget.inc @@ -0,0 +1,23 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns an array containing research budget allocations + function run($pid) { + $q = $this->db->query("SELECT research FROM player WHERE id = $pid"); + if (!($q && dbCount($q))) { + return array(); + } + + list($b) = dbFetchArray($q); + return split('!', $b); + } +} + +?> diff --git a/scripts/game/beta5/tech/library/getLaws.inc b/scripts/game/beta5/tech/library/getLaws.inc new file mode 100644 index 0000000..6213c0e --- /dev/null +++ b/scripts/game/beta5/tech/library/getLaws.inc @@ -0,0 +1,26 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns the list of laws available to a player, as well as their status + function run($pl) { + $q = $this->db->query( + "SELECT r.id, p.in_effect FROM research r, research_player p " + . "WHERE r.id = p.research AND p.player = $pl AND r.points = p.points AND r.is_law" + ); + $a = array(); + while ($r = dbFetchArray($q)) { + array_push($a, $r[0]); + array_push($a, $r[1]); + } + return $a; + } +} + +?> diff --git a/scripts/game/beta5/tech/library/getOffers.inc b/scripts/game/beta5/tech/library/getOffers.inc new file mode 100644 index 0000000..bb4532f --- /dev/null +++ b/scripts/game/beta5/tech/library/getOffers.inc @@ -0,0 +1,51 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->players = $this->lib->game->getLib('beta5/player'); + } + + + // Get a list of scientific assistance offers sent and received by a player + function run($pid) { + $pi = $this->players->call('get', $pid); + $interval = $this->lib->game->ticks['day']->interval - 2 * $this->lib->game->ticks['hour']->interval; + $q = $this->db->query("SELECT id FROM research_assistance WHERE offer_to=$pid AND accepted AND (UNIX_TIMESTAMP(NOW()) - moment) < $interval"); + $accToday = (dbCount($q) > 0); + + $q = $this->db->query("SELECT * FROM research_assistance WHERE player=$pid OR offer_to=$pid ORDER BY moment DESC"); + $ol = array(); + $t = time(); + while ($r = dbFetchHash($q)) { + $o = array(); + $o['id'] = $r["id"]; + $o['type'] = ($pid == $r['player']) ? 'S' : 'R'; + $o['time'] = $r['moment']; + $o['tech'] = $r['technology']; + $o['price'] = $r['price']; + $o['status'] = is_null($r['accepted']) ? ($t - $r['moment'] > $interval ? 'E' : 'P') : ($r['accepted'] == 't' ? 'A' : 'R'); + if ($o['status'] == 'P' && $o['type'] == 'R') { + if ($o['price'] > $pi['cash']) { + $o['pending'] = 1; + } elseif ($accToday) { + $o['pending'] = 2; + } elseif ($o['tech'] != "" && $this->lib->call('has', $pid, $o['tech'], true)) { + $o['pending'] = 3; + } elseif ($o['tech'] != "" && !$this->lib->call('checkDependencies', $pid, $o['tech'])) { + $o['pending'] = 4; + } else { + $o['pending'] = 0; + } + } + $pinf = $this->players->call('getName', ($pid == $r['player']) ? $r['offer_to'] : $r['player']); + $o['player'] = $pinf; + array_push($ol, $o); + } + return $ol; + } +} + +?> diff --git a/scripts/game/beta5/tech/library/getPoints.inc b/scripts/game/beta5/tech/library/getPoints.inc new file mode 100644 index 0000000..c3056ae --- /dev/null +++ b/scripts/game/beta5/tech/library/getPoints.inc @@ -0,0 +1,25 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->rules = $this->lib->game->getLib('beta5/rules'); + } + + + // Returns the amount of research points a player gains daily + function run($pid) { + $q = $this->db->query("SELECT SUM(pop) FROM planet WHERE owner = $pid"); + if (!($q && dbCount($q))) { + return 0; + } + + list($p) = dbFetchArray($q); + $r = $this->rules->call('get', $pid); + return floor($r['research_percent'] * $p / 100); + } +} + +?> diff --git a/scripts/game/beta5/tech/library/getTopicData.inc b/scripts/game/beta5/tech/library/getTopicData.inc new file mode 100644 index 0000000..1172744 --- /dev/null +++ b/scripts/game/beta5/tech/library/getTopicData.inc @@ -0,0 +1,27 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns data about a research topic: identifier, title, cost, description + function run($lang, $id) { + $qs = "SELECT r.cost,d.name,d.description,r.category FROM research r,research_txt d "; + $qs .= "WHERE r.id=$id AND d.research=r.id AND d.lang='$lang'"; + $q = $this->db->query($qs); + if (!($q && dbCount($q) == 1)) { + return ""; + } + + $r = dbFetchArray($q); + $str = "$id\n" . utf8entities($r[1]) . "\n" . $r[0] . "#" . $r[3] . "\n"; + $str .= preg_replace('/\n/', '
    ', utf8entities($r[2])); + return $str; + } +} + +?> diff --git a/scripts/game/beta5/tech/library/getTopics.inc b/scripts/game/beta5/tech/library/getTopics.inc new file mode 100644 index 0000000..567b2ab --- /dev/null +++ b/scripts/game/beta5/tech/library/getTopics.inc @@ -0,0 +1,36 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Returns the list of research topics for a player; the 'status argument determines + // the type of list to return: 1 for implemented, 0 for completed, and -1 for "almost + // completed" (more than 75%) + function run($pid, $type) { + $qs = "SELECT r.id, p.points, r.points FROM research_player p, research r "; + $qs .= "WHERE p.player = $pid AND r.id = p.research AND "; + if ($type == 1) { + $qs .= "NOT r.is_law AND p.in_effect <> 0"; + } elseif ($type == 0) { + $qs .= "NOT r.is_law AND p.in_effect = 0 AND r.points = p.points"; + } elseif ($type == -1) { + $qs .= "p.points < r.points AND p.points >= (75 * r.points / 100)"; + } + + $a = array(); + $q = $this->db->query($qs); + while ($r = dbFetchArray($q)) { + array_push($a, $r[0]); + if ($type == -1) + array_push($a, floor(100 * $r[1] / $r[2])); + } + return $a; + } +} + +?> diff --git a/scripts/game/beta5/tech/library/getTree.inc b/scripts/game/beta5/tech/library/getTree.inc new file mode 100644 index 0000000..b020219 --- /dev/null +++ b/scripts/game/beta5/tech/library/getTree.inc @@ -0,0 +1,43 @@ +lib = $lib; + $this->db = $lib->game->db; + } + + + function run() { + if (is_array($this->data)) { + return $this->data; + } + + $res = array(); + $noReq = array(); + $q = $this->db->query("SELECT * FROM research"); + while ($r = dbFetchHash($q)) { + $r['depends_on'] = $r['required_by'] = array(); + $q2 = $this->db->query("SELECT * FROM research_dep WHERE research=".$r['id']." OR depends_on=".$r['id']); + while ($r2 = dbFetchArray($q2)) { + $dir = ($r2[0] == $r['id']); + array_push($r[$dir ? 'depends_on' : 'required_by'], $r2[$dir ? 1 : 0]); + } + + if (($r['optional'] == 1 && !count($r['required_by'])) || $r['optional'] == 2) { + array_push($noReq, $r['id']); + } + + $res[$r['id']] = $r; + } + + foreach ($noReq as $nrId) { + $this->lib->mainClass->getAllDependencies($res, $nrId); + } + + return ($this->data = array($res,$noReq)); + } +} + +?> diff --git a/scripts/game/beta5/tech/library/has.inc b/scripts/game/beta5/tech/library/has.inc new file mode 100644 index 0000000..849b77c --- /dev/null +++ b/scripts/game/beta5/tech/library/has.inc @@ -0,0 +1,22 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + // Checks whether a player has researched a technology + function run($pid, $rid, $bt = false) { + $cond = $bt ? "p.points >= (75 * r.points / 100)" : "p.points = r.points"; + $qs = "SELECT ($cond) FROM research_player p, research r "; + $qs .= "WHERE p.player = $pid AND r.id=p.research AND r.id=$rid"; + $q = $this->db->query($qs); + list($r) = dbFetchArray($q); + return ($r == "t"); + } +} + +?> diff --git a/scripts/game/beta5/tech/library/implement.inc b/scripts/game/beta5/tech/library/implement.inc new file mode 100644 index 0000000..a34bf18 --- /dev/null +++ b/scripts/game/beta5/tech/library/implement.inc @@ -0,0 +1,45 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->rules = $this->lib->game->getLib('beta5/rules'); + } + + + // Implements a technology for a player + function run($pl, $id) { + $q = $this->db->query( + "SELECT r.cost FROM research_player p, research r " + . "WHERE p.player = $pl AND p.research = $id AND r.id = $id " + . "AND p.points = r.points AND NOT r.is_law AND p.in_effect = 0" + ); + if (!($q && dbCount($q) == 1)) { + return false; + } + + list($cost) = dbFetchArray($q); + $q = $this->db->query("SELECT cash FROM player WHERE id = $pl"); + list($cash) = dbFetchArray($q); + if ($cash < $cost) { + return false; + } + + $this->db->query("UPDATE player SET cash = cash - $cost WHERE id = $pl"); + $this->db->query("UPDATE research_player SET in_effect = 1 WHERE player = $pl AND research = $id"); + + // Change the player's rules + $q = $this->db->query("SELECT rule,modifier FROM research_effect WHERE research=$id"); + $rules = array(); + while ($r = dbFetchArray($q)) { + $rules[$r[0]] = $r[1]; + } + $this->rules->call('change', $pl, $rules); + + return true; + } +} + +?> diff --git a/scripts/game/beta5/tech/library/makeOffer.inc b/scripts/game/beta5/tech/library/makeOffer.inc new file mode 100644 index 0000000..e4fcd42 --- /dev/null +++ b/scripts/game/beta5/tech/library/makeOffer.inc @@ -0,0 +1,29 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->msg = $this->lib->game->getLib('beta5/msg'); + } + + + // Makes a research offer + function run($cpid, $tpid, $tid, $pr) { + $tm = time(); + $this->db->query( + "INSERT INTO research_assistance(player,price,offer_to,moment" . (is_null($tid) ? "" : ",technology") . ")" + . " VALUES($cpid,$pr,$tpid,$tm" . (is_null($tid) ? "" : ",$tid") . ")" + ); + $q = $this->db->query("SELECT id FROM research_assistance WHERE player=$cpid AND moment=$tm"); + list($aid) = dbFetchArray($q); + + $this->msg->call('send', $tpid, 'resdipl', array( + 'offer' => $aid, + 'msg_id' => 0 + )); + } +} + +?> diff --git a/scripts/game/beta5/tech/library/switchLaw.inc b/scripts/game/beta5/tech/library/switchLaw.inc new file mode 100644 index 0000000..d399c3b --- /dev/null +++ b/scripts/game/beta5/tech/library/switchLaw.inc @@ -0,0 +1,45 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->rules = $this->lib->game->getLib('beta5/rules'); + } + + + // Enacts or revoke a law + function run($pl, $id) { + $q = $this->db->query( + "SELECT p.in_effect, r.cost FROM research_player p, research r " + . "WHERE p.player = $pl AND p.research = $id AND r.id = $id " + . "AND p.points = r.points AND r.is_law AND p.in_effect IN (0,1)" + ); + if (!($q && dbCount($q) == 1)) { + return false; + } + + list($ie, $cost) = dbFetchArray($q); + $q = $this->db->query("SELECT cash FROM player WHERE id = $pl"); + list($cash) = dbFetchArray($q); + if ($cash < $cost) { + return false; + } + + $this->db->query("UPDATE player SET cash = cash - $cost WHERE id = $pl"); + $this->db->query("UPDATE research_player SET in_effect = ".($ie==0?"6":"-5")." WHERE player = $pl AND research = $id"); + + // Change the player's rules + $q = $this->db->query("SELECT rule,modifier FROM research_effect WHERE research = $id"); + $rules = array(); + while ($r = dbFetchArray($q)) { + $rules[$r[0]] = ($ie ? -1 : 1) * $r[1]; + } + $this->rules->call('change', $pl, $rules); + + return true; + } +} + +?> diff --git a/scripts/game/beta5/ticks/battle/library.inc b/scripts/game/beta5/ticks/battle/library.inc new file mode 100644 index 0000000..c9c2f58 --- /dev/null +++ b/scripts/game/beta5/ticks/battle/library.inc @@ -0,0 +1,521 @@ +lib = $lib; + $this->db = $lib->game->db; + $this->fleets = $lib->game->getLib('beta5/fleet'); + $this->msgs = $lib->game->getLib('beta5/msg'); + $this->planets = $lib->game->getLib('beta5/planet'); + $this->players = $lib->game->getLib('beta5/player'); + $this->rules = $lib->game->getLib('beta5/rules'); + $this->sales = $lib->game->getLib('beta5/sale'); + $this->rankings = $lib->game->getLib('main/rankings'); + } + + + function runTick() { + $this->idrInc = array(); + $toThrow = null; + try { + $locations = $this->db->safeTransaction(array($this, 'getBattleLocations')); + foreach ($locations as $lId) { + $this->db->safeTransaction(array($this, 'battleAt'), array($lId), 5); + } + } catch (Exception $e) { + $toThrow = $e; + } + + $this->db->safeTransaction(array($this, 'updateStatus')); + if (!is_null($toThrow)) { + throw $toThrow; + } + + $this->db->safeTransaction(array($this, 'updateRankings')); + } + + + public function getBattleLocations() { + $q = $this->btQuery( + "SELECT DISTINCT location FROM fleet WHERE location IS NOT NULL AND attacking" + ); + $locations = array(); + while ($r = dbFetchArray($q)) { + array_push($locations, $r[0]); + } + return $locations; + } + + + public function updateStatus() { + // Mark fleets that were in battle as available and commit + $this->btQuery("UPDATE fleet SET can_move='Y' WHERE can_move='B'"); + } + + public function updateRankings() { + // Increase inflicted damage points + $rt = $this->rankings->call('getType', 'p_idr'); + $rl = $this->rankings->call('getAll', $rt); + $idr = array(); + foreach ($rl as $r) { + $idr[$r['id']] = $r['points']; + } + foreach ($this->idrInc as $n => $inc) { + $idr[$n] += $inc; + } + $idrR = array(); + foreach ($idr as $n => $p) { + if (!is_array($idrR[$p])) { + $idrR[$p] = array(); + } + array_push($idrR[$p], $n); + } + $this->rankings->call('update', $rt, $idrR); + } + + + public function battleAt($lId) { + // Execute computations + $idrInc = $this->battleComputation($lId); + + // Increase stored IDR + foreach ($idrInc as $n => $ii) { + if (is_null($this->idrInc[$n])) { + $this->idrInc[$n] = $ii; + } else { + $this->idrInc[$n] += $ii; + } + } + } + + + private function battleComputation($lId) { + static $rnames = array('turret', 'gaship', 'fighter', 'cruiser', 'bcruiser'); + static $addLosses = array(.5, .25, 1, .25, .125); + + // Get the ID of the planet's owner and the amount of turrets + $q = $this->btQuery("SELECT name,owner,turrets,vacation FROM planet WHERE id=$lId " + . "FOR UPDATE"); + list($pName,$pOwner,$pTurrets,$vacation) = dbFetchArray($q); + $poFake = is_null($pOwner) ? 0 : $pOwner; + + // Compute the power of the planet's turrets + if ($vacation == 'YES ') { + $defPower = 0; + $pTurrets = 0; + } else { + $defPower = $this->planets->call('getPower', $lId); + } + $turretPower = $defPower; + + // Get all fleets at that location + $q = $this->btQuery("SELECT * FROM fleet WHERE location=$lId FOR UPDATE"); + $attFleets = array(0,0,0,0,0,0,0,0); + $defFleets = array(0,0,0,0,0,0,0,0); + $attPlayers = $defPlayers = 0; + $fleets = array(); + $players = array( + $poFake => array(false, $turretPower, $pTurrets, 0, 0, 0, 0, array()) + ); + $attPower = 0; + $cSales = null; + + // Extract fleet data + while ($r = dbFetchHash($q)) { + if ( ($r['owner'] == $pOwner && $vacation == 'YES ') + || ($r['attacking'] == 't' && $r['time_spent'] < 15) ) { + continue; + } + + $r['power'] = $this->fleets->call('getPower', $r['owner'], $r['gaships'], + $r['fighters'], $r['cruisers'], $r['bcruisers']); + if ($r['attacking'] == 't') { + $attFleets[0] += $r['gaships']; + $attFleets[1] += $r['fighters']; + $attFleets[2] += $r['cruisers']; + $attFleets[3] += $r['bcruisers']; + $attPower += $r['power']; + } else { + $defFleets[0] += $r['gaships']; + $defFleets[1] += $r['fighters']; + $defFleets[2] += $r['cruisers']; + $defFleets[3] += $r['bcruisers']; + $defPower += $r['power']; + } + + if (is_null($players[$r['owner']])) { + $players[$r['owner']] = array( + $r['attacking'] == 't', $r['power'], 0, $r['gaships'], + $r['fighters'], $r['cruisers'], $r['bcruisers'], array() + ); + if ($r['attacking'] == 't') { + $attPlayers ++; + } else { + $defPlayers ++; + } + } else { + $players[$r['owner']][1] += $r['power']; + $players[$r['owner']][3] += $r['gaships']; + $players[$r['owner']][4] += $r['fighters']; + $players[$r['owner']][5] += $r['cruisers']; + $players[$r['owner']][6] += $r['bcruisers']; + } + + array_push($players[$r['owner']][7], $r['id']); + $fleets[$r['id']] = $r; + } + + // If there is no defense at all, forget it + // (that can also mean that the local defense fleet is on vacation) + // Also skip if there are no attack fleets (could happen because of separate transactions) + if ($defPower == 0 || $attPower == 0) { + l::debug("Skipped battle on $pName, no fleets"); + return array(); + } + l::debug("Starting battle on $pName (owner: $poFake); $pTurrets turrets, vacation=$vacation"); + + // Compute the damage index, which is the proportion of damage inflicted by the biggest fleet + $dRandom = $defPower; + while ($dRandom > 1000) { + $dRandom = $dRandom / 10; + } + $rdPower = $defPower + rand(0, round($dRandom * 0.05)); + l::debug("Defense: power= $defPower ; random= $dRandom; rPower= $rdPower"); + + $aRandom = $attPower; + while ($aRandom > 1000) { + $aRandom = $aRandom / 10; + } + $raPower = $attPower + rand(0, round($aRandom * 0.05)); + l::debug("Attack: power= $attPower ; random= $aRandom; rPower= $raPower"); + + $bigDef = ($rdPower > $raPower); + $bigPower = $bigDef ? $rdPower : $raPower; + $smallPower = $bigDef ? $raPower : $rdPower; + $ratio = $bigPower / $smallPower; + $damageIndex = ($ratio > 10 ? 1 : ((exp($ratio - 1) / (exp($ratio - 1) + 1)))); + $attDamage = round(($bigDef ? $damageIndex : (1 - $damageIndex)) * $attPower); + $defDamage = round(($bigDef ? (1-$damageIndex) : $damageIndex) * $defPower); + l::debug("Damage index: $damageIndex (attDamage: $attDamage ; defDamage: $defDamage)"); + + // Handle heroic defense + if (! $bigDef && $ratio >= 5 && rand(0, 10000) <= 200) { + $heroicDefense = true; + $addDamage = ceil($smallPower * rand(300, 400) / 100); + if ($addDamage > $bigPower / 5) { + $addDamage = ceil($bigPower / 5); + } + $attDamage += $addDamage; + l::debug("Heroic defense! Damage increased by $addDamage (-> $attDamage)"); + } else { + $heroicDefense = false; + } + + // Compute the amount of damage to each player + $defLosses = $attLosses = 0; + $plist = array_keys($players); + $turretLoss = $tPowerLoss = 0; + foreach ($plist as $id) { + l::debug(" -> Player $id"); + if ($players[$id][0]) + $losses = ($players[$id][1] / $attPower) * $attDamage; + else + $losses = ($players[$id][1] / $defPower) * $defDamage; + l::debug(" * losses = $losses"); + + $rules = $this->rules->call('get', ($id == 0) ? null : $id); + if ($damageIndex < 1 || ($players[$id][0] && !$bigDef || !$players[$id][0] && $bigDef)) { + $losses = ($losses / 100) * $rules['battle_losses']; + l::debug(" * losses = $losses after adjustment ({$rules['battle_losses']}%)"); + } else { + l::debug(" * losses not adjusted"); + } + + // Compute damage for each type of ship + $probLoss = array(0, 0, 0, 0, 0); $oriFleet = array(); + if ($players[$id][1] > 0) { + $sPowers = array(); $power = array(); + $tLoss = 0; + $lossRatio = $losses / $players[$id][1]; + for ($i=0;$i<5;$i++) { + $sPowers[$i] = ($rules[$rnames[$i] . "_power"] / 100) + * $rules['effective_fleet_power']; + $oriFleet[$i] = $players[$id][$i+2]; + $power[$i] = $sPowers[$i] * $players[$id][$i+2]; + $shipRatio = $power[$i] / $players[$id][1]; + $pLoss = $shipRatio * $losses; + $probLoss[$i] = min($players[$id][$i+2], round($pLoss / $sPowers[$i])); + $tLoss += $probLoss[$i] * $sPowers[$i]; + } + + $i = $n = 0; + while ($tLoss < $losses && $n < 5) { + if ($probLoss[$i] < $players[$id][$i+2]) { + $probLoss[$i] ++; + $tLoss += $sPowers[$i]; + $n = 0; + } else { + $n++; + } + $i = ($i + 1) % 5; + } + } + l::debug(" * ship losses (T/G/F/C/B) = " . join(', ', $probLoss) + . " out of " . join(', ', $oriFleet)); + + // If there are turret losses, remove turrets + if ($probLoss[0] > 0) { + $turretLoss = $probLoss[0]; + $tPowerLoss = $this->planets->call('getPower', $pOwner, $turretLoss); + $this->btQuery("UPDATE planet SET turrets=turrets-$turretLoss WHERE id=$lId"); + $tm = time(); + $this->btQuery("DELETE FROM turhist WHERE $tm-moment>86400"); + $this->btQuery("INSERT INTO turhist VALUES ($lId,$tm,-$turretLoss)"); + + // Mark the planet's sale to be cancelled if that applies + $q = $this->btQuery("SELECT id,player,finalized,sold_to FROM sale WHERE planet=$lId"); + if (($r = dbFetchArray($q)) && is_null($cSales)) { + $cSales = $r; + } + } + + // Apply losses to the player's individual fleets + $removed = array(0, 0, 0, 0); + foreach ($players[$id][7] as $fid) { + $rff = $tif = 0; + $rem = array(); + for ($i=0;$i<4;$i++) { + // Ships that must be destroyed + $toRemove = $probLoss[$i+1] - $removed[$i]; + + // Ships in the fleet + $inFleet = $fleets[$fid][$rnames[$i+1]."s"]; + $tif += $inFleet; + + // Remove ships + $fromFleet = min($toRemove, $inFleet); + $removed[$i] += $fromFleet; + $rff += $fromFleet; + $rem[$i] = $fromFleet; + } + l::debug(" * fleet $fid losses (G/F/C/B) = " . join(', ', $rem)); + + // Mark the fleet's sale to be cancelled if that applies + if ($rff) { + $q = $this->btQuery("SELECT id,player,finalized,sold_to FROM sale WHERE fleet=$fid"); + if ($r = dbFetchArray($q)) { + if (is_null($r[2])) { + $ga = 'cancel'; + } else { + $ga = 'cancelTransfer'; + } + $this->sales->call($ga, $r[1], $r[0]); + + // FIXME: send messages + if (!is_null($cSales) && $cSales == $r[0]) { + $cSales = null; + } + } + } + + if ($rff == $tif) { + // The whole fleet has been lost + $this->btQuery("DELETE FROM fleet WHERE id=$fid"); + } elseif ($rff) { + // Fleet has suffered some losses + $qs = "UPDATE fleet SET "; + $qsi = false; + for ($i=0;$i<4;$i++) { + if ($rem[$i] == 0) { + continue; + } + if ($qsi) { + $qs .= ","; + } else { + $qsi = true; + } + $qs .= $rnames[$i+1]."s=".$rnames[$i+1]."s-".$rem[$i]; + } + $qs .= " WHERE id=$fid"; + $this->btQuery($qs); + } else { + // No losses, we're done + break; + } + } + + // Add losses to the correct array + if ($players[$id][0]) { + for ($i=0;$i<4;$i++) { + $attFleets[$i+4] += $probLoss[$i+1]; + } + } else { + for ($i=0;$i<4;$i++) { + $defFleets[$i+4] += $probLoss[$i+1]; + } + } + + // Store the player's losses + $lostPower = $this->fleets->call('getPower', ($id==0 ? null : $id), $probLoss[1], $probLoss[2], $probLoss[3], $probLoss[4]); + $lostPower += $this->planets->call('getPower', ($id==0 ? null : $id), $probLoss[0]); + array_shift($probLoss); + $players[$id][8] = $probLoss; + $players[$id][9] = $lostPower; + if ($players[$id][0]) { + $attLosses += $lostPower; + } else { + $defLosses += $lostPower; + } + } + + // Cancel the planet's sale if it suffered damage + if (!is_null($cSales)) { + if (is_null($cSales[2])) { + $ga = 'cancel'; + } else { + $ga = 'cancelTransfer'; + } + $this->sales->call($ga, $cSales[1], $cSales[0]); + // FIXME: send messages + } + + // Give the players "inflicted damage" points + $idrInc = array(); + foreach ($plist as $id) { + if ($id == 0) { + continue; + } + + $q = $this->btQuery("SELECT hidden FROM player WHERE id = $id"); + list($hidden) = dbFetchArray($q); + if ($hidden == 't') { + continue; + } + + if ($players[$id][0]) { + $tPower = $attPower; + $eLoss = $defLosses; + } else { + $tPower = $defPower; + $eLoss = $attLosses; + } + $ii = round(($players[$id][1] / $tPower) * $eLoss); + $n = $this->players->call('getName', $id); + if (is_null($idrInc[$n])) { + $idrInc[$n] = $ii; + } else { + $idrInc[$n] += $ii; + } + } + + // Send battle reports + $tm = time(); + foreach ($plist as $id) { + // Avoid the fake "neutral" player + if ($id == 0) { + continue; + } + $p = $players[$id]; + + // Get friendly/hostile data + if ($players[$id][0]) { + $fPower = $attPower; + $ePower = $defPower; + $fLosses = $attLosses; + $eLosses = $defLosses; + $fFleets = $attFleets; + $eFleets = $defFleets; + $tMode = ($pTurrets != 0 ? 3 : 0); + } else { + $fPower = $defPower; + $ePower = $attPower; + $fLosses = $defLosses; + $eLosses = $attLosses; + $fFleets = $defFleets; + $eFleets = $attFleets; + $tMode = ($pTurrets != 0 ? ($pOwner == $id ? 1 : 2) : 0); + } + + // Remove the player's own statistics from the list of friendlies + $fPower -= $p[1]; $fLosses -= $p[9]; + for ($i=0;$i<4;$i++) { + $fFleets[$i] -= $p[$i+3]; + $fFleets[$i+4] -= $p[8][$i]; + } + + // Send battle report + $this->msgs->call('send', $id, "battle", array( + "planet_id" => $lId, + "planet" => $pName, + "o_gaships" => $p[3], + "o_fighters" => $p[4], + "o_cruisers" => $p[5], + "o_bcruisers" => $p[6], + "o_power" => $p[1], + "ol_gaships" => $p[8][0], + "ol_fighters" => $p[8][1], + "ol_cruisers" => $p[8][2], + "ol_bcruisers" => $p[8][3], + "ol_power" => $p[9], + "a_gaships" => $fFleets[0], + "a_fighters" => $fFleets[1], + "a_cruisers" => $fFleets[2], + "a_bcruisers" => $fFleets[3], + "a_power" => $fPower, + "al_gaships" => $fFleets[4], + "al_fighters" => $fFleets[5], + "al_cruisers" => $fFleets[6], + "al_bcruisers" => $fFleets[7], + "al_power" => $fLosses, + "e_gaships" => $eFleets[0], + "e_fighters" => $eFleets[1], + "e_cruisers" => $eFleets[2], + "e_bcruisers" => $eFleets[3], + "e_power" => $ePower, + "el_gaships" => $eFleets[4], + "el_fighters" => $eFleets[5], + "el_cruisers" => $eFleets[6], + "el_bcruisers" => $eFleets[7], + "el_power" => $eLosses, + "turrets" => $pTurrets, + "tpower " => $turretPower, + "l_turrets" => $turretLoss, + "l_tpower" => $tPowerLoss, + "tmode" => $tMode, + "heroic_def" => $heroicDefense ? ($players[$id][0] ? -1 : 1) : 0 + )); + } + + // Update happiness and attack status + $this->planets->call('updateHappiness', $lId); + $this->planets->call('updateMilStatus', $lId); + + // If the planet was pending entrance in vacation mode and all enemy + // fleet is dead, set it to vacation mode. + if ($vacation == 'PEND') { + $q = $this->btQuery("SELECT COUNT(*) FROM fleet WHERE location=$lId AND attacking"); + if ($q && dbCount($q) == 1) { + list($c) = dbFetchArray($q); + if ($c == 0) { + $this->btQuery("UPDATE planet SET vacation='YES' WHERE id=$lId"); + } + } + } + + return $idrInc; + } + + + function btQuery($q) { + $r = $this->db->query($q); + l::trace("Result '$r' for query: $q"); + return $r; + } +} + +?> diff --git a/scripts/game/beta5/ticks/cash/library.inc b/scripts/game/beta5/ticks/cash/library.inc new file mode 100644 index 0000000..9f9d624 --- /dev/null +++ b/scripts/game/beta5/ticks/cash/library.inc @@ -0,0 +1,250 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + $this->fleets = $this->game->getLib('beta5/fleet'); + $this->msgs = $this->game->getLib('beta5/msg'); + $this->planets = $this->game->getLib('beta5/planet'); + $this->players = $this->game->getLib('beta5/player'); + $this->rules = $this->game->getLib('beta5/rules'); + } + + + public function runTick() { + // Update time required to abandon planets + $this->db->safeTransaction(array($this, "handleAbandon")); + + // Handle the players' income + $players = $this->db->safeTransaction(array($this, "getPlayers")); + foreach ($players as $player) { + $this->db->safeTransaction(array($this, "playerIncome"), $player); + } + + // Handle CTF victory + $this->db->safeTransaction(array($this, "checkCTF")); + } + + + public function checkCTF() { + // Check victory status on CTF games + if ($this->game->params['victory'] == 2 && ! $this->game->getLib()->call('isFinished')) { + $this->game->getLib('beta5/ctf')->call('checkTargets'); + } + } + + + public function getPlayers() { + $q = $this->db->query( + "SELECT id FROM player " + . "WHERE quit IS NULL " + . "OR UNIX_TIMESTAMP(NOW()) - quit < 86400" + ); + + $players = array(); + while ($r = dbFetchArray($q)) { + array_push($players, $r[0]); + } + + return $players; + } + + + public function playerIncome($id) { + $q = $this->db->query( + "SELECT p.cash, (a.status = 'VAC') FROM player p,account a " + . "WHERE p.id = $id AND a.id = p.userid " + . "FOR UPDATE OF p" + ); + list($cash, $vac) = dbFetchArray($q); + + $protected = $this->players->call('getProtectionLevel', $id); + + $div = ($vac == 't' ? 4 : 1); + $mult = ($protected ? 1.15 : 1); + $income = round($this->computeIncome($id) * $mult / $div); + $upkeep = round($this->computeUpkeep($id) / $div); + $profit = ceil($income - $upkeep) / 2; + + $cash += $profit; + if ($cash < 0) { + $this->debt($id, $income, $upkeep, $cash); + $cash = 0; + } + $this->db->query("UPDATE player SET cash=$cash WHERE id=$id"); + } + + + private function computeIncome($player) { + $income = 0; + $ppl = $this->players->call('getPlanets', $player); + + foreach ($ppl as $id => $name) { + $info = $this->planets->call('byId', $id); + $m = $this->planets->call('getIncome', $player, $info['pop'], $info['happiness'], + $info['ifact'], $info['mfact'], $info['turrets'], $info['corruption']); + $income += $m[0]; + } + + return $income; + } + + + private function computeUpkeep($player) { + $pfl = $this->players->call('getFleets', $player); + $upkeep = 0; + + foreach ($pfl as $id => $name) { + $info = $this->fleets->call('get', $id); + $u = $this->fleets->call('getUpkeep', $player, $info['gaships'], $info['fighters'], + $info['cruisers'], $info['bcruisers']); + $upkeep += $u; + } + + return $upkeep; + } + + + private function killFleets($id, $ratio) { + $fl = array_keys($this->players->call('getFleets', $id)); + $rules = $this->rules->call('get', $id); + + $kills = array(0, 0, 0, 0); + foreach ($fl as $fid) { + $f = $this->fleets->call('get', $fid); + + if (is_null($f['waiting']) && (is_null($f['moving']) + || !is_null($f['moving']) && $f['move']['hyperspace'] == 'f')) { + + // Fleet is idle or not in HyperSpace + $kg = ceil($ratio * $f['gaships']); + $kf = ceil($ratio * $f['fighters']); + $kc = ceil($ratio * $f['cruisers']); + $kb = ceil($ratio * $f['bcruisers']); + } else { + // Fleet is in Hyperspace + $kc = ceil($f['cruisers'] * $ratio); + $kb = ceil($f['bcruisers'] * $ratio); + + // Compute the amount of transported ships to be destroyed + // FIXME: computeHyperspaceDamage() ? + $gaSpace = $f['gaships'] * $rules['gaship_space']; + $fSpace = $f['fighters'] * $rules['fighter_space']; + $tSpace = $gaSpace + $fSpace; + if ($tSpace > 0) { + $haul = $f['cruisers'] * $rules['cruiser_haul'] + + $f['bcruisers'] * $rules['bcruiser_haul']; + $haulUsed = $tSpace / $haul; + $lHaul = $kc * $rules['cruiser_haul'] + $kb * $rules['bcruiser_haul']; + $lSpace = $haulUsed * $lHaul; + $gaLSpace = ($lSpace / $tSpace) * $gaSpace; + $fLSpace = ($lSpace / $tSpace) * $fSpace; + $kg = min($f['gaships'], ceil($gaLSpace / $rules['gaship_space'])); + $kf = min($f['fighters'], ceil($fLSpace / $rules['fighter_space'])); + } else { + $kg = $kf = 0; + } + } + + if ($kc == $f['cruisers'] && $kb == $f['bcruisers'] && $kf == $f['fighters'] + && $kg == $f['gaships']) { + + $this->fleets->call('disband', $fid, true); + } elseif ($kc + $kb + $kf + $kg > 0) { + $this->db->query( + "UPDATE fleet SET gaships=gaships - $kg, fighters = fighters - $kf, " + . "cruisers=cruisers - $kc,bcruisers = bcruisers - $kb " + . "WHERE id=$fid" + ); + $this->fleets->call('invCache', $fid); + } + $kills[0] += $kg; $kills[1] += $kf; $kills[2] += $kc; $kills[3] += $kb; + } + + // No kills? + if ($kills[0] + $kills[1] + $kills[2] + $kills[3] == 0) { + return; + } + + // Send the message + $this->msgs->call('send', $id, 'kfleet', array( + "gaships" => $kills[0], + "fighters" => $kills[1], + "cruisers" => $kills[2], + "bcruisers" => $kills[3], + )); + + $this->players->call('updateHappiness', $id); + } + + + private function debt($id, $income, $upkeep, $cash) { + if ($upkeep > 0) { + $ratio = min(-$cash * 2 / $upkeep, 1) / 5; + $this->killFleets($id, $ratio); + return; + } + + $rules = $this->rules->call('get', $id); + $ppl = $this->players->call('getPlanets', $id); + $pinf = array(); + foreach ($ppl as $pid => $name) { + $info = $this->planets->call('byId', $pid); + $ic = $this->planets->call('getIncome', $id, $info['pop'], $info['ifact'], + $info['mfact'], $info['turrets'], $info['corruption']); + $info['income'] = $ic[0]; + if ($info['income'] >= 0) { + $income -= $info['income']; + continue; + } else { + $income -= $ic[1] + $ic[2]; + $info['income'] -= $ic[1] + $ic[2]; + } + $pinf[$pid] = $info; + } + + $kill = array(0, 0); + foreach ($pinf as $pid => $p) { + $ratio = $p['income'] / $income; + $tCost = $p['turrets'] * $rules['turret_cost']; + $mCost = $p['mfact'] * $rules['factory_cost']; + $tRatio = (rand(10,20)/100) * $ratio * $tCost / (-$p['income']); + $mRatio = (rand(10,20)/100) * $ratio * $mCost / (-$p['income']); + + $tLoss = ceil($p['turrets'] * $tRatio); + $mLoss = ceil($p['mfact'] * $mRatio); + if ($tLoss + $mLoss == 0) { + continue; + } + + $this->db->query("UPDATE planet SET turrets=turrets-$tLoss,mfact=mfact-$mLoss WHERE id=$pid"); + $kill[0] += $tLoss; $kill[1] += $mLoss; + $this->planets->call('updateHappiness', $pid); + } + + if ($kill[0] + $kill[1] == 0) { + return; + } + + $this->msgs->call('send', $id, 'kimpr', array("turrets" => $kill[0], "factories" => $kill[1])); + } + + + public function handleAbandon() { + $this->db->query( + "UPDATE planet_abandon_time " + . "SET time_required = time_required + 1 " + . "WHERE time_required < 24" + ); + } +} + + +?> diff --git a/scripts/game/beta5/ticks/day/library.inc b/scripts/game/beta5/ticks/day/library.inc new file mode 100644 index 0000000..9b6418e --- /dev/null +++ b/scripts/game/beta5/ticks/day/library.inc @@ -0,0 +1,479 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + $this->main = $this->game->getLib(); + $this->planets = $this->game->getLib('beta5/planet'); + $this->players = $this->game->getLib('beta5/player'); + $this->msgs = $this->game->getLib('beta5/msgs'); + $this->fleets = $this->game->getLib('beta5/fleet'); + $this->rankings = $this->game->getLib('main/rankings'); + $this->rules = $this->game->getLib('beta5/rules'); + $this->tech = $this->game->getLib('beta5/tech'); + } + + + public function runTick() { + // Cache the whole research tree, we'll need that later + $this->tech->call('getTree'); + + // Increase neutral population + l::debug("Increasing neutral population"); + $this->db->safeTransaction(array($this, 'increasePopulation'), array(null, false)); + + // Increase population and research + $players = $this->db->safeTransaction(array($this, 'getPlayers')); + $this->playerPopulation($players); + $this->playerResearch($players); + + // Mass-update status and timers + $this->db->safeTransaction(array($this, 'statusUpdates')); + + // Dry run of the rankings computation + list($players, $playersById) = $this->db->safeTransaction( + array($this->main, 'call'), array('updateRankings', true) + ); + + // Restore neutral planets and decrease protection levels + if ((int) $this->game->params['victory'] == 0) { + $this->db->safeTransaction(array($this, 'restoreNeutrals')); + $this->db->safeTransaction(array($this, 'decreaseProtection')); + } + + // Update the data warehouse + $this->db->safeTransaction(array($this, 'updateWarehouse'), array($players, $playersById)); + } + + + public function restoreNeutrals() { + $q = $this->db->query("SELECT p.id FROM planet p, system s " + . "WHERE p.owner IS NULL AND p.status = 0 AND s.id = p.system " + . "AND s.nebula = 0" + ); + + while ($r = dbFetchArray($q)) { + $this->planets->call('restoreNeutral', $r[0]); + } + } + + + public function decreaseProtection() { + $q = $this->db->query( + "SELECT s.id, s.prot, y.id FROM system s, player y " + . "WHERE s.prot > 0 AND s.nebula = 0 " + . "AND y.id = (SELECT DISTINCT p.owner FROM planet p " + . "WHERE p.system = s.id AND owner IS NOT NULL) " + . "FOR UPDATE OF s, y"); + $removeProtection = array(); + while ($r = dbFetchArray($q)) { + if ($r[1] == 1) { + $removeProtection[$r[0]] = $r[2]; + } + } + + $this->db->query("UPDATE system SET prot = prot - 1 WHERE prot > 1 AND nebula = 0"); + foreach ($removeProtection as $system => $player) { + $this->players->call('breakProtection', $player, 'EXP'); + } + } + + + public function getPlayers() { + $q = $this->db->query( + "SELECT p.id, (a.status <> 'STD') FROM player p, account a " + . "WHERE (p.quit IS NULL OR UNIX_TIMESTAMP(NOW()) - p.quit < 86400) " + . "AND a.id = p.userid" + ); + + $players = array(); + while ($r = dbFetchArray($q)) { + $players[$r[0]] = ($r[1] == 't'); + } + + return $players; + } + + + private function playerResearch($players) { + l::debug("Increasing player research"); + foreach ($players as $playerID => $vacation) { + $this->db->safeTransaction(array($this, 'research'), array($playerID, $vacation)); + } + } + + + private function playerPopulation($players) { + l::debug("Increasing player population"); + foreach ($players as $playerID => $vacation) { + l::trace("Increasing population for player #$playerID"); + $this->db->safeTransaction(array($this, 'increasePopulation'), array($playerID, $vacation)); + } + } + + + public function statusUpdates() { + // Laws: reduce delays to enact/revoke laws + $this->db->query("UPDATE research_player SET in_effect=in_effect-1 WHERE in_effect>1"); + $this->db->query("UPDATE research_player SET in_effect=in_effect+1 WHERE in_effect<0"); + + // Remove research assistance + $this->db->query("UPDATE player SET res_assistance=NULL WHERE res_assistance IS NOT NULL"); + + // Restrictions on new players + $this->db->query("UPDATE player SET restrain=restrain-1 WHERE restrain >= 1"); + + // Decrease unhappiness because of Wormhole Super Novas and sales + $this->db->query("UPDATE player SET bh_unhappiness=bh_unhappiness-1 WHERE bh_unhappiness>0"); + $this->db->query("UPDATE planet SET bh_unhappiness=bh_unhappiness-1 WHERE bh_unhappiness>0 AND status=0"); + + // Probe building + $this->db->query("UPDATE planet SET built_probe=FALSE WHERE built_probe"); + } + + + // Advance a player's research status. + public function research($id, $onVacation) { + $q = $this->db->query("SELECT COUNT(*) FROM research_player WHERE player=$id"); + list($c) = dbFetchArray($q); + if (!$c) { + if ($this->lib->game->params['partialtechs'] == 1) { + $this->tech->call('createTree', $id); + } else { + $q = $this->db->query("SELECT id FROM research"); + while ($r = dbFetchArray($q)) { + $this->db->query("INSERT INTO research_player(player,research,possible) " + . "VALUES($id,{$r[0]},TRUE)"); + } + } + } + + // Get the whole tech tree and the player's research points + $rp = $this->tech->call('getPoints',$id); + list($res, $rLeaf) = $this->tech->call('getTree', $id); + + // Reduce by half if we're giving assistance + $q = $this->db->query("SELECT id FROM player WHERE res_assistance=$id"); + if (dbCount($q)) { + $rp = round($rp / 2); + } + + // Are we receiving assistance? + $q = $this->db->query("SELECT research,res_assistance FROM player WHERE id=$id"); + list($rb, $ra) = dbFetchArray($q); + if (!is_null($ra)) { + $rp += round(min($this->tech->call('getPoints', $ra), $rp) / 2); + } + + // If the player's on vacation, he gets a fourth of the RPs + if ($onVacation) { + $rp = round($rp / 4); + } + + // If the player is under protection, he gets a +25% bonus + if ($this->players->call('getProtectionLevel', $id)) { + $rp = round($rp * 1.25); + } + + // Divide research points between categories + $rbl = explode('!',$rb); + $rbp = array(); + for ($i=$s=0;$i<3;$i++) { + $rbp[$i] = floor($rbl[$i] * $rp / 100); + $s += $rbp[$i]; + } + for ($i=0;$s<$rb;$i=($i+1)%3) { + $rbp[$i] ++; $s ++; + } + $possible = array(array(), array(), array()); + + // Get current research topics + $q = $this->db->query("SELECT p.research,p.points FROM research_player p,research r " + . "WHERE p.research=r.id AND p.points>0 AND p.points $r[0], + 'pts' => $r[1], + 'max' => $res[$r[0]]['points'] + ); + $cat = $res[$r[0]]['category']; + array_push($possible[$cat], $rp); + $npc[$cat] ++; + } + + // Get pending research topics + $q = $this->db->query("SELECT research FROM research_player WHERE player=$id AND points=0 AND possible"); + $pending = array(array(),array(),array()); + while ($r = dbFetchArray($q)) { + array_push($pending[$res[$r[0]]['category']], $r[0]); + } + + // Get completed research + $q = $this->db->query("SELECT p.research FROM research_player p,research r " + . "WHERE p.research=r.id AND p.points=r.points AND (r.is_law OR p.in_effect <> 0) AND p.player=$id"); + $pRes = array(); + while ($r = dbFetchArray($q)) { + $pRes[$r[0]] = 1; + } + + // Find topics which have their dependencies satisfied + for ($i=0;$i<3;$i++) { + foreach ($pending[$i] as $p) { + $ok = true; + foreach ($res[$p]['depends_on'] as $dep) { + $ok = $ok && $pRes[$dep]; + } + if ($ok) { + array_push($possible[$i], array( + 'id' => $p, + 'pts' => 0, + 'max' => $res[$p]['points'] + )); + } + } + } + + // Find points left for each category + $left = array(); $nrec = 0; $points = 0; + for ($i=0;$i<3;$i++) { + $max = 0; + foreach ($possible[$i] as $rt) { + $max += $rt['max'] - $rt['pts']; + } + $left[$i] = max(0, $rbp[$i] - $max); + if ($left[$i] == 0) { + $nrec ++; + } else { + $points += $left[$i]; + $rbp[$i] -= $left[$i]; + } + } + + // Reassign extra points if possible + if ($points) { + for ($i=0;$i<3;$i++) { + if ($left[$i] == 0) { + $rbp[$i] += floor($points / $nrec); + } + } + } + + // Increment research points for existing research + for ($i=0;$i<3;$i++) { + while ($possible[$i][0]['pts'] && $npc[$i]) { + $inc = min(floor($rbp[$i] / $npc[$i]), $possible[$i][0]['max'] - $possible[$i][0]['pts']); + l::trace("Adding $inc points to research #{$possible[$i][0]['id']} for player #$id"); + $this->db->query( + "UPDATE research_player SET points = points + $inc " + . "WHERE research = {$possible[$i][0]['id']} AND player = $id" + ); + array_shift($possible[$i]); + $rbp[$i] -= $inc; + $npc[$i] --; + } + } + + // Generate probabilities for new research: the lower a tech's required points, + // the higher its probabillity + $probabilities = array(array(), array(), array()); + for ($i=0;$i<3;$i++) { + if ($rbp[$i] == 0) { + continue; + } + + $sum = 0; + foreach ($possible[$i] as $idx => $t) { + $sum += $t['max']; + } + + foreach ($possible[$i] as $idx => $t) { + $raw = $sum / $t['max']; + $n = ceil($raw * $raw); + for ($j=0;$j<$n;$j++) { + array_push($probabilities[$i], $idx); + } + } + } + + // Start new research + $doneSomething = true; + $addedTechs = array(array(), array(), array()); + for ($i=0;$i%3||$doneSomething;$i=($i+1)%3) { + if ($i%3==0) { + $doneSomething = false; + } + if (!$rbp[$i] || count($possible[$i]) == count($addedTechs[$i])) { + continue; + } + + $doneSomething = true; + do { + $ridx = $probabilities[$i][rand(0, count($probabilities[$i]) - 1)]; + } while (in_array($ridx, $addedTechs[$i])); + array_push($addedTechs[$i], $ridx); + + $r = $possible[$i][$ridx]; + $inc = min($rbp[$i], $r['max']); + $this->db->query( + "UPDATE research_player SET points = points + $inc " + . "WHERE research = {$r['id']} AND player = $id" + ); + l::trace("Starting research {$r['id']} with $inc points for player $id"); + $rbp[$i] -= $inc; + } + } + + + public function increasePopulation($id, $onVacation = false) { + $rules = $this->rules->call('get', $id); + if (!is_null($id) && $this->players->call('getProtectionLevel', $id)) { + $rules['pop_growth_factor'] = min($rules['pop_growth_factor'] + 1, 5); + } + $pow = 1.45 - (0.05 * $rules['pop_growth_factor']); + $vacFact = $onVacation ? 4 : 1; + + $q = $this->db->query("SELECT id,pop,max_pop,happiness,corruption,mfact+ifact FROM planet " + . "WHERE owner" . (is_null($id) ? " IS NULL AND status=0" : "=$id") . " FOR UPDATE"); + while ($r = dbFetchArray($q)) { + $hapVal = ($r[3] - 50) / 12; + $hapFct = exp($hapVal) / ((exp($hapVal)+1)*9); + + // Population growth for players that are on vacation is 1/4th of normal + $p = round($r[1] + pow($hapFct, $pow) * $r[1] * ($r[2] - $r[1]) / ($vacFact * $r[2])); + $growthRatio = $r[1] / $p; + + // Increase corruption + $cDec = 5 + 3 * $r[3] / 10; + if (!is_null($id)) { + $ncor = max(0, $r[4] - $cDec); + $x = ($r[1] - 2000) / 48000; + $optFact = ($r[1] / 30) - 754 * $x * $x; + $mCorInc = $r[5] / 3; + $nf = ($r[5] > $optFact * 3) ? ($optFact * 3) : $r[5]; + $x = abs($nf / $optFact - 1); + $v = 10 + $x * $x * $x * 50; + + $ncor = min(32000, $ncor + $v); + } else { + $ncor = max(0, $r[4] - $cDec * 3); + } + $ncor = round($ncor * $growthRatio * $growthRatio * $growthRatio); + + $this->db->query("UPDATE planet SET pop=$p,corruption=$ncor WHERE id={$r[0]}"); + $this->planets->call('updateHappiness', $r[0]); + } + } + + + public function updateWarehouse(&$players, &$playersById) { + // Get missing data for the warehouse: + $this->at = time(); + + // -> IDR: + $o = $this->rankings->call('getAll', $this->rankings->call('getType', 'p_idr')); + foreach ($o as $r) { + $players[$r['id']]['d_rank'] = $r['points']; + } + + // -> Alliance tag: + $q = $this->db->query("SELECT p.id,a.tag FROM alliance a,player p " + . "WHERE (p.quit IS NULL OR UNIX_TIMESTAMP(NOW())-p.quit<86400)" + . " AND p.a_status='IN' AND p.alliance IS NOT NULL AND NOT p.hidden" + . " AND a.id=p.alliance"); + while ($r = dbFetchArray($q)) { + $players[$playersById[$r[0]]]['a_tag'] = $r[1]; + } + + // -> Tech lists + $lsts = array(); + $q = $this->db->query("SELECT p.id,r.id FROM research r,research_player j,player p " + . "WHERE (p.quit IS NULL OR UNIX_TIMESTAMP(NOW())-p.quit<86400) AND NOT p.hidden" + . " AND j.player=p.id AND j.research=r.id AND j.points=r.points"); + while ($r = dbFetchArray($q)) { + if (!is_array($lsts[$r[0]])) { + $lsts[$r[0]] = array(); + } + array_push($lsts[$r[0]], $r[1]); + } + foreach ($lsts as $pid => $tl) { + $players[$playersById[$pid]]['tech_list'] = join(',', $tl); + } + + // -> Planet lists and details + $q = $this->db->query("SELECT p.id,r.id,r.name,r.pop,r.max_pop,r.ifact,r.mfact,r.turrets," + . "r.happiness,r.beacon,r.abandon,r.corruption " + . "FROM planet r,player p " + . "WHERE (p.quit IS NULL OR UNIX_TIMESTAMP(NOW())-p.quit<86400) AND NOT p.hidden" + . " AND r.owner=p.id"); + $plPlanets = array(); + while ($r = dbFetchArray($q)) { + if (!is_array($plPlanets[$r[0]])) { + $plPlanets[$r[0]] = array(); + } + array_push($plPlanets[$r[0]], array( + "planet" => $r[1], + "name" => $r[2], + "pop" => $r[3], + "max_pop" => $r[4], + "ifact" => $r[5], + "mfact" => $r[6], + "turrets" => $r[7], + "happiness" => $r[8], + "beacon" => $r[9], + "abandon" => $r[10], + "corruption" => $r[11] + )); + } + + + // Update the warehouse + foreach ($players as $name => $data) { + $pid = $data['player']; + if (is_null($pid)) { + l::warn("****** BUG: null player ID in the warehouse update loop"); + continue; + } + foreach ($data as $k => $v) { + $data[$k] = "'" . addslashes($v) . "'"; + } + if (is_null( $data['tech_list'] )) { + $data['tech_list'] = "''"; + } + + $qs = "INSERT INTO warehouse(" . join(',', array_keys($data)) + . ") VALUES (" . join(',', array_values($data)) . ")"; + $this->db->query($qs); + + $q = $this->db->query("SELECT id FROM warehouse WHERE tick_at={$this->at} AND player=$pid"); + list($id) = dbFetchArray($q); + if (!$id || is_null($plPlanets[$pid])) { + continue; + } + + foreach ($plPlanets[$pid] as $pdata) { + $pdata['id'] = $id; + + foreach ($pdata as $k => $v) { + $pdata[$k] = "'" . addslashes($v == '' ? '0' : $v) . "'"; + } + + $qs = "INSERT INTO wh_planet(" . join(',', array_keys($pdata)) + . ") VALUES (" . join(',', array_values($pdata)) . ")"; + $this->db->query($qs); + } + } + $this->db->query("DELETE FROM warehouse WHERE UNIX_TIMESTAMP(NOW())-tick_at>86400*60"); + } +} + + +?> diff --git a/scripts/game/beta5/ticks/hour/library.inc b/scripts/game/beta5/ticks/hour/library.inc new file mode 100644 index 0000000..ca9a272 --- /dev/null +++ b/scripts/game/beta5/ticks/hour/library.inc @@ -0,0 +1,911 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + $this->main = $this->game->getLib(); + $this->bq = $this->game->getLib('beta5/bq'); + $this->fleets = $this->game->getLib('beta5/fleet'); + $this->msg = $this->game->getLib('beta5/msg'); + $this->planets = $this->game->getLib('beta5/planet'); + $this->players = $this->game->getLib('beta5/player'); + $this->rankings = $this->game->getLib('main/rankings'); + $this->rules = $this->game->getLib('beta5/rules'); + $this->sales = $this->game->getLib('beta5/sale'); + $this->standby = $this->game->getLib('beta5/standby'); + } + + + public function runTick() { + // Planetary events + l::debug("Handling planetary events"); + $handledSystems = 0; + do { + $increase = $this->db->safeTransaction( + array($this, 'handleSystemGroup'), $handledSystems + ); + $handledSystems += $increase; + } while ($increase > 0); + + // Abandon planets + $this->db->safeTransaction(array($this, "handleAbandon")); + + // Stuck fleets + $this->db->safeTransaction(array($this, 'unstickFleets')); + + // Fleets waiting in hyperspace + $this->db->safeTransaction(array($this, 'hyperspaceStandby')); + + // Fleet sales + $this->db->safeTransaction(array($this, 'fleetSales')); + + // Update happiness + $this->db->safeTransaction(array($this, 'updateHappiness')); + + // Increase inflicted damage points for WHSN'd fleets + if (count($this->idrInc)) { + $this->db->safeTransaction(array($this, 'updateIDR')); + } + + // Check victory status on CTF games + if ($this->game->params['victory'] == 2) { + $this->db->safeTransaction(array($this, 'updateCTF')); + } + + // Update rankings + $this->db->safeTransaction(array($this->main, 'call'), 'updateRankings'); + } + + + public function handleSystemGroup($startAt) { + $q = $this->db->query("SELECT id,nebula FROM system ORDER BY id ASC LIMIT 5 OFFSET $startAt"); + $n = dbCount($q); + while ($r = dbFetchArray($q)) { + $this->handleSystem($r[0], $r[1]); + } + return $n; + } + + public function unstickFleets() { + l::debug("Unsticking fleets"); + $this->db->query("UPDATE fleet SET can_move = 'Y' WHERE can_move = 'H'"); + } + + public function hyperspaceStandby() { + $q = $this->db->query("SELECT id,owner,waiting FROM fleet WHERE waiting IS NOT NULL"); + $fWait = array(); + while ($r = dbFetchArray($q)) { + $fWait[$r[2]] = array($r[0],$r[1]); + } + if (!count($fWait)) { + return; + } + + l::debug("Handling hyperspace stand-by"); + $q = $this->db->query( + "SELECT * FROM hs_wait WHERE id IN (" . join(',', array_keys($fWait)) . ")" + ); + while ($r = dbFetchHash($q)) { + $r['fleet'] = $fWait[$r['id']][0]; + $r['player'] = $fWait[$r['id']][1]; + $fWait[$r['id']] = $r; + } + + $losses = array(); + $planets = array(); + foreach ($fWait as $fw) { + if ($this->handleWait($fw, &$losses)) { + array_push($planets, $fw['drop_point']); + } + } + foreach ($planets as $planetID) { + $this->planets->call('detectFleets', $planetID); + } + + $tm = time(); + foreach ($losses as $pid => $locs) { + foreach ($locs as $plid => $fl) { + $pinf = $this->planets->call('byId', $plid); + + $hsLosses = array(); + foreach ($fl as $f) { + array_push($hsLosses, array( + "p_id" => $plid, + "p_name" => $pinf['name'], + "f_name" => $f[0], + "gaships" => $f[1], + "fighters" => $f[2], + "cruisers" => $f[3], + "bcruisers" => $f[4], + "power" => $f[5] + )); + } + $this->msg->call('send', $pid, 'hsloss', $hsLosses); + } + } + + $this->fleets->call('sendMoveMessages'); + } + + + public function fleetSales() { + $q = $this->db->query("SELECT id FROM fleet WHERE sale IS NOT NULL"); + if (! dbCount($q)) { + return; + } + + l::debug("Handling fleet sales"); + while ($r = dbFetchArray($q)) { + $f = $this->fleets->call('get', $r[0]); + l::trace("FSALE: found fleet {$r[0]}"); + if (!is_null($f['sale_info']['sale']['planet'])) { + l::trace("FSALE({$r[0]}): fleet is bundled with a planet, skipping"); + continue; + } + + if ($f['sale'] > 0) { + l::debug("FSALE({$f['id']}): fleet isn't ready yet ({$f['sale']}h remaining)"); + $this->db->query("UPDATE fleet SET sale=sale-1 WHERE id={$f['id']}"); + continue; + } + + $q2 = $this->db->query( + "SELECT id,sold_to FROM sale WHERE player={$f['owner']} AND fleet={$f['id']}" + . " AND finalized IS NOT NULL ORDER BY finalized DESC LIMIT 1" + ); + list($oid,$to) = dbFetchArray($q2); + l::trace("FSALE({$f['id']}): offer #$oid, buyer #$to"); + + // Read data required to send the messages + $fps = $this->fleets->call('getPower', $f['owner'], $f['gaships'], $f['fighters'], + $f['cruisers'], $f['bcruisers']); + $fpb = $this->fleets->call('getPower', $to, $f['gaships'], $f['fighters'], $f['cruisers'], + $f['bcruisers']); + l::trace("FSALE({$f['id']}): fps= $fps, fpb= $fpb"); + + // Send messages + $this->msg->call('send', $f['owner'], 'sale', array( + 'f_name' => $f['name'], + 'f_power' => $fps, + 'pl_id' => $to, + 'pl_name' => $this->players->call('getName', $to), + 'is_sale' => 'TRUE' + )); + $this->msg->call('send', $to, 'sale', array( + 'f_name' => $f['name'], + 'f_power' => $fpb, + 'pl_id' => $f['owner'], + 'pl_name' => $this->players->call('getName', $f['owner']), + 'is_sale' => 'FALSE' + )); + l::trace("FSALE({$f['id']}): messages sent"); + + // Transfer control + $this->db->query("UPDATE fleet SET sale=NULL,owner=$to WHERE id={$f['id']}"); + $this->fleets->call('invCache', $f['id']); + // FIXME: add history + $this->db->query("DELETE FROM sale WHERE id=$oid"); + l::debug("FSALE({$f['id']}): control transfered"); + } + } + + + public function updateIDR() { + l::debug("Updating IDR"); + $rt = $this->rankings->call('getType', 'p_idr'); + $rl = $this->rankings->call('getAll', $rt); + $idr = array(); + foreach ($rl as $r) { + $idr[$r['id']] = $r['points']; + } + foreach ($this->idrInc as $n => $inc) { + $idr[$n] += $inc; + } + $idrR = array(); + foreach ($idr as $n => $p) { + if (!is_array($idrR[$p])) { + $idrR[$p] = array(); + } + array_push($idrR[$p], $n); + } + $this->rankings->call('update', $rt, $idrR); + } + + public function updateCTF() { + if (! $this->game->getLib()->call('isFinished')) { + $this->game->getLib('beta5/ctf')->call('checkTargets'); + } + } + + + public function updateHappiness() { + l::debug("Updating planetary happiness"); + + $q = $this->db->query("SELECT id,owner FROM planet WHERE status=0"); + $destroy = array(); + while ($r = dbFetchArray($q)) { + $nHap = $this->planets->call('updateHappiness', $r[0]); + + // Handle revolts + if ($nHap < 20 && !in_array($r[0], $this->oChange)) { + if (!is_null($r[1]) && is_null($destroy[$r[1]])) { + $destroy[$r[1]] = array(); + } + $pDestr = $this->planetRevolt($r[0], $nHap); + if (!is_null($r[1])) { + array_push($destroy[$r[1]], $pDestr); + } + $this->planets->call('updateHappiness', $r[0]); + } + } + + // Send damage reports for revolts + $tm = time() + 1; + foreach ($destroy as $player => $plist) { + $report = array(); + foreach ($plist as $data) { + array_push($report, array( + "planet" => $data[0], + "pname" => $data[1], + "ifact" => $data[2], + "mfact" => $data[3], + "turrets" => $data[4] + )); + } + $this->msg->call('send', $player, 'revdmg', $report); + } + + } + + + private function buildFleets($plId, $ownId, $g, $f, $c, $b) { + if ($g + $f + $c + $b == 0) { + return; + } + + $q = $this->db->query("SELECT id FROM fleet WHERE can_move='Y' AND LOWER(name)='no name' AND location=$plId AND owner=$ownId AND sale IS NULL LIMIT 1"); + if (dbCount($q)) { + list($id) = dbFetchArray($q); + $this->db->query("UPDATE fleet SET gaships=gaships+$g,fighters=fighters+$f,cruisers=cruisers+$c,bcruisers=bcruisers+$b WHERE id=$id"); + } else { + $qs = "INSERT INTO fleet(owner,location,gaships,fighters,cruisers,bcruisers)VALUES($ownId,$plId,$g,$f,$c,$b)"; + $this->db->query($qs); + } + } + + + private function planetBuildQueue($planet, $rules) { + static $types = array('turret', 'gaship', 'fighter', 'cruiser', 'bcruiser'); + + $units = $rules['mf_productivity'] * $rules['mf_productivity_factor'] * $planet['mfact']; + $cl = $planet['corruption'] / 32000; + if ($cl > .1) { + $units = $units * (1.1 - $cl); + } + + $vac = $this->players->call('isOnVacation', $planet['owner']); + if ($vac) { + $units = $units / 4; + } + + $prot = $this->players->call('getProtectionLevel', $planet['owner']); + if ($prot) { + $units = $units * 1.15; + } + + $units = round($units); + + $bq = $this->bq->call('get', $planet['id']); + $prod = array(0, 0, 0, 0, 0); + $move = 0; + while (count($bq) && $units > 0) { + if ($bq[0]['units'] <= $units) { + $units -= $bq[0]['units']; + $prod[$bq[0]['type']] += $bq[0]['quantity']; + $this->db->query("DELETE FROM buildqueue WHERE planet=".$planet['id']." AND bq_order=$move"); + $move ++; + array_shift($bq); + } else { + $units += $bq[0]['workunits']; + $wuCost = $rules['workunits_'.$types[$bq[0]['type']]]; + $mod = $units % $wuCost; + $nBuild = ($units - $mod) / $wuCost; + $prod[$bq[0]['type']] += $nBuild; + $nQt = $bq[0]['quantity'] - $nBuild; + $this->db->query("UPDATE buildqueue SET quantity=$nQt,workunits=$mod WHERE planet=".$planet['id']." AND bq_order=$move"); + $units = 0; + } + } + + if ($move) { + $this->bq->call('reorder', $planet['id']); + } + + if ($prod[0] > 0) { + $this->db->query("UPDATE planet SET turrets=turrets+".$prod[0]." WHERE id=".$planet['id']); + $tm = time(); + $this->db->query("DELETE FROM turhist WHERE $tm-moment>86400"); + $this->db->query("INSERT INTO turhist VALUES({$planet['id']},$tm,$prod[0])"); + } + + $this->buildFleets($planet['id'], $planet['owner'], $prod[1], $prod[2], $prod[3], $prod[4]); + $this->planets->call('updateMilStatus', $planet['id']); + } + + private function planetSale(&$planet) { + l::trace("SALE: handling planet {$planet['id']}"); + if ($planet['sale'] > 0) { + l::debug("SALE({$planet['id']}): not ready for sale yet ({$planet['sale']}h remaining)"); + $this->db->query("UPDATE planet SET sale=sale-1 WHERE id=".$planet['id']); + $q = $this->db->query( + "SELECT fleet FROM sale WHERE player=".$planet['owner']." AND planet=".$planet['id'] + . " AND finalized IS NOT NULL ORDER BY finalized DESC LIMIT 1" + ); + list($fleet) = dbFetchArray($q); + if (!is_null($fleet)) { + l::trace("SALE({$planet['id']}): fleet $fleet bundled"); + $this->db->query("UPDATE fleet SET sale=sale-1 WHERE id=$fleet"); + } + } else { + l::trace("SALE({$planet['id']}): running sale code"); + $q = $this->db->query( + "SELECT id,sold_to,fleet FROM sale WHERE player=".$planet['owner']." AND planet=".$planet['id'] + . " AND finalized IS NOT NULL ORDER BY finalized DESC LIMIT 1" + ); + if (!dbCount($q)) { + l::warn("***** BUG: planet {$planet['id']} has sale field set to 0, but no " + . "sales were found"); + $this->db->query("UPDATE planet SET sale=NULL WHERE id={$planet['id']}"); + return; + } + list($oid,$to,$fleet) = dbFetchArray($q); + l::trace("SALE({$planet['id']}): offer #$oid, sold to player #$to (bundled fleet: " + . (is_null($fleet) ? "none" : "#$fleet") . ")"); + + // Read data required to send the messages + $tn = addslashes($this->players->call('getName', $to)); + $fn = addslashes($this->players->call('getName', $planet['owner'])); + l::trace("SALE({$planet['id']}): original owner '$tn', new owner '$fn'"); + if (is_null($fleet)) { + $fqs = $fqb = "NULL,NULL"; + } else { + $q = $this->db->query("SELECT * FROM fleet WHERE id=$fleet"); + $fd = dbFetchHash($q); + $fps = $this->fleets->call('getPower', $fd['owner'], $fd['gaships'], $fd['fighters'], $fd['cruisers'], $fd['bcruisers']); + $fpb = $this->fleets->call('getPower', $to, $fd['gaships'], $fd['fighters'], $fd['cruisers'], $fd['bcruisers']); + $fqs = "'" . addslashes($fd['name']) . "',$fps"; + $fqb = "'" . addslashes($fd['name']) . "',$fpb"; + } + l::trace("SALE({$planet['id']}): fqs: $fqs"); + l::trace("SALE({$planet['id']}): fqb: $fqb"); + + // Send messages + // FIXME: message API + $tm = time(); + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES({$planet['owner']},$tm,'sale','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player={$planet['owner']} AND sent_on=$tm AND mtype='sale' ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_sale VALUES($mid,{$planet['id']},'".addslashes($planet['name'])."',$fqs,$to,'$tn',TRUE)"); + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($to,$tm,'sale','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=$to AND sent_on=$tm AND mtype='sale' ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_sale VALUES($mid,{$planet['id']},'".addslashes($planet['name'])."',$fqb,{$planet['owner']},'$fn',FALSE)"); + l::trace("SALE({$planet['id']}): messages sent"); + + // Check for vacation mode + $vac = $this->players->call('isOnVacation', $to); + if ($vac) { + $q = $this->db->query("SELECT COUNT(*) FROM fleet WHERE location={$planet['id']} AND attacking"); + list($c) = dbFetchArray(); + if ($c) { + $vm = 'PEND'; + } else { + $vm = 'YES'; + } + } else { + $vm = 'NO'; + } + l::trace("SALE({$planet['id']}): vacation mode for target player: $vm"); + + // Sell the planet + $this->players->call('losePlanet', $planet['owner'], $planet['id']); + $this->players->call('takePlanet', $to, $planet['id']); + $nVal = $this->planets->call('ownerChange', $planet['id'], $to, $vm); + if ($nVal > 0) { + $planet['max_pop'] = $nVal; + } + $this->db->query("UPDATE player SET bh_unhappiness=bh_unhappiness+1 WHERE id={$planet['owner']} AND bh_unhappiness<15"); + if (!is_null($fleet)) { + $this->db->query("UPDATE fleet SET sale=NULL,owner=$to WHERE id=$fleet"); + } + $planet['owner'] = $to; + // FIXME: add history + $this->db->query("DELETE FROM sale WHERE id=$oid"); + + l::debug("SALE({$planet['id']}) completed"); + } + } + + private function whsnPreparation(&$planet) { + if ($planet['bh_prep'] > 0) { + // WHSN is not ready yet + $this->db->query("UPDATE planet SET bh_prep=bh_prep-1 WHERE id=".$planet['id']); + $this->db->query("UPDATE player SET bh_unhappiness=bh_unhappiness+1 WHERE id={$planet['owner']} AND bh_unhappiness<15"); + return false; + } + + // Get fleet statistics to send messages + $players = array( + $planet['owner'] => array( + "was_owner" => dbBool(true), + "attacking" => false, + "power" => 0, + "fleets" => array(), + ) + ); + $q = $this->db->query("SELECT * FROM fleet WHERE location=".$planet['id']); + $aPower = $dPower = 0; + while ($r = dbFetchHash($q)) { + if (is_null($players[$r['owner']])) { + $players[$r['owner']] = array( + "was_owner" => dbBool(false), + "attacking" => ($r['attacking'] == 't'), + "power" => 0, + "fleets" => array(), + ); + } + $f = array( + "name" => $r['name'], + "gaships" => $r['gaships'], + "fighters" => $r['fighters'], + "cruisers" => $r['cruisers'], + "bcruisers" => $r['bcruisers'], + ); + $power = $this->fleets->call('getPower', $r['owner'], $r['gaships'], $r['fighters'], $r['cruisers'], $r['bcruisers']); + $players[$r['owner']]["power"] += ($f['power'] = $power); + array_push($players[$r['owner']]['fleets'], $f); + if ($players[$r['owner']]['attacking']) { + $aPower += $power; + } else { + $dPower += $power; + } + + // Cancel fleet sales + $qfs = $this->db->query("SELECT id,player,finalized FROM sale WHERE fleet={$r['id']}"); + if (dbCount($qfs)) { + list($saleId, $seller, $saleFin) = dbFetchArray($qfs); + if (is_null($saleFin)) { + $ga = "cancel"; + } else { + $ga = "cancelTransfer"; + } + $this->sales->call($ga, $seller, $saleId); + } + } + + // Send the messages + // FIXME: message API + $tm = time(); + foreach ($players as $pId => $data) { + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($pId,$tm,'whsn','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=$pId AND sent_on=$tm AND mtype='whsn' ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q); + + if ($data['attacking']) { + $pow = $aPower - $data['power']; + $fPower = $pow; $ePower = $dPower; + } else { + $pow = $dPower - $data['power']; + $fPower = $pow; $ePower = $aPower; + } + + $this->db->query("INSERT INTO msg_whsn VALUES($mid,{$planet['id']},'" + . addslashes($planet['name']) . "'," . $data['was_owner'] + . ",$fPower,$ePower)"); + foreach ($data['fleets'] as $f) + $this->db->query("INSERT INTO whsn_fleet VALUES($mid,'" . addslashes($f['name']) + . "',{$f['gaships']},{$f['fighters']},{$f['cruisers']}," + . "{$f['bcruisers']},{$f['power']})"); + } + + // Check for IDR points + $pow = $aPower - $dPower; + if ($pow > 0) { + $pn = $this->players->call('getName', $planet['owner']); + if (is_null($this->idrInc[$pn])) { + $this->idrInc[$pn] = $pow; + } else { + $this->idrInc[$pn] += $pow; + } + } + + // Destroy the planet and anything around it + $this->players->call('losePlanet', $planet['owner'], $planet['id']); + $this->bq->call('flush', $planet['id']); + $this->db->query("DELETE FROM fleet WHERE location=".$planet['id']); + do { + $rn = strtoupper(substr(md5(uniqid(rand())), 0, 10)); + $q = $this->db->query("SELECT id FROM planet WHERE name='PR-[$rn]'"); + } while(dbCount($q)); + $this->db->query( + "UPDATE planet " + . "SET pop = 0, max_pop = 0, happiness = 0, mfact = 0, ifact = 0, beacon = 0, " + . "built_probe = FALSE, probe_policy = NULL, bh_prep = NULL, owner = NULL, " + . "sale = NULL, status = 1, bh_unhappiness = 0, vacation = 'NO', " + . "name = 'PR-[$rn]', abandon = NULL " + . "WHERE id = {$planet['id']}" + ); + $this->db->query( + "UPDATE planet " + . "SET bh_unhappiness = bh_unhappiness + 4 " + . "WHERE system={$planet['system']} AND id <> {$planet['id']} " + . "AND status = 0 AND bh_unhappiness < 12" + ); + $this->db->query("DELETE FROM planet_abandon_time WHERE id = {$planet['id']}"); + + // Raise the empire-wide unhappiness for the planet's owner + $q = $this->db->query("SELECT bh_unhappiness FROM player WHERE id={$planet['owner']}"); + list($bhu) = dbFetchArray($q); + $nuh = max($bhu + 4, 15); + $this->db->query("UPDATE player SET bh_unhappiness=$nuh WHERE id={$planet['owner']}"); + + return true; + } + + + private function planetTaken(&$planet) { + if ($planet['turrets'] > 0) { + return false; + } + + // Check if the planet was in vacation mode before the attacking ships arrived + // If it was, it can't be taken + if ($planet['vacation'] == 'YES ') { + return false; + } + + // List fleets at location + $q = $this->db->query("SELECT owner,gaships,attacking,time_spent FROM fleet WHERE location={$planet['id']}"); + $players = array(); + while ($r = dbFetchArray($q)) { + // If there are defending fleets, forget it + if ($r[2] == 'f') { + if ($r[3] > 5) { + return false; + } + continue; + } elseif ($r[3] < 15) { + continue; + } + + // Count GA Ships per player + $players[$r[0]] += $r[1]; + } + + // Get the highest GA efficiency + $maxGAPower = $maxGAPlayer = -1; + foreach ($players as $id => $gas) { + $rules = $this->rules->call('get', $id); + $gaPower = $gas * $rules['gaship_pop']; + if ($gaPower > $maxGAPower) { + $maxGAPower = $gaPower; + $maxGAPlayer = $id; + } + } + + // Check whether the player can actually take the planet + if ($maxGAPlayer == -1 || $maxGAPower < $planet['pop']) { + return false; + } + + // A player has taken control on the planet + if (!is_null($planet['owner'])) { + // If the planet had an owner, flush build queue and cancel sales + $this->bq->call('flush', $planet['id']); + $q = $this->db->query("SELECT id,finalized,sold_to FROM sale WHERE planet={$planet['id']}"); + if (dbCount($q)) { + // Cancel sale + list($sId,$fin,$to) = dbFetchArray($q); + if (is_null($fin)) { + $ga = 'cancel'; + } else { + $ga = 'cancelTransfer'; + + // Send message + // FIXME ... + $tm = time(); + $this->db->query("INSERT INTO message(player,sent_on,mtype,ftype,is_new) VALUES($to,$tm,'plsc','INT',TRUE)"); + $q = $this->db->query("SELECT id FROM message WHERE player=$to AND sent_on=$tm AND mtype='plsc' ORDER BY id DESC LIMIT 1"); + list($mid) = dbFetchArray($q); + $this->db->query("INSERT INTO msg_plsc VALUES($mid,{$planet['owner']},$maxGAPlayer,{$planet['id']},'" + . addslashes($planet['name']) . "')"); + } + $this->sales->call($ga, $planet['owner'], $sId); + } + + $rqs = ",renamed=".time(); + } else { + $rqs = ""; + } + + // Check for vacation mode + $vm = $this->players->call('isOnVacation', $maxGAPlayer) ? 'YES' : 'NO'; + + // Transfer control + $this->players->call('losePlanet', $planet['owner'], $planet['id']); + $this->players->call('takePlanet', $maxGAPlayer, $planet['id']); + $nVal = $this->planets->call('ownerChange', $planet['id'], $maxGAPlayer, $vm); + if ($nVal > 0) { + $planet['max_pop'] = $nVal; + } + + // Update a potential abandon log entry + $tm = time(); + $this->db->query("UPDATE abandon_log SET retake_owner=$maxGAPlayer,retake_time=$tm WHERE planet={$planet['id']} AND retake_time IS NULL"); + + // Get the list of the new owner's enemies + $q = $this->db->query("SELECT enemy FROM enemy_player WHERE player=$maxGAPlayer"); + $enemies = array(); + while ($r = dbFetchArray($q)) { + array_push($enemies, $r[0]); + } + $q = $this->db->query("SELECT p.id FROM player p,enemy_alliance e WHERE p.alliance=e.alliance AND p.a_status='IN' AND e.player=$maxGAPlayer"); + while ($r = dbFetchArray($q)) { + array_push($enemies, $r[0]); + } + $enemies = array_unique($enemies); + + // Switch attacking fleets to defense unless they belong to an enemy + $qs = "UPDATE fleet SET attacking=FALSE,can_move='Y' WHERE location={$planet['id']}"; + if (count($enemies)) { + $qs .= " AND owner NOT IN (".join(',',$enemies).")"; + } + $this->db->query($qs); + + // Send messages to the former owner and the new owner + $pn = addslashes($planet['name']); + $noMsg = array($maxGAPlayer); + $mData = array( + 'p_id' => $planet['id'], + 'p_name' => $planet['name'], + 'owner' => $planet['owner'], + 'status' => 'TAKE' + ); + $this->msg->call('send', $maxGAPlayer, 'ownerch', $mData); + + $mData['owner'] = $maxGAPlayer; + if (!is_null($planet['owner'])) { + $mData['status'] = 'LOSE'; + array_push($noMsg, $pid = $planet['owner']); + $this->msg->call('send', $pid, 'ownerch', $mData); + } + + // Get the list of other players and send them a message as well + $q2 = $this->db->query("SELECT DISTINCT owner FROM fleet WHERE location={$planet['id']} AND owner NOT IN (" . join(',', $noMsg) . ")"); + $mData['status'] = 'VIEW'; + while ($r = dbFetchArray($q2)) { + $this->msg->call('send', $r[0], 'ownerch', $mData); + } + + return true; + } + + + private function handlePlanet($planet) { + // WHSN preparation + if (!is_null($planet['bh_prep']) && $this->whsnPreparation($planet)) { + return false; + } + + // Check for owner change + if ($this->planetTaken($planet)) { + $this->planets->call('updateMilStatus', $planet['id']); + return true; + } + + // Planet sale + if (!is_null($planet['sale'])) { + $this->planetSale($planet); + } + + // No build queues or revolts on neutral planets + if (is_null($planet['owner'])) { + return false; + } + + // Load owner rules + $rules = $this->rules->call('get', $planet['owner']); + + // Build queue + $this->planetBuildQueue($planet, $rules); + + return false; + } + + + private function handleSystem($sid, $neb) { + if ($neb) { + return; + } + + l::trace("Handling system #$sid"); + $q = $this->db->query("SELECT * FROM planet WHERE system=$sid"); + while ($planet = dbFetchHash($q)) { + if ($neb || $planet['status'] == 1) { + continue; + } + if ($this->handlePlanet($planet)) { + array_push($this->oChange, $planet['id']); + } + } + } + + + private function handleWait($fw, &$losses) { + $canDestroy = $this->standby->call('canDestroy', $fw['drop_point'], $fw['player']); + + // If we can destroy fleets, find out whether we must do so + if ($canDestroy) { + $prob = $this->standby->call('getLossProb', $fw['time_spent']); + l::debug("HSSB({$fw['fleet']}): at planet #{$fw['drop_point']}, loss probability = " + . "$prob (time spent: {$fw['time_spent']}h)"); + if (rand(1,100) <= $prob) { + l::debug("HSSB({$fw['fleet']}): applying fleet losses"); + + // Destroy a part of the fleet + $amount = rand(5,round(10+$prob/10)); + $fleet = $this->fleets->call('get', $fw['fleet']); + $rules = $this->rules->call('get', $fw['player']); + $fcLoss = ceil(($fleet['cruisers'] / 100) * $amount); + $fbLoss = ceil(($fleet['bcruisers'] / 100) * $amount); + l::trace("HSSB({$fw['fleet']}): cruisers lost: $fcLoss; battle cruisers lost: $fbLoss"); + + // Compute the amount of transported ships to be destroyed + $gaSpace = $fleet['gaships'] * $rules['gaship_space']; + $fSpace = $fleet['fighters'] * $rules['fighter_space']; + $tSpace = $gaSpace + $fSpace; + if ($tSpace > 0) { + $haul = $fleet['cruisers'] * $rules['cruiser_haul'] + + $fleet['bcruisers'] * $rules['bcruiser_haul']; + $haulUsed = $tSpace / $haul; + $lHaul = $fcLoss * $rules['cruiser_haul'] + $fbLoss * $rules['bcruiser_haul']; + $lSpace = $haulUsed * $lHaul; + $gaLSpace = ($lSpace / $tSpace) * $gaSpace; + $fLSpace = ($lSpace / $tSpace) * $fSpace; + $fgLoss = min($fleet['gaships'], ceil($gaLSpace / $rules['gaship_space'])); + $ffLoss = min($fleet['fighters'], ceil($fLSpace / $rules['fighter_space'])); + } else { + $fgLoss = $ffLoss = 0; + } + l::trace("HSSB({$fw['fleet']}): fighters lost: $ffLoss; GA ships lost: $fgLoss"); + + // Add details to the $losses array + if (!is_array($losses[$fleet['owner']])) { + $losses[$fleet['owner']] = array(); + } + if (!is_array($losses[$fleet['owner']][$fw['drop_point']])) { + $losses[$fleet['owner']][$fw['drop_point']] = array(); + } + array_push($losses[$fleet['owner']][$fw['drop_point']], array( + addslashes($fleet['name']), $fgLoss, $ffLoss, $fcLoss, $fbLoss, + $this->fleets->call('getPower', $fleet['owner'], $fgLoss, $ffLoss, + $fcLoss, $fbLoss) + )); + + if ($fcLoss == $fleet['cruisers'] && $fbLoss == $fleet['bcruisers'] + && $ffLoss == $fleet['fighters'] && $fgLoss == $fleet['gaships']) { + $this->fleets->call('disband',$fw['fleet'],true); + l::trace("HSSB({$fw['fleet']}): fleet disbanded"); + } elseif ($fcLoss + $fbLoss + $ffLoss + $fgLoss > 0) { + $this->db->query( + "UPDATE fleet SET gaships = gaships - $fgLoss, " + . "fighters = fighters - $ffLoss, " + . "cruisers = cruisers - $fcLoss, " + . "bcruisers = bcruisers - $fbLoss " + . "WHERE id = {$fw['fleet']}" + ); + } + } + } + + if ($fw['time_left'] == 1) { + // Fleet arrival + $this->fleets->call('arrival', $fw['fleet'], $fw['drop_point'], $fw['origin']); + // Destroy wait entry + $this->db->query("DELETE FROM hs_wait WHERE id={$fw['id']}"); + } else { + // Update wait entry + $this->db->query( + "UPDATE hs_wait SET time_left = time_left - 1, time_spent = time_spent + 1 " + . "WHERE id={$fw['id']}" + ); + } + + return !($fw['time_left'] == 1); + } + + + private function planetRevolt($id, $hap) { + $q = $this->db->query("SELECT name,ifact,mfact,turrets FROM planet WHERE id=$id"); + list($n, $if, $mf, $tr) = dbFetchArray($q); + + $pdestr = 30 - $hap; + $pdestr += rand(-floor($pdestr/2),floor($pdestr/2)); + $pdestr /= 150; + $dInd = round($if * $pdestr); + $dMil = round($mf * $pdestr); + $dTur = round($tr * $pdestr); + + if ($dInd + $dMil + $dTur != 0) { + $this->db->query( + "UPDATE planet SET ifact = ifact - $dInd, mfact = mfact - $dMil, " + . "turrets = turrets - $dTur " + . "WHERE id = $id" + ); + } + + return array($id, $n, $dInd, $dMil, $dTur); + } + + + public function handleAbandon() { + l::debug("Handling planets being abandonned"); + + // Decrease time to abandon + $this->db->query("UPDATE planet SET abandon = abandon - 1 WHERE abandon IS NOT NULL"); + + // Get the list of planets to abandon at this tick + $q = $this->db->query("SELECT id, owner FROM planet WHERE abandon = 0"); + $oList = array(); + while ($r = dbFetchArray($q)) { + if (is_null($r[1])) { + l::warn("****** BUG: planet {$r[0]} is being abandoned and has no owner"); + continue; + } + $this->abandonPlanet($r[0], $r[1]); + if (!is_array($oList[$r[1]])) { + $oList[$r[1]] = array(); + } + array_push($oList[$r[1]], $r[0]); + } + + foreach ($oList as $pid => $aPl) { + // Send the message + $message = array(); + foreach ($aPl as $plId) { + $planet = $this->planets->call('byId', $plId); + array_push($message, array("p_id" => $plId, "p_name" => $planet['name'])); + } + $this->msg->call('send', $pid, 'abandon', $message); + + // Update other planets' happiness + $this->players->call('updateHappiness', $pid); + } + } + + + private function abandonPlanet($pid, $oid) { + // Insert log entry + $this->db->query( + "INSERT INTO abandon_log(planet,abandon_time,former_owner) " + . "VALUES ($pid," . time() . ",$oid)" + ); + + // Make the planet neutral + $this->planets->call('ownerChange', $pid); + $this->players->call('losePlanet', $oid, $pid); + } +} + +?> diff --git a/scripts/game/beta5/ticks/move/library.inc b/scripts/game/beta5/ticks/move/library.inc new file mode 100644 index 0000000..513922c --- /dev/null +++ b/scripts/game/beta5/ticks/move/library.inc @@ -0,0 +1,121 @@ +lib = $lib; + $this->db = $lib->game->db; + $this->fleets = $lib->game->getLib('beta5/fleet'); + $this->planets = $lib->game->getLib('beta5/planet'); + } + + + public function runTick() { + // Increase time spent for fleets + $this->db->safeTransaction(array($this, 'increaseTimeSpent')); + + // Advance moving objects and get the list of arrivals + $this->db->safeTransaction(array($this, 'advanceMovingObjects')); + + // Fleet arrivals + if (count($this->arrivals)) { + $this->db->safeTransaction(array($this, 'fleetArrivals')); + $this->db->safeTransaction(array($this, 'fleetArrivalUpdates')); + } + + // Remove completed trajectories + $this->db->safeTransaction(array($this, 'deleteOldTrajectories')); + } + + + public function deleteOldTrajectories() { + $this->db->query("DELETE FROM moving_object WHERE time_left = 0"); + } + + + public function increaseTimeSpent() { + // Increase time spent on planets for fleets that are not moving + $this->db->query("UPDATE fleet SET time_spent = time_spent + 1 WHERE location IS NOT NULL"); + } + + + public function advanceMovingObjects() { + // Advances all moving objects that don't suffer a destination change penalty + $this->db->query("UPDATE moving_object SET time_left = time_left - 1 WHERE changed=0 AND time_left > 0"); + + // Decreases penalty + $this->db->query("UPDATE moving_object SET changed = 61 WHERE changed > 60"); + $this->db->query("UPDATE moving_object SET changed = changed - 1 WHERE changed > 0"); + + // Get all moving objects arriving at destination + $q = $this->db->query("SELECT id, m_to, m_from, wait_order FROM moving_object WHERE time_left = 0"); + if (!dbCount($q)) { + return; + } + $mobjs = array(); + while ($r = dbFetchArray($q)) { + $mobjs[$r[0]] = array($r[1], $r[2], $r[3]); + } + + $this->arrivals = $mobjs; + } + + + function fleetArrivals() { + // Find moving fleets + $q = $this->db->query( + "SELECT id, moving FROM fleet WHERE moving IN (" . join(',', array_keys($this->arrivals)) . ")" + ); + $planets = array(); $plDetect = array(); + while ($r = dbFetchArray($q)) { + list($fId,$mId) = $r; + + // Does it have an HS stand-by order? + if (!is_null($this->arrivals[$mId][2])) { + $this->db->query( + "UPDATE hs_wait SET origin={$this->arrivals[$mId][1]}, " + . "drop_point={$this->arrivals[$mId][0]}" + . " WHERE id={$this->arrivals[$mId][2]}"); + $this->db->query("UPDATE fleet SET moving=NULL,waiting=" + . $this->arrivals[$mId][2] . " WHERE id=$fId"); + + // Add the planet to the list of planets where detection is to be run + if (!in_array($this->arrivals[$mId][0], $plDetect)) { + array_push($plDetect, $this->arrivals[$mId][0]); + } + } else { + // If there is no HS stand-by order, the fleet has arrived. + $this->fleets->call('arrival', $fId, $this->arrivals[$mId][0], $this->arrivals[$mId][1]); + + // Add the planet to the list of planets to be updated + if (!in_array($this->arrivals[$mId][0], $planets)) { + array_push($planets, $this->arrivals[$mId][0]); + } + } + } + + $this->plDetect = $plDetect; + $this->plUpdate = $planets; + } + + public function fleetArrivalUpdates() { + // Update planet status + foreach ($this->plDetect as $pid) { + $this->planets->call('detectFleets', $pid); + } + foreach ($this->plUpdate as $pid) { + $this->planets->call('updateHappiness', $pid); + $this->planets->call('updateMilStatus', $pid); + } + $this->fleets->call('sendMoveMessages'); + } +} + + +?> diff --git a/scripts/game/beta5/ticks/punishment/library.inc b/scripts/game/beta5/ticks/punishment/library.inc new file mode 100644 index 0000000..798c7ae --- /dev/null +++ b/scripts/game/beta5/ticks/punishment/library.inc @@ -0,0 +1,48 @@ +lib = $lib; + $this->db = $lib->game->db; + $this->b5adm = $lib->game->getLib('admin/beta5'); + } + + + public function runTick() { + $this->db->safeTransaction(array($this, 'punish')); + } + + + public function punish() { + // Neutralize planets with funky names + $q = $this->db->query( + "SELECT id, name FROM planet " + . "WHERE mod_check AND force_rename IS NOT NULL " + . "AND UNIX_TIMESTAMP(NOW())-force_rename >= 86400" + ); + + while ($r = dbFetchArray($q)) { + $q2 = $this->db->query( + "SELECT moderator FROM msg_warnname " + . "WHERE planet={$r[0]} AND event='WARN' " + . "ORDER BY id DESC LIMIT 1" + ); + list($mod) = dbFetchArray($q2); + if (is_null($mod)) { + l::error("****** BUG: moderator ID not found for planet {$r[0]}"); + continue; + } + l::notice("resetting planet '{$r[1]}' (id: $r[0]) due to funky name"); + $this->b5adm->call('resetPlanet', $r[0], $mod, false); + } + } +} + +?> diff --git a/scripts/game/beta5/ticks/quit/library.inc b/scripts/game/beta5/ticks/quit/library.inc new file mode 100644 index 0000000..0c6e83f --- /dev/null +++ b/scripts/game/beta5/ticks/quit/library.inc @@ -0,0 +1,33 @@ +lib = $lib; + $this->db = $lib->game->db; + $this->main = $lib->game->getLib(); + } + + + public function runTick() { + $this->db->safeTransaction(array($this, 'quit')); + } + + public function quit() { + $q = $this->db->query( + "SELECT id FROM player " + . "WHERE quit IS NOT NULL AND (UNIX_TIMESTAMP(NOW()) - quit) < 86400 " + . "AND (UNIX_TIMESTAMP(NOW()) - quit) > 86339" + ); + while ($r = dbFetchArray($q)) { + $this->main->call('leaveGame', $r[0], 'QUIT'); + } + } +} + + +?> diff --git a/scripts/game/beta5/ticks/sales/library.inc b/scripts/game/beta5/ticks/sales/library.inc new file mode 100644 index 0000000..286d47e --- /dev/null +++ b/scripts/game/beta5/ticks/sales/library.inc @@ -0,0 +1,127 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + } + + + public function runTick() { + // Update sales + $this->db->safeTransaction(array($this, 'expireSales')); + + // Check CTF victory + if ($this->game->params['victory'] == 0) { + $this->db->safeTransaction(array($this, 'checkProtection')); + } elseif ($this->game->params['victory'] == 2) { + $this->db->safeTransaction(array($this, 'checkCTFVictory')); + } + } + + public function expireSales() { + $q = $this->db->query("SELECT * FROM sale WHERE expiresdb->query("SELECT auction FROM public_offer WHERE offer=".$r['id']); + if ($q2 && dbCount($q2)) { + list($auction) = dbFetchArray($q2); + $this->expirePublicOffer($r, $auction == 't'); + continue; + } + + // Private offers + // FIXME + } + $this->db->query( + "DELETE FROM sale_history WHERE end_mode IS NOT NULL AND UNIX_TIMESTAMP(NOW()) - ended > 5184000" + ); + } + + public function checkCTFVictory() { + // Check victory status on CTF games + if (! $this->game->getLib()->call('isFinished')) { + $this->game->getLib('beta5/ctf')->call('checkTargets'); + } + } + + + public function checkProtection() { + $q = $this->db->query( + "SELECT p.id, s.id FROM planet p, system s WHERE s.id = p.system AND s.prot > 0" + ); + + $checked = array(); + $pLib = $this->game->getLib('beta5/prot'); + + while ($r = dbFetchArray($q)) { + if (! in_array($r[1], $checked)) { + array_push($checked, $r[1]); + $pLib->call('checkSystem', $r[0]); + } + } + + $pLib->call('flushStatus'); + } + + + private function expirePublicOffer($offer, $isAuction) { + $tm = time(); + + // If it isn't an auction sale, just delete it + if (!$isAuction) { + // Insert entry in player's history + $this->db->query("UPDATE sale_history SET ended=$tm,end_mode=3,sell_price=NULL WHERE offer=".$offer['id']); + // FIXME: send message + $this->db->query("DELETE FROM sale WHERE id=".$offer['id']); + return; + } + + // Check for a buyer + $q = $this->db->query("SELECT player,price FROM auction WHERE offer=".$offer['id']." ORDER BY price DESC LIMIT 1"); + if (!($q && dbCount($q))) { + // Insert entry in player's history + $this->db->query("UPDATE sale_history SET ended=$tm,end_mode=3,sell_price=NULL WHERE offer=".$offer['id']); + // FIXME: send message + $this->db->query("DELETE FROM sale WHERE id=".$offer['id']); + return; + } + list($buyer,$price) = dbFetchArray($q); + + // Refund players who have failed to buy the item + $q = $this->db->query("SELECT player,MAX(price) FROM auction WHERE offer=".$offer['id']." AND player<>$buyer GROUP BY player"); + while ($r = dbFetchArray($q)) { + // FIXME: send message + $this->db->query("UPDATE player SET cash=cash+".$r[1]." WHERE id=".$r[0]); + } + + // Mark the transaction as finalized + $this->db->query("UPDATE sale SET finalized=$tm,sold_to=$buyer WHERE id=".$offer['id']); + $this->db->query("UPDATE public_offer SET price=$price WHERE offer=".$offer['id']); + $this->db->query("DELETE FROM auction WHERE offer=".$offer['id']); + $this->db->query("UPDATE player SET cash=cash+$price WHERE id=".$offer['player']); + + // Inform both the current owner and the buyer + // FIXME + + // Insert history entry + $this->db->query("UPDATE sale_history SET ended=$tm,end_mode=2,sell_price=$price,to_player=$buyer WHERE offer=".$offer['id']); + + // Mark the items for sale + if (!is_null($offer['planet'])) { + $this->db->query("UPDATE planet SET sale=3 WHERE id=".$offer['planet']); + } + if (!is_null($offer['fleet'])) { + $this->db->query("UPDATE fleet SET sale=3 WHERE id=".$offer['fleet']); + } + } +} + +?> diff --git a/scripts/game/beta5/ticks/universe/library.inc b/scripts/game/beta5/ticks/universe/library.inc new file mode 100644 index 0000000..9afdfd2 --- /dev/null +++ b/scripts/game/beta5/ticks/universe/library.inc @@ -0,0 +1,483 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + $this->main = $this->game->getLib('main'); + $this->planets = $this->game->getLib('beta5/planet'); + } + + public function runTick() { + $this->db->safeTransaction(array($this, 'genUniverse')); + } + + public function genUniverse() { + // Run a special version of the tick if we are using a CTF map + $map = (int) $this->game->params['usemap']; + if ($map > 0) { + $this->handleCTFMap($map); + return; + } + + // Read the game's parameters + $np = $this->game->params['nebulaprob']; + $this->nebulaProb = ($np > -1 && $np < 21) ? $np : 2; + $this->minSystems = $this->game->params['minsystems']; + $this->maxSystems = $this->game->params['maxsystems']; + $zs = $this->game->params['zonesize']; + $this->zoneSize = ($zs <= 0 || $zs > 10) ? 2 : $zs; + + // If there is a maximum value, check it + if ($this->maxSystems) { + $ns = $this->getAllSystems(); + if ($ns >= $this->maxSystems) { + return; + } + } + + // Get the amount of free systems + $this->reassignEmpty(); + $ns = $this->getFreeSystems(); + if ($ns >= $this->minSystems) { + return; + } + + // Get the latest system ID + $q = $this->db->query("SELECT MAX(id) FROM system"); + list($id1) = dbFetchArray($q); + + // Read the universe generator's parameters + list($dir, $size) = $this->getSystemGenParm(); + $ldir = $dir; $lsize = $lsize; + + // Generate zones + while ($ns < $this->minSystems) { + list($x1, $y1, $x2, $y2) = $this->getNextRectangle($dir, $size); + $this->makeZone($x1, $y1, $x2, $y2); + $ns = $this->getFreeSystems(); + } + + if ($ldir != $dir || $lsize != $size) { + // Update the universe generator's parameters + if ($ldir != $dir) { + $this->db->query("UPDATE gdata SET value='$dir' WHERE id='sg_dir'"); + } + if ($lsize != $size) { + $this->db->query("UPDATE gdata SET value='$size' WHERE id='sg_len'"); + } + + // Make sure we have a first ID + if (is_null($id1)) { + $q = $this->db->query("SELECT MIN(id) FROM system"); + list($id1) = dbFetchArray($q); + } else { + $id1++; + } + + // Read the last ID + $q = $this->db->query("SELECT MAX(id) FROM system"); + list($id2) = dbFetchArray($q); + + // Generate the systems' planets + for ($i=$id1;$i<=$id2;$i++) { + $this->genSystem($i); + } + + // Get the size of the next area + list($x1,$y1,$x2,$y2) = $this->getNextRectangle($dir, $size); + $w = abs($x2 - $x1) + 1; + $h = abs($y2 - $y1) + 1; + + $q = $this->db->query("SELECT MAX(id) FROM planet"); + list($idp) = dbFetchArray($q); + + // Generate new planets + $this->main->call('requestGenPlanets', $idp + 1, ($w + 5) * $h * 6); + } + } + + + private function handleCTFMap($mapID) { + // Check if there are systems + $q = $this->db->query("SELECT COUNT(*) FROM system"); + if (!($q && dbCount($q))) { + return; + } + list($nSys) = dbFetchArray($q); + if ($nSys > 0) { + return; + } + + // No systems -> we must generate the map + // Start by loading the template's definition + $q = $this->db->query("SELECT width,height FROM ctf_map_def WHERE id=$mapID"); + if (!($q && dbCount($q))) { + logText("Could not load map definition #$mapID", LOG_ERR); + return; + } + list($width, $height) = dbFetchArray($q); + + // Load the template's layout + $q = $this->db->query("SELECT * FROM ctf_map_layout WHERE map=$mapID"); + if (!($q && dbCount($q) == $width * $height)) { + logText("Could not load map layout for template #$mapID", LOG_ERR); + return; + } + + // Create systems and generate CTF-specific data along the way + $ids = array(); + $targets = array(); + while ($row = dbFetchHash($q)) { + // Insert the map system + $qStr = "INSERT INTO system(x,y,prot,assigned,nebula) VALUES (" + . "{$row['sys_x']},{$row['sys_y']},0,FALSE," + . (($row['sys_type'] == 'S') ? "0" : $row['sys_type']) . ")"; + $sys = $this->db->query($qStr); + + if (!$sys) { + logText("Error inserting system at ({$row['sys_x']},{$row['sys_y']})", LOG_ERR); + return; + } + array_push($ids, $sys); + + if ($row['sys_type'] != 'S') { + continue; + } + + if ($row['alloc_for'] == 0) { + // Insert a target record + $this->db->query("INSERT INTO ctf_target(system) VALUES ($sys)"); + array_push($targets, $sys); + } else { + // Insert an alliance area record + $this->db->query("INSERT INTO ctf_alloc(system,alliance,spawn_point) VALUES (" + . "$sys,{$row['alloc_for']},'{$row['spawn_here']}')"); + } + } + + // Generate system contents + foreach ($ids as $sys) { + $this->genSystem($sys); + } + + // Upgrade target planets + $q = $this->db->query("SELECT id FROM planet WHERE system IN (" . join(',', $targets) . ")"); + while ($r = dbFetchArray($q)) { + $q = $this->db->query("UPDATE planet SET ifact=33, mfact=33, turrets=90 WHERE id = {$r[0]}"); + $this->planets->call('updateHappiness', $r[0]); + } + } + + + private function reassignEmpty() { + $q = $this->db->query("SELECT s.id,COUNT(p.*) AS cnt FROM system s,planet p " + . "WHERE s.assigned AND s.nebula=0 AND p.owner IS NULL AND p.status=0 AND p.system=s.id " + . "GROUP BY s.id HAVING COUNT(p.*)=6"); + if (!($q && dbCount($q))) { + return; + } + + $sids = array(); + while ($r = dbFetchArray($q)) { + $allocate = true; + + $q2 = $this->db->query("SELECT id FROM planet WHERE system = {$r[0]}"); + while ($allocate && $r2 = dbFetchArray($q2)) { + $allocate = $this->planets->call('canAllocate', $r2[0]); + } + + if ($allocate) { + array_push($sids, $r[0]); + } + } + + if (empty($sids)) { + return; + } + + $q = $this->db->query("SELECT DISTINCT p.system FROM fleet f,planet p " + . "WHERE f.location IS NOT NULL AND f.location=p.id AND p.system IN (" . join(',', $sids) . ")"); + if (!$q) { + return; + } + + $flids = array(); + while ($r = dbFetchArray($q)) { + array_push($flids, $r[0]); + } + + $rsids = array(); + foreach ($sids as $sid) { + if (!in_array($sid, $flids)) { + array_push($rsids, $sid); + logText("beta5/universe: system $sid will be reassigned"); + } + } + + if (empty($rsids)) { + return; + } + + $this->db->query("UPDATE system SET assigned=FALSE WHERE id IN (" . join(',', $rsids) . ")"); + } + + + private function getAllSystems() { + $q = $this->db->query("SELECT COUNT(*) FROM system WHERE nebula = 0"); + list($n) = dbFetchArray($q); + return $n; + } + + + private function getFreeSystems() { + $q = $this->db->query("SELECT COUNT(*) FROM system WHERE NOT assigned AND nebula = 0"); + list($n) = dbFetchArray($q); + return $n; + } + + + private function getSystemGenParm() { + $q = $this->db->query("SELECT id,value FROM gdata WHERE id IN ('sg_dir','sg_len')"); + $rs = array(); + while ($r = dbFetchArray($q)) { + $rs[$r[0] == "sg_dir" ? 0 : 1] = $r[1]; + } + return $rs; + } + + + private function getNextRectangle(&$dir, &$size) { + $zs = $this->zoneSize; + $zsT = 1 + 2*$zs; + $zsI = $zsT - 1; + + if ($dir < 2) { + //$x1 = -2 - 5*$size/2; + $x1 = -$zs - $zsT * $size / 2; + // $y1 = ($dir == 0) ? (-2 - 5*$size/2) : (3 + 5*$size/2); + $y1 = ($dir == 0) ? (-$zs - $zsT * $size / 2) : ($zs + 1 + $zsT * $size / 2); + //$x2 = $x1 + 4 + (($dir == 1) ? $size * 5 : 0); + $x2 = $x1 + $zs * 2 + (($dir == 1) ? $size * $zsT : 0); + //$y2 = $y1 + 4 + (($dir == 0) ? $size * 5 : 0); + $y2 = $y1 + $zs * 2 + (($dir == 0) ? $size * $zsT : 0); + } else { + $m = $size % 2; + //$x2 = 7 + 5*($size-$m)/2; + $x2 = $zsT + $zs + $zsT * ($size-$m) / 2; + //$y2 = ($dir == 2) ? (7 + 5*($size-$m)/2) : (-3 - 5*($size-$m)/2); + $y2 = ($dir == 2) ? ($zsT + $zs + $zsT * ($size - $m) / 2) : (- $zs - 1 - $zsT * ($size - $m) /2); + //$x1 = $x2 - 4 - (($dir == 3) ? $size * 5 : 0); + $x1 = $x2 - $zsT + 1 - (($dir == 3) ? $size * $zsT : 0); + //$y1 = $y2 - 4 - (($dir == 2) ? $size * 5 : 0); + $y1 = $y2 - $zsT + 1 - (($dir == 2) ? $size * $zsT : 0); + } + + $dir = ($dir + 1) % 4; + if ($dir % 2 == 0) { + $size ++; + } + return array($x1,$y1,$x2,$y2); + } + + + private function findBorderNebulas($x1, $y1, $x2, $y2) { + $x1--;$y1--;$x2++;$y2--; + + $q = $this->db->query( + "SELECT COUNT(*) FROM system WHERE nebula > 0 AND (" + . "((x=$x1 OR x=$x2) AND y>=$y1 AND y<=$y2) OR ((y=$y1 OR y=$y2) AND x>$x1 AND x<$y2))" + ); + list($c) = dbFetchArray($q); + return $c; + } + + + private function getAdjacentNebula($x, $y) { + list($x1,$x2,$y1,$y2) = array($x+1, $x-1, $y+1, $y-1); + $q = $this->db->query( + "SELECT SUM(nebula) FROM system " + . "WHERE (x=$x AND y=$y1) OR (x=$x AND y=$y2) OR (x=$x1 AND y=$y) OR (x=$x2 AND y=$y)" + ); + list($r) = dbFetchArray($q); + return min((int)$r, 4); + } + + + private function nebulaPass($x1, $y1) { + $ds = array(0,1,1,1,2,2,2,2,2,3); + $c = 0; + + $zsT = $this->zoneSize * 2 + 1; + for ($x = $x1; $x < $x1 + $zsT; $x++) { + for ($y = $y1; $y < $y1 + $zsT; $y++) { + $q = $this->db->query("SELECT assigned,nebula FROM system WHERE x=$x AND y=$y"); + list($as,$nb) = dbFetchArray($q); + if ($as == 'f' || $nb > 0) + continue; + + $ap = $this->getAdjacentNebula($x,$y); + if ($ap == 0) { + continue; + } + + $ap -= $ds[rand(0,9)]; + if ($ap <= 0) { + $this->db->query("UPDATE system SET assigned=FALSE,nebula=0 WHERE x=$x AND y=$y"); + } else { + $this->db->query("UPDATE system SET nebula=$ap WHERE x=$x AND y=$y"); + } + $c = 1; + } + } + + return $c; + } + + + private function makeNebulas($x1,$y1) { + do { + $c = $this->nebulaPass($x1,$y1); + } while ($c); + } + + + private function makeCluster($x1,$y1) { + $np = array(1,1,1,1,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,4); + + $zs = $this->zoneSize * 2; + $x = $x1 + $zs; $y = $y1 + $zs; + $nl = $this->findBorderNebulas($x1,$y1,$x,$y); + + $zsT = $zs + 1; + for ($x = $x1; $x < $x1 + $zsT; $x++) { + for ($y = $y1; $y < $y1 + $zsT; $y++) { + if (rand(0,99) <= $this->nebulaProb && $nl == 0) { + $v = $np[rand(0,19)]; + $nl ++; + } else { + $v = 0; + } + $this->db->query("INSERT INTO system(x,y,prot,assigned,nebula) VALUES($x,$y,0,TRUE,$v)"); + } + } + + if ($nl > 0) { + $this->makeNebulas($x1,$y1); + } + $this->db->query("UPDATE system SET assigned=FALSE WHERE x>=$x1 AND y>=$y1 AND x<$x AND y<$y AND nebula=0"); + } + + + private function makeZone($x1,$y1,$x2,$y2) { + $zsT = $this->zoneSize * 2 + 1; + for ($x=$x1;$x<$x2;$x+=$zsT) { + for ($y=$y1;$y<$y2;$y+=$zsT) { + $this->makeCluster($x,$y); + } + } + } + + + private function genPlanets($system) { + $npl = 6; + + // Generate the turrets + $ttn = 17; + $i = -1; + $tna = array(); + for ($i=0;$i<$npl;$i++) { + $tna[$i] = 3; + } + while ($ttn) { + $i = ($i + 1) % $npl; + if ($tna[$i] == 8) { + continue; + } + $a = rand(0,1); + $ttn -= $a; $tna[$i] += $a; + } + + // Generate the planets' maximum populations for each tech level + $mpa = array(); + $levels = 4; + for ($tl=0;$tl<$levels;$tl++) { + $minPPop = 8500 + $tl * 10000; + $maxPPop = 11500 + $tl * 10000; + $ttp = rand(8800,9200); + $mpa[$tl] = array(); + + for ($i=0;$i<$npl;$i++) { + $mpa[$tl][$i] = $minPPop; + } + while ($ttp) { + $i = ($i + 1) % $npl; + if ($mpa[$tl][$i] >= $maxPPop) { + continue; + } + $a = rand(1, min($maxPPop - $mpa[$tl][$i], $ttp)); + $ttp -= $a; + $mpa[$tl][$i] += $a; + } + } + + // Create the planets + for ($i=0;$i<$npl;$i++) { + $qs = "INSERT INTO planet(system,orbit,name,turrets,max_pop) VALUES($system,$i,"; + do { + $rn = strtoupper(substr(md5(uniqid(rand())), 0, 7)); + $q = $this->db->query("SELECT id FROM planet WHERE name='P-[$rn]'"); + } while(dbCount($q)); + $qs .= "'P-[$rn]',{$tna[$i]},{$mpa[0][$i]})"; + $this->db->query($qs); + } + + // Compute the new planets' happiness and store their maximal populations + $q = $this->db->query("SELECT id FROM planet WHERE system=$system"); + $np = 0; + while ($r = dbFetchArray($q)) { + for ($i=0;$i<$levels;$i++) { + $this->db->query("INSERT INTO planet_max_pop(planet,tech_level,max_pop) VALUES ({$r[0]},$i,{$mpa[$i][$np]})"); + } + $this->planets->call('updateHappiness', $r[0]); + $np ++; + } + } + + + private function genNebulaOrbit($system, $n) { + $npl = 6; + for ($i=0;$i<$npl;$i++) { + $qs = "INSERT INTO planet(system,orbit,name,status,pop,max_pop,ifact,mfact,turrets,happiness) VALUES($system,$i,"; + do { + $rn = strtoupper(substr(md5(uniqid(rand())), 0, 10)); + $q = $this->db->query("SELECT id FROM planet WHERE name='NB-[$rn]'"); + } while(dbCount($q)); + $md = min(4, max($n + rand(0,2) - 1, 1)) + 1; + $qs .= "'NB-[$rn]',$md,0,0,0,0,0,0)"; + $this->db->query($qs); + } + } + + + private function genSystem($i) { + $q = $this->db->query("SELECT nebula FROM system WHERE id=$i"); + list($n) = dbFetchArray($q); + if ($n == 0) { + $this->genPlanets($i); + } else { + $this->genNebulaOrbit($i, $n); + } + } +} + + +?> diff --git a/scripts/game/main/account/library.inc b/scripts/game/main/account/library.inc new file mode 100644 index 0000000..0ba1b44 --- /dev/null +++ b/scripts/game/main/account/library.inc @@ -0,0 +1,87 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function isOnline($uid) { + $q = $this->db->query("SELECT COUNT(*) FROM account WHERE id=$uid AND last_login IS NOT NULL AND (last_logout IS NULL OR last_logoutdb->query("SELECT last_login, last_logout FROM account WHERE id = $userID"); + list($login, $logout) = dbFetchArray($q); + + if (is_null($logout) || $logout < $login) { + return 0; + } + + return $logout; + } + + + function cancelQuitCountdown($uid) { + $this->db->query("UPDATE account SET quit_ts=NULL,reason=NULL WHERE id=$uid"); + } + + + function isAdmin($uid) { + $q = $this->db->query("SELECT admin FROM account WHERE id=$uid"); + if ($q && dbCount($q) == 1) { + list($a) = dbFetchArray($q); + } else { + $a = 'f'; + } + return ($a == 't'); + } + + + function getUser($name) { + $i = (int) $name; + if ((string)$i == (string)$name) { + $qs = "id=$name"; + } else { + $qs = "LOWER(name)=LOWER('" . addslashes($name) . "')"; + } + $q = $this->db->query("SELECT * FROM account WHERE $qs"); + if (!($q && dbCount($q) == 1)) { + return null; + } + return dbFetchHash($q); + } + + + function getKickRequest($kid) { + $q = $this->db->query("SELECT * FROM adm_kick WHERE id=$kid"); + if (!($q && dbCount($q) == 1)) { + return null; + } + return dbFetchHash($q); + } + + + function kickRequestHandled($kid, $admin, $accepted) { + $this->db->query("UPDATE adm_kick SET examined_by=$admin,status='" . ($accepted ? "Y" : "N") . "' WHERE id=$kid"); + } +} + +?> diff --git a/scripts/game/main/account/library/createAccount.inc b/scripts/game/main/account/library/createAccount.inc new file mode 100644 index 0000000..8d715c9 --- /dev/null +++ b/scripts/game/main/account/library/createAccount.inc @@ -0,0 +1,58 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($user, $password, $email, $lang, $pName) { + $conf = substr(md5(uniqid(rand())), 0, 16); + $asu = addslashes($user); + + $id = $this->db->query( + "INSERT INTO account(name,email,password,status,conf_code) " + . "VALUES('$asu','$email','".addslashes($password)."','NEW','$conf')" + ); + if (!$id) { + return false; + } + + $this->db->query( + "INSERT INTO pass_change (account, old_pass, new_pass) " + . "VALUES($id, '', '" . addslashes($password) . "')" + ); + $this->db->query("INSERT INTO credits (account) VALUES ($id)"); + + $this->db->query("INSERT INTO user_preferences VALUES('language','main',$id,'$lang');"); + $this->db->query( + "INSERT INTO account_log(tracking,account,ip_addr,action) VALUES(" + . tracking::$dbId . ",$id,'".$_SERVER['REMOTE_ADDR']."','CREATE')" + ); + + // Insert the planet in the registration queue for the default game + $game = config::getDefaultGame(); + $this->db->query( + "INSERT INTO reg_queue (account, game) " + . "VALUES ($id, '{$game->name}')" + ); + $game->getLib()->call('preRegister', $id, $pName); + + $main = $this->lib->game->getLib('main'); + $rv = $main->call('sendMail', "mail-reg.$lang.txt", $email, array( + "USER" => $user, + "PASS" => $password, + "CCODE" => $conf + )); + + // If sending the mail failed, rollback the transaction + if (!$rv) { + $this->db->end(true); $this->db->begin(); + } + + return $rv; + } +} + +?> diff --git a/scripts/game/main/account/library/getAccounts.inc b/scripts/game/main/account/library/getAccounts.inc new file mode 100644 index 0000000..bad5b01 --- /dev/null +++ b/scripts/game/main/account/library/getAccounts.inc @@ -0,0 +1,27 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run() { + $q = $this->db->query("SELECT COUNT(*) FROM account WHERE status IN ('STD','VAC')"); + if (!($q && dbCount($q))) { + return null; + } + list($total) = dbFetchArray($q); + + $q = $this->db->query("SELECT COUNT(*) FROM account WHERE status IN ('STD','VAC') AND last_login IS NOT NULL AND (last_logout IS NULL OR last_logout diff --git a/scripts/game/main/account/library/getKickList.inc b/scripts/game/main/account/library/getKickList.inc new file mode 100644 index 0000000..bcf1725 --- /dev/null +++ b/scripts/game/main/account/library/getKickList.inc @@ -0,0 +1,24 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run() { + $lists = array(array(),array(),array()); + $types = array("P" => 0, "Y" => 1, "N" => 2); + + $q = $this->db->query("SELECT * FROM adm_kick"); + while ($r = dbFetchHash($q)) { + array_push($lists[$types[$r['status']]], $r); + } + + return $lists; + } +} + +?> diff --git a/scripts/game/main/account/library/getLanguage.inc b/scripts/game/main/account/library/getLanguage.inc new file mode 100644 index 0000000..1a20164 --- /dev/null +++ b/scripts/game/main/account/library/getLanguage.inc @@ -0,0 +1,22 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($id) { + $ql = $this->db->query("SELECT value FROM user_preferences WHERE id='language' AND version='main' AND account=$id"); + if ($ql && dbCount($ql)) { + list($l) = dbFetchArray($ql); + } else { + $l = 'en'; + } + return $l; + } +} + +?> diff --git a/scripts/game/main/account/library/getQuitCountdown.inc b/scripts/game/main/account/library/getQuitCountdown.inc new file mode 100644 index 0000000..a533670 --- /dev/null +++ b/scripts/game/main/account/library/getQuitCountdown.inc @@ -0,0 +1,22 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($uid) { + $q = $this->db->query("SELECT quit_ts FROM account WHERE id=$uid"); + if ($q && dbCount($q) == 1) { + list($quit) = dbFetchArray($q); + } else { + $quit = null; + } + return $quit; + } +} + +?> diff --git a/scripts/game/main/account/library/getUserName.inc b/scripts/game/main/account/library/getUserName.inc new file mode 100644 index 0000000..61a8bf6 --- /dev/null +++ b/scripts/game/main/account/library/getUserName.inc @@ -0,0 +1,25 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($uid) { + if (!is_null($this->userNames[$uid])) { + return $this->userNames[$uid]; + } + $q = $this->db->query("SELECT name FROM account WHERE id=$uid"); + if (!($q && dbCount($q))) { + return null; + } + list($this->userNames[$uid]) = dbFetchArray($q); + return $this->userNames[$uid]; + } +} + +?> diff --git a/scripts/game/main/account/library/isLeech.inc b/scripts/game/main/account/library/isLeech.inc new file mode 100644 index 0000000..bb38b3c --- /dev/null +++ b/scripts/game/main/account/library/isLeech.inc @@ -0,0 +1,32 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($id) { + $q = $this->db->query("SELECT UNIX_TIMESTAMP(NOW())-donated FROM pp_history ORDER BY donated ASC LIMIT 1"); + if (!($q && dbCount($q))) { + return true; + } + list($interval) = dbFetchArray($q); + if ($interval == 0) { + return true; + } + $days = $interval / 86400; + + $q = $this->db->query("SELECT SUM(amount) FROM pp_history WHERE account=$id"); + if (!($q && dbCount($q))) { + return false; + } + list($sum) = dbFetchArray($q); + $sum = is_null($sum) ? 0 : $sum; + + return ($sum / $days <= 0.05); + } +} + +?> diff --git a/scripts/game/main/account/library/log.inc b/scripts/game/main/account/library/log.inc new file mode 100644 index 0000000..1baefe0 --- /dev/null +++ b/scripts/game/main/account/library/log.inc @@ -0,0 +1,63 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($uid, $what) { + if (class_exists('tracking') && !is_null(tracking::$dbId)) { + $track = tracking::$dbId; + } else { + $track = 'NULL'; + } + + if (gettype($_SERVER['REMOTE_ADDR']) == 'NULL') { + $addr = 'AUTO'; + } else { + $addr = addslashes($_SERVER['REMOTE_ADDR']); + } + + switch ($what) : + case 'I': case 'i': + $w = 'IN'; break; + case 'O': case 'o': + $w = 'OUT'; break; + case 'C': case 'c': + $w = 'CREATE'; break; + case 'V': case 'v': + $w = 'CONF'; break; + case 'Q': case 'q': + $w = 'QUIT'; break; + case 'Q': case 'q': + $w = 'QUIT'; break; + case 'VS': case 'vs': + $w = 'VSTART'; break; + case 'VE': case 've': + $w = 'VEND'; break; + default: + return; + endswitch; + + $this->db->query("INSERT INTO account_log(tracking,account,ip_addr,action) VALUES ($track,$uid,'$addr','$w')"); + } +} + +?> diff --git a/scripts/game/main/account/library/requestKick.inc b/scripts/game/main/account/library/requestKick.inc new file mode 100644 index 0000000..729cf63 --- /dev/null +++ b/scripts/game/main/account/library/requestKick.inc @@ -0,0 +1,24 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($admin, $account, $reason) { + $q = $this->db->query("SELECT * FROM adm_kick WHERE to_kick=$account AND status<>'N'"); + if (!$q || dbCount($q)) { + return false; + } + + $id = $this->db->query("INSERT INTO adm_kick (to_kick,requested_by,requested_at,reason) " + . "VALUES ($account,$admin," . time() . ",'" . addslashes($reason) . "')"); + + return !!$id; + } +} + +?> diff --git a/scripts/game/main/account/library/setQuitCountdown.inc b/scripts/game/main/account/library/setQuitCountdown.inc new file mode 100644 index 0000000..85eadff --- /dev/null +++ b/scripts/game/main/account/library/setQuitCountdown.inc @@ -0,0 +1,23 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($uid, $reason) { + $reason = trim(preg_replace('/\s+/', ' ', $reason)); + if ($reason == '') { + $rqs = ""; + } else { + $rqs = ",reason='" . addslashes($reason) . "'"; + } + $this->db->query("UPDATE account SET quit_ts=UNIX_TIMESTAMP(NOW())$rqs WHERE id=$uid AND status='STD'"); + } + + +} + +?> diff --git a/scripts/game/main/account/library/terminate.inc b/scripts/game/main/account/library/terminate.inc new file mode 100644 index 0000000..68425f0 --- /dev/null +++ b/scripts/game/main/account/library/terminate.inc @@ -0,0 +1,32 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($uid, $status, $reason = null) { + foreach (config::getGames() as $game) { + if ($game->name == 'main' || $game->status() == 'FINISHED') { + continue; + } + + $lib = $game->getLib(); + $pid = $lib->call('doesUserPlay', $uid); + if (is_null($pid)) { + continue; + } + + $lib->call('leaveGame', $pid, $status); + } + + $qs = is_null($reason) ? "" : (",reason='" . addslashes($reason) . "'"); + $this->db->query("UPDATE account SET status='$status',quit_ts=NULL,vac_start=NULL$qs WHERE id=$uid"); + $this->lib->call('log', $uid, 'q'); + } +} + +?> diff --git a/scripts/game/main/actions.inc b/scripts/game/main/actions.inc new file mode 100644 index 0000000..2c1d783 --- /dev/null +++ b/scripts/game/main/actions.inc @@ -0,0 +1,238 @@ +game = $game; + $this->main = $this->game->getLib('main'); + $this->accounts = $this->game->getLib('main/account'); + $this->vacation = $this->game->getLib('main/vacation'); + $this->forums = $this->game->getLib('main/forums'); + } + + function getUserName($uid) { + return $this->accounts->call('getUserName', $uid); + } + + function createAccount($u, $p, $e, $l) { + return $this->accounts->call('createAccount', $u, $p, $e, $l); + } + + function isGameRunning($version) { + return $this->main->call('isGameRunning', $version); + } + + function getTick($version, $name, $lang = null) { + $g = config::getGame($version); + $lib = $g->getLib('main'); + return $lib->call('getTick', $name, $lang); + } + + function getTicks($version, $lang) { + $g = config::getGame($version); + $lib = $g->getLib('main'); + return $lib->call('getTicks', $lang); + } + + function getRankingType($version, $identifier) { + $g = config::getGame($version); + $rnk = $g->getLib('main/rankings'); + return $rnk->call('getType', $identifier); + } + + function getRankingTypes($version) { + $g = config::getGame($version); + $rnk = $g->getLib('main/rankings'); + return $rnk->call('getTypes'); + } + + function getRankingText($id, $lang) { + $rnk = $this->game->getLib('main/rankings'); + return $rnk->call('getText', $id, $lang); + } + + function updateRankings($id, $data) { + $rnk = $this->game->getLib('main/rankings'); + $rnk->call('update', $id, $data); + } + + function getRankings($type, $top = null) { + $rnk = $this->game->getLib('main/rankings'); + return $rnk->call('getAll', $type, $top); + } + + function getRanking($type, $id) { + $rnk = $this->game->getLib('main/rankings'); + return $rnk->call('get', $type, $id); + } + + function appendRanking($type,$id) { + $rnk = $this->game->getLib('main/rankings'); + return $rnk->call('append', $type, $id); + } + + function deleteRanking($type,$id) { + $rnk = $this->game->getLib('main/rankings'); + return $rnk->call('delete', $type, $id); + } + + function getForumCategories() { + return $this->forums->call('getCategories'); + } + + function getVersionCategory($ver) { + return $this->forums->call('getVersionCategory', $ver); + } + + function getForumCategory($c) { + return $this->forums->call('getCategory', $c); + } + + function getForums($c) { + return $this->forums->call('getForums', $c); + } + + function getForum($f) { + return $this->forums->call('get', $f); + } + + function getTopics($f, $first, $count) { + return $this->forums->call('getTopics', $f, $first, $count); + } + + function newTopic($a, $fid, $sub, $txt, $ec, $es, $st) { + return $this->forums->call('newTopic', $a, $fid, $sub, $txt, $ec, $es, $st); + } + + function postReply($a, $post, $sub, $txt, $ec, $es) { + return $this->forums->call('reply', $a, $post, $sub, $txt, $ec, $es); + } + + function postEdit($a, $pid, $sub, $txt, $ec, $es) { + return $this->forums->call('edit', $a, $pid, $sub, $txt, $ec, $es); + } + + function forumSubstitute($text, $ec, $es) { + return $this->forums->call('substitute', $text, $ec, $es); + } + + function forumSignature($u) { + return $this->forums->call('signature', $u); + } + + function getTopic($tid) { + return $this->forums->call('getTopic', $tid); + } + + function getPosts($tid, $thr, $o, $cnt, $fst) { + return $this->forums->call('getPosts', $tid, $thr, $o, $cnt, $fst); + } + + function getPost($pid) { + return $this->forums->call('getPost', $pid); + } + + function isTopicRead($topic, $player) { + return $this->forums->call('isRead', $topic, $player); + } + + function markAsRead($topic, $player) { + return $this->forums->call('markRead', $topic, $player); + } + + function markAsUnread($topic, $player) { + return $this->forums->call('markUnread', $topic, $player); + } + + function getReadTopics($fid, $uid) { + return $this->forums->call('getRead', $fid, $uid); + } + + function updateForumLast($forum) { + return $this->forums->call('updateLast', $forum); + } + + function deleteTopic($forum, $topic) { + return $this->forums->call('deleteTopic', $forum, $topic); + } + + function switchTopicSticky($forum, $topic) { + return $this->forums->call('switchSticky', $forum, $topic); + } + + function moveTopic($forum, $topic, $dest, $user) { + return $this->forums->call('move', $forum, $topic, $dest, $user); + } + + function deleteSinglePost($postId) { + return $this->forums->call('deletePost', $postId); + } + + function markForumAsRead($fid, $uid) { + return $this->forums->call('markForumRead', $fid, $uid); + } + + function getAdministrator($uid) { + return $this->forums->call('getAdministrator', $uid); + } + + function getModerator($uid) { + return $this->forums->call('getModerator', $uid); + } + + function getAccounts() { + return $this->accounts->call('getAccounts'); + } + + function isOnline($uid) { + return $this->accounts->call('isOnline', $uid); + } + + function setQuitCountdown($uid, $reason) { + $this->accounts->call('setQuitCountdown', $uid, $reason); + } + + function cancelQuitCountdown($uid) { + $this->accounts->call('cancelQuitCountdown', $uid); + } + + + //-------------------------------------------------------------------------------------------------------------------------------- + // VACATION MODE MANAGEMENT + //-------------------------------------------------------------------------------------------------------------------------------- + + function isOnVacation($uid) { + return $this->vacation->call('isOnVacation', $uid); + } + + function canSetVacation($uid) { + return $this->vacation->call('canSet', $uid); + } + + function setVacationStart($uid) { + return $this->vacation->call('setStart', $uid); + } + + function resetVacationStart($uid) { + return $this->vacation->call('resetStart', $uid); + } + + function startVacation($uid) { + return $this->vacation->call('start', $uid); + } + + function leaveVacation($uid) { + return $this->vacation->call('leave', $uid); + } +} + +?> diff --git a/scripts/game/main/actions/joinGame.inc b/scripts/game/main/actions/joinGame.inc new file mode 100644 index 0000000..097a09b --- /dev/null +++ b/scripts/game/main/actions/joinGame.inc @@ -0,0 +1,150 @@ +main = $main; + $this->lib = $this->main->getLib(); + } + + function run($user, $gId, $dryRun = true, $planet = null, $player = null) { + // Get the game and its main library + $game = config::getGame($gId); + if (is_null($game)) { + return array('error' => 0); + } + $lib = $game->getLib(); + + // Check the game's status + $status = $game->status(); + if ($status != 'RUNNING' && $status != 'READY' && $status != 'ENDING') { + return array('error' => 0); + } + + // Check if the game is available and can be joined + if (! $lib->call('canJoin')) { + return array('error' => 0); + } + + // Does the user play the game already? + if ((int)$user < 1 || $lib->call('doesUserPlay', $user)) { + return array('error' => 1); + } + + // Has the current user played that game in the past? + $returning = $lib->call("hasPlayed", $user); + + // If the user submitted the form, handle it + if ($dryRun) { + // Display a blank form + return array( + "game" => $gId, + "gName" => $game->text, + "desc" => $game->descriptions[getLanguage()], + "planet" => "", + "planetError" => 0, + "player" => "", + "playerError" => 0, + "returning" => $returning + ); + } + + // Check the specified planet and player names + $pErr = $lib->call('checkPlanetName', $planet); + if ($returning) { + $pnErr = $this->checkName($player); + } else { + $pnErr = 0; + } + + if (!($pErr || $pnErr)) { + // Try to register to this game + $res = $lib->call('register', $user, $planet, $returning ? $player : null); + switch ($res) : + case 1: return array('error' => 2); + case 2: $pnErr = 6; break; + case 3: $pErr = 6; break; + endswitch; + } + + if ($pErr || $pnErr) { + return array( + "game" => $gId, + "gName" => $game->text, + "planet" => $planet, + "planetError" => $pErr, + "player" => $player, + "playerError" => $pnErr, + "returning" => $returning + ); + } + + return array("registered" => $gId); + } + + + function checkName($n) { + if (strlen($n) > 15) { + return array('error' => 1); + } elseif (preg_match('/[^A-Za-z0-9_\.\-\+@\/'."'".' ]/', $n)) { + return array('error' => 2); + } elseif (preg_match('/^\s/', $n) || preg_match('/\s$/', $n)) { + return array('error' => 3); + } elseif (preg_match('/\s\s+/', $n)) { + return array('error' => 4); + } elseif (strlen($n) < 2) { + return array('error' => 5); + } + return 0; + } +} + + +?> diff --git a/scripts/game/main/actions/lostPassword.inc b/scripts/game/main/actions/lostPassword.inc new file mode 100644 index 0000000..c7d8070 --- /dev/null +++ b/scripts/game/main/actions/lostPassword.inc @@ -0,0 +1,139 @@ +main = $main; + $this->db = $this->main->db; + $this->lib = $this->main->getLib(); + $this->accounts = $this->main->getLib("main/account"); + } + + function run($userName, $mailAddress, $confirmationCode) { + if (is_null($userName)) { + return false; + } + + $r = $this->checkAccount($userName, $mailAddress, $confirmationCode); + if (is_null($r)) { + return array( + "error" => is_null($confirmationCode) ? 1 : 3, + "name" => $userName, + "mail" => $mailAddress, + "code" => $confirmationCode + ); + } + + list($accountId, $userName, $realCode) = $r; + + // No confirmation code + if (is_null($confirmationCode)) { + return $this->checkNewForm($accountId, $userName, $mailAddress, $realCode); + } + + return $this->generatePassword($accountId, $userName, $mailAddress); + } + + + function checkAccount($userName, $mailAddress, $confirmationCode) { + if (!($this->lib->call('isValidName', $userName) && $this->lib->call('isValidAddress', $mailAddress))) { + return null; + } + if (!(is_null($confirmation) || preg_match('/^[A-Fa-f0-9]{32,32}$/', $confirmationCode))) { + return null; + } + + $qs = "SELECT id,name,pw_conf FROM account WHERE status NOT IN ('NEW','KICKED') " + . "AND name='" . addslashes($userName) . "' AND email='" . addslashes($mailAddress) . "'"; + if (!is_null($confirmationCode)) { + $qs .= " AND pw_conf='$confirmationCode'"; + } + $q = $this->db->query($qs); + if (!($q && dbCount($q) == 1)) { + logText("main::actions::lostPassword() failed to authenticate account '$userName'", LOG_WARNING); + return null; + } + + return dbFetchArray($q); + } + + + function checkNewForm($id, $name, $mail, $code) { + $rc = 4; + if (is_null($code)) { + // No code created yet; generate one and send the mail + $code = $this->createConfirmationCode($id, $name, $mail); + if (is_null($code)) { + $rc = 2; + } + } + return array( + "error" => $rc, + "name" => $name, + "mail" => $mail, + "code" => null + ); + } + + + function createConfirmationCode($id, $name, $mail) { + $conf = substr(md5(uniqid(rand())), 0, 16); + $this->db->query("UPDATE account SET pw_conf='$conf' WHERE id='$id'"); + + $lang = $this->accounts->call('getLanguage', $id); + $rv = $this->lib->call('sendMail', "mail-change-pass-conf.$lang.txt", $mail, array( + "USER" => $name, + "CODE" => $conf + )); + + if (!$rv) { + $this->db->end(true); $this->db->start(); + return null; + } + + return $conf; + } + + + function generatePassword($id, $name, $mail) { + $newPass = ""; + $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-+/*_@=#~&!"; + for ($i=0;$i<16;$i++) { + do { + $nc = $chars{rand(0,strlen($chars)-1)}; + } while (strstr($newPass, $nc) !== false); + $newPass .= $nc; + } + + $this->db->query("UPDATE account SET pw_conf=NULL,password='$newPass' WHERE id=$id"); + + $lang = $this->accounts->call('getLanguage', $id); + $rv = $this->lib->call('sendMail', "mail-change-pass.$lang.txt", $mail, array( + "USER" => $name, + "PASS" => $newPass + )); + + if (!$rv) { + $this->db->end(true); $this->db->start(); + return array( + "error" => 5, + "name" => $name, + "mail" => $mail, + "code" => null + ); + } + + return true; + } +} + + +?> diff --git a/scripts/game/main/forums/library.inc b/scripts/game/main/forums/library.inc new file mode 100644 index 0000000..308b6da --- /dev/null +++ b/scripts/game/main/forums/library.inc @@ -0,0 +1,73 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function getVersionCategory($ver) { + $q = $this->db->query("SELECT id,description FROM f_category WHERE title='!$ver!'"); + return dbFetchHash($q); + } + + function isRead($topic, $player) { + $q = $this->db->query("SELECT * FROM f_read WHERE topic=$topic AND reader=$player"); + return $q && dbCount($q); + } + + function markRead($topic, $player) { + if ($this->isRead($topic,$player)) { + return false; + } + $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; + } + + function markUnread($topic, $player) { + $this->db->query("DELETE FROM f_read WHERE topic=$topic AND reader<>$player"); + } + + // 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; + } + + function switchSticky($forum, $topic) { + $this->db->query("UPDATE f_topic SET sticky=NOT sticky WHERE id=$topic AND forum=$forum AND deleted IS NULL"); + } + + 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); + } + } +} + +?> diff --git a/scripts/game/main/forums/library/deletePost.inc b/scripts/game/main/forums/library/deletePost.inc new file mode 100644 index 0000000..55b5cad --- /dev/null +++ b/scripts/game/main/forums/library/deletePost.inc @@ -0,0 +1,27 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($postId) { + $q = $this->db->query("SELECT forum,topic,reply_to FROM f_post WHERE id=$postId AND deleted IS NULL"); + if (!($q && dbCount($q))) { + return; + } + list($fid,$tid,$rtid) = dbFetchArray($q); + $this->db->query("UPDATE f_post SET reply_to=$rtid WHERE reply_to=$postId"); + $this->db->query("UPDATE f_forum SET posts=posts-1 WHERE id=$fid"); + $this->db->query("UPDATE f_post SET deleted=" . time() . " WHERE id=$postId"); + $q = $this->db->query("SELECT id FROM f_post WHERE topic=$tid AND deleted IS NULL ORDER BY moment DESC LIMIT 1"); + list($lastid) = dbFetchArray($q); + $this->db->query("UPDATE f_topic SET last_post=$lastid WHERE id=$tid"); + $this->lib->call('updateLast', $fid); + } +} + +?> diff --git a/scripts/game/main/forums/library/deleteTopic.inc b/scripts/game/main/forums/library/deleteTopic.inc new file mode 100644 index 0000000..84f66b0 --- /dev/null +++ b/scripts/game/main/forums/library/deleteTopic.inc @@ -0,0 +1,26 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($forum, $topic) { + $q = $this->db->query("SELECT COUNT(*) FROM f_post WHERE forum=$forum AND topic=$topic AND deleted IS NULL"); + if (!($q && dbCount($q))) { + return; + } + list($np) = dbFetchArray($q); + $tm = time(); + $this->db->query("UPDATE f_post SET deleted=$tm WHERE topic=$topic AND forum=$forum"); + $this->db->query("UPDATE f_topic SET deleted=$tm WHERE id=$topic"); + $this->db->query("UPDATE f_forum SET posts=posts-$np,topics=topics-1 WHERE id=$forum"); + $this->db->query("DELETE FROM f_read WHERE topic=$topic"); + $this->lib->call('updateLast', $forum); + } +} + +?> diff --git a/scripts/game/main/forums/library/edit.inc b/scripts/game/main/forums/library/edit.inc new file mode 100644 index 0000000..32cd998 --- /dev/null +++ b/scripts/game/main/forums/library/edit.inc @@ -0,0 +1,22 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($a, $pid, $sub, $txt, $ec, $es) { + $q = $this->db->query("SELECT topic FROM f_post WHERE id=$pid AND deleted IS NULL"); + list($tid) = dbFetchArray($q); + $this->lib->call('markUnread', $tid,$a); + $tm = time(); + $qs = "UPDATE f_post SET edited=$tm,edited_by=$a,title='".addslashes($sub)."',contents='" + .addslashes($txt)."',enable_code=".dbBool($ec).",enable_smileys=" + . dbBool($es) . " WHERE id=$pid AND deleted IS NULL"; + return !is_null($this->db->query($qs)); + } +} + +?> diff --git a/scripts/game/main/forums/library/get.inc b/scripts/game/main/forums/library/get.inc new file mode 100644 index 0000000..90d6681 --- /dev/null +++ b/scripts/game/main/forums/library/get.inc @@ -0,0 +1,28 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($f) { + $q = $this->db->query( + "SELECT f.id AS id,f.title AS title,f.description AS description," + . "f.user_post AS user_post,f.topics AS ntopics," + . "c.id AS pid,c.title AS ptitle " + . "FROM f_forum f,f_category c " + . "WHERE f.id=$f AND c.id=f.category" + ); + if (!$q||dbCount($q)!=1) { + return null; + } + $a = dbFetchHash($q); + $f['user_post'] = ($f['user_post'] == 't'); + return $a; + } +} + +?> diff --git a/scripts/game/main/forums/library/getAdministrator.inc b/scripts/game/main/forums/library/getAdministrator.inc new file mode 100644 index 0000000..7821876 --- /dev/null +++ b/scripts/game/main/forums/library/getAdministrator.inc @@ -0,0 +1,26 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($uid) { + $q = $this->db->query("SELECT category FROM f_admin WHERE \"user\"=$uid"); + $a = array(); + while ($r = dbFetchArray($q)) { + if (is_null($r[0])) { + $q = $this->db->query("SELECT id FROM f_category"); + $a = array(); + } else { + array_push($a, $r[0]); + } + } + return $a; + } +} + +?> diff --git a/scripts/game/main/forums/library/getCategories.inc b/scripts/game/main/forums/library/getCategories.inc new file mode 100644 index 0000000..127ce6f --- /dev/null +++ b/scripts/game/main/forums/library/getCategories.inc @@ -0,0 +1,23 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run() { + $q = $this->db->query("SELECT id,title,description FROM f_category WHERE title NOT ILIKE '!%!' ORDER BY corder ASC"); + $a = array(); + if ($q) { + while ($rs = dbFetchHash($q)) { + array_push($a, $rs); + } + } + return $a; + } +} + +?> diff --git a/scripts/game/main/forums/library/getCategory.inc b/scripts/game/main/forums/library/getCategory.inc new file mode 100644 index 0000000..b7962ac --- /dev/null +++ b/scripts/game/main/forums/library/getCategory.inc @@ -0,0 +1,20 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($c) { + $q = $this->db->query("SELECT id,title,description FROM f_category WHERE id=$c"); + if (!$q||dbCount($q)!=1) { + return null; + } + return dbFetchHash($q); + } +} + +?> diff --git a/scripts/game/main/forums/library/getForums.inc b/scripts/game/main/forums/library/getForums.inc new file mode 100644 index 0000000..0ab602f --- /dev/null +++ b/scripts/game/main/forums/library/getForums.inc @@ -0,0 +1,36 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($c) { + $q = $this->db->query("SELECT * FROM f_forum WHERE category=$c ORDER BY forder ASC"); + $a = array(); + if (!$q) { + return $a; + } + while ($rs = dbFetchHash($q)) { + if ($rs['last_post'] != "") { + $q2 = $this->db->query( + "SELECT u.name AS author,p.moment AS moment " + . "FROM f_post p,account u " + . "WHERE p.id=".$rs['last_post']." AND u.id=p.author" + ); + $rs['last'] = dbFetchHash($q2); + } else { + $rs['last'] = null; + } + $rs['user_post'] = ($rs['user_post'] == 't'); + $rs['admin_only'] = ($rs['admin_only'] == 't'); + array_push($a, $rs); + } + return $a; + } +} + +?> diff --git a/scripts/game/main/forums/library/getModerator.inc b/scripts/game/main/forums/library/getModerator.inc new file mode 100644 index 0000000..2ed1b13 --- /dev/null +++ b/scripts/game/main/forums/library/getModerator.inc @@ -0,0 +1,21 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($uid) { + $q = $this->db->query("SELECT forum FROM f_moderator WHERE \"user\"=$uid"); + $a = array(); + while ($r = dbFetchArray($q)) { + array_push($a, $r[0]); + } + return $a; + } +} + +?> diff --git a/scripts/game/main/forums/library/getPost.inc b/scripts/game/main/forums/library/getPost.inc new file mode 100644 index 0000000..4986f58 --- /dev/null +++ b/scripts/game/main/forums/library/getPost.inc @@ -0,0 +1,49 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->accounts = $this->lib->game->getLib("main/account"); + } + + + function run($pid) { + // Get post data + $q = $this->db->query( + "SELECT p.id AS id,p.title AS title," + . "t.id AS tid,p2.title AS tname," + . "f.id AS fid,f.title AS fname," + . "c.id AS cid,c.title AS cname," + . "p.author AS uid,u.name AS author,p.reply_to AS reply_to," + . "p.moment AS moment,p.title AS title," + . "p.contents AS contents,p.enable_code AS ec," + . "p.enable_smileys AS es,p.edited AS edited," + . "p.edited_by AS edited_by " + . "FROM f_topic t,f_post p,f_post p2,f_forum f,f_category c,account u " + . "WHERE p.id=$pid AND t.id=p.topic AND p2.id=t.first_post " + . "AND f.id=p.forum AND c.id=f.category AND u.id=p.author " + . "AND p.deleted IS NULL" + ); + if (!$q || dbCount($q) != 1) { + return null; + } + $rv = dbFetchHash($q); + $rv['html'] = $this->lib->call('substitute', + $rv['contents'], $rv['ec'], $rv['es'] + ); + $rv['html'] .= $this->lib->call('signature', $rv['uid']); + + if (!is_null($rv['edited_by'])) { + $rv['edited_by'] = $this->accounts->call('getUserName', $rv['edited_by']); + } + if (preg_match('/^!.*!$/', $rv['cname'])) { + $game = config::getGame(preg_replace('/!/', '', $rv['cname'])); + $rv['cname'] = $all[$game->game['site_path']][1]; + } + return $rv; + } +} + +?> diff --git a/scripts/game/main/forums/library/getPosts.inc b/scripts/game/main/forums/library/getPosts.inc new file mode 100644 index 0000000..11ebd19 --- /dev/null +++ b/scripts/game/main/forums/library/getPosts.inc @@ -0,0 +1,117 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->accounts = $this->lib->game->getLib("main/account"); + } + + + function run($tid, $thr, $o, $cnt, $fst) { + $os = $o?"DESC":"ASC"; + $posts = array(); + + if ($thr) { + // Read list of IDs + $q = $this->db->query( + "SELECT id,reply_to FROM f_post WHERE topic=$tid AND deleted IS NULL ORDER BY moment $os" + ); + $ids = array(); + while ($qr = dbFetchArray($q)) { + array_push($ids, $qr); + } + + // Get first post + if ($o) { + $mp = array_pop($ids); + } else { + $mp = array_shift($ids); + } + + // Initialize IDs and depths + $sids = array($mp[0]); + $dpth = array(0); + + // Create lists + $ist = array($mp[0]); + $cd = 0; + while (count($ids)) { + $od = $cd; + for ($i=0;$idb->query( + "SELECT p.id AS id,p.author AS uid,u.name AS author," + . "p.moment AS moment,p.title AS title," + . "p.contents AS contents,p.enable_code AS ec," + . "p.enable_smileys AS es,p.edited AS edited," + . "p.edited_by " + . "FROM f_post p,account u " + . "WHERE p.id IN (".join(',',$rsids).") AND u.id=p.author " + . "AND p.deleted IS NULL" + ); + while ($qr = dbFetchHash($q)) { + $qr['mine'] = ($qr['uid'] == $_SESSION['userid']); + $qr['contents'] = $this->lib->call('substitute', + $qr['contents'], $qr['ec'], $qr['es'] + ); + $qr['contents'] .= $this->lib->call('signature', $qr['uid']); + $i = array_search($qr['id'], $rsids); + $qr['depth'] = $dpth[$i+$fst]; + if ($qr['depth'] > 19) { + $qr['depth'] = 19; + } + if (!is_null($qr['edited_by'])) { + $qr['edited_by'] = $this->accounts->call('getUserName', $qr['edited_by']); + } + $posts[$i] = $qr; + $i++; + } + } else { + $q = $this->db->query( + "SELECT p.id AS id,p.author AS uid,u.name AS author," + . "p.moment AS moment,p.title AS title," + . "p.contents AS contents,p.enable_code AS ec," + . "p.enable_smileys AS es,p.edited AS edited," + . "p.edited_by AS edited_by " + . "FROM f_post p,account u " + . "WHERE p.topic=$tid AND u.id=p.author " + . "AND p.deleted IS NULL " + . "ORDER BY p.moment $os " + . "LIMIT $cnt OFFSET $fst" + ); + while ($qr = dbFetchHash($q)) { + $qr['mine'] = ($qr['uid'] == $_SESSION['userid']); + $qr['contents'] = $this->lib->call('substitute', + $qr['contents'], $qr['ec'], $qr['es'] + ); + $qr['contents'] .= $this->lib->call('signature', $qr['uid']); + $qr['depth'] = 0; + if (!is_null($qr['edited_by'])) { + $qr['edited_by'] = $this->accounts->call('getUserName', $qr['edited_by']); + } + array_push($posts, $qr); + } + } + + return $posts; + } +} + +?> diff --git a/scripts/game/main/forums/library/getTopic.inc b/scripts/game/main/forums/library/getTopic.inc new file mode 100644 index 0000000..bcd0748 --- /dev/null +++ b/scripts/game/main/forums/library/getTopic.inc @@ -0,0 +1,36 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($tid) { + // Get main topic data + $q = $this->db->query( + "SELECT t.id AS id,p.title AS title," + . "p.id AS fpid,t.last_post AS lpid," + . "f.id AS fid,f.title AS fname," + . "c.id AS cid,c.title AS cname " + . "FROM f_topic t,f_post p,f_forum f,f_category c " + . "WHERE t.id=$tid AND p.id=t.first_post " + . "AND f.id=t.forum AND c.id=f.category " + . "AND t.deleted IS NULL" + ); + if (!$q || dbCount($q) != 1) { + return null; + } + $rv = dbFetchHash($q); + + // Get post count + $q = $this->db->query("SELECT COUNT(*) FROM f_post WHERE topic=$tid AND deleted IS NULL"); + list($rv["nitems"]) = dbFetchArray($q); + + return $rv; + } +} + +?> diff --git a/scripts/game/main/forums/library/getTopics.inc b/scripts/game/main/forums/library/getTopics.inc new file mode 100644 index 0000000..91cb059 --- /dev/null +++ b/scripts/game/main/forums/library/getTopics.inc @@ -0,0 +1,33 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($f, $first, $count) { + $q = $this->db->query( + "SELECT t.id AS id,p.title AS title,p.moment AS moment," + . "u.name AS author,p2.moment AS last_moment," + . "u2.name AS last_author,t.sticky AS sticky " + . "FROM f_topic t,f_post p,account u,f_post p2,account u2 " + . "WHERE t.forum=$f AND p.id=t.first_post AND u.id=p.author " + . "AND p2.id=t.last_post AND u2.id=p2.author " + . "AND t.deleted IS NULL " + . "ORDER BY sticky DESC,last_moment DESC LIMIT $count OFFSET $first" + ); + $a = array(); + while ($q && $rs = dbFetchHash($q)) { + $q2 = $this->db->query("SELECT COUNT(*) - 1 FROM f_post WHERE topic={$rs["id"]} AND deleted IS NULL"); + list($rs['posts']) = dbFetchArray($q2); + $rs['sticky'] = ($rs['sticky'] == 't'); + array_push($a, $rs); + } + return $a; + } +} + +?> diff --git a/scripts/game/main/forums/library/move.inc b/scripts/game/main/forums/library/move.inc new file mode 100644 index 0000000..5862686 --- /dev/null +++ b/scripts/game/main/forums/library/move.inc @@ -0,0 +1,29 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($forum, $topic, $dest, $user) { + $this->db->query("SELECT * FROM f_forum WHERE id IN ($forum,$dest) FOR UPDATE"); + + $q = $this->db->query("SELECT COUNT(*) FROM f_post WHERE forum=$forum AND topic=$topic AND deleted IS NULL"); + if (!($q && dbCount($q))) { + return; + } + list($np) = dbFetchArray($q); + $this->db->query("UPDATE f_post SET forum=$dest WHERE topic=$topic AND forum=$forum"); + $this->db->query("UPDATE f_topic SET forum=$dest WHERE id=$topic AND forum=$forum"); + $this->db->query("UPDATE f_forum SET posts=posts-$np,topics=topics-1 WHERE id=$forum"); + $this->db->query("UPDATE f_forum SET posts=posts+$np,topics=topics+1 WHERE id=$dest"); + $this->lib->call('markUnread', $topic, $user); + $this->lib->call('updateLast', $forum); + $this->lib->call('updateLast', $dest); + } +} + +?> diff --git a/scripts/game/main/forums/library/newTopic.inc b/scripts/game/main/forums/library/newTopic.inc new file mode 100644 index 0000000..9c73dec --- /dev/null +++ b/scripts/game/main/forums/library/newTopic.inc @@ -0,0 +1,41 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($a, $fid, $sub, $txt, $ec, $es, $st) { + $tm = time(); + $qs = "INSERT INTO f_post(forum,author,moment,title,contents,enable_code,enable_smileys) VALUES (" + . "$fid,$a,$tm,'".addslashes($sub)."','".addslashes($txt)."'," + . dbBool($ec) . "," . dbBool($es) . ")"; + if (!$this->db->query($qs)) { + return false; + } + + $q = $this->db->query("SELECT id FROM f_post WHERE forum=$fid AND topic IS NULL AND author=$a AND moment=$tm ORDER BY id DESC LIMIT 1"); + if (!$q || dbCount($q) != 1) { + return false; + } + list($pid) = dbFetchArray($q); + + $this->db->query("INSERT INTO f_topic(forum,first_post,last_post,sticky) VALUES($fid,$pid,$pid," + . dbBool($st) . ")"); + $q = $this->db->query("SELECT id FROM f_topic WHERE forum=$fid AND first_post=$pid"); + if (!$q || dbCount($q) != 1) { + return false; + } + list($tid) = dbFetchArray($q); + + $this->db->query("UPDATE f_post SET topic=$tid WHERE id=$pid"); + $this->db->query("UPDATE f_forum SET topics=topics+1,posts=posts+1,last_post=$pid WHERE id=$fid"); + $this->lib->call('markRead', $tid, $a); + return $pid; + } +} + +?> diff --git a/scripts/game/main/forums/library/reply.inc b/scripts/game/main/forums/library/reply.inc new file mode 100644 index 0000000..2fbbb2f --- /dev/null +++ b/scripts/game/main/forums/library/reply.inc @@ -0,0 +1,33 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($a, $post, $sub, $txt, $ec, $es) { + $tm = time(); + $fid = $post['fid']; $tid = $post['tid']; $pid = $post['id']; + $qs = "INSERT INTO f_post(forum,topic,reply_to,author,moment,title,contents,enable_code,enable_smileys) VALUES (" + . "$fid,$tid,$pid,$a,$tm,'".addslashes($sub)."','".addslashes($txt)."'," + . dbBool($ec) . "," . dbBool($es) . ")"; + if (!$this->db->query($qs)) { + return false; + } + + $q = $this->db->query("SELECT id FROM f_post WHERE topic=$tid AND reply_to=$pid AND author=$a AND moment=$tm ORDER BY id DESC LIMIT 1"); + if (!$q || dbCount($q) != 1) { + return false; + } + list($pid) = dbFetchArray($q); + + $this->db->query("UPDATE f_topic SET last_post=$pid WHERE id=$tid"); + $this->db->query("UPDATE f_forum SET posts=posts+1,last_post=$pid WHERE id=$fid"); + $this->lib->call('markUnread', $tid,$a); + return $pid; + } +} + +?> diff --git a/scripts/game/main/forums/library/signature.inc b/scripts/game/main/forums/library/signature.inc new file mode 100644 index 0000000..3b3d1ed --- /dev/null +++ b/scripts/game/main/forums/library/signature.inc @@ -0,0 +1,39 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($u) { + if (is_array($this->signatures)) { + if ($this->signatures[$u] != "") + return $this->signatures[$u]; + } else { + $this->signatures = array(); + } + $q = $this->db->query( + "SELECT id,value FROM user_preferences " + . "WHERE account IN (0,$u) " + . "AND id IN ('forums_sig','forum_code','smileys') " + . "AND version='main' " + . "ORDER BY account" + ); + $p = array(); + while ($r = dbFetchArray($q)) { + $p[$r[0]] = $r[1]; + } + if ($p["forums_sig"] == "") { + $s = ""; + } else { + $s = "


    " . $this->lib->call('substitute', $p['forums_sig'], $p['forum_code'] ? 't' : 'f', $p['smileys'] ? 't' : 'f') . "
    "; + } + return ($this->signatures[$u] = $s); + } +} + +?> diff --git a/scripts/game/main/forums/library/substitute.inc b/scripts/game/main/forums/library/substitute.inc new file mode 100644 index 0000000..9decc0b --- /dev/null +++ b/scripts/game/main/forums/library/substitute.inc @@ -0,0 +1,60 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($text, $ec, $es) { + $src = array('/\\n/','/\\r/'); $dst = 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]); + } + } + } + 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); + } + } + } + + return preg_replace($src, $dst, $text); + } +} + +?> diff --git a/scripts/game/main/forums/library/updateLast.inc b/scripts/game/main/forums/library/updateLast.inc new file mode 100644 index 0000000..07d80b2 --- /dev/null +++ b/scripts/game/main/forums/library/updateLast.inc @@ -0,0 +1,21 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function run($forum) { + $q = $this->db->query("SELECT id FROM f_post WHERE forum=$forum AND deleted IS NULL ORDER BY moment DESC LIMIT 1"); + if (!($q && dbCount($q))) { + return; + } + list($id) = dbFetchArray($q); + $this->db->query("UPDATE f_forum SET last_post=$id WHERE id=$forum"); + } +} + +?> diff --git a/scripts/game/main/library.inc b/scripts/game/main/library.inc new file mode 100644 index 0000000..9dffe28 --- /dev/null +++ b/scripts/game/main/library.inc @@ -0,0 +1,37 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function isValidAddress($mAddr) { + return preg_match( + '/^[A-Za-z0-9_\.\-\+]+@([A-Za-z0-9_\.\-\+]+)+\.[A-Za-z]{2,6}/', + $mAddr + ); + } + + function isValidName($uName) { + $test = array('/[^A-Za-z0-9_\.\-\+@\/'."'".' ]/', '/\s\s+/', '/^\s/', '/\s$/'); + $r = true; + for ($i=0;$r&&$i diff --git a/scripts/game/main/library/getTick.inc b/scripts/game/main/library/getTick.inc new file mode 100644 index 0000000..ba1a124 --- /dev/null +++ b/scripts/game/main/library/getTick.inc @@ -0,0 +1,32 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($name, $lang = null) { + $tInst = $this->lib->game->ticks[$name]; + if (is_null($tInst)) { + return null; + } + + $tick = array( + "game" => $tInst->definition->public, + "first" => $tInst->first, + "last" => $tInst->last, + "interval" => $tInst->interval + ); + + if (!is_null($lang)) { + $tick['name'] = $tInst->definition->getName($lang); + $tick['description'] = $tInst->definition->getDescription($lang); + } + + return $tick; + } +} + +?> diff --git a/scripts/game/main/library/getTicks.inc b/scripts/game/main/library/getTicks.inc new file mode 100644 index 0000000..ec5d3a4 --- /dev/null +++ b/scripts/game/main/library/getTicks.inc @@ -0,0 +1,31 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($lang = null) { + $ticks = array(); + foreach ($this->lib->game->ticks as $script => $tInst) { + $tick = array( + "script" => $script, + "game" => $tInst->definition->public, + "first" => $tInst->first, + "last" => $tInst->last, + "interval" => $tInst->interval + ); + + if (!is_null($lang)) { + $tick['name'] = $tInst->definition->getName($lang); + $tick['description'] = $tInst->definition->getDescription($lang); + } + array_push($ticks, $tick); + } + return $ticks; + } +} + +?> diff --git a/scripts/game/main/library/isGameRunning.inc b/scripts/game/main/library/isGameRunning.inc new file mode 100644 index 0000000..c3676db --- /dev/null +++ b/scripts/game/main/library/isGameRunning.inc @@ -0,0 +1,31 @@ +lib = $lib; + } + + function run($game) { + $g = config::getGame($game); + if (!$g) { + return false; + } + + $lib = $g->getLib(); + if ($lib->call('isFinished')) { + return false; + } + + $now = time(); + foreach ($g->ticks as $td => $tick) { + if ($tick->first <= $now && $tick->definition->public) { + return true; + } + } + + return false; + } +} + +?> diff --git a/scripts/game/main/library/preJoin.inc b/scripts/game/main/library/preJoin.inc new file mode 100644 index 0000000..a2e695e --- /dev/null +++ b/scripts/game/main/library/preJoin.inc @@ -0,0 +1,49 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + public function run($account) { + // Get the game's ID + $q = $this->db->query("SELECT game FROM reg_queue WHERE account = $account"); + if (!($q && dbCount($q))) { + $this->db->end(false); + return null; + } + list($gameID) = dbFetchArray($q); + + // Get the game library on call its preJoin() function + $game = config::getGame($gameID); + if (!$game) { + $this->db->end(false); + return null; + } + if (!$game->getLib()->call('preJoin', $account)) { + return null; + } + + // Delete the queue entry + $this->db->query("DELETE FROM reg_queue WHERE account = $account"); + + return $gameID; + } +} + + +?> diff --git a/scripts/game/main/library/requestGenPlanets.inc b/scripts/game/main/library/requestGenPlanets.inc new file mode 100644 index 0000000..3b689c4 --- /dev/null +++ b/scripts/game/main/library/requestGenPlanets.inc @@ -0,0 +1,29 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($first, $amount) { + $dir = config::getParam('pgenreq'); + $game = $this->lib->game->name; + if (!is_dir($dir)) { + $base = dirname($dir); + if (!(is_dir($base) && is_writable($base) && @mkdir($dir))) { + logText("main::requestGenPlanets($game,$first,$amount): unable to create directory '$dir'", LOG_WARNING); + return false; + } + } + + $r = @touch("$dir/req-$game-$first-$amount"); + if (!$r) { + logText("main::requestGenPlanets($game,$first,$amount): unable to create file 'req-$game-$first-$amount'", LOG_WARNING); + } + return $r; + } +} + +?> diff --git a/scripts/game/main/library/sendMail.inc b/scripts/game/main/library/sendMail.inc new file mode 100644 index 0000000..8c8da9d --- /dev/null +++ b/scripts/game/main/library/sendMail.inc @@ -0,0 +1,48 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($file, $to, $vars) { + if (! config::$main['sendmails']) { + return true; + } + + // Read the file + $mail = @file_get_contents("{$this->lib->game->dir}/mail/$file"); + if (is_bool($mail)) { + logText("Mail '$file' to $to failed: file not found", LOG_WARNING); + return false; + } + + // Generate the substitution arrays from the variables + $vals = array_values($vars); + $subst = array(); + foreach (array_keys($vars) as $name) { + array_push($subst, "/_{$name}_/"); + } + + $mail = preg_replace($subst, $vals, $mail); + $tmp = explode("\n", $mail); + $subject = array_shift($tmp); + $mail = join("\n", $tmp); + + $header = "From: webmaster@legacyworlds.com\r\n" + . "Reply-To: webmaster@legacyworlds.com\r\n" + . "X-Mailer: LegacyWorlds\r\n" + . "Mime-Version: 1.0\r\n" + . "Content-Type: text/plain; charset=iso-8859-1"; + if (!mail($to, $subject, $mail, $header)) { + logText("Mail '$file' to $to failed: unable to send", LOG_WARNING); + return false; + } + + return true; + } +} + +?> diff --git a/scripts/game/main/links/library.inc b/scripts/game/main/links/library.inc new file mode 100644 index 0000000..6b432bd --- /dev/null +++ b/scripts/game/main/links/library.inc @@ -0,0 +1,212 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function getCategories() { + $q = $this->db->query("SELECT * FROM lk_category ORDER BY position"); + if (!$q) { + return array(); + } + + $res = array(); + while ($r = dbFetchHash($q)) { + array_push($res, $r); + } + + return $res; + } + + function getCategory($id) { + $q = $this->db->query("SELECT * FROM lk_category WHERE id=$id"); + if (!($q && count($q))) { + return null; + } + return dbFetchHash($q); + } + + function checkCategoryName($name, $id) { + $qs = is_null($id) ? "" : " AND id<>$id"; + $q = $this->db->query("SELECT * FROM lk_category WHERE title='" . addslashes($name) . "'$qs"); + return (dbCount($q) == 0); + } + + function createCategory($title, $description, $after = null) { + $q = $this->db->query("SELECT id,position FROM lk_category ORDER BY position DESC FOR UPDATE"); + if (is_null($after)) { + while ($r = dbFetchArray($q)) { + $this->db->query("UPDATE lk_category SET position=position+1 WHERE id={$r[0]}"); + } + $pos = 0; + } else { + $act = false; + while ($r = dbFetchArray($q)) { + if ($act) { + $this->db->query("UPDATE lk_category SET position=position+1 WHERE id={$r[0]}"); + } else { + $act = ($r[0] == $after); + if ($act) { + $pos = $r[1]; + } + } + } + } + + $id = $this->db->query("INSERT INTO lk_category(position,title" . (is_null($description) ? "" : ",description") + . ") VALUES ($pos,'" . addslashes($title) . "'" . (is_null($description) ? "" : (",'" . addslashes($description) . "'")) + . ")"); + return $id; + } + + function changeCategory($id, $title, $description) { + $this->db->query("UPDATE lk_category SET title='" . addslashes($title) . "',description=" + . ($description == "" ? "NULL" : ("'" . addslashes($description) . "'")) + . " WHERE id=$id"); + } + + function deleteCategory($id) { + $this->db->query("SELECT * FROM lk_category FOR UPDATE"); + + $q = $this->db->query("SELECT position FROM lk_category WHERE id=$id"); + if (!($q && count($q))) { + return; + } + list($pos) = dbFetchArray($q); + + $this->db->query("DELETE FROM lk_category WHERE id=$id"); + $this->db->query("UPDATE lk_category SET position=position-1 WHERE position>$pos"); + } + + function moveCategory($id, $up) { + $this->db->query("SELECT * FROM lk_category FOR UPDATE"); + + $q = $this->db->query("SELECT MAX(position)+1 FROM lk_category"); + list($mPos) = dbFetchArray($q); + $q = $this->db->query("SELECT position FROM lk_category WHERE id=$id"); + list($pos) = dbFetchArray($q); + + $this->db->query("UPDATE lk_category SET position=$mPos WHERE id=$id"); + if ($up) { + $this->db->query("UPDATE lk_category SET position=position+1 WHERE position=" . ($pos-1)); + $this->db->query("UPDATE lk_category SET position=".($pos-1)." WHERE id=$id"); + } else { + $this->db->query("UPDATE lk_category SET position=position-1 WHERE position=" . ($pos+1)); + $this->db->query("UPDATE lk_category SET position=".($pos+1)." WHERE id=$id"); + } + } + + + function getLinks($category) { + $q = $this->db->query("SELECT * FROM lk_link WHERE category=$category ORDER BY title"); + if (!$q) { + return array(); + } + + $res = array(); + while ($r = dbFetchHash($q)) { + array_push($res, $r); + } + + return $res; + } + + function getLink($id) { + $q = $this->db->query("SELECT * FROM lk_link WHERE id=$id"); + if (!($q && count($q))) { + return null; + } + return dbFetchHash($q); + } + + function checkLink($url, $id = null) { + $qs = is_null($id) ? "" : " AND id<>$id"; + $q = $this->db->query("SELECT * FROM lk_link WHERE url='".addslashes($url) . "'$qs"); + return (dbCount($q) == 0); + } + + function addLink($category, $title, $description, $url) { + return $this->db->query("INSERT INTO lk_link(category,url,title" . (is_null($description) ? "" : ",description") + . ") VALUES ($category,'" . addslashes($url) . "','" . addslashes($title) . "'" + . (is_null($description) ? "" : (",'" . addslashes($description) . "'")) . ")"); + } + + function changeLink($id, $title, $url, $description) { + $this->db->query("UPDATE lk_link SET title='" . addslashes($title) . "',description=" + . ($description == "" ? "NULL" : ("'" . addslashes($description) . "'")) + . ",url='" . addslashes($url) . "' WHERE id=$id"); + } + + function deleteLink($id) { + $this->db->query("DELETE FROM lk_link WHERE id=$id"); + } + + + function getBrokenReports() { + $q = $this->db->query("SELECT * FROM lk_broken"); + if (!$q) { + return array(); + } + + $res = array(); + while ($r = dbFetchHash($q)) { + array_push($res, $r); + } + + return $res; + } + + function reportBroken($link, $account) { + $this->db->query("INSERT INTO lk_broken VALUES($link, $account)"); + } + + function deleteReports($link) { + $this->db->query("DELETE FROM lk_broken WHERE link=$link"); + } + + + function getSubmissions() { + $q = $this->db->query("SELECT * FROM lk_submitted"); + if (!$q) { + return array(); + } + + $res = array(); + while ($r = dbFetchHash($q)) { + array_push($res, $r); + } + + return $res; + } + + function getSubmission($url, $account) { + $q = $this->db->query("SELECT * FROM lk_submitted WHERE url='" . addslashes($url) + . "' AND submitted_by=$account"); + if (!($q && dbCount($q))) { + return null; + } + return dbFetchHash($q); + } + + function checkSubmission($url, $account) { + $q = $this->db->query("SELECT * FROM lk_submitted WHERE url='" . addslashes($url) + . "' AND submitted_by=$account"); + return (dbCount($q) == 0); + } + + function submitLink($url, $title, $description, $account) { + $this->db->query("INSERT INTO lk_submitted VALUES('" . addslashes($url) . "',$account,'" . addslashes($title) + . "'," . (is_null($description) ? "NULL" : ("'" . addslashes($description) . "'")) . ")"); + } + + function deleteSubmissions($url) { + $this->db->query("DELETE FROM lk_submitted WHERE url='" . addslashes($url) . "'"); + } +} + +?> diff --git a/scripts/game/main/mail/mail-change-pass-conf.en.txt b/scripts/game/main/mail/mail-change-pass-conf.en.txt new file mode 100644 index 0000000..a70e57e --- /dev/null +++ b/scripts/game/main/mail/mail-change-pass-conf.en.txt @@ -0,0 +1,11 @@ +Legacy Worlds > Lost password +Dear _USER_, + +You have requested your password on the Legacy Worlds web site to be changed. You will need to enter an authentication code before the site actually changes your password to a new, random password and sends you another mail containing the new password. + +The confirmation code is _CODE_ + +If you didn't request this, you can safely ignore this message. + +Best regards, +The Legacy Worlds staff diff --git a/scripts/game/main/mail/mail-change-pass.en.txt b/scripts/game/main/mail/mail-change-pass.en.txt new file mode 100644 index 0000000..a44e5c7 --- /dev/null +++ b/scripts/game/main/mail/mail-change-pass.en.txt @@ -0,0 +1,11 @@ +Legacy Worlds > Password changed +Dear _USER_, + +You requested your password to be changed on the Legacy Worlds web site. This request has been accepted after you entered your confirmation code. + +Your new password is: _PASS_ + +You should change it just after you log on to Legacy Worlds. + +Best regards, +The Legacy Worlds staff diff --git a/scripts/game/main/mail/mail-kick-inactive.en.txt b/scripts/game/main/mail/mail-kick-inactive.en.txt new file mode 100644 index 0000000..39dc779 --- /dev/null +++ b/scripts/game/main/mail/mail-kick-inactive.en.txt @@ -0,0 +1,13 @@ +Legacy Worlds account disabled +Dear _USER_, + +This email is being sent to inform you that your account on Legacy Worlds has +been disabled. It had been inactive for 28 days. + +You can reactivate your account by logging in to the Legacy Worlds web site +using your usual user name and password. + +We hope to see you again very soon on Legacy Worlds. + +Best regards, +The Legacy Worlds staff diff --git a/scripts/game/main/mail/mail-quit.en.txt b/scripts/game/main/mail/mail-quit.en.txt new file mode 100644 index 0000000..8737163 --- /dev/null +++ b/scripts/game/main/mail/mail-quit.en.txt @@ -0,0 +1,13 @@ +Legacy Worlds account closed +Dear _USER_, + +This email is being sent to confirm that your account has been closed as +you requested. + +You can reactivate your account by logging in to the Legacy Worlds web site +using your usual user name and password. + +We hope to see you again sometime on Legacy Worlds. + +Best regards, +The Legacy Worlds staff diff --git a/scripts/game/main/mail/mail-reg.en.txt b/scripts/game/main/mail/mail-reg.en.txt new file mode 100644 index 0000000..303dfce --- /dev/null +++ b/scripts/game/main/mail/mail-reg.en.txt @@ -0,0 +1,17 @@ +Legacy Worlds registration +Dear _USER_, + +Thank you for registering at Legacy Worlds! +Before we activate your account, there's just one more step to complete your registration. + +You have to connect to the site using the username and password you chose, then validate your account using the confirmation code below. +Please note that this confirmation code is NOT your password, and you will only need to use it once. + +Your Username is: _USER_ +Your Password is: _PASS_ +Your Confirmation Code is: _CCODE_ + +If you are still having problems signing up, please contact a member of our support staff at webmaster@legacyworlds.com + +Thanks! +The Legacy Worlds staff diff --git a/scripts/game/main/mail/mail-restart.en.txt b/scripts/game/main/mail/mail-restart.en.txt new file mode 100644 index 0000000..722e731 --- /dev/null +++ b/scripts/game/main/mail/mail-restart.en.txt @@ -0,0 +1,14 @@ +Legacy Worlds registration +Dear _USER_, + +Thank you for coming back to Legacy Worlds! +There's one step you must complete before your account is re-activated. + +You have to connect to the site again using your username and password, then re-validate your account using the confirmation code below. + +Your Confirmation Code is: _CCODE_ + +If you encouter problems during the process, please contact a member of our support staff at staff@legacyworlds.com + +Thanks! +The Legacy Worlds staff diff --git a/scripts/game/main/mail/mail-warn-inactive.en.txt b/scripts/game/main/mail/mail-warn-inactive.en.txt new file mode 100644 index 0000000..949d147 --- /dev/null +++ b/scripts/game/main/mail/mail-warn-inactive.en.txt @@ -0,0 +1,11 @@ +Your account on Legacy Worlds +Dear _USER_, + +This email is being sent to warn you that your account on Legacy Worlds has +been inactive for three weeks. Unless you connect in the coming week, it will +be closed and your current games will be lost. + +We hope to see you again on Legacy Worlds very soon. + +Best regards, +The Legacy Worlds staff diff --git a/scripts/game/main/manual/library.inc b/scripts/game/main/manual/library.inc new file mode 100644 index 0000000..2270be4 --- /dev/null +++ b/scripts/game/main/manual/library.inc @@ -0,0 +1,66 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->version = $this->lib->game->version->id; + } + + + function getPage($pageId) { + $section = $this->readSectionRecord($pageId); + $tree = $this->lib->call('getSectionsIn', $pageId, 0); + $this->readSubSections($pageId, $section, $tree); + return $section; + } + + + function getSectionId($lang, $name) { + $q = $this->db->query("SELECT id FROM man_section WHERE version='{$this->version}' AND lang='$lang' AND name='" . addslashes($name) . "'"); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($secId) = dbFetchArray($q); + return $secId; + } + + + function readSectionRecord($sectionId) { + $q = dbQuery("SELECT name,title,contents,link_to FROM man_section WHERE id=$sectionId"); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($name,$title,$contents,$linkto) = dbFetchArray($q); + return array( + "id" => $sectionId, + "name" => $name, + "title" => $title, + "contents" => $contents, + "linkto" => $linkto, + "subsections" => array() + ); + } + + + function readSubSections($sectionId, &$section, &$subList) { + foreach ($subList as $secId => $sData) { + $nSec = $this->readSectionRecord($secId); + $this->readSubSections($secId, $nSec, $subList[$secId]['subs']); + array_push($section['subsections'], $nSec); + } + } +} + +?> diff --git a/scripts/game/main/manual/library/getFirstPage.inc b/scripts/game/main/manual/library/getFirstPage.inc new file mode 100644 index 0000000..fc675c2 --- /dev/null +++ b/scripts/game/main/manual/library/getFirstPage.inc @@ -0,0 +1,30 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->version = $this->lib->mainClass->version; + } + + function run($lang) { + // Get the manual's root node identifier + $q = $this->db->query("SELECT id FROM man_section WHERE version='{$this->version}' AND lang='$lang' AND in_section IS NULL"); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($rootId) = dbFetchArray($q); + + // Get the root node's first page + $q = $this->db->query("SELECT id FROM man_section WHERE in_section=$rootId AND after_section=$rootId AND is_page"); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($fPage) = dbFetchArray($q); + + return $fPage; + } +} + +?> diff --git a/scripts/game/main/manual/library/getNavLinks.inc b/scripts/game/main/manual/library/getNavLinks.inc new file mode 100644 index 0000000..75ce80a --- /dev/null +++ b/scripts/game/main/manual/library/getNavLinks.inc @@ -0,0 +1,67 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($pageId) { + // Get the version, language and parent section for the page + $q = $this->db->query("SELECT version,lang,in_section,after_section FROM man_section WHERE id=$pageId"); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($version,$lang,$parent,$after) = dbFetchArray($q); + + // Get the manual's root node identifier + $q = $this->db->query("SELECT id FROM man_section WHERE version='$version' AND lang='$lang' AND in_section IS NULL"); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($rootId) = dbFetchArray($q); + + // Get the root node's first page + $q = $this->db->query("SELECT id FROM man_section WHERE in_section=$rootId AND after_section=$rootId AND is_page"); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($fPage) = dbFetchArray($q); + + // Get identifiers + $rv = array($pageId == $fPage ? null : $fPage); + $rv[1] = ($parent == $rootId) ? (is_null($rv[0]) ? null : $rv[0]) : $parent; + $rv[2] = ($after == $rootId) ? null : $after; + $q = $this->db->query("SELECT id FROM man_section WHERE is_page AND after_section=$pageId"); + if ($q && dbCount($q) == 1) { + list($rv[3]) = dbFetchArray($q); + } else { + $rv[3] = null; + } + + // Get names + $nnl = array(); + for ($i=0;$i<4;$i++) { + if (!is_null($rv[$i])) { + array_push($nnl, $rv[$i]); + } + } + if (count($nnl)) { + $q = $this->db->query("SELECT id,name FROM man_section WHERE id IN (" . join(',', array_unique($nnl)) . ")"); + $l = array(); + while ($r = dbFetchArray($q)) { + $l[$r[0]] = $r[1]; + } + for ($i=0;$i<4;$i++) { + if (!is_null($rv[$i])) { + $rv[$i] = $l[$rv[$i]]; + } + } + } + + return $rv; + } +} + +?> diff --git a/scripts/game/main/manual/library/getPageId.inc b/scripts/game/main/manual/library/getPageId.inc new file mode 100644 index 0000000..10bc6dd --- /dev/null +++ b/scripts/game/main/manual/library/getPageId.inc @@ -0,0 +1,31 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($secId) { + // Find the section itself + $q = $this->db->query("SELECT is_page,in_section FROM man_section WHERE id=$secId"); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($isPage, $parentId) = dbFetchArray($q); + + // Find the page in which the section is + while ($isPage != 't') { + $q = $this->db->query("SELECT id,is_page,in_section FROM man_section WHERE id=$parentId"); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($secId, $isPage, $parentId) = dbFetchArray($q); + } + + return $secId; + } +} + +?> diff --git a/scripts/game/main/manual/library/getSectionsIn.inc b/scripts/game/main/manual/library/getSectionsIn.inc new file mode 100644 index 0000000..ea1ede1 --- /dev/null +++ b/scripts/game/main/manual/library/getSectionsIn.inc @@ -0,0 +1,35 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($rootId, $type = 1) { + $qs = ($type > 0) ? (" AND is_page" . ($type == 2 ? " AND in_menu" : "")) : " AND NOT is_page"; + $q = $this->db->query("SELECT id,name,title,after_section FROM man_section WHERE in_section=$rootId$qs"); + $sl = array(); + $as = array(); + while ($r = dbFetchArray($q)) { + $sl[$r[0]] = array( + "name" => $r[1], + "title" => $r[2] + ); + $as[$r[3]] = $r[0]; + } + + $cid = $rootId; + $nl = array(); + while (!is_null($as[$cid])) { + $cid = $as[$cid]; + $nl[$cid] = $sl[$cid]; + $nl[$cid]['subs'] = $this->run($cid, $type); + } + + return $nl; + } +} + +?> diff --git a/scripts/game/main/manual/library/getStructure.inc b/scripts/game/main/manual/library/getStructure.inc new file mode 100644 index 0000000..2ec791a --- /dev/null +++ b/scripts/game/main/manual/library/getStructure.inc @@ -0,0 +1,24 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->version = $this->lib->mainClass->version; + } + + function run($lang) { + // Get the manual's root node identifier + $q = $this->db->query("SELECT id FROM man_section WHERE version='{$this->version}' AND lang='$lang' AND in_section IS NULL"); + if (!($q && dbCount($q) == 1)) { + return array(); + } + list($rootId) = dbFetchArray($q); + + // List sections + return $this->lib->call('getSectionsIn', $rootId, 2); + } +} + +?> diff --git a/scripts/game/main/manual/library/readXMLFile.inc b/scripts/game/main/manual/library/readXMLFile.inc new file mode 100644 index 0000000..831836c --- /dev/null +++ b/scripts/game/main/manual/library/readXMLFile.inc @@ -0,0 +1,158 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($path) { + logText("manual/readXMLFile: extracting XML file '$path'", LOG_DEBUG); + + // Get the section's name from the path + $section = basename($path, ".lwdoc"); + + // Open the file + $file = @file_get_contents($path); + if ($file === FALSE) { + logText("manual/readXMLFile: failed to read file '$path'", LOG_DEBUG); + return 1; + } + $file = utf8_encode($file); + + // Parse it + $doc = new DOMDocument(); + if (!$doc->loadXML($file)) { + logText("manual/readXMLFile: failed to parse file '$path'", LOG_DEBUG); + return 2; + } + $root = $doc->documentElement; + $node = $root->firstChild; + $version = $title = $lang = ""; + + // Extract the version, language and title + for ($i=0;$i<3;$i++) { + while ($node && $node->nodeType != XML_ELEMENT_NODE) { + $node = $node->nextSibling; + } + + $name = $node->nodeName; + $val = $node->textContent; + if ($name == "version") { + $version = $val; + } elseif ($name == "language") { + $language = $val; + } elseif ($name == "title") { + $title = $val; + } else { + logText("manual/readXMLFile('$path'): unexpected tag '$name'", LOG_DEBUG); + return 3; + } + + $node = $node->nextSibling; + } + if (!$node || $version == "" || $language == "" || $title == "") { + logText("manual/readXMLFile('$path'): incomplete file header", LOG_DEBUG); + return 4; + } + + // Generate the main section + $sections = array( + $section => array( + "language" => $language, + "version" => $version, + "in_section" => null, + "is_page" => true, + "in_menu" => ($root->getAttribute('hide') !== "1"), + "title" => $title, + "contents" => "", + "subs" => array() + ) + ); + + // Read the sections + $stack = array(); + while ($node) { + $cs = empty($stack) ? $section : $stack[count($stack)-1][1]; + if ($node->nodeType == XML_ELEMENT_NODE && $node->nodeName == "section") { + $nFile = $node->getAttribute('file'); + if ($nFile != '') { + // This subsection must be read from another file + if ($nFile{0} != "/") + $nFile = dirname($path) . "/$nFile"; + $a = $this->run($nFile); + if (!is_array($a)) { + logText("manual/readXMLFile('$path'): error $a in sub-file '$nFile'", LOG_DEBUG); + return $a; + } + $id = basename($nFile, '.lwdoc'); + $a[$id]['in_section'] = $cs; + $sections = array_merge($sections, $a); + array_push($sections[$cs]['subs'], $id); + $node = $node->nextSibling; + } else { + // Inline subsection + $id = $node->getAttribute('id'); + array_push($sections[$cs]['subs'], $id); + $sections[$id] = array( + "language" => $language, + "version" => $version, + "in_section" => $cs, + "is_page" => false, + "title" => $node->getAttribute('title'), + "linkto" => $node->getAttribute('linkto'), + "contents" => $this->getEmbeddedXHTML($node), + "subs" => array() + ); + array_push($stack, array($node, $id)); + $node = $node->firstChild; + } + } else { + $node = $node->nextSibling; + if (!empty($stack)) { + do { + if (!$node) { + $x = array_pop($stack); + $node = $x[0]->nextSibling; + } + } while (!$node && !empty($stack)); + } + } + } + + return $sections; + } + + + function getEmbeddedXHTML($node) { + if (!$node->hasChildNodes()) { + return $node->textContent; + } + + $st = ""; + for ($i=0;$i<$node->childNodes->length;$i++) { + $cnode = $node->childNodes->item($i); + if ($cnode->nodeType == XML_TEXT_NODE) { + $st .= $cnode->nodeValue; + } elseif ($cnode->nodeType == XML_ELEMENT_NODE && $cnode->nodeName != 'section') { + $st .= "<" . $cnode->nodeName; + if ($attribnodes = $cnode->attributes) { + $st .= " "; + foreach ($attribnodes as $anode) { + $st .= $anode->nodeName . "='" . $anode->nodeValue . "'"; + } + } + $nodeText = $this->getEmbeddedXHTML($cnode); + if (empty($nodeText) && !$attribnodes) { + $st .= " />"; // unary + } else { + $st .= ">" . $nodeText . "nodeName . ">"; + } + } + } + return $st; + } +} + +?> diff --git a/scripts/game/main/manual/library/search.inc b/scripts/game/main/manual/library/search.inc new file mode 100644 index 0000000..1a1050b --- /dev/null +++ b/scripts/game/main/manual/library/search.inc @@ -0,0 +1,229 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($text, $lang) { + $version = "'{$this->lib->mainClass->version}'"; + $text = preg_replace('/\s+/', ' ', trim($text)); + + // Read the banned words list + $banList = array(); + $q = $this->db->query("SELECT word FROM man_index_ban WHERE lang='$lang'"); + while ($r = dbFetchArray($q)) { + array_push($banList, $r[0]); + } + + // Generate the three lists: required, excluded and normal words + $required = $excluded = $normal = array(); + $tl = explode(' ', $text); + foreach ($tl as $word) { + if ($word{0} == '+' || $word{0} == '-') { + $qual = $word{0}; + $word = substr($word, 1); + } else { + $qual = ''; + } + + $word = preg_replace('/["\s\.,;:!\(\)<>&]+/', ' ', $word); + $rwl = explode(' ', $word); + foreach ($rwl as $rword) { + $rword = strtolower($rword); + if (in_array($rword, $banList)) { + continue; + } + $rword = addslashes($rword); + + if ($qual == '') { + array_push($normal, $rword); + } elseif ($qual == '+') { + array_push($required, $rword); + } else { + array_push($excluded, $rword); + } + } + } + $required = array_unique($required); + $excluded = array_unique($excluded); + $normal = array_unique($normal); + + // Read the index for each list + $words = array($required, $normal, $excluded); + $lists = array(array(), array(), array()); + for ($i=0;$idb->query("SELECT i.word,i.wcount,i.section" + . " FROM man_index i,man_section s " + . "WHERE i.lang='$lang' AND s.id=i.section AND s.version=$version " + . "AND word in ('" . join("','", $words[$i]) . "')"); + while ($r = dbFetchArray($q)) { + if (!is_array($lists[$i][$r[0]])) { + $lists[$i][$r[0]] = array(); + } + $lists[$i][$r[0]][$r[2]] = $r[1]; + } + } + + if (count($words[0])) { + // Look for sections that have all required words + $secReq = array(); + foreach ($required as $word) { + if (!is_array($lists[0][$word])) { + continue; + } + foreach (array_keys($lists[0][$word]) as $s) { + $secReq[$s] ++; + } + } + + $secOk = array(); + foreach ($secReq as $s => $n) { + if ($n == count($words[0]) && !in_array($s, $secOk)) { + array_push($secOk, $s); + } + } + } else { + // Look for sections that have these words + $secOk = array(); + if (count($words[1])) { + foreach ($lists[1] as $word => $sections) { + foreach (array_keys($sections) as $section) { + if (!in_array($section, $secOk)) { + array_push($secOk, $section); + } + } + } + } + } + + // Remove sections that have excluded words + if (count($words[2])) { + // Generate a list of banned sections + $bannedSections = array(); + foreach ($lists[2] as $word => $sections) { + foreach ($sections as $s => $c) { + if (!in_array($s, $bannedSections)) { + array_push($bannedSections, $s); + } + } + } + + // Remove banned sections from the list + $newList = array(); + foreach ($secOk as $s) { + if (!in_array($s, $bannedSections)) { + array_push($newList, $s); + } + } + $secOk = $newList; + } + + /* + * For each section in the generated list, find out: + * - how many occurences of each required or normal word it + * has, + * - the maximum amount of occurences a section has of each + * required or normal word. + */ + + // Initialise the word list + $sWords = array_unique(array_merge($words[0], $words[1])); + $mWords = array(); + foreach ($sWords as $word) { + $mWords[$word] = 0; + } + + // Generate the section list and update maximum counts + $wPerSec = array(); + foreach ($secOk as $section) { + $wPerSec[$section] = array(); + foreach ($sWords as $word) { + $count = $lists[is_null($lists[0][$word]) ? 1 : 0][$word][$section]; + if (!is_null($count)) { + $wPerSec[$section][$word] = $count; + if ($count > $mWords[$word]) { + $mWords[$word] = $count; + } + } else { + $wPerSec[$section][$word] = 0; + } + } + } + + /* Now we assign a "distance" from the optimal value to + * each section by using each word as a dimension; the closer + * a section to the optimal value, the higher it will be in + * the displayed results. + */ + + // Eliminate words that are not on any section + $nWords = array(); + foreach ($sWords as $word) { + if ($mWords[$word] > 0) { + array_push($nWords, $word); + } + } + + // Compute the maximum distance + $sum = 0; + foreach ($nWords as $word) { + $d = $mWords[$word]; + $sum += $d * $d; + } + $dMax = sqrt($sum); + + // Compute the revelance of each section + $dSec = array(); + foreach ($secOk as $section) { + $sum = 0; + foreach ($nWords as $word) { + $d = $mWords[$word] - $wPerSec[$section][$word]; + $sum += $d * $d; + } + $dSec[$section] = 1 - (sqrt($sum) / $dMax); + } + + // Find out the page for each section + $pages = array(); + $nSecs = array(); + foreach ($secOk as $section) { + $pageId = $this->lib->call('getPageId', $section); + if (is_null($pageId)) { + continue; + } + $pages[$pageId] += $dSec[$section]; + $nSecs[$pageId] += 1; + } + + // Sort the pages according to their points + $pts = array(); + foreach (array_keys($pages) as $p) { + $pp = sprintf("%.2f", $pages[$p] / $nSecs[$pageId]); + if (!is_array($pts[$pp])) { + $pts[$pp] = array(); + } + array_push($pts[$pp], $p); + } + $k = array_keys($pts); + sort($k); + + // Return the results + $results = array(); + foreach (array_reverse($k) as $points) { + foreach ($pts[$points] as $p) { + array_push($results, $p); + } + } + + return $results; + } +} + +?> diff --git a/scripts/game/main/manual/library/updateSections.inc b/scripts/game/main/manual/library/updateSections.inc new file mode 100644 index 0000000..e8c0177 --- /dev/null +++ b/scripts/game/main/manual/library/updateSections.inc @@ -0,0 +1,107 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($sections) { + $versions = array(); + foreach ($sections as $sName => $sData) { + if (is_null($versions[$sData['version']])) { + $versions[$sData['version']] = "'" . addslashes($sData['version']) . "'"; + } + $this->db->query("DELETE FROM man_section WHERE name='" . addslashes($sName) + . "' AND version=" . $versions[$sData['version']] + . " AND lang='" . addslashes($sData['language']) . "'"); + } + + // Insert the data itself + $now = time(); + foreach ($sections as $sName => $sData) { + if (is_null($versions[$sData['version']])) { + continue; + } + $this->db->query("INSERT INTO man_section (version,lang,name,last_update,is_page,in_menu,title,contents) VALUES(" + . $versions[$sData['version']] . ",'" . addslashes($sData['language']) . "','" + . addslashes($sName) . "',$now," . dbBool($sData['is_page']) . "," + . dbBool($sData['in_menu']) . ",'" . addslashes($sData['title']) + . "','" . addslashes($sData['contents']) . "')"); + $q = $this->db->query("SELECT id FROM man_section WHERE name='" . addslashes($sName) + . "' AND version=" . $versions[$sData['version']] + . " AND lang='" . addslashes($sData['language']) . "'"); + list($sections[$sName]['dbid']) = dbFetchArray($q); + } + + // Create links between data + foreach ($sections as $sName => $sData) { + if (count($sData['subs'])) { + // In / After section + foreach ($sData['subs'] as $i => $sn) { + $ri = $i - 1; + while ($ri >= 0) { + $sn2 = $sections[$sData['subs'][$ri]]; + if ($sn2['is_page'] == $sections[$sn]['is_page']) { + $previous = $sn2['dbid']; + break; + } + $ri --; + } + if ($ri == -1) { + $previous = $sData['dbid']; + } + + $this->db->query("UPDATE man_section SET in_section={$sData['dbid']},after_section=$previous WHERE id={$sections[$sn]['dbid']}"); + } + } + + if ($sData['linkto'] != '') { + if (is_array($sections[$sData['linkto']])) { + $this->db->query("UPDATE man_section SET link_to={$sections[$sData['linkto']]['dbid']} WHERE id={$sData['dbid']}"); + } else { + $q = $this->db->query("SELECT id FROM man_section WHERE name='" . addslashes($sData['linkto']) . "' AND version=" + . $versions[$sData['version']] . " AND lang='" . addslashes($sData['language']) . "'"); + if ($q && dbCount($q) == 1) { + list($toid) = dbFetchArray($q); + $this->db->query("UPDATE man_section SET link_to=$toid WHERE id={$sData['dbid']}"); + } + } + } + } + + // Update indices + $banwords = array(); + foreach ($sections as $sName => $sData) { + + if (!is_array($banwords[$sData['language']])) { + $ban = array(); + $q = $this->db->query("SELECT word FROM man_index_ban WHERE lang='" . addslashes($sData['language']) . "'"); + while ($r = dbFetchArray($q)) { + array_push($ban, $r[0]); + } + $banwords[$sData['lang']] = $ban; + } + + $text = preg_replace('/<[^>]+>/', '', $sData['title']) . " "; + $text .= preg_replace('/<[^>]+>/', '', $sData['contents']); + $text = preg_replace('/["\s\.,;:!\(\)<>&]+/', ' ', $text); + + $tl = explode(' ', $text); + $rtl = array(); + foreach ($tl as $word) { + $word = strtolower($word); + if ($word != '' && !in_array($word, $banwords[$sData['lang']])) { + $rtl[$word] ++; + } + } + foreach ($rtl as $word => $count) { + $this->db->query("INSERT INTO man_index(word,wcount,lang,section) VALUES ('" . addslashes($word) + . "',$count,'" . addslashes($sData['language']) . "',{$sData['dbid']})"); + } + } + } +} + +?> diff --git a/scripts/game/main/paypal/library.inc b/scripts/game/main/paypal/library.inc new file mode 100644 index 0000000..9dfe6db --- /dev/null +++ b/scripts/game/main/paypal/library.inc @@ -0,0 +1,147 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function newTicket($account) { + $this->db->query("DELETE FROM pp_ticket WHERE UNIX_TIMESTAMP(NOW())-created>2592000"); + + do { + $v = md5(uniqid(rand())); + $q = "SELECT * FROM pp_ticket WHERE md5_id='$v'"; + $qr = $this->db->query($q); + } while ($qr && dbCount($qr)); + + if (is_null($qr)) { + return null; + } + + $this->db->query("INSERT INTO pp_ticket(account,md5_id) VALUES($account,'$v')"); + return $v; + } + + + function getUserContributions($account) { + $q = $this->db->query("SELECT SUM(amount) FROM pp_history WHERE account=$account"); + if (!($q && dbCount($q))) { + return 0; + } + list($amount) = dbFetchArray($q); + return $amount; + } + + + function getTotalContributions() { + $q = $this->db->query("SELECT SUM(amount) FROM pp_history"); + if (!($q && dbCount($q))) { + return 0; + } + list($amount) = dbFetchArray($q); + return $amount; + } + + + function getMonthContributions() { + $q = $this->db->query("SELECT SUM(amount) FROM pp_history WHERE donated>UNIX_TIMESTAMP(NOW())-2592000"); + if (!($q && dbCount($q))) { + return 0; + } + list($amount) = dbFetchArray($q); + return $amount; + } + + + function getUserHistory($account) { + $q = $this->db->query("SELECT donated,amount FROM pp_history WHERE account=$account ORDER BY donated DESC"); + if (!($q && dbCount($q))) { + return array(); + } + + $res = array(); + while ($r = dbFetchArray($q)) { + array_push($res, array( + "time" => strftime("%Y-%m-%d %H:%M:%S", $r[0]), + "amount" => number_format($r[1]) + )); + } + + return $res; + } + + + function checkIPN() { + // Generate the verification request + $ra = array(); + foreach ($_POST as $key => $value) { + $value = urlencode(stripslashes($value)); + array_push($ra, "$key=$value"); + } + $req = join('&', $ra); + + // Connect to the PayPal server + $curl = curl_init("http://www.paypal.com/cgi-bin/webscr?cmd=_notify%2dvalidate&$req"); + curl_setopt($curl, CURLOPT_HEADER, 0); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + $rv = curl_exec($curl); + logText("PAYPAL: CURL request returned '$rv'", LOG_DEBUG); + + if (preg_match("/VERIFIED/", $rv)) { + $handled = true; + $ok = true; + } elseif (preg_match("/INVALID/", $rv)) { + $handled = true; + $ok = false; + } else { + logText("PAYPAL: remote server didn't return VERIFIED or INVALID while checking IPN!", LOG_ERR); + logText("PAYPAL: item number was '{$_POST['item_number']}'", LOG_INFO); + return false; + } + + if (!$ok) { + logText("PAYPAL: remote server returned INVALID while checking IPN!", LOG_ERR); + logText("PAYPAL: item number was '{$_POST['item_number']}'", LOG_INFO); + } + return $ok; + } + + + function logIPN($input) { + $fields = array( + "receiver_email", "item_number", "payment_status", "pending_reason", "payment_date", "mc_gross", + "mc_fee", "tax", "mc_currency", "txn_id", "txn_type", "payer_email", "payer_status", "payment_type", + "verify_sign", "referrer_id" + ); + + $q1 = "INSERT INTO pp_ipn(received"; + $q2 = ") VALUES (" . time(); + foreach ($fields as $f) { + $q1 .= ",$f"; + $q2 .= ",'" . addslashes($input[$f]) . "'"; + } + + return $this->db->query("$q1$q2)"); + } + + + function addDonation($ticket, $amount) { + $q = $this->db->query("SELECT account FROM pp_ticket WHERE md5_id='" . addslashes($ticket) . "'"); + if (!($q || dbCount($q))) { + logText("PAYPAL: couldn't find account number for ticket $ticket", LOG_ERR); + return; + } + list($account) = dbFetchArray($q); + + $this->db->query("DELETE FROM pp_ticket WHERE md5_id='" . addslashes($ticket) . "'"); + $this->db->query("INSERT INTO pp_history VALUES($account," . time() . ",$amount)"); + $this->db->query("UPDATE credits SET credits_obtained = credits_obtained + " . round(10000 * $amount) . " WHERE account = $account"); + } +} + +?> diff --git a/scripts/game/main/rankings/library.inc b/scripts/game/main/rankings/library.inc new file mode 100644 index 0000000..8fcb315 --- /dev/null +++ b/scripts/game/main/rankings/library.inc @@ -0,0 +1,52 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function getType($identifier) { + $version = $this->lib->game->version->id; + $name = $this->lib->game->name; + + $q = $this->db->query("SELECT g.id FROM ranking_def d, ranking_game g " + . "WHERE d.version='$version' AND d.name='$identifier' AND g.ranking=d.id AND g.game='$name'"); + list($id) = dbFetchArray($q); + return $id; + } + + function getTypes() { + $game = $this->lib->game; + $version = $game->version; + + $q = $this->db->query("SELECT g.id,d.name FROM ranking_def d, ranking_game g " + . "WHERE d.version='{$version->id}' AND g.game='{$game->name}' AND g.ranking=d.id"); + $rs = array(); + while ($r = dbFetchArray($q)) { + $rs[$r[1]] = $r[0]; + } + return $rs; + } + + function getText($id, $lang) { + $q = $this->db->query("SELECT t.name AS name,t.description AS description " + . "FROM ranking_text t, ranking_game g " + . "WHERE g.id=$id AND t.ranking=g.ranking AND t.lang='$lang'"); + return dbFetchHash($q); + } + + function get($type, $id) { + $q = $this->db->query("SELECT points,ranking FROM ranking WHERE r_type=$type AND LOWER(id)='".addslashes(strtolower($id))."' ORDER BY ranking,id"); + return dbFetchHash($q); + } +} + +?> diff --git a/scripts/game/main/rankings/library/append.inc b/scripts/game/main/rankings/library/append.inc new file mode 100644 index 0000000..946c260 --- /dev/null +++ b/scripts/game/main/rankings/library/append.inc @@ -0,0 +1,30 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($type, $id, $additional = null) { + $q = $this->db->query("SELECT ranking FROM ranking WHERE r_type=$type AND points=0"); + if ($q && dbCount($q)) { + list($rk) = dbFetchArray($q); + } else { + $q = $this->db->query("SELECT MAX(ranking) FROM ranking WHERE r_type=$type"); + list($mrk) = dbFetchArray($q); + if (is_null($mrk)) { + $rk = 1; + } else { + $q = $this->db->query("SELECT COUNT(*) FROM ranking WHERE r_type=$type AND ranking=$mrk"); + list($nrk) = dbFetchArray($q); + $rk = $mrk + $nrk; + } + } + $this->db->query("INSERT INTO ranking(r_type,id,additional,points,ranking) VALUES(" + . "$type,'" . addslashes($id) . "'," . (is_null($additional) ? "NULL" : ("'" . addslashes($additional) . "'")) . ",0,$rk)"); + } +} + +?> diff --git a/scripts/game/main/rankings/library/delete.inc b/scripts/game/main/rankings/library/delete.inc new file mode 100644 index 0000000..b0136e0 --- /dev/null +++ b/scripts/game/main/rankings/library/delete.inc @@ -0,0 +1,20 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($type,$id) { + $q = $this->db->query("SELECT ranking FROM ranking WHERE r_type=$type AND id='".addslashes($id)."'"); + list($rk) = dbFetchArray($q); + if (!is_null($rk)) { + $this->db->query("DELETE FROM ranking WHERE r_type=$type AND id='".addslashes($id)."'"); + $this->db->query("UPDATE ranking SET ranking=ranking-1 WHERE r_type=$type AND ranking>$rk"); + } + } +} + +?> diff --git a/scripts/game/main/rankings/library/getAll.inc b/scripts/game/main/rankings/library/getAll.inc new file mode 100644 index 0000000..15271dc --- /dev/null +++ b/scripts/game/main/rankings/library/getAll.inc @@ -0,0 +1,27 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($type, $top = null) { + if (is_null($top)) { + $qs = ""; + } else { + $qs = "AND ranking<=$top"; + } + $rs = array(); + + $q = $this->db->query("SELECT id,points,ranking FROM ranking WHERE r_type=$type $qs ORDER BY ranking ASC,id ASC"); + while ($r = dbFetchHash($q)) { + array_push($rs, $r); + } + + return $rs; + } +} + +?> diff --git a/scripts/game/main/rankings/library/update.inc b/scripts/game/main/rankings/library/update.inc new file mode 100644 index 0000000..2f0d315 --- /dev/null +++ b/scripts/game/main/rankings/library/update.inc @@ -0,0 +1,39 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($id, $data) { + // $data = array($points => array($name)); + + $plist = array_keys($data); + sort($plist); + $plist = array_reverse($plist); + + $a = array(); $cr = 1; + for ($i=0;$i $pn, + 'points' => $plist[$i], + 'ranking' => $cr + )); + $s ++; + } + $cr += $s; + } + + $this->db->query("DELETE FROM ranking WHERE r_type=$id"); + foreach ($a as $entry) { + $this->db->query("INSERT INTO ranking(r_type,id,additional,points,ranking) VALUES(" + . "$id, '" . addslashes($entry['name']) . "',NULL,".$entry['points'].",".$entry['ranking'].")"); + } + } +} + +?> diff --git a/scripts/game/main/ticks/day/library.inc b/scripts/game/main/ticks/day/library.inc new file mode 100644 index 0000000..dee0085 --- /dev/null +++ b/scripts/game/main/ticks/day/library.inc @@ -0,0 +1,150 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->main = $this->lib->game->getLib(); + $this->accounts = $this->lib->game->getLib("main/account"); + } + + function runTick() { + // Delete logs and update vacation credits + $this->db->safeTransaction(array($this, "updateStatus")); + // Manage inactive / leaving accounts + $this->db->safeTransaction(array($this, "terminateAccounts")); + + // Send mails if needed + foreach ($this->mails as $mail) { + if ($this->main->call('sendMail', $mail[0], $mail[1], $mail[2])) { + l::info($mail[3]); + } else { + l::warn("Failed to send mail {$mail[0]} to {$mail[1]}"); + } + } + } + + public function updateStatus() { + // Delete old logs + $this->db->query( + "DELETE FROM account_log " + . "WHERE UNIX_TIMESTAMP(NOW())-t>3600*24*60 AND action IN ('IN','OUT')" + ); + + // Add vacation credits + $this->db->query( + "UPDATE account SET vac_credits=vac_credits + 1 " + . "WHERE status='STD' AND vac_credits<240 AND vac_start IS NULL" + ); + } + + public function terminateAccounts() { + $this->mails = array(); + + // Send mails to accounts that have been inactive for 21 days + $this->warnInactive(); + + // Close really inactive accounts + $this->closeInactive(); + + // Close accounts on request + $this->closeQuitters(); + + // Delete accounts that haven't been confirmed yet + $this->deleteUnconfirmed(); + } + + + private function sendMail($file, $addr, $data, $log) { + array_push($this->mails, array($file, $addr, $data, $log)); + } + + + private function warnInactive() { + // Get accounts that have been inactive for 21 days + $q = $this->db->query( + "SELECT id, name, email FROM account " + . "WHERE NOT admin AND status='STD' " + . "AND last_logout IS NOT NULL AND last_logout > last_login " + . "AND (UNIX_TIMESTAMP(NOW()) - last_logout BETWEEN 24*3600*21 AND 24 * 3600 * 22)" + ); + while ($r = dbFetchArray($q)) { + list($id, $name, $addr) = $r; + + // Get the user's language + $l = $this->accounts->call('getLanguage', $id); + + // Send the warning e-mail + $this->sendMail("mail-warn-inactive.$l.txt", $addr, array("USER" => $name), + "main/day: inactivity warning mail sent to player $name"); + } + } + + + private function closeInactive() { + // Disable accounts that have been inactive for 28 days + $q = $this->db->query( + "SELECT id, name, email FROM account " + . "WHERE NOT admin AND status='STD' AND last_logout IS NOT NULL " + . "AND UNIX_TIMESTAMP(NOW()) - last_logout >= 24 * 3600 * 28" + ); + while ($r = dbFetchArray($q)) { + list($uid, $name, $addr) = $r; + $this->closeAccount($uid, $name, $addr, 'INAC', 'kick-inactive'); + } + } + + + private function closeQuitters() { + // Close accounts for players who requested to quit + $q = $this->db->query( + "SELECT id, name, email FROM account " + . "WHERE (status='STD' OR status='VAC') AND quit_ts IS NOT NULL " + . "AND UNIX_TIMESTAMP(NOW()) > quit_ts + 86400" + ); + while ($r = dbFetchArray($q)) { + list($uid, $name, $addr) = $r; + $this->closeAccount($uid, $name, $addr, 'QUIT', 'quit'); + } + } + + + private function closeAccount($uid, $name, $addr, $reason, $mail) { + // Get the user's language + $l = $this->accounts->call('getLanguage', $uid); + + // Send the termination e-mail + $this->sendMail("mail-$mail.$l.txt", $addr, array("USER" => $name), + "main/day: mail sent to player $name ($reason)"); + + // Terminate the account + $this->accounts->call('terminate', $uid, $reason); + } + + + private function deleteUnconfirmed() { + // Silently delete accounts that have the 'NEW' status and have been created more than 6 days ago + $q = $this->db->query( + "SELECT a.id FROM account a,account_log l " + . "WHERE a.status='NEW' AND l.account=a.id AND l.action='CREATE' " + . "AND UNIX_TIMESTAMP(NOW()) - l.t > 6 * 24 * 3600" + ); + $nd = array(); + while ($r = dbFetchArray($q)) { + array_push($nd, $r[0]); + } + if (count($nd)) { + $this->db->query("DELETE FROM account WHERE id IN (" . join(',', $nd) . ")"); + } + } +} + +?> diff --git a/scripts/game/main/ticks/deathofrats/library.inc b/scripts/game/main/ticks/deathofrats/library.inc new file mode 100644 index 0000000..38b85b7 --- /dev/null +++ b/scripts/game/main/ticks/deathofrats/library.inc @@ -0,0 +1,1468 @@ + array('VICIOUS-LP'), + 2 => array('VICIOUS-MP', 'VICIOUS-LP'), + 3 => array('VICIOUS-HP', 'VICIOUS-MP', 'VICIOUS-LP'), + ); + + /** Single player badness points, days at maximum score and days with + * decreasing score. + */ + private static $spBadness = array( + // Action => array( Points, MaxDays, DecDays ) + "PROXY" => array( 40, 30, 30 ), + "CLCOOK-SIP" => array( 5, 2, 1 ), + "CLCOOK-DIP" => array( 2, 2, 1 ), + "BAT" => array( 80, 60, 60 ) + ); + + /** Multiplayer badness points, days at maximum score and days with + * decreasing score. + */ + private static $mpBadness = array( + // Action => array( Points, MaxDays, DecDays ) + "SIMPLE" => array( 15, 6, 1 ), + "SIMPLE-10" => array( 18, 6, 1 ), + "VICIOUS-LP" => array( 15, 3, 3 ), + "VICIOUS-MP" => array( 30, 7, 7 ), + "VICIOUS-HP" => array( 50, 21, 15 ), + "PASS" => array( 90, 90, 90 ), + "NOPASS" => array( -20, 90, 30 ) + ); + + /** In-game badness points, days at maximum score and days with + * decreasing score. + */ + private static $igBadness = array( + // Action => array( Points, MaxDays, DecDays ) + "LSE" => array( 5, 3, 1 ), + "SE" => array( 10, 4, 3 ), + "HSE" => array( 15, 5, 5 ), + "VHSE" => array( 20, 8, 7 ) + ); + + private $lastExec; + private $now; + private $connections; + private $perAccount; + private $proxiedAccounts; + private $nEvents; + private $browserNames; + private $nChanges; + private $userPlays = array(); + + public function __construct($lib) { + $this->lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->db; + config::$main['debug'] = 2; + } + + public function runTick() { + $this->nEvents = $this->nChanges = 0; + $this->now = time(); + $this->browserNames = array(); + + // Fetch the time at which the Death of Rats was last activated + sleep(1); + $this->lastExec = $this->db->safeTransaction(array($this, "getLastExec")); + if ($this->lastExec == $this->now) { + l::notice("Not running the Death of Rats - last execution is now"); + return; + } + + // Execute account-level checks + $this->accountChecks(); + + // Execute in-game checks + $ingameChecks = $this->db->safeTransaction(array($this, 'getInGameCheckList')); + if (count($ingameChecks)) { + l::debug("Executing in-game checks"); + foreach ($ingameChecks as $pair) { + $this->db->safeTransaction(array($this, 'checkInGamePair'), $pair); + } + } + $finalPoints = $this->db->safeTransaction(array($this, 'finalizeInGameData')); + + // Take action if needed + foreach ($finalPoints as $id => $points) { + if ($points < 100) { + continue; + } + $accounts = explode(',', $id); + $this->db->safeTransaction(array($this, 'takeAction'), $accounts); + } + } + + /** This method performs account-level checks. + */ + private function accountChecks() { + // If the Death of Rats had never been activated, act as if it had + // been one hour earlier, and check accounts with identical + // password. + if (is_null($this->lastExec)) { + l::notice("First execution, checking identical passwords"); + $this->lastExec = $this->now - 3600; + $this->db->safeTransaction(array($this, 'checkIdenticalPassword')); + } else { + l::debug("Checking password changes"); + $this->nChanges += $this->db->safeTransaction(array($this, 'checkPassChange')); + } + + // For each player who logged in or created an account, check + // single-player suspicious behaviour. + $this->connections = $this->db->safeTransaction(array($this, 'getLastLogons')); + $this->nChanges += count($this->connections); + if (! $this->nChanges) { + l::info("No new records, skipping account-level checks."); + return; + } + + // If we had connection records... + if (!empty($this->connections)) { + l::debug("Analysing " . count($this->connections) . " new record(s)"); + $this->makePerAccountRecords(); + + // Start with open proxies + l::debug("Checking for open proxies ..."); + $this->checkOpenProxies(); + if (count($this->proxiedAccounts)) { + l::info("Logging " . count($this->proxiedAccounts) . " account(s) using open proxies"); + $this->db->safeTransaction(array($this, 'logOpenProxies')); + } + + // Now examine per-account entries to find different types of rats + l::debug("Checking single player badness"); + foreach ($this->perAccount as $records) { + $this->db->safeTransaction(array($this, 'checkSinglePlayer'), array($records)); + } + + // Check for different players logging on from the same IP and on the same tracking + l::debug("Checking simple multiing / pass sharing"); + $this->db->safeTransaction(array($this, 'checkSimpleMultis')); + + // Check for players who clear cookies before logging on to another account + l::debug("Checking vicious multiing / pass sharing"); + $this->db->safeTransaction(array($this, 'checkViciousMultis')); + } + + // Flush the main logs + $this->db->safeTransaction(array($this, 'finalizeMainData')); + } + + + /** This method returns the last execution time of the Death of Rats. + */ + public function getLastExec() { + $q = $this->db->query("SELECT ts FROM dor_exec ORDER BY ts DESC LIMIT 1"); + if (dbCount($q)) { + list($last) = dbFetchArray($q); + } else { + $last = null; + } + return $last; + } + + /** This method flushes both logs and adds the execution entry + */ + public function finalizeMainData() { + l::debug("Updating main Death of Rats status"); + $this->db->query( + "LOCK TABLE dor_exec, dor_single, dor_multi, pass_change, dor_single_points, dor_multi_points " + . "IN ACCESS EXCLUSIVE MODE" + ); + $this->flushSinglePlayerLog(); + $this->flushMultiPlayerLog(); + $this->db->query( + "INSERT INTO dor_exec (ts, entries, events) " + . "VALUES ({$this->now}, {$this->nChanges}, {$this->nEvents})" + ); + $this->db->query("DELETE FROM pass_change WHERE ts < {$this->now}"); + $this->db->query("DELETE FROM dor_exec WHERE ts < {$this->now} - 7 * 24 * 3600"); + + $spBadness = $this->computeSinglePlayerBadness(); + $this->computeMultiPlayerBadness($spBadness); + } + + /** This method flushes the in-game log and computes final badness + * points for each player. + */ + public function finalizeInGameData() { + l::debug("Updating Death of Rats status for in-game checks"); + $this->flushInGameLog(); + return $this->computeInGameBadness(); + } + + + /*********************************************************************** + * ACTIONS TO PERFORM ON POSITIVE DECISIONS * + ***********************************************************************/ + + /** Main action handler for a pair of accounts that have been + * positively identified by the Death of Rats. + */ + public function takeAction($account1, $account2) { + // Check and send warnings; return if no further + // action is required. + if ($this->checkWarnings($account1, $account2)) { + return; + } + + // Punish the accounts if needed + $this->checkPunishment($account1, $account2); + } + + + /** This function checks the status of two accounts on the "warning" + * angle of things. + */ + private function checkWarnings($account1, $account2) { + // Check if both accounts were warned + $q = $this->db->query( + "SELECT account1,MAX(ts) FROM dor_warning " + . "WHERE account1 IN ($account1, $account2) " + . "GROUP BY account1" + ); + if (dbCount($q) == 0) { + // Both accounts need a warning + $this->sendWarning($account1, $account2); + $this->sendWarning($account2, $account1); + return true; + } elseif (dbCount($q) == 2) { + // Both accounts have been warned, check the grace + // period. + list($account, $mts) = dbFetchArray($q); + list($account, $mts2) = dbFetchArray($q); + $mts = ($mts > $mts2) ? $mts : $mts2; + return ($this->now - $mts > self::postWarningGrace * 86400); + } + + // One of the accounts has never been warned, send him a warning + list($account, $mts) = dbFetchArray($q); + if ($account == $account1) { + $this->sendWarning($account2, $account1); + } else { + $this->sendWarning($account1, $account2); + } + + return true; + } + + + /** This method sends a warning to a player about him being flagged with + * another player. + */ + private function sendWarning($account1, $account2) { + l::trace(" Warning account #$account1"); + $this->db->query( + "INSERT INTO dor_warning (account1, account2, ts) " + . "VALUES ($account1, $account2, {$this->now})" + ); + // FIXME: should send messages in all games they play together + } + + + /** This method checks whether two accounts should be punished. + */ + private function checkPunishment($account1, $account2) { + $q = $this->db->query( + "SELECT account FROM dor_punishment " + . "WHERE account IN ($account1, $account2) " + . "AND {$this->now} - ts < " . self::punishmentDays * 86400 + ); + + if (dbCount($q) >= 2) { + // Both are already punished + return; + } elseif (dbCount($q) == 0) { + // Punish both accounts + $this->punish($account1, $account2); + $this->punish($account2, $account1); + return; + } + + // Punish the account that is still unpunished + list($account) = dbFetchArray($q); + $this->punish($account == $account1 ? $account2 : $account1, + $account == $account1 ? $account1 : $account2); + } + + + /** This method punishes a player for abusive pass sharing or multiing + * with another player. + */ + private function punish($account1, $account2) { + l::trace(" PUNISHING ACCOUNT #$account1"); + $this->db->query( + "INSERT INTO dor_punishment (account, other_account, ts) " + . "VALUES ($account1, $account2, {$this->now})" + ); + // FIXME: should send messages in all games they play together + } + + + /*********************************************************************** + * BADNESS POINTS COMPUTATION * + ***********************************************************************/ + + private function computeSinglePlayerBadness() { + // Generate the array of query parts + $qParts = array(); + foreach (self::$spBadness as $type => $data) { + array_push($qParts, "(message = '$type' AND {$this->now} - ts < " + . (24 * 3600 * ($data[1] + $data[2])) . ")"); + } + + // Retrieve entries + $qString = "SELECT * FROM dor_single WHERE " . join(" OR ", $qParts) . " ORDER BY ts"; + $q = $this->db->query($qString); + + // Compute points + $points = array(); + while ($r = dbFetchHash($q)) { + $pPoints = $points[$r['account']]; + + $badness = self::$spBadness[$r['message']]; + $bPoints = $badness[0]; + $time = $this->now - $r['ts']; + + if ($time > $badness[1] * 24 * 3600) { + $time -= $badness[1] * 24 * 3600; + $time = ($badness[2] * 24 * 3600) - $time; + $bPoints = round($bPoints * $time / ($badness[2] * 24 * 3600)); + } + + $points[$r['account']] = max(0, $pPoints + $bPoints); + } + + // Insert points + $spData = new db_copy("dor_single_points", db_copy::copyToClean); + $spData->setAccessor($this->db); + foreach ($points as $account => $bPoints) { + if ($bPoints == 0) { + continue; + } + $spData->appendRow(array($account, $bPoints)); + } + $spData->execute(); + + return $points; + } + + + private function computeMultiPlayerBadness($spBadness) { + // Generate the array of query parts + $qParts = array(); + foreach (self::$mpBadness as $type => $data) { + array_push($qParts, "(message = '$type' AND {$this->now} - ts < " + . (24 * 3600 * ($data[1] + $data[2])) . ")"); + } + + // Retrieve entries + $qString = "SELECT * FROM dor_multi WHERE " . join(" OR ", $qParts) . " ORDER BY ts"; + $q = $this->db->query($qString); + + // Compute points + $points = array(); + while ($r = dbFetchHash($q)) { + $id = $r['account1'] . ',' . $r['account2']; + $pPoints = $points[$id]; + + $badness = self::$mpBadness[$r['message']]; + $bPoints = $badness[0]; + $time = $this->now - $r['ts']; + + if ($time > $badness[1] * 24 * 3600) { + $time -= $badness[1] * 24 * 3600; + $time = ($badness[2] * 24 * 3600) - $time; + $bPoints = round($bPoints * $time / ($badness[2] * 24 * 3600)); + } + + $points[$id] = max(0, $pPoints + $bPoints); + } + + // Add single player badness to records + foreach ($points as $id => $bPoints) { + $accounts = explode(',', $id); + $bPoints += ceil(($spBadness[$accounts[0]] + $spBadness[$accounts[1]]) / 2); + $points[$id] = $bPoints; + } + + // Insert points + $mpData = new db_copy("dor_multi_points", db_copy::copyToClean); + $mpData->setAccessor($this->db); + foreach ($points as $id => $bPoints) { + if ($bPoints == 0) { + continue; + } + $accounts = explode(',', $id); + $mpData->appendRow(array($accounts[0], $accounts[1], $bPoints)); + } + $mpData->execute(); + } + + + private function computeInGameBadness() { + // Generate the array of query parts + $qParts = array(); + foreach (self::$igBadness as $type => $data) { + array_push($qParts, "(message LIKE '$type-%' AND {$this->now} - ts < " + . (24 * 3600 * ($data[1] + $data[2])) . ")"); + } + + // Retrieve entries + $qString = "SELECT * FROM dor_ingame_check WHERE " . join(" OR ", $qParts) . " ORDER BY ts"; + $q = $this->db->query($qString); + + // Compute points + $points = array(); + while ($r = dbFetchHash($q)) { + $id = $r['account1'] . ',' . $r['account2']; + $pPoints = $points[$id]; + + list($message, $count) = explode("-", $r['message']); + + $badness = self::$igBadness[$message]; + $bPoints = $badness[0] * $count; + $time = $this->now - $r['ts']; + + if ($time > $badness[1] * 24 * 3600) { + $time -= $badness[1] * 24 * 3600; + $time = ($badness[2] * 24 * 3600) - $time; + $bPoints = round($bPoints * $time / ($badness[2] * 24 * 3600)); + } + + $points[$id] = max(0, $pPoints + $bPoints); + } + + // Insert points + $igData = new db_copy("dor_final_points", db_copy::copyToClean); + $igData->setAccessor($this->db); + foreach ($points as $id => $bPoints) { + if ($bPoints == 0) { + continue; + } + $accounts = explode(',', $id); + $igData->appendRow(array($accounts[0], $accounts[1], $bPoints)); + } + $igData->execute(); + + return $points; + } + + + /*********************************************************************** + * VARIOUS HELPER METHODS * + ***********************************************************************/ + + /** This method compares two IP addresses. It returns 2 if they are equal, + * 1 if they are on the same /24, or 0 if they don't match at all. + */ + static private function ipSimilarity($ip1, $ip2) { + if ($ip1 == $ip2) { + return 2; + } + + $l = strrpos($ip1, "."); + if (is_bool($l)) { + l::notice("IP address without dots, that's weird ... IP was \"$ip1\""); + return 0; + } + + if (substr($ip1, 0, $l + 1) == substr($ip2, 0, $l + 1)) { + return 1; + } + return 0; + } + + + /** This method returns the name of the browser associated with a + * tracking cookie ID. + */ + private function getBrowserName($id) { + if (is_null($this->browserNames[$id])) { + $q = $this->db->query("SELECT browser FROM web_tracking WHERE id = $id"); + list($this->browserNames[$id]) = dbFetchArray($q); + } + return $this->browserNames[$id]; + } + + + /*********************************************************************** + * PASSWORDS * + ***********************************************************************/ + + /** This method checks the list of accounts for identical passwords. It is + * only called once, the first time the Death of Rats is started. + */ + public function checkIdenticalPassword() { + $q = $this->db->query( + "SELECT password FROM account " + . "WHERE status IN ('STD', 'VAC', 'NEW') " + . "GROUP BY password " + . "HAVING COUNT(*) > 1" + ); + while ($r = dbFetchArray($q)) { + $qId = $this->db->query( + "SELECT id FROM account " + . "WHERE password = '" . addslashes($r[0]) . "' " + . "AND status IN ('STD', 'VAC', 'NEW')" + ); + l::notice("Found " . dbCount($qId) . " accounts with password '{$r[0]}'"); + + $accounts = array(); + while ($rID = dbFetchArray($qId)) { + array_push($accounts, $rID[0]); + } + + for ($i = 0; $i < count($accounts) - 1; $i ++) { + for ($j = $i + 1; $j < count($accounts); $j ++) { + $this->multiPlayerLog("PASS", $accounts[$i], $accounts[$j]); + } + } + } + } + + /** This method checks for players changing their password to another + * player's password. + */ + public function checkPassChange() { + // Get all password changes + $q = $this->db->query("SELECT * FROM pass_change ORDER BY ts"); + if (!dbCount($q)) { + return 0; + } + + // Create per-account changes list + $accounts = array(); + while ($r = dbFetchHash($q)) { + if (!is_array($accounts[$r['account']])) { + $accounts[$r['account']] = array("changes" => array()); + } + if (is_null($accounts[$r['account']]['initial'])) { + $accounts[$r['account']]['initial'] = addslashes($r['old_pass']); + } + $pass = addslashes($r['new_pass']); + $accounts[$r['account']]['last'] = $pass; + } + + // For each account, verify the changes + foreach ($accounts as $account => $data) { + if ($data['initial'] == $data['last']) { + continue; + } + + // Get accounts that use these passwords + $passwords = array(); + if ($data['initial'] != '') { + $passwords[] = $data['initial']; + } + $passwords[] = $data['last']; + + $q = $this->db->query( + "SELECT id, password FROM account " + . "WHERE id <> $account AND password IN ('" . join("','", $passwords) . "')" + ); + if (!dbCount($q)) { + continue; + } + $otherList = array(); + while ($r = dbFetchArray($q)) { + $r[1] = addslashes($r[1]); + if (!is_array($otherList[$r[1]])) { + $otherList[$r[1]] = array($r[0]); + } else { + $otherList[$r[1]][] = $r[0]; + } + } + + // If the old password was used by another account, mark it + if ($data['initial'] != '' && is_array($otherList[$data['initial']])) { + foreach ($otherList[$data['initial']] as $account2) { + $this->multiPlayerLog("NOPASS", $account, $account2); + } + } + + // If the new password is used by another account, mark it + if (is_array($otherList[$data['last']])) { + foreach ($otherList[$data['last']] as $account2) { + $this->multiPlayerLog("PASS", $account, $account2); + } + } + } + + return count($accounts); + } + + + /*********************************************************************** + * ACCESSING PREVIOUS RECORDS * + ***********************************************************************/ + + /** This method lists the latest entries from the account_log table. + */ + public function getLastLogons() { + $q = $this->db->query( + "SELECT account, tracking, ip_addr, action, t FROM account_log " + . "WHERE action IN ('IN', 'OUT', 'CREATE') " + . "AND t > {$this->lastExec} AND t <= {$this->now}" + . "ORDER BY t" + ); + $records = array(); + while ($r = dbFetchHash($q)) { + array_push($records, $r); + } + return $records; + } + + + /** This method goes through all records read from the database and + * creates separate lists for each account. + */ + public function makePerAccountRecords() { + $paRecords = array(); + + foreach ($this->connections as $record) { + if (!is_array($paRecords[$record['account']])) { + $paRecords[$record['account']] = array(); + } + array_push($paRecords[$record['account']], $record); + } + + $this->perAccount = $paRecords; + } + + + /*********************************************************************** + * OPEN PROXIES * + ***********************************************************************/ + + /** This method checks for open proxies in the latest log entries. + */ + private function checkOpenProxies() { + $IPs = array(); + + // Make lists of accounts for each IP + foreach ($this->connections as $record) { + if ($record['ip_addr'] == 'AUTO' || $record['action'] == 'OUT') { + continue; + } + $ip = $record['ip_addr']; + $account = $record['account']; + if (!is_array($IPs[$ip])) { + $IPs[$ip] = array($account); + } elseif (!in_array($account, $IPs[$ip])) { + array_push($IPs[$ip], $account); + } + } + + // Check for proxies on the IPs + $requests = array(); + $proxies = array(); + foreach (array_keys($IPs) as $ip) { + if (count($requests) < 20) { + array_push($requests, $ip); + continue; + } + + try { + $results = pcheck::check($requests); + } catch (Exception $e) { + l::error("Failed to check some addresses for open proxies"); + l::info($e->getMessage()); + return; + } + + foreach ($results as $host => $status) { + if ($status == 1) { + array_push($proxies, $host); + } + } + + $requests = array(); + } + + // If there are some requests we didn't execute, do it + if (count($requests)) { + try { + $results = pcheck::check($requests); + } catch (Exception $e) { + l::error("Failed to check some addresses for open proxies"); + l::info($e->getMessage()); + return; + } + + foreach ($results as $host => $status) { + if ($status == 1) { + array_push($proxies, $host); + } + } + } + + // Check for proxied accounts + $proxyAccounts = array(); + foreach ($proxies as $ip) { + foreach ($IPs[$ip] as $account) { + if (in_array($account, $proxyAccounts)) { + continue; + } + array_push($proxyAccounts, $account); + } + } + + $this->proxiedAccounts = $proxyAccounts; + } + + + /** This method logs access to accounts using open proxies. A log + * entry is only added every 24h. + */ + public function logOpenProxies() { + // Get all recent open proxy logs + $this->db->query( + "SELECT account FROM dor_single " + . "WHERE message = 'PROXY' AND {$this->now} - ts < 86400" + ); + $recent = array(); + while ($r = dbFetchArray($q)) { + $recent[] = $r[0]; + } + + // Insert proxy logs + foreach ($this->proxiedAccounts as $account) { + if (in_array($account, $recent)) { + continue; + } + $this->singlePlayerLog("PROXY", $account); + } + } + + + /*********************************************************************** + * SINGLE PLAYER SUSPICIOUS BEHAVIOUR * + ***********************************************************************/ + + public function checkSinglePlayer($records) { + // Ignore accounts that have already been kicked + $q = $this->db->query( + "SELECT status FROM account WHERE id = {$records[0]['account']}" + ); + list($status) = dbFetchArray($q); + if ($status == 'KICKED') { + return; + } + l::trace("CHECKING SINGLE PLAYER {$records[0]['account']}"); + + // Check log-ons for people who have just tripped the "banned log-in" detector + $baDetected = false; + foreach ($records as $record) { + if ($record['action'] == 'OUT') { + continue; + } + if ($record['action'] == 'CREATE') { + $time = 300; + } else { + $time = 120; + } + $q = $this->db->query( + "SELECT * FROM banned_attempt " + . "WHERE ip_addr = '{$record['ip_addr']}' AND ts <= {$record['t']} " + . "AND {$record['t']} - ts <= $time" + ); + if (dbCount($q)) { + $baDetected = true; + break; + } + } + + // Banned log-in attempt detected, check for a similar record in the past 2 weeks + if ($baDetected) { + $q = $this->db->query( + "SELECT * FROM dor_single " + . "WHERE account = {$records[0]['account']} AND message = 'BAT' " + . "AND {$this->now} - ts < 14 * 24 * 3600" + ); + if (!dbCount($q)) { + $this->singlePlayerLog('BAT', $records[0]['account']); + } + } + + // If the first record isn't the account's creation, get + // the previous record + if ($records[0]['action'] != 'CREATE') { + $q = $this->db->query( + "SELECT account, tracking, ip_addr, action, t FROM account_log " + . "WHERE account = {$records[0]['account']} AND t <= {$this->lastExec} " + . "AND (action = 'IN' OR action = 'CREATE')" + . "ORDER BY t DESC LIMIT 1" + ); + if (dbCount($q)) { + array_unshift($records, dbFetchHash($q)); + l::trace(" Found previous action with tracking {$records[0]['tracking']}"); + } + } + + // For each couple of records in the list, check for cookie deletion + $lastExam = null; + for ($i = 1; $i < count($records); $i ++) { + if (is_null($records[$i]['tracking']) || $records[$i]['ip_addr'] == 'AUTO') { + continue; + } + l::trace(" Checking record with tracking ID {$records[$i]['tracking']} (action '" + . "{$records[$i]['action']}')"); + + // Find previous valid record + for ($j = $i - 1; $j >= 0; $j --) { + if ($records[$j]['action'] == 'OUT' && $records[$j]['ip_addr'] == 'AUTO' + || is_null($records[$j]['tracking']) + || $records[$j]['tracking'] == $records[$i]['tracking']) { + continue; + } + break; + } + if ($j < 0 || $j === $lastExam) { + continue; + } + $lastExam = $j; + + l::trace(" Found suspicious records: {$records[$j]['tracking']} / {$records[$i]['tracking']}"); + + // Check IP address + $ipCheck = self::ipSimilarity($records[$j]['ip_addr'], $records[$i]['ip_addr']); + l::trace(" IP similarity: $ipCheck"); + if ($ipCheck == 0) { + continue; + } + + // Get browser string for each tracking ID + $browser1 = $this->getBrowserName($records[$j]['tracking']); + $browser2 = $this->getBrowserName($records[$i]['tracking']); + $leven = levenshtein($browser1, $browser2); + l::trace(" Browser similarity: $leven"); + if ($leven > 10) { + // *Probably* 2 different browsers, skip + continue; + } + + // Check for earlier records that would contradict cookie deletion + $q = $this->db->query( + "SELECT COUNT(*) FROM account_log " + . "WHERE t < {$records[$j]['t']} AND tracking = {$records[$i]['tracking']}" + ); + list($count) = dbFetchArray($q); + if ($count > 0) { + l::trace(" Found older uses of tracking ID {$records[$i]['tracking']}" + . ", multiple computers"); + continue; + } + + l::trace(" ADDING RECORD"); + $this->singlePlayerLog($ipCheck == 2 ? "CLCOOK-SIP" : "CLCOOK-DIP", $records[$i]['account']); + } + } + + + /********************************************** + * MULTI PLAYERS DETECTION * + **********************************************/ + + /** This method gets all tracking cookie IDs used in the records, and + * generates an "extended" record including the previous X actions on + * each tracking record. + */ + private function extendRecord($nActions = 1) { + // Get the list of all tracking cookies used in the log + $tracking = array(); + foreach ($this->connections as $record) { + if (is_null($record['tracking'])) { + continue; + } + if (!in_array($record['tracking'], $tracking)) { + $tracking[] = $record['tracking']; + } + } + + if (!empty($tracking)) { + // First we need to get the previous record for all tracking cookies + $q = $this->db->query( + "SELECT account, tracking, ip_addr, action, t FROM account_log " + . "WHERE tracking IN (" . join(',', $tracking) . ") " + . "AND t <= {$this->lastExec} " + . "AND action IN ('IN', 'CREATE', 'OUT') " + . "ORDER BY t DESC" + ); + + // Create the list of records to merge into the log fragment + $extraRecords = array(); + $okTrackings = array(); + while (count($extraRecords) < $nActions * count($tracking) && $r = dbFetchHash($q)) { + if ($okTrackings[$r['tracking']] == $nActions) { + continue; + } + $extraRecords[] = $r; + $okTrackings[$r['tracking']] ++; + } + } else { + $extraRecords = array(); + } + return array_merge(array_reverse($extraRecords), $this->connections); + } + + + /** This method checks for the simplest type of multi accounts, + * but also catches occasional pass-sharers. + */ + public function checkSimpleMultis() { + // Get the extra actions we need + $connections = $this->extendRecord(); + + // Extract the guys who got caught logging in on the same browser + // from the list of records. + $caught = array(); + foreach (array_keys($this->perAccount) as $account) { + // Find all tracking IDs used by this account + l::trace("CHECKING ACCOUNT $account"); + $aTrack = array(); + foreach ($connections as $record) { + if (!is_null($record['tracking']) && $record['account'] == $account + && !in_array($record['tracking'], $aTrack)) { + $aTrack[] = $record['tracking']; + } + } + + // Find all other accounts who have a record with the same tracking + foreach ($aTrack as $tracking) { + l::trace(" Checking tracking #$tracking"); + $lastUse = null; $prevAccount = null; + foreach ($connections as $record) { + if ($tracking != $record['tracking']) { + continue; + } + if (is_null($lastUse)) { + $lastUse = $record['t']; + $prevAccount = $record['account']; + l::trace(" Last tracking use by $prevAccount"); + continue; + } + + if ($record['account'] != $prevAccount) { + l::trace(" Tracking used by different account, {$record['account']}"); + $bothAccounts = array($record['account'], $prevAccount); + sort($bothAccounts); + $bothAccounts = join(',', $bothAccounts); + $time = ($record['t'] - $lastUse <= 10) ? 2 : 1; + if ($time > $caught[$bothAccounts]) { + $caught[$bothAccounts] = $time; + } + $prevAccount = $record['account']; + } + $lastUse = $record['t']; + } + } + } + + // Adds the log entries + $nFound = 0; + foreach ($caught as $accounts => $type) { + list($a1, $a2) = explode(',', $accounts); + $nFound ++; + $this->multiPlayerLog($type == 2 ? "SIMPLE-10" : "SIMPLE", $a1, $a2); + } + if ($nFound) { + l::notice("$nFound simple multiing / pass sharing attempt(s) found"); + } + } + + + /** This method checks for vicious multi accounts: people who clear + * cookies before logging on to other accounts. To perform this check, + * the game will start by looking up 'IN' records from any account using + * a different tracking cookie ID but the same browser and a close IP + * address. For each different account found, it will check the sequence + * of tracking cookies used. + */ + public function checkViciousMultis() { + $caught = array(); + foreach ($this->perAccount as $account => $records) { + l::trace(" Verifying account $account"); + for ($i = 0; $i < count($records); $i ++) { + $recPair = $this->findInterestingPair($records, $i); + if (is_null($recPair)) { + continue; + } + list($R1, $R2) = $recPair; + l::trace(" Found pair of records: {$R1['tracking']} vs {$R2['tracking']} (" + . gmstrftime("%H:%M:%S %Y-%m-%d", $R1['t']) . " / " + . gmstrftime("%H:%M:%S %Y-%m-%d", $R2['t']) . ")"); + + // Get the list of logons between R1 and R2 and + // presenting ... troubling ... similarities + $R3List = $this->findIntermediaryLogons($R1, $R2); + if (empty($R3List)) { + continue; + } + + // Check the records in the list of intermediary + // logons. + foreach ($R3List as $R3) { + $caughtID = array($account, $R3['account']); + sort($caughtID); + $caughtID = join(',', $caughtID); + if ($caught[$caughtID] == 3) { + // We already caught this pair as + // VICIOUS-HP, don't recheck + continue; + } + + l::trace(" Checking triplet with record from account {$R3['account']} " + . "(tracking {$R3['tracking']})"); + $check = $this->checkViciousTriplet($R1, $R2, $R3); + if ($caught[$caughtID] < $check) { + $caught[$caughtID] = $check; + } + } + } + } + + // For each guy we caught, add an entry to the log if one doesn't exist + // in the past 6 hours with a probability at least as high. + $nVicious = 0; + foreach ($caught as $ids => $level) { + list($id1, $id2) = explode(',', $ids); + $levels = self::$viciousLevels[$level]; + + $q = $this->db->query( + "SELECT COUNT(*) FROM dor_multi " + . "WHERE account1 = $id1 AND account2 = $id2 " + . "AND message IN ('" . join("','", $levels) . "') " + . "AND {$this->now} - ts <= 6 * 3600" + ); + list($flagged) = dbFetchArray($q); + if ($flagged > 0) { + continue; + } + + $nVicious ++; + $this->multiPlayerLog($levels[0], $id1, $id2); + } + if ($nVicious) { + l::notice("$nVicious vicious multiing attempt(s) found"); + } + } + + + /** This method scans the log for two records from the same player, + * starting at the $i-th record in the list, where the IP is close, + * the browser is similar and the tracking is different. + */ + private function findInterestingPair($records, $i) { + $record = $records[$i]; + if ($record['action'] != 'IN') { + return null; + } + + // Try getting a previous record that matches + $found = null; + for ($j = $i - 1; $j >= 0; $j --) { + $oRecord = $records[$j]; + if ($oRecord['tracking'] == $record['tracking']) { + // Found a previous record with the same tracking cookie + return null; + } + + if ($oRecord['action'] != 'IN' || ! self::ipSimilarity($record['ip_addr'], $oRecord['ip_addr']) + || $oRecord['tracking'] == $record['tracking']) { + continue; + } + + $browser1 = $this->getBrowserName($record['tracking']); + $browser2 = $this->getBrowserName($oRecord['tracking']); + if ($browser1 != $browser2 && levenshtein($brower1, $browser2) > 10) { + // *Probably* 2 different browsers, skip + continue; + } + + $found = $oRecord; + break; + } + + // If we didn't find a matching record in the list, try finding + // one from the database + if (is_null($found)) { + $classC = substr($record['ip_addr'], 0, strrpos($record['ip_addr'], '.') - 1) . "%"; + $q = $this->db->query( + "SELECT account, tracking, ip_addr, action, t FROM account_log " + . "WHERE account = {$record['account']} AND action = 'IN' " + . "AND tracking <> {$record['tracking']} AND tracking IS NOT NULL " + . "AND t < {$record['t']} AND ip_addr LIKE '$classC' " + . "ORDER BY t DESC LIMIT 10" + ); + if (!dbCount($q)) { + // Nothing found, return + } + while ($oRecord = dbFetchHash($q)) { + // Check browsers + $browser1 = $this->getBrowserName($record['tracking']); + $browser2 = $this->getBrowserName($oRecord['tracking']); + if ($browser1 != $browser2 && levenshtein($brower1, $browser2) > 10) { + // *Probably* 2 different browsers, skip + continue; + } + + // Check if there are intermediary records + $q2 = $this->db->query( + "SELECT COUNT(*) FROM account_log " + . "WHERE account = {$record['account']} AND action = 'IN' " + . "AND tracking = {$record['tracking']} " + . "AND t < {$record['t']} AND t > {$oRecord['t']}" + ); + if (dbCount($q2)) { + continue; + } + + $found = $oRecord; + break; + } + + if (is_null($found)) { + return null; + } + } + + return array($record, $found); + } + + + /** This method checks the database for logons that present the + * following characteristics compared to R1 and R2: + * R3.account != R1.account + * R3.ip close to R1.ip + * R3.browser =~ R1.browser + * R3.tracking != R1.tracking + * R3.tracking != R2.tracking + * R3.timestamp BETWEEN R1.timestamp AND R2.timestamp + */ + private function findIntermediaryLogons($R1, $R2) { + // Get all logons matching the specifications, except + // for the part about the browser which must be checked + // manually. + $classC = substr($record['ip_addr'], 0, strrpos($record['ip_addr'], '.') - 1) . "%"; + $q = $this->db->query( + "SELECT account, tracking, ip_addr, action, t FROM account_log " + . "WHERE account <> {$R1['account']} AND tracking <> {$R1['tracking']} " + . "AND tracking <> {$R2['tracking']} AND ip_addr LIKE '$classC' " + . "AND t > {$R2['t']} AND t < {$R1['t']}" + ); + if (!dbCount($q)) { + return array(); + } + + // Check the browser names + $results = array(); + $browser1 = $this->getBrowserName($R1['tracking']); + while ($R3 = dbFetchHash($q)) { + $browser2 = $this->getBrowserName($R3['tracking']); + if ($browser1 == $browser2 || levenshtein($browser1, $browser2) <= 10) { + array_push($results, $R3); + } + } + + return $results; + } + + + /** This method verifies a triplet of records and tries to determine + * whether they were attempts at "vicious multiing". + */ + public function checkViciousTriplet($R1, $R2, $R3) { + // Check if there are previous records R4 such as: + // R4.account = R3.account + // R4.tracking = R3.tracking + // R4.timestamp < R2.timestamp + $q = $this->db->query( + "SELECT COUNT(*) FROM account_log " + . "WHERE account = {$R3['account']} AND tracking = {$R3['tracking']} " + . "AND t < {$R3['t']}" + ); + list($R4Exists) = dbFetchArray($q); + if ($R4Exists > 0) { + return 0; + } + + // Get browser names + $browser1 = $this->getBrowserName($R1['tracking']); + $browser2 = $this->getBrowserName($R2['tracking']); + $browser3 = $this->getBrowserName($R3['tracking']); + + // Compute browser similarities for (R1,R2) and (R1,R3) + $brSim12 = ($browser1 == $browser2) ? 2 : ((levenshtein($browser1, $browser2) <= 10) ? 1 : 0); + $brSim13 = ($browser1 == $browser3) ? 2 : ((levenshtein($browser1, $browser3) <= 10) ? 1 : 0); + $brSim23 = ($browser2 == $browser3) ? 2 : ((levenshtein($browser2, $browser3) <= 10) ? 1 : 0); + + // Compute IP similarities for (R1,R2) and (R1,R3) + $ipSim12 = self::ipSimilarity($R1['ip_addr'], $R2['ip_addr']); + $ipSim13 = self::ipSimilarity($R1['ip_addr'], $R3['ip_addr']); + $ipSim23 = self::ipSimilarity($R2['ip_addr'], $R3['ip_addr']); + + if ($ipSim12 == 2 && $ipSim13 != 2 || $brSim12 == 2 && $brSim13 != 2) { + return 0; // Probably not vicious multiing + } elseif ($ipSim12 == 2 && $ipSim13 == 2 && $brSim12 == 2 && $brSim13 == 2) { + return 3; // High probability + } elseif ($ipSim12 == 1 && ($ipSim13 == 2 || $ipSim23 == 2) && $brSim12 == 2 && $brSim13 == 2) { + return 3; // High probability + } elseif ($ipSim12 == 1 && ($ipSim13 == 2 || $ipSim23 == 2) && $brSim12 == 1 + && ($brSim13 == 2 || $brSim23 == 2)) { + return 2; // Medium probability + } else { + return 1; // Low probability + } + } + + + /********************************************** + * IN-GAME CHECKS * + **********************************************/ + + /** This method lists players who should be further investigated + * by the Death of Rats. + */ + public function getInGameCheckList() { + // Get all pairs of players which have a score higher than 75 + $q = $this->db->query( + "SELECT account1, account2 FROM dor_multi_points WHERE points >= 75" + ); + if (!dbCount($q)) { + return array(); + } + + // Create a unique list + $potentialChecks = array(); + $uniquePlayers = array(); + while ($r = dbFetchArray($q)) { + sort($r); + $id = join(',', $r); + if (! array_key_exists($id, $potentialChecks)) { + $potentialChecks[$id] = $r; + } + array_push($uniquePlayers, $r[0]); + array_push($uniquePlayers, $r[1]); + } + $uniquePlayers = array_unique($uniquePlayers); + + // Get accounts that are no longer active + $q = $this->db->query( + "SELECT id FROM account " + . "WHERE id IN (" . join(',', $uniquePlayers) . ") " + . "AND status NOT IN ('STD', 'VAC')" + ); + $noCheckPlayers = array(); + while ($r = dbFetchArray($q)) { + $noCheckPlayers[] = $r[0]; + } + + // Check accounts that have been punished recently + $q = $this->db->query( + "SELECT account FROM dor_punishment " + . "WHERE account IN (" . join(',', $uniquePlayers) . ") " + . "AND {$this->now} - ts <= " . (self::punishmentDays * 86400) + ); + while ($r = dbFetchArray($q)) { + $noCheckPlayers[] = $r[0]; + } + + // Check pairs that have been warned recently + $q = $this->db->query( + "SELECT account1, account2 FROM dor_warning " + . "WHERE {$this->now} - ts <= " . (self::postWarningGrace * 86400) + ); + $noCheckPairs = array(); + while ($r = dbFetchArray($q)) { + $noCheckPairs[] = join(',', $r); + } + + // Get the list of recent checks on players + $q = $this->db->query( + "SELECT account1, account2 FROM dor_ingame_check " + . "WHERE {$this->now} - ts <= " . (self::inGameCheckDelay * 3600) + . "AND message = 'CHECK' " + . "GROUP BY account1, account2" + ); + while ($r = dbFetchArray($q)) { + $noCheckPairs[] = join(',', $r); + } + + // Create the list of pairs to check + $noCheckPlayers = array_unique($noCheckPlayers); + $noCheckPairs = array_unique($noCheckPairs); + $checkPairs = array(); + foreach ($potentialChecks as $id => $accounts) { + if (in_array($id, $noCheckPairs) || in_array($accounts[0], $noCheckPlayers) + || in_array($accounts[1], $noCheckPlayers)) { + continue; + } + array_push($checkPairs, $accounts); + } + + return $checkPairs; + } + + + /** This method checks a pair of accounts for in-game suspicious behaviour. + */ + public function checkInGamePair($account1, $account2) { + foreach (config::getGames() as $game) { + if ($game->name == 'main' || ($game->status() != 'RUNNING' && $game->status() != 'ENDING')) { + continue; + } + + if (! ($p1 = $this->doesUserPlay($game, $account1)) + || ! ($p2 = $this->doesUserPlay($game, $account2))) { + continue; + } + + // Get events and log them + $events = $this->runGameChecks($game, $p1, $p2); + if (count($events) > 1) { + l::trace(" Found " . (count($events) - 1) . " suspicious event(s)"); + } + foreach ($events as $eType) { + $this->inGameLog($game->name, $eType, $account1, $account2); + } + } + } + + /** This method checks whether an account plays a specific game. + */ + private function doesUserPlay($game, $account) { + if (is_null($this->userPlays[$game->name . "-" . $account])) { + $this->userPlays[$game->name . "-" . $account] = $game->getLib()->call( + 'doesUserPlay', $account + ); + } + return $this->userPlays[$game->name . "-" . $account]; + } + + /** This method runs checks on a pair of players in a specific game. + */ + private function runGameChecks($game, $p1, $p2) { + l::trace(" Investigating players #$p1 and #$p2 in game \"" . $game->text . "\""); + return $game->getLib()->call('investigate', $p1, $p2, + $this->now, self::inGameCheckDelay * 3600); + } + + + /********************************************** + * LOG METHODS FOR PLAYER ENTRIES * + **********************************************/ + + private function initSinglePlayerLog() { + if (!is_null($this->spLog)) { + return; + } + $this->spLog = new db_copy("dor_single", db_copy::copyTo); + $this->spLog->setAccessor($this->db); + } + + private function singlePlayerLog($logType, $id) { + $this->initSinglePlayerLog(); + $this->spLog->appendRow(array( + $logType, $id, $this->now + )); + $this->nEvents ++; + } + + private function flushSinglePlayerLog() { + if (is_null($this->spLog)) { + return; + } + $this->spLog->execute(); + $this->spLog = null; + } + + /********************************************** + * LOG METHODS FOR PLAYER/PLAYER ENTRIES * + **********************************************/ + + private function initMultiPlayerLog() { + if (!is_null($this->mpLog)) { + return; + } + $this->mpLog = new db_copy("dor_multi", db_copy::copyTo); + $this->mpLog->setAccessor($this->db); + } + + private function multiPlayerLog($logType, $id1, $id2) { + $this->initMultiPlayerLog(); + $this->mpLog->appendRow(array( + $logType, $id1, $id2, $this->now + )); + $this->mpLog->appendRow(array( + $logType, $id2, $id1, $this->now + )); + $this->nEvents ++; + } + + private function flushMultiPlayerLog() { + if (is_null($this->mpLog)) { + return; + } + $this->mpLog->execute(); + $this->mpLog = null; + } + + /********************************************** + * LOG METHODS FOR IN-GAME CHECKS * + **********************************************/ + + private function initInGameLog() { + if (!is_null($this->igLog)) { + return; + } + $this->igLog = new db_copy("dor_ingame_check", db_copy::copyTo); + $this->igLog->setAccessor($this->db); + } + + private function inGameLog($game, $logType, $id1, $id2) { + $this->initInGameLog(); + $this->igLog->appendRow(array( + $id1, $id2, $logType, $this->now, $game + )); + } + + private function flushInGameLog() { + if (is_null($this->igLog)) { + return; + } + $this->igLog->execute(); + $this->igLog = null; + } +} + +?> diff --git a/scripts/game/main/ticks/mark/library.inc b/scripts/game/main/ticks/mark/library.inc new file mode 100644 index 0000000..91a1c5f --- /dev/null +++ b/scripts/game/main/ticks/mark/library.inc @@ -0,0 +1,12 @@ + diff --git a/scripts/game/main/ticks/session/library.inc b/scripts/game/main/ticks/session/library.inc new file mode 100644 index 0000000..91f20a3 --- /dev/null +++ b/scripts/game/main/ticks/session/library.inc @@ -0,0 +1,68 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->accounts = $this->lib->game->getLib("main/account"); + } + + + public function runTick() { + $this->db->safeTransaction(array($this, 'removeTracking')); + $this->db->safeTransaction(array($this, 'removeSessions')); + } + + + public function removeTracking() { + // Removes old tracking data (> 3 months or > 1 day and last used within a minute of creation) + $this->db->query( + "UPDATE web_tracking SET stored_data = '' " + . "WHERE UNIX_TIMESTAMP(NOW()) - last_used > 7776000" + ); + $this->db->query( + "DELETE FROM web_tracking " + . "WHERE (UNIX_TIMESTAMP(NOW()) - last_used > 86400 AND last_used - created < 60)" + ); + } + + public function removeSessions() { + // Removes old sessions + $q = $this->db->query( + "SELECT s.id, t.id, a.id FROM web_session s, web_tracking t, account a " + . "WHERE UNIX_TIMESTAMP(NOW()) - s.last_used > 3600 " + . "AND a.id = s.account " + . "AND t.id = s.tracking " + . "FOR UPDATE OF s, t, a" + ); + if (! dbCount($q)) { + return; + } + + $accounts = array(); + while ($r = dbFetchArray($q)) { + list($sid, $tid, $uid) = $r; + if (is_null($uid)) { + continue; + } + + $this->accounts->call('log', $uid, 'o'); + array_push($accounts, $uid); + l::info("Player #$uid logged out automatically"); + } + + $this->db->query( + "UPDATE account SET last_logout = UNIX_TIMESTAMP(NOW()) " + . "WHERE id IN (" . join(',', $accounts) . ")" + ); + $this->db->query("DELETE FROM web_session WHERE UNIX_TIMESTAMP(NOW()) - last_used > 3600"); + } +} + +?> diff --git a/scripts/game/main/ticks/vacation/library.inc b/scripts/game/main/ticks/vacation/library.inc new file mode 100644 index 0000000..ec49e73 --- /dev/null +++ b/scripts/game/main/ticks/vacation/library.inc @@ -0,0 +1,62 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->main = $this->lib->game->getLib(); + $this->vacation = $this->lib->game->getLib("main/vacation"); + } + + public function runTick() { + $this->db->safeTransaction(array($this, "decreaseCredits")); + $this->db->safeTransaction(array($this, "enterVacation")); + } + + public function decreaseCredits() { + // Decrease credits for players in vacation mode + $q = $this->db->query( + "SELECT id FROM account WHERE status = 'VAC' AND vac_credits = 0 FOR UPDATE" + ); + if ($q && dbCount($q)) { + while ($r = dbFetchArray($q)) { + list($uid) = $r; + $this->vacation->call('leave', $uid); + } + } + $this->db->query("UPDATE account SET vac_credits = vac_credits - 1 WHERE status = 'VAC'"); + } + + public function enterVacation() { + // Look for players who should enter vacation mode + $q = $this->db->query( + "SELECT id FROM account " + . "WHERE status='STD' AND vac_start IS NOT NULL AND UNIX_TIMESTAMP(NOW()) > vac_start " + . "FOR UPDATE" + ); + if (! dbCount($q)) { + return; + } + + while ($r = dbFetchArray($q)) { + list($uid) = $r; + $this->vacation->call('start', $uid); + } + $this->db->query( + "UPDATE account SET status = 'VAC', vac_start = NULL $vsWhere" + . "WHERE status='STD' AND vac_start IS NOT NULL " + . "AND UNIX_TIMESTAMP(NOW()) > vac_start" + ); + } +} + + +?> diff --git a/scripts/game/main/vacation/library.inc b/scripts/game/main/vacation/library.inc new file mode 100644 index 0000000..27d2d07 --- /dev/null +++ b/scripts/game/main/vacation/library.inc @@ -0,0 +1,45 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + + function isOnVacation($uid) { + $q = $this->db->query("SELECT COUNT(*) FROM account WHERE id=$uid AND status='VAC'"); + list($onVac) = dbFetchArray($q); + return ($onVac == 1); + } + + + function getStatus($uid) { + $q = $this->db->query("SELECT status,vac_start,vac_credits FROM account WHERE id=$uid"); + if ($q && dbCount($q) == 1) { + return dbFetchHash($q); + } + return null; + } + + + function setStart($uid) { + $vs = time() + 86400; + $this->db->query("UPDATE account SET vac_start=$vs WHERE id=$uid AND status='STD'"); + logText("Account $uid requested vacation mode"); + } + + + function resetStart($uid) { + $this->db->query("UPDATE account SET vac_start=NULL WHERE id=$uid AND status='STD'"); + logText("Account $uid cancelled request for vacation mode"); + } +} + +?> diff --git a/scripts/game/main/vacation/library/canSet.inc b/scripts/game/main/vacation/library/canSet.inc new file mode 100644 index 0000000..cc49d86 --- /dev/null +++ b/scripts/game/main/vacation/library/canSet.inc @@ -0,0 +1,31 @@ +lib = $lib; + $this->db = $this->lib->game->db; + } + + function run($uid) { + $q = $this->db->query("SELECT t FROM account_log WHERE account=$uid AND action='VEND'"); + if (!$q) { + return false; + } elseif (dbCount($q) > 0) { + list($last) = dbFetchArray($q); + if (!(time() - $last > 604800 /* 7 * 24 * 3600 */ )) { + return false; + } + } + + $q = $this->db->query("SELECT vac_credits,quit_ts FROM account WHERE id=$uid"); + if (!($q && dbCount($q) == 1)) { + return false; + } + list($cred,$quit) = dbFetchArray($q); + + return is_null($quit) && ($cred > 0); + } +} + +?> diff --git a/scripts/game/main/vacation/library/leave.inc b/scripts/game/main/vacation/library/leave.inc new file mode 100644 index 0000000..8644824 --- /dev/null +++ b/scripts/game/main/vacation/library/leave.inc @@ -0,0 +1,31 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->accounts =& $this->lib->game->getLib('main/account'); + } + + function run($uid) { + foreach (config::getGames() as $game) { + if ($game->name == 'main') { + continue; + } + + $lib = $game->getLib(); + $pid = $lib->call('doesUserPlay', $uid); + if (!is_null($pid)) { + $lib->call('leaveVacation', $pid); + } + } + + $this->accounts->call('log', $uid, 've'); + $this->db->query("UPDATE account SET status='STD' WHERE id=$uid"); + + logText("Account $uid has left vacation mode"); + } +} + +?> diff --git a/scripts/game/main/vacation/library/start.inc b/scripts/game/main/vacation/library/start.inc new file mode 100644 index 0000000..826f1da --- /dev/null +++ b/scripts/game/main/vacation/library/start.inc @@ -0,0 +1,31 @@ +lib = $lib; + $this->db = $this->lib->game->db; + $this->accounts = $this->lib->game->getLib('main/account'); + } + + function run($uid) { + foreach (config::getGames() as $game) { + if ($game->name == 'main') { + continue; + } + + $lib = $game->getLib(); + $pid = $lib->call('doesUserPlay', $uid); + if (!is_null($pid)) { + $lib->call('startVacation', $pid); + } + } + + $this->db->query("UPDATE account SET status='VAC',vac_start=NULL,vac_credits=vac_credits-1 WHERE id=$uid"); + $this->accounts->call('log', $uid, 'vs'); + + logText("Account $uid has entered vacation mode"); + } +} + +?> diff --git a/scripts/legacyworlds.xml b/scripts/legacyworlds.xml new file mode 100644 index 0000000..f328dd4 --- /dev/null +++ b/scripts/legacyworlds.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Hour Tick + + This tick is the one occuring the most often in game. + It triggers military factories production, happiness + level computation for each planet, planet owner changes + and revolt parameters as well as updates to fleets + waiting in Hyperspace. + + + + Battle Tick + + This tick marks the time for battle outcome calculations. + + + + Cash Tick + + Twice a day this tick implies the generation of your income. + + + + Day Tick + + Once a day this tick triggers major updates in your empire, + including population growth, research progress update, + ranking update, account aging, delays on laws. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This is the main public game for LegacyWorlds Beta 5. + + + + + + + + + + + + + + + + + diff --git a/scripts/lib/account.inc b/scripts/lib/account.inc new file mode 100644 index 0000000..d7882db --- /dev/null +++ b/scripts/lib/account.inc @@ -0,0 +1,13 @@ +getLib('main/account'); + $lib->call('log', $uid, $what); +} + +?> diff --git a/scripts/lib/actions.inc b/scripts/lib/actions.inc new file mode 100644 index 0000000..1b4a132 --- /dev/null +++ b/scripts/lib/actions.inc @@ -0,0 +1,93 @@ +game = $game; + + if (is_null($libraries)) { + $libraries = array(); + } + $this->__initLibraries($libraries); + } + + /** This method loads libraries from the game instance and + * stores them inside the $__libraries array. + */ + private function __initLibraries($libraries) { + $libs = array(); + foreach ($libraries as $key => $name) { + $libs[$key] = $this->game->getLib($name); + } + $this->__libraries = $libs; + } + + /** The __get() overload method is useful to access the + * libraries associated with this game action. + */ + protected function __get($var) { + if (array_key_exists($var, $this->__libraries)) { + return $this->__libraries[$var]; + } + return null; + } +} + + +/** This function allows the user to call a game action function; it must + * have at least one argument, which may be either a string (the name of the + * action function to call for the current game version) or an array + * containing two strings (the version identifier and the name of the action). + * Other arguments may follow; they will be passed to the action function. + */ +function gameAction() { + $sitePath = input::$path; + + $n = func_num_args(); + if ($n == 0) { + l::fatal(22, "Requested game was '$sitePath'"); + } + $args = func_get_args(); + $gas = array_shift($args); + + if (is_array($gas)) { + list($v,$a) = $gas; + } elseif (is_string($gas)) { + $v = $sitePath; + $a = $gas; + } else { + l::fatal(23, "Requested game was '$sitePath'"); + } + array_unshift($args, $a); + + $g = config::getGame($v); + l::deprecated("gameAction($v, $a, ...)"); + return call_user_func_array(array($g, 'deprecatedAction'), $args); +} + + +?> diff --git a/scripts/lib/ajax.inc b/scripts/lib/ajax.inc new file mode 100644 index 0000000..15ed221 --- /dev/null +++ b/scripts/lib/ajax.inc @@ -0,0 +1,84 @@ +version->id) . "/ajax.inc"; + if (!file_exists($f)) { + return array(); + } + return include($f); + } + + static function init() { + $handler = handler::$h; + + // Get AJAX functions from handler + $a1 = is_array($handler->ajax) ? $handler->ajax : array(); + if (is_array($a1['func'])) { + ajax::$fHandler = $a1['func']; + $ml = is_array($a1['method']) ? $a1['method'] : array(); + foreach ($a1['func'] as $f) { + $m = ($ml[$f] == "") ? ($handler->ajaxPOST ? "POST" : "GET") : $ml[$f]; + ajax::$method[$f] = $m; + } + } + + // Get theme-specific AJAX functions + $a2 = ajax::getTheme(); + if (is_array($a2['func'])) { + ajax::$fTheme = $a2['func']; + $ml = is_array($a2['method']) ? $a2['method'] : array(); + foreach ($a2['func'] as $f) { + $m = ($ml[$f] == "") ? ($handler->ajaxPOST ? "POST" : "GET") : $ml[$f]; + ajax::$method[$f] = $m; + } + } + + // Create init string + ajax::$init = $a2['init'] . $a1['init']; + } + + + static function canCall($function) { + return in_array($function, ajax::$fTheme) || in_array($function, ajax::$fHandler); + } + + static function call($function, $args) { + // We don't want an error in that part of the code to make the + // client-side JS script go insane + ob_start(); + + // Call the function + if (in_array($function, ajax::$fTheme)) { + $res = call_user_func_array("thm_{$function}", $args); + } else if (in_array($function, ajax::$fHandler)) { + $res = call_user_func_array(array(handler::$h, $function), $args); + } else { + $res = null; + } + + // Log any error / warning / whatever + $buffer = ob_get_contents(); + if ($buffer != '') { + $b = explode("\n", $buffer); + foreach ($b as $line) { + if ($line != '') { + l::warning("AJAX ($function): $line"); + } + } + } + ob_end_clean(); + + return $res; + } +} + + +?> diff --git a/scripts/lib/classloader.inc b/scripts/lib/classloader.inc new file mode 100644 index 0000000..d9644cd --- /dev/null +++ b/scripts/lib/classloader.inc @@ -0,0 +1,25 @@ + diff --git a/scripts/lib/config.inc b/scripts/lib/config.inc new file mode 100644 index 0000000..785c7cd --- /dev/null +++ b/scripts/lib/config.inc @@ -0,0 +1,130 @@ +versions = $versions; + $this->games = $games; + $this->defGame = $defGame; + } + + private static function parseXML($xmlData) { + require_once('xml_config.inc'); + return xml_config::parse($xmlData); + } + + private static function tryLoadSerialized() { + $config = &config::$main; + + if (!file_exists("{$config['cachedir']}/config.ser")) { + return false; + } + + $f = @file_get_contents("{$config['cachedir']}/config.ser"); + if ($f === false) { + return false; + } + return @unserialize($f); + } + + private static function writeCache($cObj) { + $config = &config::$main; + + $text = serialize($cObj); + $mask = umask(0002); + $w = @file_put_contents("{$config['cachedir']}/config.ser", $text, LOCK_EX); + umask($mask); + if ($w === false) { + l::warn("CONFIG: failed to cache configuration"); + } + } + + private static function checkUpdate($checksum = null) { + $config = &config::$main; + + $f = @file_get_contents("{$config['scriptdir']}/legacyworlds.xml"); + if ($f === false) { + l::error("CONFIG: could not open file '{$config['scriptdir']}/legacyworlds.xml'"); + return false; + } + + $md5 = md5($f); + if (!is_null($checksum) && $checksum == $md5) { + return false; + } + l::notice("CONFIG: XML configuration file modified, updating"); + + $data = config::parseXML($f); + if (is_object($data)) { + $data->checksum = $md5; + config::writeCache($data); + } + return $data; + } + + + static function load() { + global $config; + if (!is_null($config) && is_null(config::$main)) { + config::$main = $config; + } + + $sData = config::tryLoadSerialized(); + if (!is_object($sData)) { + $sData = config::checkUpdate(); + } else { + $nData = config::checkUpdate($sData->checksum); + if (is_object($nData)) { + $sData = $nData; + } + } + + if (!is_object($sData)) { + l::fatal(0, "The XML configuration could not be parsed and serialized data could not be read"); + } + config::$config = $sData; + } + + + static function reload() { + $nData = config::checkUpdate(config::$config->checksum); + if (is_object($nData)) { + config::$config = $nData; + l::info("CONFIG: configuration updated"); + } + return is_object($nData); + } + + + static function getGames() { + return config::$config->games; + } + + static function getGame($name) { + return config::$config->games[$name]; + } + + static function getDefaultGame() { + return config::$config->games[config::$config->defGame]; + } + + static function hasGame($name) { + return is_object(config::$config->games[$name]); + } + + static function getParam($name) { + return config::$config->games['main']->params[$name]; + } +} + +config::load(); + +?> diff --git a/scripts/lib/data_tree.inc b/scripts/lib/data_tree.inc new file mode 100644 index 0000000..f917b31 --- /dev/null +++ b/scripts/lib/data_tree.inc @@ -0,0 +1,130 @@ +name = $name; + $this->attributes = array(); + } + + function getName() { + return $name; + } + + abstract function getContents(); + + abstract function addContents($contents); + + function getAttribute($name) { + return isset($this->attributes[$name]) ? $this->attributes[$name] : null; + } + + function setAttribute($name, $value) { + $this->attributes[$name] = $value; + } + + function toXML($document) { + $element = $document->createElement($this->name); + foreach ($this->attributes as $k => $v) { + $element->setAttribute($k, $v); + } + return $element; + } + + function toLWData($document) { + $root = $document->createElement("Node"); + $root->setAttribute("name", $this->name); + foreach ($this->attributes as $k => $v) { + $node = $document->createElement("Attr"); + $root->appendChild($node); + $node->setAttribute("name", $k); + $text = $document->createTextNode($v); + $node->appendChild($text); + } + return $root; + } +} + + +class data_leaf extends data_gen { + + private $contents; + + function __construct($name, $contents = "") { + parent::__construct($name); + $this->contents = $contents; + } + + function getContents() { + return $this->contents; + } + + function addContents($contents) { + $this->contents .= $contents; + } + + function toXML($document) { + $element = parent::toXML($document); + $value = $document->createTextNode($this->contents); + $element->appendChild($value); + return $element; + } + + function toLWData($document) { + $element = parent::toLWData($document); + $element->setAttribute("node", 0); + if ($this->contents != '') { + $node = $document->createElement("Text"); + $value = $document->createTextNode($this->contents); + $node->appendChild($value); + $element->appendChild($node); + } + return $element; + } +} + + +class data_node extends data_gen { + + private $contents; + + function __construct($name) { + parent::__construct($name); + $this->contents = array(); + } + + function getContents() { + return $this->contents; + } + + function addContents($data) { + if ($data instanceof data_gen) { + array_push($this->contents, $data); + } + } + + function toXML($document) { + $element = parent::toXML($document); + foreach ($this->contents as $sub) { + $node = $sub->toXML($document); + $element->appendChild($node); + } + return $element; + } + + function toLWData($document) { + $element = parent::toLWData($document); + $element->setAttribute("node", 1); + foreach ($this->contents as $sub) { + $node = $sub->toLWData($document); + $element->appendChild($node); + } + return $element; + } +} + + +?> diff --git a/scripts/lib/db.inc b/scripts/lib/db.inc new file mode 100644 index 0000000..9dc25af --- /dev/null +++ b/scripts/lib/db.inc @@ -0,0 +1,69 @@ +open(); + } + if ($fatal && !$rv) { + l::fatal(1, array("SQL: Database connection failed", pg_last_error())); + } + return $rv; +} + +function dbClose() { + db::$database->close(); +} + +function dbQuery($q) { + return db::$database->query($q); +} + +function dbBegin() { + db::$database->begin(); +} + +function dbEnd() { + db::$database->end(); +} + + + +/** This function returns the results count for the $qr database query + * result + */ +function dbCount($qr) { + return @pg_num_rows($qr); +} + + +/** This function returns the next row from the $qr query result as a hash + * table + */ +function dbFetchHash($qr) { + return @pg_fetch_assoc($qr); +} + + +/** This function returns the next row from the $qr query result as an array */ +function dbFetchArray($qr) { + return @pg_fetch_row($qr); +} + + +/** This function turns a value into a DB-specific boolean value. */ +function dbBool($v) { + return $v ? "true" : "false"; +} + +?> diff --git a/scripts/lib/db_accessor.inc b/scripts/lib/db_accessor.inc new file mode 100644 index 0000000..bd85781 --- /dev/null +++ b/scripts/lib/db_accessor.inc @@ -0,0 +1,127 @@ +db = $db; + $this->namespace = $namespace; + } + + public function setNamespace() { + if (self::$lastNamespace === $this->namespace) { + return; + } + + if (is_null($this->namespace) || $this->namespace == '') { + $this->db->query('SET search_path=public,main'); + } else { + $this->db->query('SET search_path=public,main,' . $this->namespace); + } + + self::$lastNamespace = $this->namespace; + } + + public function enableExceptions() { + $this->db->enableExceptions(); + } + + public function disableExceptions() { + $this->db->disableExceptions(); + } + + public function query($q) { + $this->setNamespace(); + return $this->db->query($q); + } + + public function begin() { + $this->db->begin(); + } + + public function end($rb = false) { + return $this->db->end($rb); + } + + public function needsReset() { + return $this->db->needsReset(); + } + + public function reset($attempts = 5, $delay = 1) { + if ($rv = $this->db->reset($attempts, $delay)) { + self::$lastNamespace = false; + } + return $rv; + } + + + /** This method starts a transaction and runs in safely, trying to reset the connection + * if it fails. + */ + public function safeTransaction($callback, $args = null, $maxResets = 1, + $attempts = 5, $delay = 1, $maxDeadlocks = 10) { + + if (is_null($args)) { + $args = array(); + } elseif (!is_array($args)) { + $args = array($args); + } + + $resets = $deadlocks = 0; + $this->enableExceptions(); + + try { + do { + $ok = false; + try { + $this->begin(); + $returnValue = call_user_func_array($callback, $args); + $this->end(); + $ok = true; + } catch (db_deadlock_exception $e) { +/* $this->end(false); + self::$lastNamespace = false; +if ($deadlocks == $maxDeadlocks) { */ + throw $e; +// } +// $deadlocks ++; + } catch (db_sql_exception $e) { + $this->end(); + throw $e; + } catch (db_srv_exception $e) { + if ($resets == $maxResets) { + throw $e; + } + $this->reset($attempts, $delay); + $resets ++; + } + } while (!$ok); + } catch (Exception $e) { + $this->disableExceptions(); + throw $e; + } + + $this->disableExceptions(); + return $returnValue; + } + + + /** This method copies raw data into a table. + */ + public function copyTo($tableName, $data) { + $this->setNamespace(); + return $this->db->copyTo($tableName, $data); + } + + + /** This method copies raw data from a table. + */ + public function copyFrom($tableName) { + $this->setNamespace(); + return $this->db->copyFrom($tableName); + } +} + +?> diff --git a/scripts/lib/db_connection.inc b/scripts/lib/db_connection.inc new file mode 100644 index 0000000..3841744 --- /dev/null +++ b/scripts/lib/db_connection.inc @@ -0,0 +1,446 @@ +query = $query; + } + + + public function getQuery() { + return $this->query; + } + +} + +class db_deadlock_exception extends Exception { + + public function __construct($error) { + parent::__construct("Deadlock detected ($error)"); + } + +} + + + +class db { + + static $database = null; + static $accessors = array(); + + private $isOpen = false; + private $cString = ''; + private $conn = null; + private $inTrans = false; + private $queries = 0; + private $__needsReset = false; + private $useExceptions = 0; + private $trace = false; + + + static function connect() { + if (self::$database && ! self::$database->open()) { + throw new db_srv_exception("unable to connect"); + } else { + new db(); + } + return self::$database; + } + + public function __construct() { + if (!$this->open()) { + throw new db_srv_exception("unable to connect"); + } + self::$database = $this; + } + + public function open() { + if ($this->isOpen) { + return true; + } + + // Get access to the 'main' game + $main = config::getGame('main'); + if (!$main) { + if ($this->useExceptions) { + throw new db_srv_exception("failed to fetch 'main' pseudo-game"); + } + return false; + } + + // Generate the connection string + if ($this->cString == '') { + $cString = "dbname='{$main->params['dbname']}'"; + if ($main->params['dbhost'] != '') { + $cString .= " host='{$main->params['dbhost']}' sslmode='disable'"; + } + if ($main->params['dbuser'] != '') { + $cString .= " user='{$main->params['dbuser']}'"; + } + if ($main->params['dbpass'] != '') { + $cString .= " password='{$main->params['dbpass']}'"; + } + $this->cString = $cString; + } + + // Connect to the database + ob_start(); + $this->conn = pg_connect($this->cString); + $error = ob_get_contents(); + ob_end_clean(); + + // Check status + if (!$this->conn || pg_connection_status($this->conn) == PGSQL_CONNECTION_BAD) { + $this->conn = null; + if ($this->useExceptions) { + throw new db_srv_exception("database connection failed: $error"); + } + return false; + } + + $this->isOpen = true; + $this->inTrans = false; + $this->queries = 1; + if (!@pg_query($this->conn, 'SET search_path=public,main')) { + $error = pg_last_error(); + @pg_close($this->conn); + $this->conn = null; + if ($this->useExceptions) { + throw new db_sql_exception('SET search_path=public,main', + "failed to initialise search path: $error"); + } + return false; + } + $this->begin(); + + return true; + } + + + public function close() { + if (! $this->isOpen) { + return; + } + + $this->end(); + @pg_close($this->conn); + $this->isOpen = false; + self::$accessors = array(); + + if ($this->queries >= 20) { + l::debug("SQL: connection closed after {$this->queries} queries"); + } + } + + + public function enableExceptions() { + $this->useExceptions ++; + } + + public function disableExceptions() { + if ($this->useExceptions > 0) { + $this->useExceptions --; + } + } + + + private function fail($logTxt, $query) { + if ($this->error) { + if ($this->useExceptions) { + throw new db_sql_exception($query, $logText); + } + return; + } + l::error("SQL: $logTxt"); + l::info("QUERY: $query"); + l::backtrace(); + $this->error = true; + if ($this->useExceptions) { + throw new db_sql_exception($query, $logTxt); + } + } + + + public function begin() { + if ($this->inTrans || $this->__needsReset) { + return; + } + if (@pg_query($this->conn, "BEGIN TRANSACTION")) { + $this->queries ++; + $this->inTrans = true; + $this->error = false; + } else { + l::error("SQL: could not start transaction"); + l::info("SQL: error was " . ($error = pg_last_error($this->conn))); + if (pg_connection_status($this->conn) == PGSQL_CONNECTION_BAD) { + $this->__needsReset = true; + if ($this->useExceptions) { + throw new db_srv_exception($error); + } + } else { + if ($this->useExceptions) { + throw new db_sql_exception("BEGIN TRANSACTION", $error); + } + } + } + } + + public function end($rollback = false) { + if (!$this->inTrans) { + return true; + } + + $r = ($rollback || $this->error); + $query = $r ? "ROLLBACK" : "COMMIT"; + if (@pg_query($this->conn, $query)) { + $this->queries ++; + $this->inTrans = false; + } else { + $cStat = pg_connection_status($this->conn); + $error = pg_last_error($this->conn); + if ($cStat == PGSQL_CONNECTION_BAD) { + l::notice("SQL: could not end transaction, connection is in an invalid status"); + $this->__needsReset = true; + if ($this->useExceptions) { + throw new db_srv_exception($error); + } + } else { + l::error("SQL: could not end transaction while " . ($r ? "rolling back" : "comitting")); + l::info("SQL ERROR: $error"); + l::backtrace(); + if ($this->useExceptions) { + throw new db_sql_exception($query, $error); + } + } + return false; + } + return true; + } + + public function query($query) { + if (!$this->inTrans) { + $this->fail("query executed outside of transaction", $query); + return null; + } + + $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); + if ($cStat == PGSQL_CONNECTION_BAD) { + l::notice("SQL: connection is gone while executing query"); + l::debug("QUERY: $query"); + $this->__needsReset = true; + $this->error = true; + if ($this->useExceptions) { + throw new db_srv_exception($error); + } + return; + } 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 { + $this->fail("an error has occured: $error", $query); + } + } 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; + } + } + return $r; + } + + public function getGameAccess($prefix) { + if (is_null($this->accessors[$prefix])) { + $this->accessors[$prefix] = new db_accessor($this, $prefix); + } + return $this->accessors[$prefix]; + } + + public function needsReset() { + return $this->__needsReset; + } + + public function reset($attempts = 5, $delay = 1) { + l::notice("SQL: resetting connection after {$this->queries} queries"); + $this->__needsReset = false; + $i = 0; + do { + $rv = @pg_connection_reset($this->conn); + if (!$rv) { + $error = pg_last_error($this->conn); + $i ++; + l::warn("Reconnection attempt #$i failed" . ($i < $attempts ? ", retrying" : "")); + sleep($delay); + } + } while ($i < $attempts && !$rv); + if ($rv) { + $this->error = false; + $this->inTrans = false; + $this->queries = 0; + $this->trace = true; + } else { + $this->isOpen = false; + if ($this->useExceptions) { + throw new db_srv_exception($error); + } + } + return $rv; + } + + + public function copyTo($table, $data) { + $actualData = array(); + foreach ($data as $row) { + $actualRow = array(); + foreach ($row as $column) { + if (is_null($column)) { + $output = "\\N"; + } elseif (is_string($column)) { + $output = preg_replace( + array('/\\\\/', '/\x08/', '/\f/', '/\r/', '/\\n/', '/\t/', '/\v/'), + array('\\\\\\', '\\b', '\\f', '\\r', '\\n', '\\t', '\\v'), + $column + ); + } else { + $output = $column; + } + array_push($actualRow, $output); + } + array_push($actualData, join("\t", $actualRow)); + } + + $result = @pg_copy_from($this->conn, $table, $actualData); + if (!$result) { + $cStat = pg_connection_status($this->conn); + $error = pg_last_error($this->conn); + if ($cStat == PGSQL_CONNECTION_BAD) { + l::notice("SQL: connection is gone while copying into table '$table'"); + $this->__needsReset = true; + $this->error = true; + if ($this->useExceptions) { + throw new db_srv_exception($error); + } + return false; + } elseif (preg_match('/deadlock/i', $error)) { + l::error("SQL: deadlock detected while copying into table '$table'"); + if ($this->useExceptions) { + throw new db_deadlock_exception($error); + } + $this->error = true; + return false; + } else { + $this->fail("an error has occured: $error", "COPY \"$table\" FROM STDIN"); + } + } + + return true; + } + + public function copyFrom($table) { + $result = pg_copy_to($this->conn, $table); + if ($result === FALSE) { + $cStat = pg_connection_status($this->conn); + $error = pg_last_error($this->conn); + if ($cStat == PGSQL_CONNECTION_BAD) { + l::notice("SQL: connection is gone while copying from table '$table'"); + $this->__needsReset = true; + $this->error = true; + if ($this->useExceptions) { + throw new db_srv_exception($error); + } + return false; + } elseif (preg_match('/deadlock/i', $error)) { + l::error("SQL: deadlock detected while copying from table '$table'"); + if ($this->useExceptions) { + throw new db_deadlock_exception($error); + } + $this->error = true; + return false; + } else { + $this->fail("an error has occured: $error", "COPY \"$table\" TO STDOUT"); + } + } + + $actualData = array(); + foreach ($result as $row) { + $actualRow = array(); + $splitRow = explode("\t", $row); + foreach ($splitRow as $column) { + if ($column == "\\N") { + $output = null; + } else { + $output = preg_replace( + array('/\\\\b/', '/\\\\f/', + '/\\\\r/', '/\\\\n/', '/\\\\t/', '/\\\\v/', '/\\\\\\\\/'), + array("\x08", "\x0c", "\r", "\n", "\t", "\x0b", "\\"), + $column + ); + } + array_push($actualRow, $output); + } + array_push($actualData, $actualRow); + } + + return $actualData; + } +} + + +?> diff --git a/scripts/lib/db_copy.inc b/scripts/lib/db_copy.inc new file mode 100644 index 0000000..981bce9 --- /dev/null +++ b/scripts/lib/db_copy.inc @@ -0,0 +1,210 @@ +table = $table; + $this->mode = $mode; + $this->db = $this->data = $this->colNames = null; + } elseif (is_object($table) && $table instanceof db_copy) { + $this->table = $table->table; + $this->mode = ($mode != self::copyFrom ? $mode : self::copyTo); + $this->db = $table->db; + $this->data = $table->data; + $this->colNames = $table->colNames; + } else { + throw new Exception("Invalid parameters to the db_copy constructor"); + } + } + + + /** This method sets the database accessor to use when performing the copy + * operation. + */ + public function setAccessor($db) { + if (!is_object($db) || !(($db instanceof db) || ($db instanceof db_accessor))) { + throw new Exception("Invalid database accessor: " . gettype($db) + . (is_object($db) ? (" (" . get_class($db) . ")") : "") ); + } + $this->db = $db; + } + + + /** This method executes the operation. It returns true on success or false + * on error. However, if database exceptions are enabled, SQL failure will + * cause an exception to be thrown. + */ + public function execute() { + if (is_null($this->db)) { + l::debug("db_copy::execute() called but no accessor set"); + return false; + } + + if ($this->mode == self::copyFrom) { + if (!is_null($this->data)) { + return true; + } + $this->data = $this->db->copyFrom($this->table); + return !is_null($this->data); + } + + if (is_null($this->data)) { + return false; + } + + if ($this->mode == self::copyToClean) { + $this->db->query("DELETE FROM \"{$this->table}\""); + } + return $this->db->copyTo($this->table, $this->data); + } + + + /** This method is called with an arbitrary number of arguments. It sets + * the names for the columns, which is required for some of the operations. + */ + public function setColumnNames() { + $n = func_num_args(); + if ($n == 0) { + return; + } + + $args = func_get_args(); + $names = array(); + for ($i = 0; $i < $n; $i ++) { + if (!is_string($args[$i])) { + return; + } + $names[$args[$i]] = $i; + } + + $this->colNames = $names; + } + + + /** This method returns the amount of rows in the object. + */ + public function rows() { + return is_null($this->data) ? 0 : count($this->data); + } + + + /** This method appends a row to the current data. The row must be passed + * as an array of values. This method will fail and return FALSE if the + * current mode is copyFrom or if the data is not an array. + */ + public function appendRow($data) { + if (!is_array($data) || $this->mode == self::copyFrom) { + return false; + } + if (!is_array($this->data)) { + $this->data = array(); + } + array_push($this->data, $data); + } + + + /** This method removes a row using its index. It will return FALSE if the + * index is invalid or if the current mode is copyFrom. In any other case + * it will return the row that has been removed. + */ + public function removeRow($index) { + if ($this->mode == self::copyFrom || !is_int($index) || $index < 0 + || is_null($this->data) || $index >= count($this->data)) { + return false; + } + + return array_splice($this->data, $index, 1); + } + + + /** This method returns a row from the current data. It will return FALSE + * if the index is invalid, or the row on success. + */ + public function getRow($index) { + if (!is_int($index) || $index < 0 || is_null($this->data) || $index >= count($this->data)) { + return false; + } + + return $this->data[$index]; + } + + + /** This method searches for rows using a column identifier (either an + * integer or, if the column names have been specified, a column name) + * and a value to look for. It returns FALSE if the column identifier + * is invalid, or an array of row indices if rows are found. The + * additional $unique parameter can specify that the value will be + * unique to prevent the method from going through all rows in that + * case; it is false by default. + */ + public function lookupRow($column, $value, $unique = false) { + if (is_null($this->data)) { + return false; + } + + if (is_int($column)) { + if ($column < 0) { + return false; + } + } elseif (is_string($column)) { + if (is_null($this->colNames) || !array_key_exists($column, $this->colNames)) { + return false; + } + $column = $this->colNames[$column]; + } else { + return false; + } + + $results = array(); + for ($i = 0; $i < count($this->data); $i ++) { + if (count($this->data[$i]) < $column) { + return false; + } + if ($this->data[$i][$column] != $value) { + continue; + } + + array_push($results, $i); + if ($unique) { + break; + } + } + + return $results; + } +} + + +?> diff --git a/scripts/lib/engine.inc b/scripts/lib/engine.inc new file mode 100644 index 0000000..f91c72c --- /dev/null +++ b/scripts/lib/engine.inc @@ -0,0 +1,57 @@ +maintenance(config::$main['maintenance']); + exit(0); + } + + engine::$e = $engine; + l::setFatalHandler('engine::fatalError'); + } + + static function fatalError($errno, $errorText, $information) { + die(engine::$e->displayFatalError($errno, $errorText, $information)); + } +} + + +?> diff --git a/scripts/lib/engines/css.inc b/scripts/lib/engines/css.inc new file mode 100644 index 0000000..13b46f2 --- /dev/null +++ b/scripts/lib/engines/css.inc @@ -0,0 +1,71 @@ +id = (int) input::$input['id']; + } + + + function initRPC() { + } + + + function outputData() { + header("Content-Type: text/css"); + + if (!is_array(tracking::$data['cssId'])) { + tracking::$data['cssId'] = array(); + } else { + $now = time(); + foreach (tracking::$data['cssId'] as $ck => $t) { + if ($now - $t > 345600) { + unset(tracking::$data['cssId'][$ck]); + } + } + } + + $cssId = input::$path . "/" . input::$page . "/{$this->id}"; + if (!isset(tracking::$data['cssId'][$cssId])) { + tracking::$data['cssId'][$cssId] = time(); + } + + $time = tracking::$data['cssId'][$cssId]; + $lastModified = substr(date('r', $time), 0, -5).'GMT'; + $etag = '"'.md5($lastModified).'"'; + header("Last-Modified: $lastModified GMT"); + header("ETag: $etag"); + if (!input::$IE) { + header("Cache-Control: no-cache, must-revalidate"); + } + $ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : false; + $ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : false; + if (($ifNoneMatch || $ifModifiedSince) && (!$ifNoneMatch || $ifNoneMatch == $etag) && (!$ifModifiedSince || $ifModifiedSince != $lastModified)) { + header('HTTP/1.0 304 Not Modified'); + return; + } + + displayResource($this->id, "css"); + l::fatal(30, "CSS resource {$this->id}"); + } +} + + diff --git a/scripts/lib/engines/js.inc b/scripts/lib/engines/js.inc new file mode 100644 index 0000000..d0dfadc --- /dev/null +++ b/scripts/lib/engines/js.inc @@ -0,0 +1,71 @@ +id = (int) input::$input['id']; + } + + + function initRPC() { + } + + + function outputData() { + header("Content-Type: text/javascript"); + + if (!is_array(tracking::$data['jsId'])) { + tracking::$data['jsId'] = array(); + } else { + $now = time(); + foreach (tracking::$data['jsId'] as $ck => $t) { + if ($now - $t > 345600) { + unset(tracking::$data['jsId'][$ck]); + } + } + } + + $jsId = input::$path . "/" . input::$page . "/{$this->id}"; + if (!isset(tracking::$data['jsId'][$jsId])) { + tracking::$data['jsId'][$jsId] = time(); + } + + $time = tracking::$data['jsId'][$jsId]; + $lastModified = substr(date('r', $time), 0, -5).'GMT'; + $etag = '"'.md5($lastModified).'"'; + header("Last-Modified: $lastModified GMT"); + header("ETag: $etag"); + if (!input::$IE) { + header("Cache-Control: no-cache, must-revalidate"); + } + $ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : false; + $ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : false; + if (($ifNoneMatch || $ifModifiedSince) && (!$ifNoneMatch || $ifNoneMatch == $etag) && (!$ifModifiedSince || $ifModifiedSince != $lastModified)) { + header('HTTP/1.0 304 Not Modified'); + return; + } + + displayResource($this->id, "js"); + l::fatal(30, "JS resource {$this->id}"); + } +} + + diff --git a/scripts/lib/engines/page.inc b/scripts/lib/engines/page.inc new file mode 100644 index 0000000..a5a7f94 --- /dev/null +++ b/scripts/lib/engines/page.inc @@ -0,0 +1,452 @@ + + + + Legacy Worlds + + +

    + The LegacyWorlds game engine encountered a fatal error:
    +
    + Please report this error by sending an e-mail to the staff at + webmaster@legacyworlds.com + explaining what happened. Please make sure you include information such + as the name of the Web browser you're using, the address of the page on + which you got this message, etc ... The more information you give us, the + better we'll be able to fix the problem. +

    + + +outputPage('main', 'login', array('login' => $default, 'error' => $error)); + endRequest(); + } + + + function displayConfirm($error = false) { + input::$path = 'main'; + input::$page = 'confirm'; + $this->outputPage('main', 'confirm', array('error' => $error)); + endRequest(); + } + + function displayKicked() { + input::$path = 'main'; + input::$page = 'kicked'; + $this->outputPage('main', 'kicked', null); + endRequest(); + } + + + function initSession() { + $create = handler::$h->needsAuth || input::$input['userlogin'] == 1 || input::$input['authcode'] != '' || input::$input['ac_restart'] != ""; + $r = session::handle($create); + if ($r == 1 && $create) { + $this->displayLogin(); + } elseif ($r == 2 && $create) { + l::trace("tracknew? " . (tracking::$new ? "yes" : "no")); + l::fatal(12); + } + } + + + function checkGameRegistration() { + if (input::$path == "main") { + return; + } + + $game = input::$game; + if ($game->status() == 'PRE' || $game->status() == 'FINISHED') { + input::$path = 'main'; + input::$page = 'notfound'; + $this->outputPage('main', 'notfound', array()); + endRequest(); + } + + $lib = $game->getLib(); + $pid = $lib->call("doesUserPlay", $_SESSION['userid']); + if (is_null($pid)) { + input::$path = 'main'; + input::$page = 'notregistered'; + $this->outputPage('main', 'notregistered', array( + "id" => $game->name, + "name" => $game->text + )); + endRequest(); + } + if (!is_array($_SESSION["{$game->name}_data"])) { + $_SESSION["{$game->name}_data"] = array("player" => $pid); + } + dbQuery("UPDATE main.credits SET resources_used = resources_used + 1 WHERE account = {$_SESSION['userid']}"); + } + + function checkCredits() { + if (! is_null($_SESSION['annoy_until'])) { + if (time() >= $_SESSION['annoy_until']) { + $_SESSION['annoy_until'] = null; + return; + } + $this->outputPage('main', 'annoy', array( + "time" => $_SESSION['annoy_until'] - time() + )); + endRequest(); + } + + $q = dbQuery("SELECT resources_used, credits_obtained FROM credits WHERE account = {$_SESSION['userid']}"); + list($used, $cred) = dbFetchArray($q); + if ($used < $cred) { + return; + } + + $time = min(60, round(5 + ($used - $cred) / 1000)); + $_SESSION['annoy_until'] = time() + $time; + + $this->outputPage('main', 'annoy', array( + "time" => $time + 1 + )); + endRequest(); + } + + function checkConfirmationCode($code) { + $q = "SELECT status,conf_code FROM account WHERE id=" . $_SESSION['userid']; + $qr = dbQuery($q); + + if (!$qr || dbCount($qr) != 1) { + l::warn("Reading the {$_SESSION['userid']} account's confirmation code failed"); + killSession(); + $this->displayLogin(); + } + + list($status, $cc) = dbFetchArray($qr); + if ($cc != strtolower($code)) { + l::notice("Account {$_SESSION['userid']} entered an invalid confirmation code"); + $this->displayConfirm(true); + } + + // Validate account + dbQuery("UPDATE account SET conf_code=NULL,status='STD' WHERE id={$_SESSION['userid']}"); + $_SESSION['authok'] = true; + accountLog('v', $_SESSION['userid']); + + if ($status == 'NEW') { + // New accounts -> register to default game + $main = config::getGame('main'); + $game = $main->getLib()->call('preJoin', $_SESSION['userid']); + input::$path = 'main'; + input::$page = 'play'; + if (is_null($game)) { + $this->outputPage('main', 'play', 2); + } else { + $this->outputPage('main', 'play', array('registered' => $game)); + } + endRequest(); + } else { + $input = $_SESSION['original_request']; + $this->checkGameRegistration(); + } + } + + function doRestart($uid) { + $conf = substr(md5(uniqid(rand())), 0, 16); + + $q = dbQuery("SELECT name,email FROM account WHERE id='$uid' AND conf_code IS NULL"); + if (!($q && dbCount($q) == 1)) { + return false; + } + list($u, $e) = dbFetchArray($q); + + if (!dbQuery("UPDATE account SET conf_code='$conf' WHERE id='$uid'")) { + return false; + } + + dbQuery( + "INSERT INTO account_log(tracking,account,ip_addr,action) VALUES(" + . tracking::$dbId . ",$uid,'".$_SERVER['REMOTE_ADDR']."','CREATE')" + ); + + $mail = @file_get_contents(config::$main['scriptdir'] . "/game/main/mail/mail-restart.en.txt"); + if (is_bool($mail)) { + return false; + } + $tmp = explode("\n", $mail); + $sub = array_shift($tmp); + $mail = preg_replace( + array('/_USER_/', '/_CCODE_/'), + array($u, $conf), join("\n", $tmp) + ); + $hdr = "From: webmaster@legacyworlds.com\r\n" + . "Reply-To: webmaster@legacyworlds.com\r\n" + . "X-Mailer: LegacyWorlds\r\n" + . "Mime-Version: 1.0\r\n" + . "Content-Type: text/plain; charset=utf-8"; + if (!mail($e, $sub, $mail, $hdr)) { + return false; + } + + return true; + } + + function restartAccount($mail) { + if (is_null(input::$input['ac_restart'])) { + input::$path = 'main'; + input::$page = 'restart'; + $this->outputPage('main', 'restart', array("email"=>$mail)); + endRequest(); + } else { + $this->doRestart($_SESSION['userid']); + $this->displayLogin(); + } + } + + function accountValidation($loggedIn = false) { + $q = dbQuery("SELECT status,reason,email,conf_code FROM account WHERE id={$_SESSION['userid']}"); + + if (!$q || dbCount($q) != 1) { + $engine->displayLogin($_SESSION['login'], true); + } + + list($status,$reason,$mail,$code) = dbFetchArray($q); + if ($status == 'KICKED') { + l::notice("Got a login attempt from kicked user {$_SESSION['userid']}; setting banned attempt flag"); + dbQuery("INSERT INTO banned_attempt (ip_addr) VALUES ('{$_SERVER['REMOTE_ADDR']}')"); + tracking::$data['bat'] = true; + tracking::$data['uid'] = $_SESSION['userid']; + session::kill(); + $this->displayKicked(); + } + + if ($status == 'STD' || $status == 'VAC') { + if (! $_SESSION['authok']) { + $_SESSION['authok'] = true; + $this->checkCredits(); + } elseif (! is_null($_SESSION['annoy_until'])) { + $this->checkCredits(); + } + $this->checkGameRegistration(); + $_SESSION['last_page'] = time(); + return; + } + + if (is_null(input::$input['authcode']) && !is_null($code)) { + $_SESSION['original_request'] = input::$input; + $this->displayConfirm(false); + } elseif ($loggedIn) { + session::kill(); + $this->displayLogin(); + } elseif (!is_null(input::$input['authcode']) && !is_null($code)) { + $this->checkConfirmationCode(input::$input['authcode']); + } else { + $this->restartAccount($mail); + } + } + + function checkAccountAuth($l, $p) { + $l1 = addslashes($l); $p1 = addslashes($p); + $q = "SELECT id,name,password FROM account WHERE lower(name) = lower('$l1')"; + $qr = dbQuery($q); + + if (!$qr || dbCount($qr) != 1) { + l::info("Account authentication failed, login '".urlencode($l)."' not found"); + if ($qr) { + l::debug("Query returned " . dbCount($qr) . " row(s)", LOG_DEBUG); + } + return false; + } + + list($uid,$l2,$pw) = dbFetchArray($qr); + if ($p != $pw) { + l::info("Account authentication failed, login '".urlencode($l)."' used invalid password"); + l::debug("User ID: $uid ; real user name: $l2", LOG_DEBUG); + return false; + } + + // Close existing sessions for the same user + $q = dbQuery("SELECT id,tracking FROM web_session WHERE account=$uid"); + if ($q && dbCount($q)) { + list($sid, $tid) = dbFetchArray($q); + dbQuery( + "INSERT INTO account_log(tracking,account,ip_addr,action) " + . "VALUES ($tid,$uid,'AUTO','OUT')" + ); + dbQuery("DELETE FROM web_session WHERE id=$sid"); + } + + $_SESSION['login'] = $l2; + $_SESSION['userid'] = $uid; + l::info("Player '$l2' (#$uid) logged in"); + tracking::$data['previous_login'] = $l; + session::setAccount($uid); + + accountLog('i', $uid); + $q = dbQuery("UPDATE account SET last_login=UNIX_TIMESTAMP(NOW()) WHERE id=$uid"); + return true; + } + + function checkAuth() { + $create = handler::$h->needsAuth || input::$input['userlogin'] == 1 || input::$input['authcode'] != '' || input::$input['ac_restart'] != ""; + + if (!$create) { + if ($_SESSION['authok']) { + $this->accountValidation(true); + } + return; + } + + if (tracking::$data['bat']) { + // User tried to log in after being kicked + session::kill(); + $this->displayKicked(); + } elseif ($_SESSION['login'] != "") { + $this->accountValidation(); + } elseif (input::$input['userlogin'] == 1) { + if ($this->checkAccountAuth(input::$input['login'], input::$input['password'])) { + input::$input = $_SESSION['original_request']; + $this->accountValidation(); + } else { + sleep(5); + $this->displayLogin(input::$input['login'], true); + } + } else { + $_SESSION['original_request'] = input::$input; + $this->displayLogin(); + } + } + + + function handleInput() { + handler::$h->handle(input::$input); + if (is_null(handler::$h->output)) { + l::fatal(18, "In handler for ".input::$path." / " . input::$page); + } + } + + + function initRPC() { + ajax::init(); + } + + + function outputData() { + $this->outputPage(input::$game->version->id, handler::$h->output, handler::$h->data); + } + + function outputPage($version, $page, $args) { + $lang = getLanguage(); + $f = $this->checkOutput($version, $page, $lang); + + self::$version = $version; + $layoutFile = config::$main['scriptdir'] . "/site/$version/page.inc"; + loader::load($layoutFile, "page_layout"); + $this->layout = new page_layout(); + + ob_start(); + $this->layout->header($page, $lang); + $this->layout->includeFile($f, $args); + $this->layout->footer($page, $lang); + $data = ob_get_contents(); + ob_end_clean(); + + $this->sendHTTP($data); + } + + /** This function checks for the presence of a file containing the specified + * page in the specified language. It dies with an internal error if the file + * isn't found, and returns the file's path otherwise. + */ + function checkOutput($version, $page, $lg) { + $file = config::$main['scriptdir']."/site/$version/output/$page.$lg.inc"; + if (file_exists($file) && is_readable($file) && is_file($file)) { + return $file; + } + + $file = config::$main['scriptdir']."/site/$version/output/$page.inc"; + if (!(file_exists($file) && is_readable($file) && is_file($file))) { + l::fatal(16, "Page was '$page', language was '$lg'"); + } + + return $file; + } + + + function sendHTTP($data) { + header("Content-Type: text/html; charset=utf-8"); + if (input::$IE || input::$safari) { + // Don't even bother with the cache; in fact, make sure the cache doesn't interfere + header("Expires: Thu, 19 Feb 1981 05:00:00 GMT"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); + header("Cache-Control: no-cache, must-revalidate"); + header("Pragma: no-cache"); + + // Send the data directly + endRequest(false); + echo $data; + exit(0); + } + + // Generate a HTML resource from the data + addRawResource('html', $data); + $resId = storeResource("html", 21600, false); + $htmlId = md5($_SERVER['REQUEST_URI']) . ":$resId"; + + // Check the tracking cookie for the required contents + if (!is_array(tracking::$data['htmlId'])) { + tracking::$data['htmlId'] = array(); + } else { + $now = time(); + foreach (tracking::$data['htmlId'] as $ck => $t) { + if ($now - $t > 21600) { + unset(tracking::$data['htmlId'][$ck]); + } + } + } + if (!isset(tracking::$data['htmlId'][$htmlId])) { + tracking::$data['htmlId'][$htmlId] = time(); + } + + // Send the page's header + $time = tracking::$data['htmlId'][$htmlId]; + $lastModified = substr(date('r', $time), 0, -5).'GMT'; + $etag = '"'.md5($lastModified).'"'; + header("Last-Modified: $lastModified GMT"); + header("ETag: $etag"); + header("Cache-Control: no-cache, must-revalidate"); + + // Sends either a 304 reply or the data depending on the browser's will + $ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : false; + $ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : false; + if (($ifNoneMatch || $ifModifiedSince) && (!$ifNoneMatch || $ifNoneMatch == $etag) && (!$ifModifiedSince || $ifModifiedSince != $lastModified)) { + header('HTTP/1.0 304 Not Modified'); + } else { + endRequest(false); + echo $data; + exit(0); + } + } +} + + +?> diff --git a/scripts/lib/engines/redirect.inc b/scripts/lib/engines/redirect.inc new file mode 100644 index 0000000..88da38d --- /dev/null +++ b/scripts/lib/engines/redirect.inc @@ -0,0 +1,34 @@ +redirect = makeLink('main', 'index'); + $this->outputData(); + } + + + function displayFatalError($code, $i, $i2) { + header("Location: http://www.legacyworlds.com"); + } + + function handleInput() { + $this->redirect = handler::$h->redirect(input::$input); + } + + function outputData() { + header("Location: {$this->redirect}"); + } + + + function initSession() { + session::handle(false); + } + + + function checkAuth() { } + + function initRPC() { } +} + + diff --git a/scripts/lib/engines/rpc.inc b/scripts/lib/engines/rpc.inc new file mode 100644 index 0000000..20dd3ac --- /dev/null +++ b/scripts/lib/engines/rpc.inc @@ -0,0 +1,167 @@ +rpcHeader(); + echo "-:m"; + } + + + function displayFatalError($code, $i, $i2) { + $this->rpcHeader(); + echo "-:f:$code"; + } + + + function initSession() { + $create = handler::$h->needsAuth; + $r = session::handle($create); + if ($r == 1 && $create) { + $this->needsLogin(); + } elseif ($r == 2 && $create) { + l::debug("(RPC engine) tracknew? " . (tracking::$new ? "yes" : "no")); + l::fatal(12); + } + } + + + function sendKick($reason) { + $this->rpcHeader(); + echo "-:k:$reason"; + endRequest(); + } + + + function needsLogin() { + $this->rpcHeader(); + echo "-:a"; + endRequest(); + } + + + function checkAuth() { + if (!handler::$h->needsAuth) { + return; + } + + if (tracking::$data['bat']) { + // User tried to log in after being kicked + session::kill(); + + $q = dbQuery("SELECT reason FROM account WHERE id=" . tracking::$data['uid'] . " AND status='KICKED'"); + if ($q && dbCount($q) == 1) { + list($reason) = dbFetchArray($q); + } else { + $reason = ""; + } + $this->sendKick($reason); + } elseif ($_SESSION['login'] != "") { + $q = dbQuery("SELECT status,reason FROM account WHERE id={$_SESSION['userid']}"); + + if (!($q && dbCount($q) == 1)) { + $this->needsLogin(); + } + + list($status,$reason) = dbFetchArray($q); + + if ($status == "STD" || $status == "VAC") { + $this->checkGameRegistration(); + return; + } + + if ($status == 'KICKED') { + session::kill(); + $this->sendKick($reason); + } + + $this->rpcHeader(); + echo "-:"; + endRequest(); + } else { + $this->needsLogin(); + } + } + + private function checkGameRegistration() { + if (input::$path == "main") { + return; + } + + $game = input::$game; + if (!($game->status() == 'PRE' || $game->status() == 'FINISHED')) { + $lib = $game->getLib(); + $pid = $lib->call("doesUserPlay", $_SESSION['userid']); + if (!is_null($pid)) { + if (!is_array($_SESSION["{$game->name}_data"])) { + $_SESSION["{$game->name}_data"] = array("player" => $pid); + } + dbQuery("UPDATE main.credits SET resources_used = resources_used + 1 WHERE account = {$_SESSION['userid']}"); + return; + } + } + + $this->rpcHeader(); + echo "-:"; + endRequest(); + } + + function handleInput() { + // Get arguments and function name + $this->func = preg_replace('/[^A-Za-z0-9_]/', '', input::$input['rs']); + $this->args = empty(input::$input['rsargs']) ? array() : input::$input['rsargs']; + } + + + function initRPC() { + ajax::init(); + } + + + function outputData() { + if (is_array($_SESSION) && $_SESSION['authok']) { + if (is_null($_SESSION['last_page'])) { + $_SESSION['last_page'] = time(); + } else if (time() - $_SESSION['last_page'] >= 7200) { + l::notice("Player '{$_SESSION['login']}' (#{$_SESSION['userid']}) is idling - forcing log out"); + session::kill(); + $this->needsLogin(); + } + } + $this->rpcHeader(); + + $page = input::$page; + if (ajax::canCall($this->func)) { + $res = ajax::call($this->func, $this->args); + + // Trace activity +/* FIXME: Disabled for now, should be somewhere else. + $spyOn = array(); + if (in_array($_SESSION['userid'], $spyOn)) { + logText("*** TRACE ({$_SESSION['userid']}:{$_SESSION['login']}) RPC: {$page}::{$this->func} called, arguments:"); + foreach ($this->args as $arg) { + logText("*** TRACE ({$_SESSION['userid']}:{$_SESSION['login']}) RPC: -> $arg"); + } + logText("*** TRACE ({$_SESSION['userid']}:{$_SESSION['login']}) RPC: returning $res"); + } +*/ + + echo "+:$res"; + } else { + l::notice("RPC: unknown function call {$page}::{$this->func}"); + l::debug("Referer: " . $_SERVER['HTTP_REFERER']); + echo "-:c:{$this->func}"; + } + } +} + + diff --git a/scripts/lib/engines/template.inc b/scripts/lib/engines/template.inc new file mode 100644 index 0000000..0a7fcda --- /dev/null +++ b/scripts/lib/engines/template.inc @@ -0,0 +1,33 @@ +document = new DomDocument('1.0', 'utf-8'); + $this->outputMode = "xml"; + } + + function maintenance($maintenance) { + $this->data = new data_node('Maintenance'); + $this->data->addContents(new data_leaf('Until', strftime("%Y-%m-%d %H:%M:%S", $maintenance['until']))); + $this->data->addContents(new data_leaf('Current', gmstrftime("%Y-%m-%d %H:%M:%S"))); + $this->data->addContents(new data_leaf('Reason', $maintenance['reason'])); + + $this->rawOutput($this->genOutput('xml')); + } + + + function displayFatalError($code, $i, $i2) { + $this->data = new data_node('FatalError'); + $this->data->setAttribute('code', $code); + $this->data->addContents(new data_leaf('Text', $i)); + + return $this->genOutput('xml'); + } + + + private function loginFailed($code) { + $this->data = new data_node('Failed'); + $this->data->setAttribute('code', $code); + $this->rawOutput($this->genOutput('xml')); + } + + + private function kicked($reason) { + $this->data = new data_node('Kicked'); + $this->data->addContents(new data_leaf('Reason', $reason)); + $this->rawOutput($this->genOutput('xml')); + } + + + function initSession() { + // Check for "single-shot" request + if (input::$input['__s'] == 1) { + $_SESSION = array(); + return; + } + + // FIXME: handle session, but why bother doing it when the whole thing will be rewritten? + } + + + function checkAuth() { + // Check authentication directly in "single-shot" mode + if (input::$input['__s'] == 1) { + $this->singleShotAuth(input::$input['__l'], input::$input['__p']); + $this->outputMode = "lw"; + return; + } + + // FIXME: handle authentication, but why bother doing it when the whole thing will be rewritten? + } + + + private function singleShotAuth($login, $password) { + // Check the account's login and password + if (!$this->checkAccountAuth($login, $password)) { + sleep(5); + $this->loginFailed(0); + } + + // Make sure the account is in a valid state + $q = dbQuery("SELECT status,reason FROM account WHERE id={$_SESSION['userid']}"); + if (!$q || dbCount($q) != 1) { + $this->loginFailed(1); + } + + list($status,$reason) = dbFetchArray($q); + if ($status == 'KICKED') { + $this->kicked($reason); + } elseif ($status == 'QUIT' || $status == 'INAC') { + $this->loginFailed(2); + } elseif ($status == 'NEW') { + $this->loginFailed(3); + } + + $_SESSION['authok'] = true; + if (!$this->checkVersionRegistration()) { + $this->loginFailed(4); + } + + $this->singleShotLog(); + } + + + private function checkAccountAuth($l, $p) { + $l1 = addslashes($l); $p1 = addslashes($p); + $q = "SELECT id,name,password FROM account WHERE lower(name)=lower('$l1')"; + $qr = dbQuery($q); + + if (!$qr || dbCount($qr) != 1) { + l::info("Single-shot account authentication failed: login '".urlencode($l)."' not found"); + if ($qr) { + l::debug("Query returned " . dbCount($qr) . " row(s)", LOG_DEBUG); + } + return false; + } + + list($uid,$l2,$pw) = dbFetchArray($qr); + if ($p != $pw) { + l::info("Single-shot account authentication failed: login '" + . urlencode($l) . "' used invalid password"); + l::debug("User ID: $uid ; real user name: $l2"); + return false; + } + + $_SESSION['login'] = $l2; + $_SESSION['userid'] = $uid; + + return true; + } + + + private function checkVersionRegistration() { + if (input::$path == "main") { + return true; + } + + $game = config::getGame(input::$path); + $lib = $game->getLib(); + $pid = $lib->call("doesUserPlay", $_SESSION['userid']); + if (is_null($pid)) { + return false; + } + if (!is_array($_SESSION["{$game->name}_data"])) { + $_SESSION["{$game->name}_data"] = array("player" => $pid); + } + return true; + } + + + private function singleShotLog() { + $uid = $_SESSION['userid']; + + $q = dbQuery("SELECT id FROM web_session WHERE account = $uid"); + if (!($q && dbCount($q))) { + $in = time(); + $out = $in + 1; + $q = dbQuery("UPDATE account SET last_login=$in,last_logout=$out WHERE id=$uid"); + } + + dbQuery("INSERT INTO account_log(account,ip_addr,action,t) VALUES ($uid,'AUTO','IN',unix_timestamp(now()) - 1)"); + dbQuery("INSERT INTO account_log(account,ip_addr,action) VALUES ($uid,'AUTO','OUT')"); + } + + + function handleInput() { + $data = handler::$h->xml(input::$input); + if (!($data instanceof data_gen)) { + l::fatal(18, "In XML handler for " . input::$path . "/" . input::$page); + } + + $this->data = $data; + } + + + function initRPC() { /*NO RPC*/ } + + + function outputData() { + $data = $this->genOutput($this->outputMode); + + if (input::$IE || input::$safari || tracking::$disabled) { + $this->rawOutput($data); + } else { + $this->cachedOutput($data); + } + } + + + private function cachedOutput($data) { + // Generate an XML resource from the data + addRawResource('xml', $data); + $resId = storeResource("xml", 21600, false); + $xmlId = md5($_SERVER['REQUEST_URI']) . ":$resId"; + + // Check the tracking cookie for the required contents + if (!is_array(tracking::$data['xmlId'])) { + tracking::$data['xmlId'] = array(); + } + if (!isset(tracking::$data['xmlId'][$xmlId])) { + tracking::$data['xmlId'][$xmlId] = time(); + } + + // Send the page's header + $time = tracking::$data['xmlId'][$xmlId]; + $lastModified = substr(date('r', $time), 0, -5).'GMT'; + $etag = '"'.md5($lastModified).'"'; + header("Content-Type: text/xml; charset=utf-8"); + header("Last-Modified: $lastModified GMT"); + header("ETag: $etag"); + header("Cache-Control: no-cache, must-revalidate"); + + // Sends either a 304 reply or the data depending on the browser's will + $ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : false; + $ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : false; + if (($ifNoneMatch || $ifModifiedSince) && (!$ifNoneMatch || $ifNoneMatch == $etag) && (!$ifModifiedSince || $ifModifiedSince != $lastModified)) { + header('HTTP/1.0 304 Not Modified'); + } else { + endRequest(false); + echo $data; + exit(0); + } + } + + + private function rawOutput($data) { + header("Content-Type: text/xml; charset=utf-8"); + header("Expires: Thu, 19 Feb 1981 05:00:00 GMT"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); + header("Cache-Control: no-cache, must-revalidate"); + header("Pragma: no-cache"); + + // Send the data directly + endRequest(false); + echo $data; + exit(0); + } + + + private function genOutput($mode) { + if ($mode == 'xml') { + $data = $this->data->toXML($this->document); + } else { + $data = $this->data->toLWData($this->document); + } + $this->document->appendChild($data); + return $this->document->saveXML(); + } +} + +?> diff --git a/scripts/lib/game.inc b/scripts/lib/game.inc new file mode 100644 index 0000000..3118502 --- /dev/null +++ b/scripts/lib/game.inc @@ -0,0 +1,243 @@ +version = $version; + $this->name = $name; + $this->namespace = $namespace; + $this->public = $public; + $this->canJoin = $canJoin; + $this->text = $text; + $this->ticks = array(); + $this->params = array(); + $this->descriptions = array(); + $this->initExternal(); + } + + + private function initExternal() { + $this->dir = $this->version->getDirectory(); + $this->siteDir = $this->version->getSiteDirectory(); + } + + + function getDBAccess() { + if ($this->db) { + return $this->db; + } + + if (!db::$database) { + return null; + } + return ($this->db = db::$database->getGameAccess($this->namespace)); + } + + + function addTick($tick) { + $td = $tick->definition->script; + if (is_null($this->ticks[$td])) { + $this->ticks[$td] = $tick; + } + } + + + private function __loadActionObject() { + if ($this->actionObj) { + return; + } + + $acn = $this->version->loadActionsClass(); + $this->actionObj = new $acn($this); + + foreach (get_class_methods($acn) as $method) { + $this->actions[strtolower($method)] = array(false, $method); + } + + if (is_array($this->actionObj->index)) { + foreach ($this->actionObj->index as $action) { + if (strtolower($action) == strtolower($acn)) { + continue; + } + $this->actions[strtolower($action)] = array(true, $action, null); + } + } + } + + + private function __doAction($action, $args, $log = false) { + // Load actions object + $this->__loadActionObject(); + + // Check action + if (!is_array($this->actions[$action])) { + l::fatal(23, "Unknown action was '$action' on game '{$this->name}'"); + } + + if ($this->actions[$action][0]) { + // Load separate action class + if (!is_object($this->actions[$action][2])) { + $acn = $this->version->loadAction($this->actions[$action][1]); + $this->actions[$action][2] = new $acn($this); + } + $rv = call_user_func_array(array($this->actions[$action][2], 'run'), $args); + } else { + // Call the action instance's method + $rv = call_user_func_array(array($this->actionObj, $this->actions[$action][1]), $args); + } + + return $rv; + } + + + public function action() { + $n = func_num_args(); + if ($n == 0) { + l::fatal(22, "Empty action call on game '{$this->name}'"); + } + + // Get arguments + $args = func_get_args(); + $action = strtolower(array_shift($args)); + + return $this->__doAction($action, $args); + } + + + public function deprecatedAction() { + $n = func_num_args(); + if ($n == 0) { + l::fatal(22, "Empty action call on game '{$this->name}'"); + } + + // Get arguments + $args = func_get_args(); + $action = strtolower(array_shift($args)); + + return $this->__doAction($action, $args); + } + + + function getLib($lib = null) { + if (is_null($lib)) { + $lib = $this->version->id; + } + + if (is_null($this->libraries[$lib])) { + $this->libraries[$lib] = new library($lib, $this); + } + $this->getDBAccess(); + return $this->libraries[$lib]; + } + + + function runTick($name, $okNotFound = false) { + if (!is_null($this->ticks[$name])) { + $this->ticks[$name]->run(); + } elseif ($okNotFound) { + $tick = $this->getLib($this->version->id . "/ticks/$name"); + if (is_null($tick)) { + l::error("TICK: tick library '$libName' not found"); + } else { + $tick->call('runTick'); + } + } + } + + + private function earliestTick() { + $then = time() + 10 * 365 * 24 * 60 * 60; + foreach ($this->ticks as $td => $tick) { + if ($tick->first < $then && $tick->definition->public) { + $then = $tick->first; + } + } + return $then; + } + + private function latestTick() { + $then = 0; + foreach ($this->ticks as $td => $tick) { + if (is_null($tick->last)) { + continue; + } + if ($tick->last > $then) { + $then = $tick->last; + } + } + return $then; + } + + private function computeStatus() { + $visible = $this->public && $this->canJoin; + $this->__firstTick = $this->earliestTick($this); + $this->__lastTick = $this->latestTick($this); + $now = time(); + + if ($this->__lastTick && $this->__lastTick < $now) { + $this->__status = 'FINISHED'; + } elseif (!$visible) { + $this->__status = 'PRE'; + } else { + $running = ($this->__firstTick <= $now); + if ($running) { + if ($this->__lastTick) { + $this->__status = "ENDING"; + } else { + $gLib = $this->getLib(); + $this->__status = $gLib->call('isFinished') ? 'VICTORY' : 'RUNNING'; + } + } else { + $this->__status = 'READY'; + } + } + } + + + function status() { + if (is_null($this->__status)) { + $this->computeStatus(); + } + return $this->__status; + } + + function firstTick() { + if (is_null($this->__firstTick)) { + $this->computeStatus(); + } + return $this->__firstTick; + } + + function lastTick() { + if (is_null($this->__lastTick)) { + $this->computeStatus(); + } + return $this->__lastTick; + } + + + + function __sleep() { + return array('version', 'name', 'namespace', 'public', 'canJoin', 'text', 'descriptions', 'ticks', 'params'); + } + + function __wakeup() { + $this->initExternal(); + } + + function sessName() { + if (class_exists('input')) { + return input::$game->name . "_data"; + } + return ""; + } + +} + + +?> diff --git a/scripts/lib/handler.inc b/scripts/lib/handler.inc new file mode 100644 index 0000000..9304ba4 --- /dev/null +++ b/scripts/lib/handler.inc @@ -0,0 +1,77 @@ +siteDir}/handlers") || !is_dir($game->siteDir."/handlers")) { + l::fatal(8, array("Handlers directory for path '$path' not found (page '$page')")); + } + + $ifile = "{$game->siteDir}/handlers/$page.inc"; + if (!(file_exists($ifile) && is_readable($ifile) && is_file($ifile))) { + l::notice("Requested path '" . input::$path . "' doesn't match any handler"); + l::debug("Referer was '{$_SERVER['HTTP_REFERER']}' ; requested page was '" . input::$page . "'"); + $path = input::$path = 'main'; + $page = input::$page = 'notfound'; + input::$eType = null; + $game = input::$game = config::getGame('main'); + $ifile = "{$game->siteDir}/handlers/$page.inc"; + } + + if (!include_once($ifile)) { + l::fatal(10, "File inclusion failed: '$ifile'"); + } + if (!class_exists('page_handler')) { + l::fatal(11, "File '$ifile' did not define a page_handler class"); + } + + handler::$h = $handler = new page_handler(); + $handler->game = $game; + + /* The handler is loaded, check the engine type vs. the handler's + * supported and default engines. + */ + + // Get the list of supported engines + if (is_array($handler->engines)) { + $engines = $handler->engines; + } else { + $engines = array('page', 'css', 'js', 'rpc'); + } + // Get the default engine + if (is_null($handler->defaultEngine)) { + $dEngine = $engines[0]; + } else { + $dEngine = $handler->defaultEngine; + } + + // Set the engine type to default if it isn't set + if (is_null(input::$eType)) { + input::$eType = $dEngine; + } + + // Check the engine's type against the list of supported engines + if (!in_array(input::$eType, $engines)) { + l::fatal(31, "$path/$page: trying to use unsupported engine type '" . input::$eType . "'"); + } + + return $handler; + } +} + + +?> diff --git a/scripts/lib/input.inc b/scripts/lib/input.inc new file mode 100644 index 0000000..61d6f8e --- /dev/null +++ b/scripts/lib/input.inc @@ -0,0 +1,120 @@ + 2) { + l::notice("Invalid path requested: '$p'"); + l::debug("Referer was '{$_SERVER['HTTP_REFERER']}'"); + input::$path = 'main'; + input::$page = 'notfound'; + input::$eType = null; + input::$game = config::getGame('main'); + } else { + if (count($tmp) == 1) { + input::$path = 'main'; + } else { + input::$path = array_shift($tmp); + } + input::$page = $tmp[0]; + } + } + + // Find the game instance for this game + if (!config::hasGame(input::$path)) { + l::notice("Requested path '" . input::$path . "' doesn't match any game"); + l::debug("Referer was '{$_SERVER['HTTP_REFERER']}' ; requested page was '" . input::$page . "'"); + input::$path = 'main'; + input::$page = 'notfound'; + input::$eType = null; + input::$game = config::getGame('main'); + } else { + input::$game = config::getGame(input::$path); + } + + l::setPrefix(input::$path . "/" . input::$page . (is_null(input::$eType) ? "" : ( "." . input::$eType))); + + return array(input::$path, input::$page, input::$eType); + } + + + /** This function reads the arguments in the $_POST variable, then in the $_GET + * variable, and stores them inside an hash table. If PHP's crappy "magic + * quotes" are enabled, remove them. + * NOTE: This behaviour should be reversed in a production version. + */ + function read() { + input::$IE = preg_match('/MSIE/', $_SERVER['HTTP_USER_AGENT']) + && !preg_match('/Opera/', $_SERVER['HTTP_USER_AGENT']); + input::$safari = preg_match('/AppleWebKit/', $_SERVER['HTTP_USER_AGENT']); + + $p = array(); + foreach ($_POST as $k => $v) { + $p[$k] = $v; + } + foreach ($_GET as $k => $v) { + $p[$k] = $v; + } + if (get_magic_quotes_gpc()) { + foreach ($p as $k => $v) { + if (is_scalar($v)) { + $p[$k] = stripslashes($v); + } elseif (is_array($v)) { + $p[$k] = array(); + foreach ($v as $ak => $av) { + $p[$k][$ak] = stripslashes($av); + } + } + } + } + input::$input = $p; + return $p; + } +} + + +?> diff --git a/scripts/lib/library.inc b/scripts/lib/library.inc new file mode 100644 index 0000000..5d6c696 --- /dev/null +++ b/scripts/lib/library.inc @@ -0,0 +1,79 @@ +name = $name; + $this->game = $game; + } + + function loadClass($name = null) { + // Get the path to the class to be loaded + $path = config::$main['scriptdir'] . "/game/{$this->name}/library"; + if (!is_null($name)) { + $path .= "/$name"; + } + $path .= ".inc"; + + // Get the class' name + $cn = preg_replace('#/#', '_', strtolower($this->name)); + $cn .= is_null($name) ? "_library" : "_$name"; + + // Load it + loader::load($path, $cn); + return $cn; + } + + function call() { + $n = func_num_args(); + if ($n == 0) { + l::fatal(22, "Empty library call for library '{$this->name}' on game '{$this->game->game['site_path']}'"); + } + + // Load the main class if that is needed + if (!$this->mainClass) { + $lcn = $this->loadClass(); + $this->mainClass = new $lcn($this); + + foreach (get_class_methods($lcn) as $method) { + $this->functions[strtolower($method)] = array(false, $method); + } + + if (is_array($this->mainClass->index)) { + foreach ($this->mainClass->index as $function) { + if (strtolower($function) == strtolower($lcn)) { + continue; + } + $this->functions[strtolower($function)] = array(true, $function, null); + } + } + } + + // Check function + $args = func_get_args(); + $function = strtolower(array_shift($args)); + if (!is_array($this->functions[$function])) { + l::fatal(23, "Unknown function call '$function' in library '{$this->name}' on game '{$this->game->game['site_path']}'"); + } + + if ($this->functions[$function][0]) { + // Load separate class + if (!is_object($this->functions[$function][2])) { + $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); + } else { + // Call the function instance's method + $rv = call_user_func_array(array($this->mainClass, $this->functions[$function][1]), $args); + } + + return $rv; + } +} + +?> diff --git a/scripts/lib/log.inc b/scripts/lib/log.inc new file mode 100644 index 0000000..43a41ab --- /dev/null +++ b/scripts/lib/log.inc @@ -0,0 +1,295 @@ + "Could not open configuration file", + 1 => "Could not connect to database", + 2 => "Failed to set up tracking data", + 3 => "Failed to set up tracking data", + 4 => "Failed to set up tracking data", + 5 => "Failed to set up tracking data", + 6 => 'Invalid request', + 7 => 'Invalid request', + 8 => 'Page not found', + 9 => 'Page not found', + 10 => 'Internal error', + 11 => 'Internal error', + 12 => "Failed to set up session data", + 13 => "Failed to set up session data", + 14 => "Failed to set up session data", + 15 => "Failed to set up session data", + 16 => 'Internal error', + 17 => 'Internal error', + 18 => "Internal error", + 19 => 'Internal error', + 20 => 'Internal error', + 21 => 'Internal error', + 22 => 'Internal error', + 23 => 'Internal error', + 24 => 'Internal error', + 25 => 'Internal error', + 26 => 'Invalid extension', + 27 => 'Unhandled extension', + 28 => 'Internal error', + 29 => 'Internal error', + 30 => 'Resource not found', + 31 => 'Unhandler extension', + ); + + + /** This variable indicates whether the logging system has + * been initialised. + */ + private static $initialised = false; + + + /** This variable contains the prefix to use when writing + * to syslog. + */ + private static $syslogPrefix = "lwEngine"; + + + /** This variable defines a text that is used as a prefix + * when logging strings. + */ + private static $prefix = ""; + + + /** This variable prevents multiple "deprecated" entries from + * being logged. + */ + private static $deprecatedLogged = false; + + + /** This variable prevents multiple "FIXME" entries from + * being logged. + */ + private static $fixmeLogged = false; + + + /** This function is the default fatal error display function. */ + private function defaultFatalError($errno, $error, $info) { + ob_start(); +?> + + + Legacy Worlds + + +

    + The LegacyWorlds game engine encountered a fatal error:
    +
    + Please report this error by sending an e-mail to the staff at + webmaster@legacyworlds.com + explaining what happened. +

    + + + 30 ? 1 : (31 - $cnLength)) . $data['class'] + . " :: " . $data['function']; + if (!is_null($data['file'])) { + $cnLength = strlen($data['function']); + $fn = preg_replace("#^$base/#", "", $data['file']); + $str .= str_repeat(' ', $cnLength > 25 ? 1 : (26 - $cnLength)) + . " (line {$data['line']}, file '$fn')"; + } + self::info($str); + } + } + + + /** This method changes the syslog prefix. + */ + public function setSyslogPrefix($prefix) { + self::$syslogPrefix = $prefix; + if (self::$initialised) { + closelog(); + self::$initialised = false; + } + } + + /** This method changes the string prefix. + */ + public function setPrefix($prefix) { + self::$prefix = "$prefix "; + } + + /** This method changes the function to call + * for fatal errors. + */ + public function setFatalHandler($function) { + self::$fatalErrorCallback = $function; + } + + /** This function logs the use of a deprecated + * function call and prevents further logging of + * similar occurences. + */ + public function deprecated($function) { + if (config::$main['debug'] == 2 && !self::$deprecatedLogged) { + l::trace("DEPRECATED: $function"); + l::backtrace(); + self::$deprecatedLogged = true; + } + } + + /** This function logs FIXME's. */ + public function FIXME($text) { + if (config::$main['debug'] >= 1 && !self::$fixmeLogged) { + l::debug("FIXME: $text"); + if (config::$main['debug'] == 2) { + l::backtrace(); + } + self::$fixmeLogged = true; + } + } + + + /******************* LOGGING METHODS ************************/ + /* These methods should replace logText() wherever possible */ + /************************************************************/ + public function crit($txt) { self::init(); self::__write($txt, LOG_CRIT); } + public function critical($txt) { self::init(); self::__write($txt, LOG_CRIT); } + public function error($txt) { self::init(); self::__write($txt, LOG_ERR); } + public function warn($txt) { self::init(); self::__write($txt, LOG_WARNING); } + public function warning($txt) { self::init(); self::__write($txt, LOG_WARNING); } + public function notice($txt) { self::init(); self::__write($txt, LOG_NOTICE); } + public function info($txt) { self::init(); self::__write($txt, LOG_INFO); } + public function debug($txt) { + if (config::$main['debug'] >= 1) { + self::init(); self::__write($txt, LOG_DEBUG); + } + } + public function trace($txt) { + if (config::$main['debug'] == 2) { + self::init(); self::__write($txt, LOG_DEBUG); + } + } +} + + + +/** This function writes an entry to the system log. */ +function logText($txt, $level = null) { + l::deprecated("logText()"); + l::write($txt, $level); +} + + +/** This function displays one of the game engine's fatal errors and adds + * log entries accordingly. + */ +function fatalError($errno, $information = null) { + l::deprecated("fatalError($errno)"); + l::fatal($errno, $information); +} + +?> diff --git a/scripts/lib/output.inc b/scripts/lib/output.inc new file mode 100644 index 0000000..0d94136 --- /dev/null +++ b/scripts/lib/output.inc @@ -0,0 +1,141 @@ +initSession(); + engine::$e->checkAuth(); + + if (is_array($_SESSION) && is_array(config::$main['trace']) + && in_array($_SESSION['userid'], config::$main['trace'])) { + + $path = input::$path; + $page = input::$page; + ob_start(); + print "REQUEST AT $path/$page." . input::$eType . "\n"; + print "==========================================\n"; + print "Time: " . gmstrftime("%Y-%m-%d %H:%M:%S", time()) . "\n\n"; + print "SESSION DATA\n"; + print_r($_SESSION); + print "\n\nINPUT:"; + print_r(input::$input); + print "\n\n"; + $log = ob_get_contents(); + $logFile = fopen("/tmp/trace-{$_SESSION['userid']}" . gmstrftime("-%Y-%m-%d", time()) . ".log", "a"); + fwrite($logFile, $log); + fclose($logFile); + ob_end_clean(); + } + + engine::$e->handleInput(); + engine::$e->initRPC(); + engine::$e->outputData(); + endRequest(); +} + +?> diff --git a/scripts/lib/pcheck.inc b/scripts/lib/pcheck.inc new file mode 100644 index 0000000..0261175 --- /dev/null +++ b/scripts/lib/pcheck.inc @@ -0,0 +1,104 @@ + 22) { + return false; + } + + return $pid; + } + + private static function getQueue() { + if (! is_null(self::$queue)) { + return; + } + + $key = ftok(config::$main['scriptdir'] . "/lib/pcheck_manager.inc", "C"); + if ($key == -1) { + throw new Exception("Could not create the control queue's key"); + } + + self::$queue = @msg_get_queue($key); + if (self::$queue === FALSE) { + self::$queue = null; + throw new Exception("Could not access the control queue (using key $key)"); + } + + self::$pid = posix_getpid(); + } + + private static function sendRequest($addresses) { + array_unshift($addresses, self::$pid); + if (!@msg_send(self::$queue, 1, $addresses, true, false)) { + throw new Exception("Error while sending request"); + } + } + + private static function getResults() { + $wait = 30; + do { + $success = @msg_receive(self::$queue, self::$pid, $type, 32768, $result, + true, MSG_IPC_NOWAIT, $error); + + if (!$success && $error != MSG_ENOMSG) { + throw new Exception("Error while waiting for results"); + } elseif ($success) { + if (is_array($result)) { + return $result; + } elseif ($result == 'PING') { + $wait = 30; + } else { + throw new Exception("Invalid message received"); + } + } else { + sleep(1); + } + + $wait --; + + } while ($wait); + + throw new Exception("Timeout while waiting for results"); + } + + public static function check($addresses, $force = false) { + if (!($force || self::isRunning())) { + throw new Exception("The detector doesn't seem to be running"); + } + self::getQueue(); + self::sendRequest($addresses); + return self::getResults(); + } + +} + + +?> diff --git a/scripts/lib/pcheck_manager.inc b/scripts/lib/pcheck_manager.inc new file mode 100644 index 0000000..80e5ac6 --- /dev/null +++ b/scripts/lib/pcheck_manager.inc @@ -0,0 +1,629 @@ +debug) { + return; + } + $pipe = fopen(config::$main['cs_fifo'], "w"); + fwrite($pipe, "$command\n"); + fclose($pipe); + } + + + /** This method is called by the error manager in case a fatal error + * prevents the manager from functionning properly. + */ + public static function fatalError($errno, $errorText, $information) { + $instance->shutdown(); + exit(1); + } + + + /** The constructor forks, initialises the message queues, starts + * the detector threads then calls waitForInstructions(). It never + * returns. + */ + public function __construct($debug) { + self::$instance = $this; + $this->debug = $debug; + + $this->backgroundProcess(); + $this->initMessageQueues(); + $this->initSignals(); + $this->initData(); + $this->initThreads(); + $this->initCache(); + + // Sends our PID to the controller + self::sendToControl("PCPID " . ($this->pid = posix_getpid())); + + l::notice("Proxy detector initialised"); + l::debug("Timeout: " . self::$timeout . "s; threads: " . count($this->threads)); + l::debug("Using URL {$this->url}"); + + $this->mainLoop(); + + $this->shutdown(); + exit(0); + } + + + /** This method initialises the manager's signal handlers. + */ + private function initSignals() { + pcntl_signal(SIGTERM, array($this, "terminateHandler")); + pcntl_signal(SIGINT, array($this, "terminateHandler")); + pcntl_signal(SIGCHLD, array($this, "threadEndHandler")); + } + + + /** This method handles the TERM and INT signals, which both cause + * a clean shutdown. + */ + public function terminateHandler($signo) { + if (! $this->mustEnd) { + l::notice("Main thread terminating on SIG" . ($signo == SIGTERM ? "TERM" : "INT")); + $this->mustEnd = true; + } + } + + + /** This method handles SIGCHLD and takes appropriate measures if + * it has been caused by an error. + */ + public function threadEndHandler($signo) { + // Wait for the child processes and stores their IDs + $ended = array(); + do { + $pid = pcntl_waitpid(-1, $status, WNOHANG); + if ($pid > 0) { + $ended[$pid] = $status; + } + } while ($pid > 0); + + foreach ($this->threads as $thread) { + if (array_key_exists($thread->pid, $ended)) { + $thread->ended = true; + } + } + + if ($this->ending) { + l::trace("Threads have ended: " . join(', ', array_keys($ended))); + } else { + l::notice("Some children have met an untimely end! Terminating."); + $this->mustEnd = true; + } + } + + + /** This method causes the proxy checked to be run in the background. + */ + private function backgroundProcess() { + if ($this->debug) { + return; + } + + // Fork to the background + $pid = pcntl_fork(); + if ($pid == -1) { + l::crit("The open proxy detector failed to start."); + exit(1); + } elseif ($pid) { + exit(0); + } + posix_setsid(); + } + + + /** This method initialises the message queues: a first queue to be + * used as a control channel and on which requests will be received, + * and a second queue to communicate with the threads. + */ + private function initMessageQueues() { + // Create the control queue's key + $ctrlKey = ftok(config::$main['scriptdir'] . "/lib/pcheck_manager.inc", "C"); + if ($ctrlKey == -1) { + l::crit("Could not create the control queue's key"); + exit(1); + } + + // Create the thread queue's key + $thrdKey = ftok(config::$main['scriptdir'] . "/lib/pcheck_manager.inc", "T"); + if ($ctrlKey == -1) { + l::crit("Could not create the thread queue's key"); + exit(1); + } + + // Create the control queue + $ctrlQueue = msg_get_queue($ctrlKey, 0666 | IPC_CREAT); + if ($ctrlQueue === FALSE) { + l::crit("Could not create the control queue (using key $ctrlKey)"); + exit(1); + } + + // Create the thread queue + $thrdQueue = msg_get_queue($thrdKey, 0600 | IPC_CREAT); + if ($thrdQueue === FALSE) { + l::crit("Could not create the thread queue (using key $thrdKey)"); + @msg_remove_queue($ctrlQueue); + exit(1); + } + + $this->control = $ctrlQueue; + $this->threadQueue = $thrdQueue; + } + + + /** This method destroys the queues. + */ + private function destroyMsgQueues() { + @msg_remove_queue($this->control); + @msg_remove_queue($this->threadQueue); + } + + + /** This method initialises the data used by the proxy detector + * threads. + */ + private function initData() { + $serv = config::getParam('pcheck_server'); + $this->url = "http://$serv" . config::getParam('pcheck_path'); + $timeout = (int) config::getParam('pcheck_timeout'); + + self::$requests = array( + "GET" => "GET {$this->url}?k=__key__ HTTP/1.0\r\n" + . "Host: $serv\r\n" + . "Cache-Control: no-cache\r\n" + . "Pragma: no-cache\r\n" + . "User-Agent: OpenCheck 1.0\r\n" + . "\r\n", + "POST" => "POST {$this->url} HTTP/1.0\r\n" + . "Host: $serv\r\n" + . "Cache-Control: no-cache\r\n" + . "Pragma: no-cache\r\n" + . "User-Agent: OpenCheck 1.0\r\n" + . "Content-Length: 34\r\n" + . "\r\n" + . "k=__key__\r\n" + ); + self::$timeout = $timeout; + } + + + /** This method initialises all threads. + */ + private function initThreads() { + $nThreads = (int) config::getParam('pcheck_threads'); + for ($i = 0; $i < $nThreads; $i ++) { + try { + $thread = new pcheck_thread($this->threadQueue); + } catch (Exception $e) { + l::crit("Thread " . ($i + 1) . " failed to initialise, exiting!"); + $this->shutdown(); + exit(1); + } + array_push($this->threads, $thread); + } + $this->nFree = $nThreads; + } + + /** This method shuts down the manager. + */ + private function shutdown() { + $this->ending = true; + + // Kill threads + foreach ($this->threads as $thread) { + $thread->send(array("type" => "QUIT")); + } + + // Wait until all threads have ended + do { + $endOk = true; + foreach ($this->threads as $thread) { + if (! $thread->ended) { + $endOk = false; + sleep(1); + break; + } + } + } while (!$endOk); + + // Destroy message queues + $this->destroyMsgQueues(); + } + + + /** This method contains the manager's main loop, which handles everything + * from receiving requests and sending results to managing the detection + * threads. + */ + private function mainLoop() { + $this->requests = array(); + $this->reqHostsFound = array(); + $this->jobsQueue = array(); + $this->jobsData = array(); + + $ticker = 0; + while (!$this->mustEnd) { + // Check for incoming requests + $success = msg_receive($this->control, 1, $type, 32768, + $message, true, MSG_IPC_NOWAIT, $error); + if (!$success && $error != MSG_ENOMSG) { + l::error("Manager failed to receive from control queue"); + break; + } elseif ($success) { + $this->requestReceived(array_shift($message), $message); + continue; + } + + // Check for incoming results from the detection threads + $success = msg_receive($this->threadQueue, 1, $type, 32768, + $message, true, MSG_IPC_NOWAIT, $error); + if (!$success && $error != MSG_ENOMSG) { + l::error("Manager failed to receive from thread queue"); + break; + } elseif ($success) { + // A result has been received + $this->resultReceived(array_shift($message), $message); + continue; + } + + sleep(1); + $ticker ++; + + // For each request in progress, send a message every 10 ticks + // to signal the process we're not dead yet + if ($ticker % 10 == 0 && count($this->requests)) { + $this->sendPing(); + } + + // Send PID to controller every 20 ticks + if ($ticker == 20) { + $ticker = 0; + self::sendToControl("PCPID {$this->pid}"); + $this->flushCache(); + } + } + } + + + /** This method handles the reception of a new request. + */ + private function requestReceived($fromPID, $hosts) { + l::debug("Request received from $fromPID"); + + $now = time(); + $this->requests[$fromPID] = array(); + $this->reqHostsFound[$fromPID] = 0; + foreach ($hosts as $host) { + if (is_array($this->cache[$host]) && $now - $this->cache[$host]['last'] < 86400) { + // Cached entry found, store result + $this->requests[$fromPID][$host] = $this->cache[$host]['status']; + $this->reqHostsFound[$fromPID] ++; + continue; + } + + // No cached entry found + $this->requests[$fromPID][$host] = -2; + if (is_array($this->jobsData[$host])) { + // We're already trying to detect this host + continue; + } + + // This host needs to be detected + $this->addToJobsQueue($host); + } + + if ($this->reqHostsFound[$fromPID] == count($this->requests[$fromPID])) { + // The request could be satisfied directly from cached data + $this->sendResponse($fromPID); + } + } + + + /** This method stores the results of a scan performed by one of the + * detection threads. + */ + private function resultReceived($fromThread, $result) { + list($host, $found) = $result; + + // If a proxy was detected, log it + if ($found) { + l::info("Found open proxy at $host on port $port"); + } + + // Store the results + $this->jobsData[$host][1] |= $found; + $this->jobsData[$host][0] --; + if ($this->jobsData[$host][0] == 0) { + $this->hostFinished($host); + } + + // Increase amount of free threads, set the thread as free + $this->nFree ++; + foreach ($this->threads as $thread) { + if ($thread->pid == $fromThread) { + $thread->free = true; + break; + } + } + + // Shift the jobs queue + $this->moveQueue(); + } + + + /** This method adds the jobs required to scan a host to the queue. + */ + private function addToJobsQueue($host) { + l::trace("Adding host $host to the queue..."); + + $this->jobsData[$host] = array(count(self::$ports), false); + foreach (self::$ports as $port) { + array_push($this->jobsQueue, array($host, $port)); + } + + $this->moveQueue(); + } + + + /** This method returns the response to a request through the queue. + */ + private function sendResponse($requestPID) { + $request = $this->requests[$requestPID]; + + $nRequests = array(); + $nReqHF = array(); + foreach ($this->requests as $id => $data) { + if ($id != $requestPID) { + $nRequests[$id] = $data; + $nReqHF[$id] = $this->reqHostsFound[$id]; + } + } + $this->reqHostsFound = $nReqHF; + $this->requests = $nRequests; + + l::debug("Sending response to process #$requestPID"); + msg_send($this->control, $requestPID, $request, true); + } + + + /** This method sends a "ping" packet to all waiting processes. + */ + private function sendPing() { + l::trace("Pinging processes"); + foreach (array_keys($this->requests) as $id) { + msg_send($this->control, $id, "PING", true); + } + } + + + /** This method is called when the processing of a host + * is complete. + */ + private function hostFinished($host) { + l::trace("Host scanning finished for $host"); + $result = $this->jobsData[$host][1]; + + // Remove the entry from jobsData + $nData = array(); + foreach ($this->jobsData as $h => $d) { + if ($h != $host) { + $nData[$h] = $d; + } + } + $this->jobsData = $nData; + + // Store result in cache + $this->storeCache($host, $result); + + // Check all requests that contained this host + $checkRequests = array(); + foreach (array_keys($this->requests) as $request) { + if (array_key_exists($host, $this->requests[$request])) { + $this->reqHostsFound[$request] ++; + $this->requests[$request][$host] = $result ? 1 : 0; + array_push($checkRequests, $request); + } + } + + // For each request that contained the host, check if it's completed + $finished = array(); + foreach ($checkRequests as $request) { + if ($this->reqHostsFound[$request] == count($this->requests[$request])) { + array_push($finished, $request); + } + } + + // Send responses to completed requests + foreach ($finished as $request) { + $this->sendResponse($request); + } + } + + + /** This method sends free threads a scanning order if the + * jobs queue isn't empty. + */ + private function moveQueue() { + while ($this->nFree > 0 && count($this->jobsQueue)) { + $job = array_shift($this->jobsQueue); + foreach ($this->threads as $thread) { + if ($thread->free) { + l::trace("Assigning port {$job[1]} at {$job[0]} to thread {$thread->pid}"); + $this->nFree --; + $thread->free = false; + $thread->send(array( + "type" => "SCAN", + "scan" => $job + )); + break; + } + } + } + } + + + /** This method stores the result of a scan in the cache. + */ + private function storeCache($host, $result) { + $this->cache[$host] = array( + "status" => $result ? 1 : 0, + "last" => time() + ); + + $this->cacheModified = time(); + } + + /** This method reads the cache from the database. + */ + private function initCache() { + $this->cacheModified = 0; + $this->cache = array(); + + $success = false; + do { + try { + $db = db::connect(); + $db->enableExceptions(); + $db->query("LOCK TABLE proxy_detector IN ACCESS EXCLUSIVE MODE"); + $cacheRead = new db_copy("proxy_detector"); + $cacheRead->setAccessor($db); + $cacheRead->execute(); + $db->end(); + $db->close(); + $db->disableExceptions(); + $success = true; + } catch (Exception $e) { + l::notice("Could not read cache from database. Will retry in 20 seconds."); + l::info($e->getMessage()); + if (!is_null($db)) { + l::trace("Closing database connection"); + $db->close(); + $db->disableExceptions(); + } + sleep(20); + } + } while (! $success); + + for ($i = 0; $i < $cacheRead->rows(); $i ++) { + $row = $cacheRead->getRow($i); + $this->cache[$row[0]] = array( + "last" => $row[1], + "status" => $row[2] == 't' ? 1 : 0 + ); + } + } + + + /** This method tries to store the cache's contents in the + * database. + */ + private function flushCache() { + if (! $this->cacheModified || time() - $this->cacheModified < 20) { + return; + } + + l::debug("Flushing cache to database"); + $db = null; + try { + $db = db::connect(); + $db->enableExceptions(); + $db->query("LOCK TABLE proxy_detector IN ACCESS EXCLUSIVE MODE"); + $toWrite = $this->formatCache(); + $toWrite->setAccessor($db); + $toWrite->execute(); + $db->end(); + $db->close(); + $db->disableExceptions(); + } catch (Exception $e) { + l::notice("Could not write cache to database."); + l::info($e->getMessage()); + if (!is_null($db)) { + l::trace("Closing database connection"); + $db->close(); + $db->disableExceptions(); + } + return; + } + + $this->cacheModified = 0; + } + + + /** This method prepares the cache for copy into the database. + */ + private function formatCache() { + $cache = new db_copy("proxy_detector", db_copy::copyToClean); + $now = time(); + foreach ($this->cache as $host => $data) { + if ($now - $data['last'] > 3 * 86400) { + continue; + } + $cache->appendRow(array($host, $data['last'], $data['status'] ? 't' : 'f')); + } + return $cache; + } +} + + +?> diff --git a/scripts/lib/pcheck_thread.inc b/scripts/lib/pcheck_thread.inc new file mode 100644 index 0000000..eb9cadd --- /dev/null +++ b/scripts/lib/pcheck_thread.inc @@ -0,0 +1,144 @@ +pid = $pid; + $this->free = true; + $this->queue = $queue; + return; + } + + // In the child, store our PID and the queue, then starts + // waiting for instructions. + $this->pid = posix_getpid(); + $this->queue = $queue; + $this->waitLoop(); + } + + + /** This method is called by the manager to send messages to the + * threads. + */ + public function send($message) { + return $this->ended ? false : msg_send($this->queue, $this->pid, $message, true); + } + + + /** This method implements the instruction loop. + */ + private function waitLoop() { + $quit = false; + $error = false; + + do { + $success = msg_receive($this->queue, $this->pid, $type, 32768, $message, true); + if (! $success) { + l::error("Child failed to receive a message"); + $quit = $error = true; + } elseif ($message['type'] == 'QUIT') { + $quit = true; + } elseif ($message['type'] == 'SCAN') { + list($host, $port) = $message['scan']; + + $found = $this->executeCheck($host, $port, "GET") + || $this->executeCheck($host, $port, "POST"); + + if (!msg_send($this->queue, 1, array($this->pid, $host, $found), true)) { + l::error("Child failed to send message"); + $quit = $error = true; + } + } else { + l::notice("Unknown message {$message['type']} received"); + } + } while (!$quit); + + exit($error ? 1 : 0); + } + + + /** This method will try to check one port for open proxy software, using + * a specific method (POST or GET). + */ + private function executeCheck($ipAddress, $port, $method) { + $key = md5(uniqid(rand())); + + // Open the socket + $socket = @fsockopen("tcp://$ipAddress", $port, $errno, $errstr, pcheck_manager::$timeout); + if ($socket === FALSE) { + return false; + } + + // Make sure I/O doesn't timeout + stream_set_timeout($socket, pcheck_manager::$timeout); + + // Send the request + $result = @fwrite($socket, preg_replace('/__key__/', $key, pcheck_manager::$requests[$method])); + if ($result !== FALSE) { + $info = stream_get_meta_data($socket); + if ($info['timed_out']) { + $result = false; + } + } + + // Get the page + if ($result !== FALSE) { + $result = @fread($socket, 4096); + } + if ($result !== FALSE) { + $info = stream_get_meta_data($socket); + if ($info['timed_out']) { + $result = false; + } + } + + // Close the socket + @fclose($socket); + if ($result === FALSE) { + return false; + } + + return preg_match("/Key is \\<$key\\>/", $result); + } +} + + +?> diff --git a/scripts/lib/prefs.inc b/scripts/lib/prefs.inc new file mode 100644 index 0000000..e769fde --- /dev/null +++ b/scripts/lib/prefs.inc @@ -0,0 +1,112 @@ + $value) { + prefs::$prefs['main'][$pref] = $value; + } + } + } + + + /** This function sets user preferences. */ + static function set($path, $val) { + if (!$_SESSION['authok']) { + return false; + } + + if (!is_array(prefs::$prefs)) { + prefs::load(); + } + + list($ver, $pref) = explode('/', $path); + $q = dbQuery("DELETE FROM user_preferences WHERE account={$_SESSION['userid']} AND id='$pref' AND version='$ver'"); + $q = dbQuery("INSERT INTO user_preferences VALUES('$pref','$ver',{$_SESSION['userid']},'".addslashes($val)."')"); + if ($q) { + prefs::$prefs[$ver][$pref] = $val; + return true; + } + return false; + } + + /** This function returns a value from the preferences using a version/pref_name path. + */ + static function get($path, $default = null) { + if (!is_array(prefs::$prefs)) { + prefs::load(); + } + + list($version, $name) = explode('/', $path); + if (is_array(prefs::$prefs[$version])) { + $v = prefs::$prefs[$version][$name]; + } else { + $v = null; + } + if (is_null($v) && !is_null($default)) { + $v = $default; + } + return $v; + } + +} + +?> diff --git a/scripts/lib/resource.inc b/scripts/lib/resource.inc new file mode 100644 index 0000000..93884c9 --- /dev/null +++ b/scripts/lib/resource.inc @@ -0,0 +1,149 @@ + 0) { + if ($write) { + $q = dbQuery("SELECT md5 FROM web_cache WHERE unix_timestamp(now())-last_used>$deleteOld AND rtype='$type'"); + while ($r = dbFetchArray($q)) { + @unlink(config::$main['cachedir'] . "/{$r[0]}.$type"); + } + } + dbQuery("DELETE FROM web_cache WHERE unix_timestamp(now())-last_used>$deleteOld AND rtype='$type'"); + } + + // Check for an existing entry + $md5 = md5(serialize($resources[$type])); + $q = dbQuery("SELECT id FROM web_cache WHERE rtype='$type' AND md5='$md5' FOR SHARE"); + if ($q && dbCount($q)) { + list($id) = dbFetchArray($q); + return $id; + } + + // Generate the output file + if ($write && !generateResourceCache($type, $md5)) { + l::warn("Resource file generation failed (type $type)"); + return null; + } + + // Add the database entry + return dbQuery("INSERT INTO web_cache(rtype,md5,last_used) VALUES('$type','$md5'," . time() . ")"); +} + + +/** This function reads a resource, identified by its DB identifier, from the + * database. If the resource is found in the base, it then tries to send the + * file's contents. + */ +function displayResource($id, $rtype) { + $q = dbQuery("SELECT rtype,md5 FROM web_cache WHERE id=$id FOR UPDATE"); + if (!($q && dbCount($q) == 1)) { + l::warn("Resource ID '$id' not in the database"); + return false; + } + + list($dbtype,$md5) = dbFetchArray($q); + if ($rtype != $dbtype) { + l::warn("Resource ID '$id' has wrong type $dbtype (expected $rtype)"); + return false; + } + + dbQuery("UPDATE web_cache SET last_used=" . time() . " WHERE id=$id"); + endRequest(false); + + $path = config::$main['cachedir'] . "/$md5.$rtype"; + if (readfile($path) === FALSE) { + l::warn("File not found for resource '$id': $path"); + return false; + } + exit(0); +} + + +?> diff --git a/scripts/lib/session.inc b/scripts/lib/session.inc new file mode 100644 index 0000000..ab68bb8 --- /dev/null +++ b/scripts/lib/session.inc @@ -0,0 +1,185 @@ + diff --git a/scripts/lib/tick.inc b/scripts/lib/tick.inc new file mode 100644 index 0000000..84f5a70 --- /dev/null +++ b/scripts/lib/tick.inc @@ -0,0 +1,94 @@ +version = $version; + $this->script = $script; + $this->public = $public; + $this->text = array(); + $this->version->addTickDefinition($this); + } + + function addText($lang, $name, $description) { + $this->text[$lang] = array($name, $description); + } + + function getName($lang) { + return ($this->text[$lang] ? $this->text[$lang][0] : ""); + } + + function getDescription($lang) { + return ($this->text[$lang] ? $this->text[$lang][1] : ""); + } + + function getPath() { + return "{$this->version->id}/ticks/{$this->script}"; + } +} + + +class tick_instance { + + function __construct($game, $script, $first, $interval, $last) { + $this->game = $game; + $this->definition = $game->version->getTickDefinition($script); + $this->first = $first; + $this->interval = $interval; + $this->last = $last; + $this->game->addTick($this); + $this->computeNext(); + } + + function computeNext() { + $now = time(); + + if (!is_null($this->last) && $now >= $this->last) { + $this->next = null; + return; + } + + if ($now < $this->first) { + $this->next = $this->first; + return; + } + + $diff = $now - $this->first; + $mod = $diff % $this->interval; + $mul = ($diff - $mod) / $this->interval; + $this->next = $this->first + ($mul + 1) * $this->interval; + } + + function run() { + $libName = $this->definition->getPath(); + $tick = $this->game->getLib($libName); + + l::setPrefix("{$this->game->name}::{$this->definition->script}"); + if (is_null($tick)) { + l::error("tick library '$libName' not found"); + } else { + try { + $tick->call('runTick'); + } catch (Exception $e) { + l::error("tick failed with exception " . get_class($e)); + l::info("exception message: " . $e->getMessage()); + } + } + l::setPrefix(""); + } + + function __sleep() { + return array('game', 'definition', 'first', 'interval', 'last'); + } + + function __wakeup() { + $this->computeNext(); + } +} + + +?> diff --git a/scripts/lib/tick_manager.inc b/scripts/lib/tick_manager.inc new file mode 100644 index 0000000..10fc544 --- /dev/null +++ b/scripts/lib/tick_manager.inc @@ -0,0 +1,257 @@ +debug = $debug; + if (!$this->debug) { + // Forks to the background + $pid = pcntl_fork(); + if ($pid == -1) { + die("The tick manager failed to launch: fork() failed.\n"); + } elseif ($pid) { + exit(0); + } + + // We're in the child, detach from parent as much as possible + $this->pid = posix_getpid(); + posix_setsid(); + + // Tell the system control script that we are running + if (file_exists(config::$main['cs_fifo'])) { + $pipe = fopen(config::$main['cs_fifo'], "w"); + fwrite($pipe, "TMPID {$this->pid}\n"); + fclose($pipe); + } + } + + if (file_exists(config::$main['cachedir'] . "/stop_ticks")) { + unlink(config::$main['cachedir'] . "/stop_ticks"); + $this->ticksStopped = true; + touch(config::$main['cachedir'] . "/ticks_stopped"); + } elseif (file_exists(config::$main['cachedir'] . "/ticks_stopped")) { + $this->ticksStopped = true; + } else { + $this->ticksStopped = false; + } + + if ($this->ticksStopped) { + l::warn("Ticks manager starting but ticks are stopped"); + } else { + l::notice("Ticks manager started"); + } + + l::setFatalHandler('tick_manager::fatalError'); + $this->run(); + } + + + function run() { + $this->setSignals(false); + $this->setAlarm(); + while (1) { + // Sleeps for a while + sleep(10); + + // Check for configuration update + $this->update(); + + // Check last tick + if (time() - $this->lastTick >= 6) { + l::error("No ticks for 6+ seconds! Trying to re-schedule!"); + $this->setSignals(false); + $this->setAlarm(); + $this->lastTick = time(); + } + } + } + + + function setSignals($mask) { + $myHandler = array($this, "signalHandler"); + $sigs = array(SIGTERM, SIGINT, SIGCHLD); + foreach ($sigs as $s) { + if ($mask) { + pcntl_signal($s, SIG_IGN); + } else { + pcntl_signal($s, $myHandler); + } + } + + if ($mask) { + pcntl_signal(SIGALRM, SIG_IGN); + } else { + pcntl_signal(SIGALRM, array($this, "alarm")); + } + } + + + function signalHandler($signo) { + switch ($signo) : + case SIGTERM: + case SIGINT: + l::notice("Main thread terminating on SIG" . ($signo == SIGTERM ? "TERM" : "INT")); + exit(0); + case SIGCHLD: + do { + $wr = pcntl_waitpid(-1, $status, WNOHANG); + } while ($wr > 0); + break; + endswitch; + } + + + function update() { + // Mask signals + $this->setSignals(true); + + // Reload configuration + if (config::reload()) { + l::notice("Configuration changed, re-scheduling"); + $this->setAlarm(); + } + + // Stop/start ticks + if ($this->ticksStopped && file_exists(config::$main['cachedir'] . "/start_ticks")) { + l::notice("Ticks restarted"); + $this->ticksStopped = false; + unlink(config::$main['cachedir'] . "/ticks_stopped"); + } elseif (file_exists(config::$main['cachedir'] . "/stop_ticks")) { + l::notice("Ticks stopped"); + $this->ticksStopped = true; + touch(config::$main['cachedir'] . "/ticks_stopped"); + } + @unlink(config::$main['cachedir'] . "/stop_ticks"); + @unlink(config::$main['cachedir'] . "/start_ticks"); + + // Tell the system control script that we are still running + if (!$this->debug && file_exists(config::$main['cs_fifo'])) { + $pipe = fopen(config::$main['cs_fifo'], "w"); + fwrite($pipe, "TMPID {$this->pid}\n"); + fclose($pipe); + } + + // Unmask child signals + $this->setSignals(false); + } + + + function setAlarm() { + $min = null; + foreach (config::$config->games as $name => $game) { + foreach ($game->ticks as $tName => $tick) { + if (!is_null($tick->next) && (is_null($min) || $tick->next < $min)) { + $min = $tick->next; + } + } + } + if (is_null($min)) { + l::warn("No ticks left to execute"); + return; + } + + $delay = $min - time(); + $rdelay = $delay <= 1 ? 2 : $delay; + pcntl_alarm($rdelay); + } + + + function alarm($sig) { + $this->setSignals(true); + + $exec = array(); + $now = time(); + foreach (config::$config->games as $name => $game) { + foreach ($game->ticks as $tName => $tick) { + if (!is_null($tick->next) && $tick->next <= $now) { + array_push($exec, $tick); + $oNext = $tick->next; + $tick->computeNext(); + if ($tick->next !== $oNext && !$this->ticksStopped) { + if (is_null($tick->next)) { + l::info("Not rescheduling {$name}::{$tName}"); + } + } + } + } + } + if (!count($exec)) { + l::warning("No ticks scheduled for execution!"); + } + + $this->setAlarm(); + + foreach ($exec as $tick) { + $this->startThread($tick); + } + + $this->setSignals(false); + } + + + function startThread($tick) { + // Don't even try if ticks are stopped + if ($this->ticksStopped) { + $this->lastTick = time(); + return; + } + + // Fork + $pid = pcntl_fork(); + if ($pid == -1) { + l::error("Fork failed for {$tick->game->name}::{$tick->definition->script}!"); + return false; + } elseif ($pid) { + $this->lastTick = time(); + return $pid; + } + + // We're in the child, detach from parent as much as possible + if (!$this->debug) { + posix_setsid(); + } + + // Connect to the DB and check if the game's running + if (!dbConnect(false)) { + exit(0); + } + $tick->game->getDBAccess(); + if ($tick->game->version->id != 'main') { + $status = $tick->game->status(); + if ($status == 'FINISHED' + || ($status == 'PRE' || $status == 'READY') && $tick->definition->public) { + exit(0); + } + } + + // Execute the tick + l::info("TICK: {$tick->game->name}::{$tick->definition->script}"); + ob_start(); + $tick->run($t); + $text = ob_get_contents(); + ob_end_clean(); + + if ($text != '') { + $text = explode("\n", $text); + foreach ($text as $line) { + l::notice("{$tick->game->name}::{$tick->definition->script} OUTPUT: $line"); + } + } + + dbClose(); + + exit(0); + } +} + + +?> diff --git a/scripts/lib/tracking.inc b/scripts/lib/tracking.inc new file mode 100644 index 0000000..64d1c14 --- /dev/null +++ b/scripts/lib/tracking.inc @@ -0,0 +1,179 @@ + 5) { + tracking::$disabled = true; + return true; + } + + $q = "INSERT INTO web_tracking(cookie,created,last_used,ip_addr," + . "browser,stored_data) VALUES ('" . tracking::$id + . "',unix_timestamp(now()),unix_timestamp(now()),'".$_SERVER['REMOTE_ADDR']."','" + . addslashes($_SERVER['HTTP_USER_AGENT']) . "'," + . "'a:0:{}')"; + return dbQuery($q); + } + + + /** This function updates a tracking entry's last access timestamp. */ + private static function updateAccess() { + $q = "UPDATE web_tracking SET last_used=unix_timestamp(now()) WHERE cookie='" . tracking::$id . "'"; + return dbQuery($q); + } + + + /** This function reads tracking data from the web_tracking table and stores it + * in the tracking::$data variable. + */ + private static function readData() { + $trackId = tracking::$id; + + $q = "SELECT stored_data,id FROM web_tracking WHERE cookie = '$trackId' FOR UPDATE"; + $qr = dbQuery($q); + if (!$qr || dbCount($qr) != 1) { + l::notice("Tracking data not found for cookie '$trackId'"); + return false; + } + + $tmp = dbFetchArray($qr); + $trackData = unserialize($tmp[0]); + $trackDBId = $tmp[1]; + if (!is_array($trackData)) { + // Make sure we delete the tracking data that caused the problem + l::notice("Invalid tracking data for '$trackId'"); + l::debug("DB id= $trackDBId, data type= '" . gettype($trackData) . "'"); + l::info("Moving entry out of the way"); + //dbQuery("DELETE FROM web_tracking WHERE id=$trackDBId"); + dbQuery("UPDATE web_tracking SET cookie='DISABLED $trackDBId' WHERE id=$trackDBId"); + dbEnd(); + } else { + tracking::$dbId = $trackDBId; + tracking::$data = $trackData; + tracking::$dataMD5 = md5($tmp[0]); + } + + return is_array($trackData); + } + + + /** This function initializes the tracking system */ + static function init() { + tracking::$cName = config::getParam('trackname'); + + list($trackId,$trackNew) = tracking::readId(); + if (handler::$h->noTracking && (is_null($trackId) || $trackNew)) { + tracking::$disabled = true; + return; + } + + if (is_null($trackId)) { + l::fatal(2); + } + + tracking::$id = $trackId; + tracking::$new = $trackNew; + + if ($trackNew && !tracking::createData()) { + l::fatal(3); + } elseif (!$trackNew && !tracking::updateAccess()) { + l::fatal(4); + } + + if (tracking::$disabled) { + return; + } + + if (tracking::readData()) { + setcookie(tracking::$cName, $trackId, time() + 31536000, dirname($_SERVER['SCRIPT_NAME'])); + } else { + $trackDBId = tracking::$dbId; + l::fatal(5, "Tracking data: ID='$trackId',DB ID=$trackDBId" . ($trackNew ? ",new" : "")); + } + } + + + /** This function updates the web_tracking table using the serialized contents + * of tracking::$data + */ + static function store() { + if (is_null(tracking::$dbId)) { + if (tracking::$disabled) { + return 1; + } + l::warn("storeTrackingData: database identifier is null"); + return 1; + } + + $serialized = serialize(tracking::$data); + if (self::$dataMD5 != md5($serialized)) { + $txt = pg_escape_string(serialize(tracking::$data)); + $q = "UPDATE web_tracking SET last_used=unix_timestamp(now()),stored_data='$txt' WHERE id='" . tracking::$dbId . "'"; + } else { + $q = "UPDATE web_tracking SET last_used=unix_timestamp(now()) WHERE id='" . tracking::$dbId . "'"; + } + return dbQuery($q); + } +} + + +?> diff --git a/scripts/lib/version.inc b/scripts/lib/version.inc new file mode 100644 index 0000000..67ea5b1 --- /dev/null +++ b/scripts/lib/version.inc @@ -0,0 +1,48 @@ +id = $id; + $this->playable = $playable; + $this->text = $text; + $this->tickDefs = array(); + } + + function addTickDefinition($def) { + if (is_null($this->tickDefs[$def->script])) { + $this->tickDefs[$def->script] = $def; + } + } + + function getTickDefinition($id) { + return $this->tickDefs[$id]; + } + + function getDirectory() { + return config::$main['scriptdir'] . "/game/{$this->id}"; + } + + function getSiteDirectory() { + return config::$main['scriptdir'] . "/site/{$this->id}"; + } + + function loadActionsClass() { + $cn = "actions_{$this->id}"; + loader::load($this->getDirectory() . "/actions.inc", $cn); + return $cn; + } + + function loadAction($action) { + $cn = "{$this->id}_{$action}"; + loader::load($this->getDirectory() . "/actions/$action.inc", $cn); + + return $cn; + } +} + +?> diff --git a/scripts/lib/xml_config.inc b/scripts/lib/xml_config.inc new file mode 100644 index 0000000..96a0235 --- /dev/null +++ b/scripts/lib/xml_config.inc @@ -0,0 +1,376 @@ +firstChild; + + while ($node) { + if ($node->nodeType == XML_ELEMENT_NODE && $node->nodeName == 'Param') { + $aName = $node->getAttribute('name'); + $aValue = $node->getAttribute('value'); + if ($aName == "") { + l::warn("CONFIG: a main parameter is missing a 'name' attribute"); + } elseif (is_null(xml_config::$mGame->params[$aName])) { + xml_config::$mGame->params[$aName] = $aValue; + } else { + l::notice("CONFIG: duplicate main parameter '$aName'"); + } + } elseif ($node->nodeType == XML_ELEMENT_NODE) { + l::notice("CONFIG: found unexpected tag '{$node->nodeName}' in MainParams section"); + } + $node = $node->nextSibling; + } + } + + + private function parseMainTicks($root) { + $node = $root->firstChild; + + while ($node) { + if ($node->nodeType == XML_ELEMENT_NODE && $node->nodeName == 'Tick') { + $tScript = $node->getAttribute('script'); + $tFirst = $node->getAttribute('first'); + $tInterval = $node->getAttribute('interval'); + $tLast = $node->getAttribute('last'); + if ($tScript == "" || $tFirst == "" || $tInterval == "") { + l::warn("CONFIG: a main tick declaration is missing a mandatory attribute"); + } elseif ((int)$tInterval == 0) { + l::warn("CONFIG: invalid interval for main tick '$tScript'"); + } elseif (is_null(xml_config::$mVersion->getTickDefinition($tScript))) { + new tick_definition(xml_config::$mVersion, $tScript, false); + new tick_instance(xml_config::$mGame, $tScript, (int)$tFirst, + (int)$tInterval, $tLast ? (int)$tLast : null); + } else { + l::notice("CONFIG: duplicate main tick '$tScript'"); + } + } elseif ($node->nodeType == XML_ELEMENT_NODE) { + l::notice("CONFIG: found unexpected tag '{$node->nodeName}' in MainTicks section"); + } + $node = $node->nextSibling; + } + } + + + private function parseTickDefinition($version, $root) { + $script = $root->getAttribute('script'); + $public = ($root->getAttribute('public') == 1); + + if ($script == "") { + l::warn("CONFIG: invalid tick definition for version {$version->id}"); + return; + } + if (!is_null($version->getTickDefinition($script))) { + l::notice("CONFIG: duplicate tick definition '$script' for version {$version->id}"); + return; + } + + $def = new tick_definition($version, $script, $public); + if (!$public) { + return; + } + + $names = array(); + $descs = array(); + $node = $root->firstChild; + while ($node) { + if ($node->nodeType != XML_ELEMENT_NODE) { + $node = $node->nextSibling; + continue; + } + + if ($node->nodeName != "Name" && $node->nodeName != 'Description') { + l::warn("CONFIG: unexpected tag '{$node->nodeName}' found in tick definition " + . "'{$script}' for version '{$version->id}'"); + $node = $node->nextSibling; + continue; + } + + $lang = $node->getAttribute('lang'); + if ($lang == '') { + l::warn("CONFIG: missing language for {$node->nodeName} in tick definition " + . "'{$script}' for version '{$version->id}'"); + $node = $node->nextSibling; + continue; + } + + $contents = trim($node->textContent); + if ($contents == '') { + l::warn("CONFIG: missing contents for {$node->nodeName} in tick definition " + . "'{$script}' for version '{$version->id}'"); + $node = $node->nextSibling; + continue; + } + + if ($node->nodeName == 'Name') { + $names[$lang] = $contents; + } else { + $descs[$lang] = $contents; + } + + $node = $node->nextSibling; + } + + foreach ($names as $lang => $name) { + $def->addText($lang, $name, $descs[$lang]); + } + } + + + private function parseVersion($root) { + $id = $root->getAttribute("id"); + $cp = ($root->getAttribute("playable") == 1); + $tx = $root->getAttribute("text"); + + if ($id == "" || $tx == "") { + l::warn("CONFIG: invalid version definition (missing identifier or text)"); + return null; + } elseif ($id == 'main') { + l::warn("CONFIG: invalid version definition (using 'main' identifier)"); + return null; + } + + $version = new version($id, $cp, $tx); + + $node = $root->firstChild; + while ($node) { + if ($node->nodeType == XML_ELEMENT_NODE && $node->nodeName == 'Tick') { + xml_config::parseTickDefinition($version, $node); + } elseif ($node->nodeType == XML_ELEMENT_NODE) { + l::warn("CONFIG: found unexpected tag '{$node->nodeName}' in MainTicks section"); + } + $node = $node->nextSibling; + } + + return $version; + } + + + private function parseVersions($root) { + $node = $root->firstChild; + + $versions = array(); + while ($node) { + if ($node->nodeType == XML_ELEMENT_NODE && $node->nodeName == 'Version') { + $v = xml_config::parseVersion($node); + if (!is_null($v)) { + if (is_null($versions[$v->id])) { + $versions[$v->id] = $v; + } else { + l::notice("CONFIG: found duplicate definition for version '{$v->id}'"); + } + } + } elseif ($node->nodeType == XML_ELEMENT_NODE) { + l::notice("CONFIG: found unexpected tag '{$node->nodeName}' in Versions section"); + } + $node = $node->nextSibling; + } + + return $versions; + } + + + private function parseGame($root) { + $id = $root->getAttribute('id'); + if ($id == '') { + l::warn("CONFIG: game definition is missing an identifier"); + return null; + } + if ($id == 'main') { + l::warn("CONFIG: game definition with 'main' identifier ignored"); + return null; + } + + $version = $root->getAttribute('version'); + if (is_null(xml_config::$versions[$version])) { + l::warn("CONFIG: game '$id' has unknown version '$version'"); + return null; + } + + $namespace = $root->getAttribute('namespace'); + if ($namespace == '') { + l::warn("CONFIG: no namespace defined for game '$id'"); + return null; + } + $text = $root->getAttribute('text'); + if ($namespace == '') { + l::warn("CONFIG: no text for game '$id'"); + return null; + } + + $public = ($root->getAttribute('public') == 1); + $canJoin = ($root->getAttribute('canjoin') == 1); + + $game = new game(xml_config::$versions[$version], $id, $namespace, $public, $canJoin, $text); + + $node = $root->firstChild; + while ($node) { + if ($node->nodeType != XML_ELEMENT_NODE) { + $node = $node->nextSibling; + continue; + } + + if ($node->nodeName == 'Param') { + $aName = $node->getAttribute('name'); + $aValue = $node->getAttribute('value'); + if ($aName == "") { + l::warn("CONFIG: a parameter is missing a 'name' attribute for game '$id'"); + } elseif (is_null($game->params[$aName])) { + $game->params[$aName] = $aValue; + } else { + l::notice("CONFIG: duplicate parameter '$aName'"); + } + } elseif ($node->nodeName == 'Description') { + $lang = $node->getAttribute('lang'); + $contents = trim($node->textContent); + if ($lang == "") { + l::warn("CONFIG: a description is missing the 'lang' attribute for game '$id'"); + } elseif ($contents == "") { + l::warn("CONFIG: description in language '$lang' has no contents for game '$id'"); + } elseif (!is_null($game->descriptions[$lang])) { + l::notice("CONFIG: description in language '$lang' appears twice for game '$id'"); + } else { + $game->descriptions[$lang] = $contents; + } + } elseif ($node->nodeName == 'Tick') { + $tScript = $node->getAttribute('script'); + $tFirst = $node->getAttribute('first'); + $tInterval = $node->getAttribute('interval'); + $tLast = $node->getAttribute('last'); + if ($tScript == "" || $tFirst == "" || $tInterval == "") { + l::warn("CONFIG: a tick declaration is missing a mandatory attribute " + . "for game '$id'"); + } elseif ((int)$tInterval == 0) { + l::warn("CONFIG: invalid interval for tick '$tScript' in game '$id'"); + } elseif (is_null($game->version->getTickDefinition($tScript))) { + l::warn("CONFIG: no definition for tick '$tScript' in game '$id' " + . "(version '$version')"); + } elseif (is_null($game->ticks[$tScript])) { + new tick_instance($game, $tScript, (int)$tFirst, (int)$tInterval, + $tLast ? (int)$tLast : null); + } else { + l::notice("CONFIG: duplicate tick initialiser for tick '$tScript' in game '$id'"); + } + } else { + l::notice("CONFIG: found unexpected tag '{$node->nodeName}' in game '$id' definition"); + } + + $node = $node->nextSibling; + } + + return $game; + } + + + private function parseGames($root) { + $defaultId = $root->getAttribute('default'); + $games = array(); + + $node = $root->firstChild; + while ($node) { + if ($node->nodeType == XML_ELEMENT_NODE && $node->nodeName == 'Game') { + $g = xml_config::parseGame($node); + if (!is_null($g)) { + if (is_null($games[$g->name])) { + $games[$g->name] = $g; + } else { + l::notice("CONFIG: found duplicate definition for game '{$v->name}'"); + } + } + } elseif ($node->nodeType == XML_ELEMENT_NODE) { + l::notice("CONFIG: found unexpected tag '{$node->nodeName}' in Games section"); + } + $node = $node->nextSibling; + } + + if (count($games) && ($defaultId == '' || is_null($games[$defaultId]))) { + $defaultId = $games[0]->name; + l::notice("CONFIG: no default game, using '$defaultId'"); + } + xml_config::$defGame = $defaultId; + + return $games; + } + + + function parse($xmlData) { + $doc = new DOMDocument(); + if (!$doc->loadXML($xmlData)) { + l::error("CONFIG: error while parsing XML configuration"); + return false; + } + + xml_config::$mVersion = new version('main', false, 'Legacy Worlds'); + xml_config::$mGame = new game(xml_config::$mVersion, 'main', '', false, false, 'Legacy Worlds'); + xml_config::$versions = null; + xml_config::$games = null; + xml_config::$defGame = null; + + $root = $doc->documentElement; + $node = $root->firstChild; + while ($node) { + if ($node->nodeType == XML_ELEMENT_NODE) { + switch ($node->nodeName) : + case 'MainParams': + xml_config::parseMainParams($node); + break; + + case 'MainTicks': + xml_config::parseMainTicks($node); + break; + + case 'Versions': + if (is_array(xml_config::$versions)) { + l::notice("CONFIG: found duplicate set of version definitions"); + } else { + xml_config::$versions = xml_config::parseVersions($node); + } + break; + + case 'Games': + if (is_array(xml_config::$games)) { + l::notice("CONFIG: found duplicate set of game definitions"); + } elseif (is_array(xml_config::$versions)) { + xml_config::$games = xml_config::parseGames($node); + } else { + l::notice("CONFIG: game definitions found before version " + . "definitions, ignored"); + } + break; + + default: + l::notice("CONFIG: found unknown tag '{$node->nodeName}' at toplevel"); + break; + endswitch; + } + $node = $node->nextSibling; + } + + if (!is_array(xml_config::$versions) || count(xml_config::$versions) == 0) { + l::error("CONFIG: no versions have been defined!"); + return false; + } + + if (!is_array(xml_config::$games) || count(xml_config::$games) == 0) { + l::error("CONFIG: no games have been defined!"); + return false; + } + + xml_config::$versions['main'] = xml_config::$mVersion; + xml_config::$games['main'] = xml_config::$mGame; + return new config(xml_config::$versions, xml_config::$games, xml_config::$defGame); + } + +} + +?> diff --git a/scripts/loader.inc b/scripts/loader.inc new file mode 100644 index 0000000..10ef45b --- /dev/null +++ b/scripts/loader.inc @@ -0,0 +1,29 @@ + diff --git a/scripts/proxycheck.php b/scripts/proxycheck.php new file mode 100644 index 0000000..816d225 --- /dev/null +++ b/scripts/proxycheck.php @@ -0,0 +1,67 @@ + 1) { + /* Checks for command line arguments */ + if (count($argv) > 2 && ($argv[1] == '-c' || $argv[1] == '-f')) { + $addresses = $argv; + array_shift($addresses); + array_shift($addresses); + + print "Running manual check, please wait ...\n"; + try { + $results = pcheck::check($addresses, $argv[1] == '-f'); + } catch (Exception $e) { + print "{$argv[0]}: " . $e->getMessage() . "\n"; + exit(1); + } + foreach ($results as $address => $proxy) { + print "\t$address - "; + switch ($proxy) { + case -1: print "detection failed"; break; + case 0: print "no proxy detected"; break; + case 1: print "OPEN PROXY DETECTED!"; break; + } + print "\n"; + } + + exit(0); + + } elseif ($argv[1] != "-d") { + die("Syntax: {$argv[0]}\n\t -> to run as a daemon\n\t{$argv[0]} -d\n\t -> to run in debugging mode\n\t{$argv[0]} -c ip [ip [...]]\n\t -> to check addresses for open proxies\n\t{$argv[0]} -f ip [ip [...]]\n\t -> same as -c, but doesn't check for a running server\n"); + } +} + +/* Starts the main thread */ +@posix_setgid(33); +new pcheck_manager($argv[1] == "-d"); + +?> diff --git a/scripts/site/beta5/handlers/admin.inc b/scripts/site/beta5/handlers/admin.inc new file mode 100644 index 0000000..ac62128 --- /dev/null +++ b/scripts/site/beta5/handlers/admin.inc @@ -0,0 +1,698 @@ +accounts = $this->game->getLib('main/account'); + return $this->accounts->call('isAdmin', $_SESSION['userid']); + } + + + function manualUpdate() { + $this->logAdm("is regenerating the manual"); + $manual = $this->game->getLib('main/manual'); + $x = $manual->call('readXMLFile', config::$main['scriptdir'] . '/../manual/beta5/en/main.lwdoc'); + if (is_array($x)) { + $manual->call('updateSections', $x); + } + } + + + function handleKickReq(&$input, &$data) { + if (is_null($input['kr'])) { + return false; + } elseif (!is_null($input['cancel'])) { + return true; + } + + $data['error'] = 0; + $data['name'] = trim($input['p']); + $data['reason'] = trim($input['r']); + + $a = $this->accounts->call('getUser', $data['name']); + if (is_null($a) || $a['status'] == 'KICKED' || $a['admin'] == 't') { + $data['error'] = 1; + } elseif (strlen($data['reason']) < 10) { + $data['error'] = 2; + } else { + $r = $this->accounts->call('requestKick', $_SESSION['userid'], $a['id'], $data['reason']); + $data['error'] = $r ? 0 : 3; + if ($r) { + $this->logAdm("is requesting to kick {$data['name']}"); + } + } + + return ($data['error'] == 0); + } + + + function handlePNCommand($command, $id) { + $pd = $this->planets->call('byId', $id); + if (is_null($pd)) { + return; + } + + $pLog = "planet '{$pd['name']}' (#$id)"; + if (!is_null($pd['owner'])) { + $pLog .= " owned by player {$pd['owner']}"; + } + + if ($command == "v") { + $this->b5adm->call('validatePlanetName', $id); + $this->logAdm("validated $pLog"); + $this->db->query("UPDATE credits SET credits_obtained = credits_obtained + 200 WHERE account = {$_SESSION['userid']}"); + } elseif ($command == "r") { + $this->b5adm->call('resetPlanet', $id, $_SESSION[game::sessName()]['player']); + $this->logAdm("reset $pLog"); + } elseif ($command == "w") { + $this->b5adm->call('sendPlanetWarning', $id, $_SESSION[game::sessName()]['player']); + $this->logAdm("warned $pLog"); + $this->db->query("UPDATE credits SET credits_obtained = credits_obtained + 200 WHERE account = {$_SESSION['userid']}"); + } + } + + + function handlePlanetNames(&$input, &$data) { + $this->db = $this->game->db; + if (!is_null($input['pc'])) { + $this->planets = $this->game->getLib("beta5/planet"); + $this->handlePNCommand($input['pc'], (int) $input['id']); + } + + list($mode, $q) = $this->b5adm->call('getPlanetsModList', $input['m']); + if ($input['m'] == 'o') { + $this->logAdm("is accessing the complete planet list"); + } + $perPage = 30; + $lines = dbCount($q); + $mod = $lines % $perPage; + $pages = ($lines - $mod) / $perPage + ($mod > 0 ? 1 : 0); + + $page = (int) $input['p']; + if ($page < 0 || $pages == 0) { + $page = 0; + } elseif ($page >= $pages) { + $page = $pages - 1; + } + + $data['list'] = array(); + $this->players = $this->game->getLib('beta5/player'); + for ($i = 0; $i < ($page + 1) * $perPage && $i < $lines; $i ++) { + $r = dbFetchHash($q); + if ($i >= $page * $perPage) { + if (!is_null($r['owner'])) { + $r['oname'] = $this->players->call('getName', $r['owner']); + } + array_push($data['list'], $r); + } + } + + $data['mode'] = $mode; + $data['pages'] = $pages; + $data['page'] = $page; + } + + + function handleKickList(&$input, &$data) { + if (is_null($input['i']) || is_null($input['a'])) { + $data['lists'] = $this->accounts->call('getKickList'); + return; + } + + $kr = $this->accounts->call('getKickRequest', (int)$input['i']); + if (is_null($kr) || ($kr['requested_by'] == $_SESSION['userid'] && $input['a'] == 1) || $kr['status'] != 'P') { + return; + } + + if ($input['a'] == 1) { + $this->accounts->call('terminate', $kr['to_kick'], 'KICKED', $kr['reason']); + $this->logAdm("accepted request to kick account #{$kr['to_kick']}"); + } + $this->accounts->call('kickRequestHandled', $kr['id'], $_SESSION['userid'], $input['a'] == 1); + + $data['lists'] = $this->accounts->call('getKickList'); + } + + + function linkMainList() { + $categories = $this->lib->call('getCategories'); + for ($i=0;$ilib->call('getLinks', $categories[$i]['id']); + } + + return array("lklist", $categories); + } + + + function checkCat(&$title, &$desc, $id = null) { + $title = preg_replace('/\s+/', ' ', trim($title)); + $desc = preg_replace('/\s+/', ' ', trim($desc)); + if (strlen($title) < 5) { + return 1; + } elseif (strlen($title) > 64) { + return 2; + } elseif (!$this->lib->call('checkCategoryName', $title, $id)) { + return 3; + } elseif ($desc != '' && strlen($desc) < 10) { + return 4; + } + return 0; + } + + + function linkMainAddCat(&$input) { + if ($input['cancel'] != "") { + return $this->linkMainList(); + } + if ($input['r'] != 1) { + return array('lkcat', array()); + } + $title = $input['title']; + $desc = $input['desc']; + $error = $this->checkCat($title, $desc); + + if ($error) { + return array('lkcat', array( + 'title' => $title, + 'desc' => $desc, + 'error' => $error, + )); + } + $this->lib->call('createCategory', $title, ($desc == "" ? null : $desc)); + return $this->linkMainList(); + } + + + function linkMainEditCat(&$input) { + if ($input['cancel'] != "") { + return $this->linkMainList(); + } + + $id = (int)$input['cid']; + $cat = $this->lib->call('getCategory', $id); + if (is_null($cat)) { + return null; + } + + if ($input['r'] != 1) { + return array('lkcat', array( + 'id' => $id, + 'title' => $cat['title'], + 'desc' => $cat['description'] + )); + } + + $title = $input['title']; + $desc = $input['desc']; + $error = $this->checkCat($title, $desc, $id); + + if ($error) { + return array('lkcat', array( + 'id' => $id, + 'title' => $title, + 'desc' => $desc, + 'error' => $error, + )); + } + $this->lib->call('changeCategory', $id, $title, $desc); + return $this->linkMainList(); + } + + + function linkMainDelCat(&$input) { + $id = (int)$input['cid']; + $cat = $this->lib->call('getCategory', $id); + if (!is_null($cat)) { + $this->lib->call('deleteCategory', $id); + } + return $this->linkMainList(); + } + + + function linkMainMoveCat($id, $up) { + $cat = $this->lib->call('getCategory', $id); + if (!is_null($cat)) { + $this->lib->call('moveCategory', $id, $up); + } + return $this->linkMainList(); + } + + + function checkLink(&$title, &$url, &$desc, $id = null) { + $title = preg_replace('/\s+/', ' ', trim($title)); + $url = preg_replace('/\s+/', ' ', trim($url)); + $desc = preg_replace('/\s+/', ' ', trim($desc)); + if (strlen($title) < 5) { + return 1; + } elseif (strlen($title) > 64) { + return 2; + } elseif ($desc != '' && strlen($desc) < 10) { + return 6; + } elseif (!preg_match('/^(http|https):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?(\/.*)?$/i', $url, $m)) { + return 3; + } else { + list($junk, $proto, $hostname, $port) = $m; + if (!preg_match('/^\d+\.\d+\.\d+\.\d+$/', $hostname)) { + $ip = gethostbyname($hostname); + if ($ip === $hostname) { + return 4; + } + } + if (!$this->lib->call('checkLink', $url, $id)) { + return 5; + } + } + return 0; + } + + + function linkMainAddLink(&$input) { + if ($input['cancel'] != "") { + return $this->linkMainList(); + } + + $cid = (int) $input['cid']; + $cat = $this->lib->call('getCategory', $cid); + if (is_null($cat)) { + return $this->linkMainList(); + } + + if ($input['r'] != 1) { + return array('lklk', array( + 'cid' => $cid + )); + } + $title = $input['title']; + $url = $input['url']; + $desc = $input['desc']; + $error = $this->checkLink($title, $url, $desc); + + if ($error) { + return array('lklk', array( + 'cid' => $cid, + 'title' => $title, + 'url' => $url, + 'desc' => $desc, + 'error' => $error, + )); + } + $this->lib->call('addLink', $cid, $title, ($desc == "" ? null : $desc), $url); + return $this->linkMainList(); + } + + + function linkMainEditLink(&$input) { + if ($input['cancel'] != "") { + return $this->linkMainList(); + } + + $lid = (int) $input['lid']; + $lnk = $this->lib->call('getLink', $lid); + if (is_null($lnk)) { + return $this->linkMainList(); + } + + if ($input['r'] != 1) { + return array('lklk', array( + 'id' => $lid, + 'title' => $lnk['title'], + 'url' => $lnk['url'], + 'desc' => $lnk['description'] + )); + } + + $title = $input['title']; + $url = $input['url']; + $desc = $input['desc']; + $error = $this->checkLink($title, $url, $desc, $lid); + + if ($error) { + return array('lklk', array( + 'id' => $lid, + 'title' => $title, + 'url' => $url, + 'desc' => $desc, + 'error' => $error, + )); + } + $this->lib->call('changeLink', $lid, $title, $url, ($desc == "" ? null : $desc)); + return $this->linkMainList(); + } + + + function linkMainDelLink(&$input) { + $id = (int)$input['lid']; + $lnk = $this->lib->call('getLink', $id); + if (!is_null($lnk)) { + $this->lib->call('deleteLink', $id); + } + return $this->linkMainList(); + } + + + function linkMain(&$input) { + $this->lib = $this->game->getLib('main/links'); + switch ($input['ac']) : + case '0': return $this->linkMainAddCat($input); + case '1': return $this->linkMainAddLink($input); + case '2': return $this->linkMainEditCat($input); + case '3': return $this->linkMainDelCat($input); + case '4': return $this->linkMainMoveCat((int)$input['cid'], true); + case '5': return $this->linkMainMoveCat((int)$input['cid'], false); + case '10': return $this->linkMainEditLink($input); + case '11': return $this->linkMainDelLink($input); + default: return $this->linkMainList(); + endswitch; + } + + + function linkReports(&$input) { + $lib = $this->game->getLib('main/links'); + $acc = $this->game->getLib('main/account'); + + if ($input['id'] != '') { + $id = (int) $input['id']; + $lnk = $lib->call('getLink', $id); + if (!is_null($lnk)) { + if ($input['dl'] == 1) { + $lib->call('deleteLink', $id); + } elseif ($input['dr'] == 1) { + $lib->call('deleteReports', $id); + } + } + } + + $links = array(); + $reports = $lib->call('getBrokenReports'); + foreach ($reports as $rep) { + if (is_null($links[$rep['link']])) { + $links[$rep['link']] = $lib->call('getLink', $rep['link']); + $links[$rep['link']]['category'] = $lib->call('getCategory', $links[$rep['link']]['category']); + $links[$rep['link']]['reporters'] = array(); + } + array_push($links[$rep['link']]['reporters'], utf8entities($acc->call('getUserName', $rep['reported_by']))); + } + + return $links; + } + + + function linkSubmissions(&$input) { + $lib = $this->game->getLib('main/links'); + $acc = $this->game->getLib('main/account'); + + if ($input['su'] != '') { + if ($input['sid'] == '') { + $lib->call('deleteSubmissions', $input['su']); + } elseif ($input['cid'] == '') { + $sub = $lib->call('getSubmission', $input['su'], (int)$input['sid']); + if (!is_null($sub)) { + $cats = $lib->call('getCategories'); + return array('lksadd', array( + "sub" => $sub, + "cats" => $cats + )); + } + } elseif ($input['cancel'] == '') { + $sub = $lib->call('getSubmission', $input['su'], (int)$input['sid']); + $cat = $lib->call('getCategory', (int)$input['cid']); + if (!(is_null($sub) || is_null($cat))) { + $d = $sub['description']; + $lib->call('addLink', $cat['id'], $sub['title'], $d == "" ? null : $d, $sub['url']); + $lib->call('deleteSubmissions', $sub['url']); + } + } + } + + $subs = $lib->call('getSubmissions'); + $urls = array(); + foreach ($subs as $sub) { + if (!is_array($urls[$sub['url']])) { + $urls[$sub['url']] = array(); + } + array_push($urls[$sub['url']], array( + "sid" => $sub['submitted_by'], + "submitter" => utf8entities($acc->call('getUserName', $sub['submitted_by'])), + "title" => utf8entities($sub['title']), + "description" => utf8entities($sub['description']) + )); + } + + return array('lksub', $urls); + } + + + function manageAccounts(&$input) { + $aName = $input['an']; + if ($aName == '') { + return array("account" => ''); + } + + // Get account data + $account = $this->accounts->call('getUser', $aName); + if (is_null($account)) { + return array( + "account" => $aName, + "notfound" => true + ); + } + $rv = array( + "account" => $account['name'], + "status" => $account['status'], + "email" => $account['email'], + "admin" => $account['admin'] == 't', + "conf_code" => $account['conf_code'], + "password" => $account['password'] + ); + + if ($input['sa'] || $rv['admin']) { + $this->logAdm("is inspecting account {$account['name']}"); + return $rv; + } + + $this->db = $this->game->db; + if ($input['mma'] && strcasecmp($input['ma'], $account['email'])) { + // Change email address + $rv['email'] = $nMail = strtolower($input['ma']); + if (!preg_match('/^[A-Za-z0-9_\.\-\+]+@([A-Za-z0-9_\.\-\+]+)+\.[A-Za-z]{2,6}/', $nMail)) { + $rv['mailerr'] = 1; + return $rv; + } + + $q = $this->db->query("SELECT name FROM main.account WHERE LOWER(email)='" + . addslashes($nMail) . "'"); + if ($q && dbCount($q)) { + $rv['mailerr'] = 2; + list($rv['oacc']) = dbFetchArray($q); + return $rv; + } elseif (!$q) { + $rv['mailerr'] = 3; + return $rv; + } + + $q = $this->db->query("UPDATE main.account SET email='" . addslashes($nMail) + . "' WHERE id={$account['id']}"); + if (!$q) { + $rv['mailerr'] = 3; + } else { + $this->logAdm("changed email for account {$account['name']}"); + $rv['mailerr'] = -1; + } + return $rv; + } elseif ($input['mpw'] && strcmp($input['pw'], $account['password'])) { + // Change password + $rv['password'] = $nPass = strtolower($input['pw']); + if (strlen($nPass) < 4) { + $rv['passerr'] = 1; + return $rv; + } + + $q = $this->db->query("UPDATE main.account SET password='" . addslashes($nPass) + . "' WHERE id={$account['id']}"); + if (!$q) { + $rv['passerr'] = 2; + } else { + $this->logAdm("changed password for account {$account['name']}"); + $rv['passerr'] = -1; + } + return $rv; + } + + return $rv; + } + + + private function getSpamFormData($in) { + $action = is_null($in['e']) + ? (is_null($in['p']) + ? (is_null($in['cc']) ? 'none' : 'cancel') + : 'preview') + : 'post'; + $subject = $in['sub']; + $txt = $in['txt']; + $allGames = ($in['ag'] == '1'); + return array($action,$subject,$txt,$allGames); + } + + private function checkSpamData($s, &$t) { + $maxNL = 500; $maxNC = 100; + + if (strlen($s) < 2) { + $e = 1; + } elseif (strlen($s) > 100) { + $e = 2; + } elseif (strlen($t) < 3) { + $e = 3; + } else { + $ot = $t; $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) { + $e = 4; + } else { + $t = $nt; + $e = 0; + } + } + return $e; + } + + + private function handleSpam($input) { + list($action, $subject, $text, $allGames) = $this->getSpamFormData($input); + + if ($action == 'cancel') { + return array('main', array()); + } + + $data = array( + 'sub' => $subject, + 'txt' => $text, + 'prev' => '', + 'err' => 0, + 'ag' => $allGames + ); + if ($action == 'none') { + return array('spam', $data); + } + + $data['err'] = $err = $this->checkSpamData($subject, $text); + if ($err) { + return array('spam', $data); + } + + if ($action == 'preview') { + $fLib = $this->game->getLib('main/forums'); + $data['prev'] = $fLib->call('substitute', $text, 't', 'f'); + return array('spam', $data); + } + + // Send the spam + if ($data['ag']) { + $this->logAdm("is sending spam '$subject' in all games"); + foreach (config::getGames() as $game) { + if ($game->name == 'main') { + continue; + } + + $status = $game->status(); + if ($status == 'FINISHED' || $status == 'PRE') { + continue; + } + + $aLib = $game->getLib('admin/beta5'); + $aLib->call('sendSpam', $_SESSION['userid'], $subject, $text); + } + } else { + $this->logAdm("is sending spam '$subject' in game {$this->game->text}"); + $aLib = $this->game->getLib('admin/beta5'); + $aLib->call('sendSpam', $_SESSION['userid'], $subject, $text); + } + + return array('main', array()); + } + + + function handle($input) { + if (!$this->isAdmin()) { + $this->logAdm("is a rat bastard who tried to use the admin page!"); + $this->output = "rat"; + return; + } + + $this->b5adm = $this->game->getLib("admin/beta5"); + + $this->output = "admin"; + $d = array(); + switch ($input['c']) : + case 'k': + if ($this->handleKickReq($input, $d)) { + $sp = "main"; + } else { + $sp = "kickreq"; + } + break; + case 'kl': + $this->handleKickList($input, $d); + $sp = "kicklst"; + break; + case 'p': + $this->handlePlanetNames($input, $d); + $sp = "pnlist"; + break; + case 'lk': + list($sp, $d) = $this->linkMain($input); + break; + case 'lkr': + $d = $this->linkReports($input); + $sp = "lkrep"; + break; + case 'lks': + list($sp, $d) = $this->linkSubmissions($input); + break; + case 'a': + $sp = 'acmgmt'; + $d = $this->manageAccounts($input); + break; + case 's': + list($sp, $d) = $this->handleSpam($input); + break; + case 'm': + $this->manualUpdate(); + default: + $sp = "main"; + $d = array(); + break; + endswitch; + + $this->data = array( + "subpage" => $sp, + "data" => $d + ); + } +} + +?> diff --git a/scripts/site/beta5/handlers/alliance.inc b/scripts/site/beta5/handlers/alliance.inc new file mode 100644 index 0000000..d8880f5 --- /dev/null +++ b/scripts/site/beta5/handlers/alliance.inc @@ -0,0 +1,1266 @@ + "makeAllianceTooltips();\ninitAlliancePage();", + "func" => array( + // Main page + "getGeneralData", "getTagInformation", "createAlliance", "sendRequest", + "cancelRequest", "leaveAlliance", "modifySettings", "leaderStepDown", + "mainPage", + // Leader election + "getCandidates", "setVote", "runForPresidency", "cancelCandidate", + "takeControl", + // Lists + "getPlanetList", "getMemberList", "kickMembers", "changeMemberRank", + "getAttackList", + // Pending requests + "getPending", "acceptRequests", "rejectRequests", + // Forums + "getForums", "newForum", "changeForum", "delForum", + "moveForum", "getForumAcl", + // Ranks + "getRanks", "newRank", "changeRank", "delRank" + ), + "method"=> array( + "newForum" => "POST", + "changeForum" => "POST", + "newRank" => "POST", + "changeRank" => "POST", + ), + ); + + + //------------------------------------------- + // MAIN PAGE FUNCTIONS + //------------------------------------------- + + 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) { + return array('',''); + } + return $r; + } + + function mainPage() + { + $_SESSION[game::sessName()]['alliance_page'] = 'Main'; + } + + function getTagInformation($tag) { + $tag = trim($tag); + if ($tag == "") { + return "ERR#0"; + } + + $aid = gameAction('getAlliance', $tag); + if (is_null($aid)) { + return "ERR#1"; + } + + $ainf = gameAction('getAllianceInfo', $aid); + if (is_null($ainf)) { + return "ERR#1"; + } + + $s = $ainf['id'] . '#' . $ainf['nplanets'] . '#' . $ainf['avgx'] . '#' . $ainf['avgy']; + $rk = $this->getAllianceRanking($tag); + $s .= "#" . $rk['ranking'] . "#" . $rk['points']; + + if (!is_null($ainf['leader'])) { + $alinf = gameAction('getPlayerInfo', $ainf['leader']); + } else { + $alinf = array(); + } + + if ($this->game->params['victory'] == 1) { + $vVal = $this->game->getLib('beta5/alliance')->call('updateVictory', $aid); + } elseif ($this->game->params['victory'] == 2) { + $vVal = $this->game->getLib('beta5/ctf')->call('getTeamPoints', $aid); + } else { + $vVal = ""; + } + + return "$s#$vVal\n" . $ainf['tag'] . "\n" . $ainf['name'] . "\n" . $alinf['name']; + } + + function getGeneralData() { + $pLib = $this->game->getLib('beta5/player'); + $aLib = $this->game->getLib('beta5/alliance'); + + $pinf = $pLib->call('get', $player = $_SESSION[game::sessName()]['player']); + if (is_null($pinf)) { + l::warn("Couldn't find current player"); + return; + } + + if (!is_null($pinf['aid'])) { + $lockedAlliances = (int) $this->game->params['lockalliances']; + $lockedAlliances = ($lockedAlliances > 1) ? 1 : 0; + $retval = $pinf['aid'] . "#1#$lockedAlliances\n"; + $retval .= $pinf['alliance'] . "\n"; + $retval .= $pinf['aname'] . "\n"; + + $ainf = $aLib->call('get', $pinf['aid']); + $s = $ainf['id'] . '#' . $ainf['nplanets'] . '#' . $ainf['avgx'] . '#' . $ainf['avgy']; + $s .= '#' . ($ainf['democracy'] == 't' ? 1 : 0); + $rk = $this->getAllianceRanking($ainf['tag']); + $s .= "#" . $rk['ranking'] . "#" . $rk['points'] . "#" + . $ainf['enable_tt']; + if (!is_null($ainf['leader'])) { + $alinf = $pLib->call('get', $ainf['leader']); + if (!is_null($ainf['successor'])) { + $asinf = $pLib->call('get', $ainf['successor']); + $succ = $asinf['name']; + } else { + $succ = ""; + } + $lName = $alinf['name']; + } else { + $lName = ""; + } + if ($this->game->params['victory'] == 1) { + $vVal = $aLib->call('updateVictory', $pinf['aid']); + } elseif ($this->game->params['victory'] == 2) { + $vVal = $this->game->getLib('beta5/ctf')->call('getTeamPoints', $pinf['aid']); + } else { + $vVal = ""; + } + $retval .= "$s#$vVal\n$lName\n$succ\n"; + + $pr = $aLib->call('getPrivileges', $player); + $retval .= "{$pr['list_access']}#{$pr['attacks']}#" + . "{$pr['can_set_grades']}#{$pr['can_kick']}#{$pr['can_accept']}#" + . "{$pr['forum_admin']}#{$pr['can_vote']}#{$pr['can_be_cand']}#" + . "{$pr['is_leader']}\n"; + if (is_null($_SESSION[game::sessName()]['alliance_page'])) { + $_SESSION[game::sessName()]['alliance_page'] = 'Main'; + } + $retval .= $_SESSION[game::sessName()]['alliance_page']; + + if ($pr['attacks']) { + $retval .= "\n" . count($aLib->call('getMilitary', $pinf['aid'])); + } + } elseif (!is_null($pinf['arid'])) { + $retval = $pinf['arid'] . "#0\n"; + $retval .= $pinf['alliance_req'] . "\n"; + $retval .= $pinf['aname']; + } else { + $retval = ""; + } + return $retval; + } + + function sendRequest($tag) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } elseif ((int) input::$game->params['lockalliances'] > 1) { + return "ERR#3"; + } + + $tag = preg_replace('/\s+/', ' ', trim($tag)); + if ($tag == "") { + return "ERR#0"; + } + + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (!(is_null($p["aid"]) && is_null($p['arid']))) { + return "ERR#2"; + } + + $a = gameAction('getAlliance', $tag); + if (is_null($a)) { + return "ERR#1"; + } + + gameAction('sendJoinRequest', $pid, $a); + return $this->getGeneralData(); + } + + function cancelRequest() { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#201"; + } elseif ((int) input::$game->params['lockalliances'] > 1) { + return "ERR#5"; + } + + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['arid'])) + return "ERR#4"; + gameAction('cancelJoinRequest', $pid, $p['arid']); + return $this->getGeneralData(); + } + + function createAlliance($tag, $name) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } elseif ((int) input::$game->params['lockalliances'] > 1) { + return "ERR#6"; + } + + $tag = preg_replace('/\s/', ' ', trim($tag)); + $name = preg_replace('/\s+/', ' ', trim($name)); + if (preg_match('/\s\s+/', $tag)) { + return "ERR#0"; + } + if ($tag == "") { + return "ERR#1"; + } + if ($name == "") { + return "ERR#2"; + } + if (strlen($tag) > 5 || strlen($name) > 64) { + return "ERR#3"; + } + + $a = gameAction('getAlliance', $tag); + if (!is_null($a)) { + return "ERR#4"; + } + + $a = gameAction('createAlliance', $tag, $name, $_SESSION[game::sessName()]['player']); + if (is_null($a)) { + return "ERR#5"; + } + return $this->getGeneralData(); + } + + function modifySettings($dem, $techTrade, $succ) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['aid'])) { + return "ERR#0"; + } + $a = gameAction('getAllianceInfo', $p['aid']); + if ($a['leader'] != $pid) { + return "ERR#0"; + } + + $succ = trim($succ); + if ($succ == "") { + $sid = null; + } else { + $sid = gameAction('getPlayer', $succ); + if (is_null($sid)) { + return "ERR#1"; + } + if ($sid == $pid) { + return "ERR#2"; + } + $sinf = gameAction('getPlayerInfo', $sid); + if ($sinf['aid'] !== $p['aid']) { + return "ERR#3"; + } + } + + gameAction('setAllianceDemo', $p['aid'], + ($dem == 1) || ((int) input::$game->params['lockalliances'] > 1)); + $this->game->getLib('beta5/alliance')->call('setTechTradeMode', $p['aid'], $techTrade); + gameAction('setAllianceSuccessor', $p['aid'], $sid); + return $this->getGeneralData(); + } + + function leaderStepDown() { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['aid'])) + return "ERR#0"; + $a = gameAction('getAllianceInfo', $p['aid']); + if ($a['leader'] != $pid || is_null($a['successor'])) + return "ERR#0"; + + gameAction('allianceStepDown', $p['aid']); + return $this->getGeneralData(); + } + + function leaveAlliance() { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } elseif ((int) input::$game->params['lockalliances'] > 1) { + return "ERR#4"; + } + + gameAction('leaveAlliance', $_SESSION[game::sessName()]['player']); + return $this->getGeneralData(); + } + + + //------------------------------------------- + // PENDING REQUESTS + //------------------------------------------- + + function getPending($ids) { + if ((int) input::$game->params['lockalliances'] > 1) { + return ""; + } + $_SESSION[game::sessName()]['alliance_page'] = 'Pending'; + + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['aid'])) + return ""; + + $pr = gameAction('getAlliancePrivileges', $pid); + if ($pr['can_accept'] != 1) + return ""; + + $l = gameAction('getAllianceRequests', $p['aid']); + $ls = array(); + + $lo = explode('#', $ids); + foreach ($lo as $id) + { + if ($id == "" || (int)$id != $id || !is_null($l[$id])) + continue; + array_push($ls, "-#$id"); + } + + foreach ($l as $id => $n) + { + if (in_array($id, $lo)) + continue; + array_push($ls, "+#$id#" . utf8entities($n)); + } + return join("\n", $ls); + } + + function acceptRequests($lall, $lacc) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } elseif ((int) input::$game->params['lockalliances'] > 1) { + return "ERR#3"; + } + + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['aid'])) + return "ERR#0"; + + $pr = gameAction('getAlliancePrivileges', $pid); + if ($pr['can_accept'] != 1) + return "ERR#1"; + + $lo = explode('#', $lacc); + + // Apply alliance capping rules if required + $cap = (int) input::$game->params['alliancecap']; + if ($cap > 0 && $cap <= 100) { + $libB5 = input::$game->getLib('beta5'); + $libAlliance = input::$game->getLib('beta5/alliance'); + + $players = $libB5->call('getPlayerCount'); + $maxPlayers = ceil($players * $cap / 100); + $members = count($libAlliance->call('getMembers', $p['aid'])); + + //logText("Alliance cap= $maxPlayers players ($cap% of $players), new size = " . ($members + count($lo))); + + if ($members + count($lo) > $maxPlayers) { + return "ERR#2"; + } + } + + foreach ($lo as $id) + { + if ($id == "" || (int)$id != $id) + continue; + gameAction('acceptAllianceRequest', $p['aid'], $id, $pid); + } + + return $this->getPending($lall); + } + + function rejectRequests($lall, $lrej) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } elseif ((int) input::$game->params['lockalliances'] > 1) { + return "ERR#3"; + } + + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['aid'])) { + return "ERR#0"; + } + + $pr = gameAction('getAlliancePrivileges', $pid); + if ($pr['can_accept'] != 1) + return "ERR#1"; + + $lo = explode('#', $lrej); + foreach ($lo as $id) { + if ($id == "" || (int)$id != $id) { + continue; + } + gameAction('rejectAllianceRequest', $p['aid'], $id, $pid); + } + + return $this->getPending($lall); + } + + + //------------------------------------------- + // LISTINGS + //------------------------------------------- + + function getPlanetList($ids) { + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['aid'])) { + return ""; + } + $pr = gameAction('getAlliancePrivileges', $pid); + if ($pr['list_access'] < 2) { + return ""; + } + $_SESSION[game::sessName()]['alliance_page'] = 'PList'; + + $pl = gameAction('getAlliancePlanets', $p['aid']); + $lo = explode('#', $ids); + $ls = array(); + + // Remove planets that are no longer in the list + foreach ($lo as $id) + { + if ($id == "" || (int)$id != $id || !is_null($pl[$id])) + continue; + array_push($ls, "-#$id"); + } + + // Add new planets and update current list + foreach ($pl as $id => $pd) + { + if (in_array($id, $lo)) + $s = "="; + else + $s = "+"; + $s .= "#$id#"; + if ($pid != $pd['ownerId']) + $s .= $pd['ownerId']; + $s .= "#" . $pd['x'] . "#" . $pd['y'] . "#" . $pd['orbit'] . "#"; + if ($pr['list_access'] == 3) + $s .= $pd['fact'] . "#" . $pd['turrets']; + else + $s .= "#"; + $s .= "#"; + if ($pr['attacks'] == 1) + $s .= $pd['attack'] ? 1 : 0; + $s .= "\n" . utf8entities($pd['owner']) . "\n" . utf8entities($pd['name']); + array_push($ls, $s); + } + return join("\n", $ls); + } + + function getMemberList($mIds) { + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['aid'])) + return ""; + $pr = gameAction('getAlliancePrivileges', $pid); + if ($pr['list_access'] < 1) + return ""; + $_SESSION[game::sessName()]['alliance_page'] = 'MList'; + + $rkl = gameAction('getAllianceRanks', $p['aid']); + $mbl = gameAction('getAllianceMembers', $p['aid']); + $a = gameAction('getAllianceInfo', $p['aid']); + + // Transform the member list into a list of strings indicating changes + $mcl = array(); + $lo = explode('#', $mIds); + $now = time(); + foreach ($lo as $mid) { + if ($mid == "" || (int)$mid != $mid) { + continue; + } + if (is_null($mbl[$mid])) { + array_push($mcl, "-#$mid"); + } else { + $rank = ($mid == $a['leader']) ? "-" + : (is_null($mbl[$mid]['rank']) + ? $a['default_grade'] + : $mbl[$mid]['rank']); + $lastOnline = $this->game->getLib('beta5/player')->call('lastOnline', $mid); + if ($lastOnline != 0) { + $lastOnline = ceil(($now - $lastOnline) / 60); + } + $onVacation = $this->game->getLib('beta5/player')->call('isOnVacation', $mid) ? 1 : 0; + array_push($mcl, "=#$mid#$rank#$lastOnline#$onVacation"); + } + } + foreach ($mbl as $id => $cnt) + { + if (in_array($id,$lo)) + continue; + $rank = ($id == $a['leader']) ? "-" + : (is_null($mbl[$id]['rank']) + ? $a['default_grade'] + : $mbl[$id]['rank']); + $isMe = ($id == $pid) ? 0 : 1; + $lastOnline = $this->game->getLib('beta5/player')->call('lastOnline', $id); + if ($lastOnline != 0) { + $lastOnline = ceil(($now - $lastOnline) / 60); + } + $onVacation = $this->game->getLib('beta5/player')->call('isOnVacation', $id) ? 1 : 0; + array_push($mcl, "+#$id#$rank#$isMe#$lastOnline#$onVacation#" . $mbl[$id]['name']); + } + + // Transform ranks into a list of strings + $rks = array(); + foreach ($rkl as $i => $n) + array_push($rks, "$i#$n"); + + $s = count($rkl) . "#" . count($mcl) . "\n"; + $s .= join('#', $pr['change_ranks']) . "\n"; + $s .= join('#', $pr['kick_ranks']) . "\n"; + $s .= join("\n", array_merge($mcl, $rks)); + + return $s; + } + + function changeMemberRank($mIds, $rId, $allIds) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $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['list_access'] < 1 || $pr['can_set_grades'] == 0 || !in_array($rId, $pr['change_ranks'])) + return "ERR#1"; + $a = gameAction('getAllianceInfo', $aid); + if ($rId == $a['default_grade']) + $rId = 'NULL'; + + $mlist = gameAction('getAllianceMembers', $aid); + $mlids = array_keys($mlist); + $m2c = explode('#', $mIds); + foreach ($m2c as $id) + { + if (!in_array($id, $mlids) || $id == $pid || !(is_null($mlist[$id]['rank']) || in_array($mlist[$id]['rank'], $pr['change_ranks']))) + continue; + gameAction('changeMemberRank', $id, $rId); + } + return $this->getMemberList($allIds); + } + + function kickMembers($mIds, $allIds) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } elseif ((int) input::$game->params['lockalliances'] > 1) { + return "ERR#2"; + } + + $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['list_access'] < 1 || $pr['can_kick'] == 0) + return "ERR#1"; + $a = gameAction('getAllianceInfo', $aid); + + $mlist = gameAction('getAllianceMembers', $aid); + $mlids = array_keys($mlist); + $m2c = explode('#', $mIds); + foreach ($m2c as $id) + { + if (is_null($mlist[$id]['rank'])) + $mlist[$id]['rank'] = $a['default_grade']; + if (!in_array($id, $mlids) || $id == $pid || !in_array($mlist[$id]['rank'], $pr['kick_ranks'])) + continue; + gameAction('kickAllianceMember', $id); + } + return $this->getMemberList($allIds); + } + + function getAttackList() { + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['aid'])) + return ""; + $pr = gameAction('getAlliancePrivileges', $pid); + if ($pr['attacks'] == 0) + return ""; + $_SESSION[game::sessName()]['alliance_page'] = 'MStat'; + + $pl = gameAction('getAllianceMilitary', $p['aid']); + $ls = array(); + + foreach ($pl as $id => $data) + { + $s = "$id#"; + if ($pr['list_access'] > 1) + $s .= ($data['owner'] == $pid) ? -1 : $data['owner']; + $s .= "#".$data['friendly']."#".$data['enemy']."#"; + $s .= count($data['f_list']) . '#' . count($data['e_list']); + $s .= "#".$data['x'].",".$data['y'].",".$data['orbit']; + $s .= "\n".$data['name']; + if ($pr['list_access'] > 1) + $s .= "\n" . gameAction('getPlayerName', $data['owner']); + for ($i=0;$iparams['lockalliances'] > 1; + $a = gameAction('getAllianceInfo', $aid); + if ($a['democracy'] == 'f' && !$locked) { + return ""; + } + $_SESSION[game::sessName()]['alliance_page'] = 'Vote'; + + $pr = gameAction('getAlliancePrivileges', $pid); + $cl = gameAction('getAllianceCandidates', $aid); + $ml = gameAction('getAllianceMembers', $aid); + $cpl = array(); + $mv = 0; + foreach ($cl as $cid => $cd) + { + $cpl[$cd['pid']] = $cid; + if ($cd['votes'] > $mv) + $mv = $cd['votes']; + } + + $afSel = ($pr['can_vote'] == 1 || $locked) && is_null($cpl[$pid]); + $afRun = ($pr['can_be_cand'] == 1 || $locked) && is_null($cpl[$pid]); + $afCancel = ($pr['can_be_cand'] == 1 || $locked) && !is_null($cpl[$pid]) && !$pr['is_leader']; + $afTake = !is_null($cpl[$pid]) && !$pr['is_leader'] && ($cl[$cpl[$pid]]['votes'] >= 0.2 * count($ml)) && ($cl[$cpl[$pid]]['votes'] == $mv); + $voteAct = is_null($cpl[$pid]) ? $p['vote'] : $cpl[$pid]; + + $s = ($afSel?1:0) . "#" . ($afRun?1:0) . "#" . ($afCancel?1:0) . "#" . ($afTake?1:0) . "#$voteAct"; + foreach ($cl as $cid => $cd) + $s .= "\n$cid#".$cd['votes']."#".utf8entities($cd['name']); + + return $s; + } + + function setVote($vid) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['aid'])) { + return "ERR#0"; + } + $aid = $p['aid']; + $pr = gameAction('getAlliancePrivileges', $pid); + $locked = (int) input::$game->params['lockalliances'] > 1; + if (!($pr['can_vote'] || $locked)) { + return "ERR#1"; + } + + $cl = gameAction('getAllianceCandidates', $aid); + $cpl = array(); + foreach ($cl as $cid => $cd) + $cpl[$cd['pid']] = $cid; + if (!(is_null($cpl[$pid]) && ($vid == "" || !is_null($cl[$vid])))) { + return "ERR#2"; + } + gameAction('setAllianceVote', $pid, ($vid == "") ? 'NULL' : $vid); + return $this->getCandidates(); + } + + function runForPresidency() { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['aid'])) { + return "ERR#0"; + } + $aid = $p['aid']; + $pr = gameAction('getAlliancePrivileges', $pid); + $locked = (int) input::$game->params['lockalliances'] > 1; + if (!($pr['can_be_cand'] || $locked)) { + return "ERR#1"; + } + + $cl = gameAction('getAllianceCandidates', $aid); + foreach ($cl as $cid => $cd) { + if ($cd['pid'] == $pid) { + return "ERR#3"; + } + } + + gameAction('newAllianceCandidate', $aid, $pid); + return $this->getCandidates(); + } + + function cancelCandidate() { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['aid'])) { + return "ERR#0"; + } + $aid = $p['aid']; + $pr = gameAction('getAlliancePrivileges', $pid); + $locked = (int) input::$game->params['lockalliances'] > 1; + if (!($pr['can_be_cand'] || $locked)) { + return "ERR#1"; + } + + $cl = gameAction('getAllianceCandidates', $aid); + $nc = true; + foreach ($cl as $cid => $cd) { + if ($cd['pid'] == $pid) { + $nc = false; + break; + } + } + + if ($nc) { + return "ERR#4"; + } + gameAction('removeAllianceCandidate', $aid, $pid); + return $this->getCandidates(); + } + + function takeControl() { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['aid'])) { + return "ERR#0"; + } + $aid = $p['aid']; + $pr = gameAction('getAlliancePrivileges', $pid); + $locked = (int) input::$game->params['lockalliances'] > 1; + if (!($pr['can_be_cand'] || $locked)) { + return "ERR#1"; + } + + $cl = gameAction('getAllianceCandidates', $aid); + $ml = gameAction('getAllianceMembers', $aid); + $cpl = array(); + $mv = 0; + foreach ($cl as $cid => $cd) { + $cpl[$cd['pid']] = $cid; + if ($cd['votes'] > $mv) { + $mv = $cd['votes']; + } + } + + $afTake = !is_null($cpl[$pid]) && !$pr['is_leader'] && ($cl[$cpl[$pid]]['votes'] >= 0.2 * count($ml)) && ($cl[$cpl[$pid]]['votes'] == $mv); + if ($afTake) { + gameAction('takePresidency', $aid, $pid); + } else { + return "ERR#5"; + } + return $this->getCandidates(); + } + + + //------------------------------------------- + // 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"; + } + } + return $s; + } + + function getForums() { + $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"; + } + $_SESSION[game::sessName()]['alliance_page'] = 'FAdmin'; + + return $this->doGetForums($aid); + } + + function newForum($name, $userPost, $after, $description, $acl) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $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"; + + $afl = gameAction('getAllianceForums', $aid); + if (count($afl) >= 30) { + return "ERR#5"; + } + + $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 (!$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); + } + gameAction('setForumAccess', $mId, $fread, $fmod); + + return $this->doGetForums($aid); + } + + function changeForum($id, $name, $userPost, $description, $acl) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $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"; + + $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); + + $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); + } + gameAction('setForumAccess', $id, $fread, $fmod); + + return $this->doGetForums($aid); + } + + function delForum($id) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $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#8"; + } + gameAction('deleteAllianceForum', $id); + return $this->doGetForums($aid); + } + + function moveForum($id, $up) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $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])) + gameAction('moveAllianceForum', $id, ($up == "1")); + return $this->doGetForums($aid); + } + + function getForumAcl($id) { + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['aid'])) + return ""; + $aid = $p['aid']; + $pr = gameAction('getAlliancePrivileges', $pid); + if (!$pr['forum_admin']) + return ""; + if ($id != "" && (int)$id != (string)$id) + return ""; + + $l = gameAction('getAllianceRanks', $aid); + $rs = array(); + foreach ($l as $rId => $rName) + { + $s = "$rId#"; + $pr = gameAction('getRankPrivileges', $rId); + + if ($id == "") + $s .= $pr['forum_admin'] ? 3 : 0; + else + $s .= $pr['forum_admin'] ? 3 : (in_array($id, $pr['f_mod']) ? 2 : (in_array($id, $pr['f_read']) ? 1 : 0)); + $s .= "#".utf8entities($rName); + array_push($rs, $s); + } + return join("\n", $rs); + } + + + //------------------------------------------- + // RANKS MANAGEMENT + //------------------------------------------- + + function doGetRanks($aid) { + $aLib = $this->game->getLib('beta5/alliance'); + + $afl = $aLib->call('getForums', $aid); + $forums = array(); + foreach ($afl as $fid => $fd) { + array_push($forums, "$fid#" . $fd['title']); + } + + $arl = $aLib->call('getRanks', $aid); + $ranks = array(); + foreach ($arl as $rid => $rname) { + $r = array(); + + $pr = $aLib->call('getRankPrivileges', $rid); + $nbp = $aLib->call('getRankSize', $aid, $rid); + $str = "$rid#$nbp#{$pr['list_access']}#{$pr['attacks']}#{$pr['can_set_grades']}" + . "#{$pr['can_kick']}#{$pr['can_accept']}#{$pr['forum_admin']}#" + . "{$pr['dipl_contact']}#{$pr['can_vote']}#{$pr['can_be_cand']}" + . "#{$pr['tech_trade']}"; + array_push($r, $str); + array_push($r, $rname); + + if ($pr['can_kick']) { + array_push($r, join('#', $pr['kick_ranks'])); + } + if ($pr['can_set_grades']) { + array_push($r, join('#', $pr['change_ranks'])); + } + array_push($r, join('#', $pr['f_read'])); + array_push($r, join('#', $pr['f_mod'])); + + array_push($ranks, join("\n", $r)); + } + + $a = array(count($forums) . "#" . count($ranks)); + $a = array_merge($a, $forums); + $a = array_merge($a, $ranks); + return join("\n", $a); + } + + function doRankEdit($aid, $name, $privileges, $rkick, $rchange, $fread, $fmod, $id = null) { + $rl0 = gameAction('getAllianceRanks', $aid); + $rl = array_keys($rl0); + $rn = array_values($rl0); + if (!(is_null($id) || in_array($id, $rl))) { + return "ERR#5"; + } + + // Check for valid name + $a = gameAction('getAllianceInfo', $aid); + if ($id != $a['default_grade']) { + $name = preg_replace('/\s+/', ' ', trim($name)); + if (strlen($name) < 4) { + return "ERR#1"; + } elseif (strlen($name) > 32) { + return "ERR#2"; + } elseif (in_array($name, $rn) && (is_null($id) || $rl0[$id] != $name)) { + return "ERR#3"; + } + } else { + $name = null; + } + + // Check privileges + $pl = split('#', $privileges); + $pnames = array('list_access', 'attacks', 'can_set_grades', 'can_kick', + 'can_accept', 'forum_admin', 'dipl_contact', 'can_vote', 'can_be_cand', + 'tech_trade'); + if (count($pl) != count($pnames)) { + return "ERR#6"; + } + $privs = array(); + for ($i = 0; $i < count($pnames); $i ++) { + $pl[$i] = (int) $pl[$i]; + switch ($i) { + case 0: $mv = 3; break; + case 9: $mv = 4; break; + default: $mv = 1; break; + } + if ($pl[$i] < 0 || $pl[$i] > $mv) { + return "ERR#6"; + } + $privs[$pnames[$i]] = $pl[$i]; + } + + // Check kickable ranks + if ($privs['can_kick'] && $rkick != "") { + $rkl = split('#', $rkick); + for ($i=0;$idoGetRanks($aid); + } + + function getRanks() { + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['aid'])) + return ""; + $aid = $p['aid']; + $a = gameAction('getAllianceInfo', $aid); + if ($a['leader'] != $pid) + return ""; + $_SESSION[game::sessName()]['alliance_page'] = 'RAdmin'; + + return $this->doGetRanks($aid); + } + + function newRank($name, $privileges, $rkick, $rchange, $fread, $fmod) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['aid'])) { + return "ERR#0"; + } + $aid = $p['aid']; + $a = gameAction('getAllianceInfo', $aid); + if ($a['leader'] != $pid) { + return "ERR#4"; + } + + return $this->doRankEdit($aid, $name, $privileges, $rkick, $rchange, $fread, $fmod); + } + + function changeRank($id, $name, $privileges, $rkick, $rchange, $fread, $fmod) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['aid'])) + return "ERR#0"; + $aid = $p['aid']; + $a = gameAction('getAllianceInfo', $aid); + if ($a['leader'] != $pid) + return "ERR#4"; + + return $this->doRankEdit($aid, $name, $privileges, $rkick, $rchange, $fread, $fmod, $id); + } + + function delRank($id, $dmId) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $pid = $_SESSION[game::sessName()]['player']; + $p = gameAction('getPlayerInfo', $pid); + if (is_null($p['aid'])) { + return "ERR#0"; + } + $aid = $p['aid']; + $a = gameAction('getAllianceInfo', $aid); + if ($a['leader'] != $pid) { + return "ERR#4"; + } + + $rl = array_keys(gameAction('getAllianceRanks', $aid)); + if ($id == $a['default_grade'] || !in_array($id, $rl) || !in_array($dmId, $rl)) { + return "ERR#5"; + } + if ($dmId == $a['default_grade']) { + $dmId = "NULL"; + } + gameAction('deleteAllianceRank', $aid, $id, $dmId); + + return $this->doGetRanks($aid); + } + + //------------------------------------------- + // MAIN HANDLER + //------------------------------------------- + + function handle($input) + { + $this->data = $this->getGeneralData(); + $this->output = "alliance"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/allies.inc b/scripts/site/beta5/handlers/allies.inc new file mode 100644 index 0000000..eb0030d --- /dev/null +++ b/scripts/site/beta5/handlers/allies.inc @@ -0,0 +1,237 @@ + "makeAlliesTooltips();\ninitPage();", + 'func' => array( + 'getTrusted', 'moveAllies', 'removeAllies', 'addAlly', + 'removeRAllies', 'removeBanRAllies', + 'removeBans', 'addBan' + ) + ); + + + /******************** + * GETTING THE LIST * + ********************/ + + /** This method generates the AJAX data for the lists returned + * by the getTrustedAllies() action. + */ + private function formatData($taData) { + $result = array(); + array_push($result, count($taData['allies']) . "#" . count($taData['reverse']) + . "#" . count($taData['blacklist'])); + + foreach ($taData['allies'] as $data) { + array_push($result, "{$data['id']}#" . utf8entities($data['name'])); + } + + foreach ($taData['reverse'] as $id => $data) { + array_push($result, "$id#{$data['level']}#" . utf8entities($data['name'])); + } + + foreach ($taData['blacklist'] as $id => $name) { + array_push($result, "$id#" . utf8entities($name)); + } + + return join("\n", $result); + } + + + /** AJAX callback that returns the list of trusted allies, the + * reverse list as well as the blacklist. + */ + public function getTrusted() { + $taData = $this->game->action('getTrustedAllies', $_SESSION[game::sessName()]['player']); + return $this->formatData($taData); + } + + + /******************* + * MANAGING ALLIES * + *******************/ + + /** AJAX callback to add an ally to the player's list. + */ + public function addAlly($name) { + $result = $this->game->action('addTrustedAlly', $_SESSION[game::sessName()]['player'], $name); + if (is_array($result)) { + return $this->formatData($result); + } + + switch ($result) : + case beta5_addTrustedAlly::playerNotFound: $error = -1; break; + case beta5_addTrustedAlly::playerOnVacation: $error = 200; break; + case beta5_addTrustedAlly::noAllyName: $error = 1; break; + case beta5_addTrustedAlly::invalidAllyName: $error = 0; break; + case beta5_addTrustedAlly::allyNotFound: $error = 2; break; + case beta5_addTrustedAlly::allyIsPlayer: $error = 3; break; + case beta5_addTrustedAlly::allyIsEnemy: $error = 6; break; + case beta5_addTrustedAlly::playerBlacklisted: $error = 9; break; + case beta5_addTrustedAlly::allyAlreadyListed: $error = 4; break; + case beta5_addTrustedAlly::maxPlayerTrust: $error = 14; break; + case beta5_addTrustedAlly::maxAllyTrust: $error = 5; break; + endswitch; + + return "ERR#$error"; + } + + + /************************* + * MANAGING REVERSE LIST * + *************************/ + + /** AJAX callback to remove a player from his allies' lists. + */ + public function removeRAllies($list) { + $result = $this->game->action('removeTrustingAllies', $_SESSION[game::sessName()]['player'], + explode('#', $list)); + if (is_array($result)) { + return $this->formatData($result); + } + + switch ($result) : + case beta5_removeTrustingAllies::playerNotFound: $error = -1; break; + case beta5_removeTrustingAllies::playerOnVacation: $error = 200; break; + case beta5_removeTrustingAllies::trustingPlayerNotFound: $error = 7; break; + endswitch; + return "ERR#$error"; + } + + + /********************** + * MANAGING BLACKLIST * + **********************/ + + public function addBan($name) { + $result = $this->game->action('banTrustingAlly', $_SESSION[game::sessName()]['player'], $name); + if (is_array($result)) { + return $this->formatData($result); + } + + switch ($result) : + case beta5_banTrustingAlly::playerNotFound: $error = -1; break; + case beta5_banTrustingAlly::playerOnVacation: $error = 200; break; + case beta5_banTrustingAlly::emptyName: $error = 11; break; + case beta5_banTrustingAlly::invalidName: $error = 15; break; + case beta5_banTrustingAlly::targetNotFound: $error = 2; break; + case beta5_banTrustingAlly::targetIsPlayer: $error = 13; break; + case beta5_banTrustingAlly::alreadyBanned: $error = 12; break; + endswitch; + return "ERR#$error"; + } + + + // ----------------------------------- OLD CODE BELOW!!!! + + public function findLevels($list) { + $al = explode('#', $list); + $l = gameAction('getPlayerAllies', $_SESSION[game::sessName()]['player']); + $ll = array(); + $i = 0; + $c = count($l); + foreach ($al as $id) + { + for ($j=0;$j<$c&&$l[$j]['id']!=$id;$j++) ; + if ($j<$c) + array_push($ll,$j); + $i ++; + if ($i == 5) + break; + } + return $ll; + } + + public function moveAllies($list, $dir) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $levels = $this->findLevels($list); + sort($levels); + if (!count($levels)) + return $this->getTrusted(); + + $pid = $_SESSION[game::sessName()]['player']; + if ($dir == "0") + { + $l = gameAction('getPlayerAllies', $pid); + if ($levels[0] == count($l) - 1) + return $this->getTrusted(); + $levels = array_reverse($levels); + } + elseif ($levels[0] == 0) + return $this->getTrusted(); + + $act = 'moveAlly' . (($dir == "0") ? "Down" : "Up"); + foreach ($levels as $l) + gameAction($act, $pid, $l); + + return $this->getTrusted(); + } + + public function removeAllies($list) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $levels = $this->findLevels($list); + if (count($levels)) { + $pid = $_SESSION[game::sessName()]['player']; + foreach ($levels as $l) + gameAction('removeAlly', $pid, $l); + gameAction('reorderPlayerAllies', $pid); + } + return $this->getTrusted(); + } + + public function removeBanRAllies($list) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $tb = gameAction('getPlayerIsAlly', $_SESSION[game::sessName()]['player']); + $pid = $_SESSION[game::sessName()]['player']; + $al = explode('#', $list); + foreach ($al as $opid) { + if (is_null($tb[$opid])) + return "ERR#7"; + elseif (gameAction('checkTAListBan', $pid, $opid)) + return "ERR#8"; + } + foreach ($al as $opid) { + gameAction('removeAlly', $opid, $tb[$opid]['level']); + gameAction('reorderPlayerAllies', $opid); + gameAction('addTAListBan', $pid, $opid); + } + return $this->getTrusted(); + } + + public function removeBans($list) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $pid = $_SESSION[game::sessName()]['player']; + $tb = gameAction('getTAListBans', $pid); + $al = explode('#', $list); + foreach ($al as $opid) + if (is_null($tb[$opid])) + return "ERR#10"; + foreach ($al as $opid) + gameAction('delTAListBan', $pid, $opid); + return $this->getTrusted(); + } + + + /** Main webpage handler. + */ + public function handle($input) { + $this->data = $this->getTrusted(); + $this->output = "allies"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/comms.inc b/scripts/site/beta5/handlers/comms.inc new file mode 100644 index 0000000..8b75332 --- /dev/null +++ b/scripts/site/beta5/handlers/comms.inc @@ -0,0 +1,57 @@ + array('getCommsData'), + 'init' => "makeCommsTooltips();\ninitPage();" + ); + + public function getCommsData() { + // Get the data + $data = $this->game->action('getCommsOverview', $_SESSION[game::sessName()]['player']); + + // Issue first line: nCFolders#nGenCats#nAForums + $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)); + } + + return join("\n", $result); + } + + public function handle($input) { + $this->data = $this->getCommsData(); + $this->output = "comms"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/diplomacy.inc b/scripts/site/beta5/handlers/diplomacy.inc new file mode 100644 index 0000000..b73e07c --- /dev/null +++ b/scripts/site/beta5/handlers/diplomacy.inc @@ -0,0 +1,115 @@ + array('getInformation'), + 'init' => "makeDiplomacyTooltips();\ninitPage();" + ); + + 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) { + return array('',''); + } + return $r; + } + + function getInformation() + { + $out = array(); + $pid = $_SESSION[game::sessName()]['player']; + $pinf = gameAction('getPlayerInfo', $pid); + if (!is_null($pinf['arid'])) + { + $ainf = gameAction('getAllianceInfo', $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']); + array_push($out, utf8entities($alinf)); + } + elseif (!is_null($pinf['aid'])) + { + $ainf = gameAction('getAllianceInfo', $pinf['aid']); + $pr = gameAction('getAlliancePrivileges', $pid); + + $s = "2#" . $ainf['nplanets'] . '#' . $ainf['avgx'] . '#' . $ainf['avgy']; + list($points,$ranking) = $this->getAllianceRanking($ainf['tag']); + $s .= "#$ranking#$points#"; + $s .= $pr['is_leader'] . "#" . (count($pr['f_read']) + count($pr['f_mod'])); + 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'])) + array_push($out, "-"); + else + { + $rkl = gameAction('getAllianceRanks', $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']))) + continue; + $tot = $fd['topics']; + $unread = $tot - gameAction('getReadTopics', $fid, $pid); + array_push($out, "$fid#$tot#$unread#".utf8entities($fd['title'])); + } + } + else + array_push($out, "0"); + + $pm = gameAction('getAllMessages', $pid, 'IN'); + $pmn = gameAction('getNewMessages', $pid, 'IN'); + $it = gameAction('getAllMessages', $pid, 'INT'); + $itn = gameAction('getNewMessages', $pid, 'INT'); + + $rook = !gameAction('isPlayerRestrained', $pid); + if ($rook) + { + $rol = gameAction('getResearchOffers', $pid); + $now = time(); $nrec = 0; $sentTo = ""; + foreach ($rol as $roffer) + { + if ($now - $roffer['time'] > 86400) + break; + if ($roffer['type'] == "S") + $sentTo = utf8entities($roffer['player']); + elseif ($roffer['status'] == "P") + $nrec ++; + } + } + else + $nrec=$sentTo=""; + + array_push($out, join('#', gameAction('getDiploSummary', $pid))); + array_push($out, "$pm#$pmn#$it#$itn"); + array_push($out, ($rook ? 1 : 0) . "#$nrec#$sentTo"); + + return join("\n", $out); + } + + function handle($input) + { + $this->data = $this->getInformation(); + $this->output = "diplomacy"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/empire.inc b/scripts/site/beta5/handlers/empire.inc new file mode 100644 index 0000000..2542481 --- /dev/null +++ b/scripts/site/beta5/handlers/empire.inc @@ -0,0 +1,43 @@ + array("getEmpireData"), + "init" => "makeEmpireTooltips();\nempire_write(document.getElementById('init-data').value);" + ); + + + public function getEmpireData() { + $data = $this->game->action('getEmpireOverview', $_SESSION[game::sessName()]['player']); + if (is_null($data)) { + return; + } + + $s = ""; + foreach ($data['planets'] as $id => $n) { + $s .= ($s == "" ? "" : "#") . "$id#$n"; + } + + $str = join('#', $data['planetStats']) + . "\n$s\n{$data['fleetStats']['fleets']}#{$data['fleetStats']['battle']}#" + . "{$data['fleetStats']['power']}#{$data['fleetStats']['upkeep']}#" + . "{$data['fleetStats']['at_home']}#{$data['fleetStats']['home_battle']}#" + . "{$data['fleetStats']['foreign']}#{$data['fleetStats']['foreign_battle']}#" + . "{$data['fleetStats']['moving']}#{$data['fleetStats']['waiting']}#" + . "{$data['fleetStats']['gaships']}#{$data['fleetStats']['fighters']}#" + . "{$data['fleetStats']['cruisers']}#{$data['fleetStats']['bcruisers']}\n" + . "{$data['techStats']['points']}#" . join('#', $data['techStats']['budget']) + . "#{$data['techStats']['new']}#{$data['techStats']['foreseen']}\n" + . "{$data['income']}#{$data['profit']}"; + + return $str; + } + + function handle($input) { + $this->data = $this->getEmpireData(); + $this->output = "empire"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/enemylist.inc b/scripts/site/beta5/handlers/enemylist.inc new file mode 100644 index 0000000..392557f --- /dev/null +++ b/scripts/site/beta5/handlers/enemylist.inc @@ -0,0 +1,89 @@ + "makeEnemyListTooltips();\ninitList();", + 'func' => array('getEnemies', 'removeEnemies', 'addEnemy'), + ); + + function getEnemies() + { + $pid = $_SESSION[game::sessName()]['player']; + $rs = array(); + + $epl = gameAction('getEnemyPlayers', $pid); + foreach ($epl as $id => $name) + array_push($rs, "0#$id#".utf8entities($name)); + + $eal = gameAction('getEnemyAlliances', $pid); + foreach ($eal as $id => $name) + array_push($rs, "1#$id#".utf8entities($name)); + + return join("\n", $rs); + } + + function removeEnemies($type, $list) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $pid = $_SESSION[game::sessName()]['player']; + $l = explode('#', $list); + $action = 'removeEnemy' . ($type == "1" ? 'Alliance' : 'Player'); + foreach ($l as $eid) { + gameAction($action, $pid, (int)$eid); + } + return $this->getEnemies(); + } + + function addEnemy($type, $name) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + if ($type != "0" && $type != "1") { + return "ERR#0"; + } + $name = preg_replace('/\s+/', ' ', trim($name)); + if ($name == "") + return "ERR#1"; + elseif (($type == 0 && strlen($name) > 15) || ($type == 1 && strlen($name) > 5)) + return "ERR#0"; + + $pid = $_SESSION[game::sessName()]['player']; + if ($type == 0 && $this->game->params['victory'] == 0 && strtolower($name) == 'ai>peacekeepers') { + return "ERR#9"; + } + $eid = gameAction($type == 0 ? "getPlayer" : "getAlliance", $name); + if (is_null($eid)) + return "ERR#" . ($type == 0 ? 2 : 3); + + $list = array_keys(gameAction('getEnemy'. ($type == 0 ? 'Players' : 'Alliances'), $pid)); + if (in_array($eid, $list)) + return "ERR#" . ($type == 0 ? 4 : 5); + + if ($type == 0 && $eid == $pid) + return "ERR#6"; + elseif ($type == 0 && gameAction('isPlayerAlly', $pid, $eid)) + return "ERR#8"; + elseif ($type == 1) + { + $pinf = gameAction('getPlayerInfo', $pid); + if (!is_null($pinf['aid']) && $pinf['aid'] == $eid) + return "ERR#7"; + } + + gameAction('addEnemy' . ($type == 0 ? 'Player' : 'Alliance'), $pid, $eid); + + return $this->getEnemies(); + } + + function handle($input) + { + $this->data = $this->getEnemies(); + $this->output = "enemylist"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/fleets.inc b/scripts/site/beta5/handlers/fleets.inc new file mode 100644 index 0000000..92f6c6c --- /dev/null +++ b/scripts/site/beta5/handlers/fleets.inc @@ -0,0 +1,1003 @@ + 'initFleets();', + 'func' => array( + 'getFleetsList','setFilter', 'renameFleets', + 'mergeFleets', 'switchStatus', 'manualSplit', + 'autoSplit', 'disbandFleets', 'setOrders', + 'getLocation', 'getMapData', 'moveMapToId', + 'moveMapToName', 'getTrajectory', 'getFleetTrajectory', + 'sellFleets', 'cancelSales', 'getSaleDetails' + ) + ); + + //-------------------------------------------------------------------------------------------- + // FLEETS LIST FUNCTIONS + + function getOfflineAllies($player) + { + $list = gameAction('checkPlayerAllies', $player); + array_push($list, $player); + $res = array(); + foreach ($list as $pid) + { + $str = "$pid#" . ($pid == $player ? "1" : "0") . "#"; + $rules = gameAction('loadPlayerRules', $pid); + $str .= $rules['fighter_space'] . "#" . $rules['gaship_space'] . "#"; + $str .= $rules['cruiser_haul'] . "#" . $rules['bcruiser_haul'] . "#"; + $str .= $rules['gaship_pop'] . "#" . ($pid == $player ? gameAction('isPlayerRestrained', $player) : 0); + array_push($res, $str); + } + return array($list, $res); + } + + function getLocations($plist, $dest) + { + $allLocations = array(); $allOwned = array(); $allyHasPlanets = array(); + + foreach ($plist as $cPlayer) + { + $locations = gameAction('getFleetLocations', $cPlayer); + foreach ($locations as $id) + if (!in_array($id, $allLocations)) + array_push($allLocations, $id); + + $ownPlanets = array_keys(gameAction('getPlayerPlanets', $cPlayer)); + foreach ($ownPlanets as $id) + { + if (!in_array($id, $allLocations)) + array_push($allLocations, $id); + if (!in_array($id, $allOwned)) + array_push($allOwned, $id); + } + if (count($ownPlanets)) { + $allyHasPlanets[$cPlayer] = true; + } + + // FIXME: planets being probed should be accounted for as well + } + + if (!is_null($dest) && !in_array($dest['id'], $allLocations)) + array_push($allLocations, $dest['id']); + + return array($allLocations, $allOwned, $allyHasPlanets); + } + + function getRawFleets($locations, $players) + { + $fleets = array(); + + // Get all fleets at these locations + foreach ($locations as $plId) + { + $locFleets = array_keys(gameAction('getFleetsAt', $plId)); + foreach ($locFleets as $id) + $fleets[$id] = gameAction('getFleet', $id); + } + + // Get moving fleets and fleets standing by in HS + $targets = array(); $allyHasFleets = array(); + foreach ($players as $cPlayer) + { + $ownFleets = array_keys(gameAction('getPlayerFleets', $cPlayer)); + foreach ($ownFleets as $id) + { + if (is_array($fleets[$id])) + continue; + + $fleets[$id] = gameAction('getFleet', $id); + if (!is_null($fleets[$id]['move'])) + { + $to = $fleets[$id]['move']['m_to']; + $from = $fleets[$id]['move']['m_from']; + $cur = $fleets[$id]['move']['cur'] = gameAction('getObjectLocation', $fleets[$id]['move']['id']); + } + else + { + $to = $fleets[$id]['wait']['drop_point']; + $from = $fleets[$id]['wait']['origin']; + $cur = null; + } + + if (!in_array($to, $locations)) + array_push($targets, $to); + if (!in_array($from, $locations)) + array_push($targets, $from); + if (!(is_null($cur) || in_array($cur, $locations))) + array_push($targets, $cur); + } + if (count($ownFleets)) { + $allyHasFleets[$cPlayer] = true; + } + } + + return array($fleets, array_unique($targets), $allyHasFleets); + } + + function genFleetLines($cPlayer, $fleets, $allies) + { + $players = array(); $lines = array(); + foreach ($fleets as $id => $data) + { + if (!in_array($data['owner'], $players)) + array_push($players, $data['owner']); + + if (in_array($data['owner'], $allies)) + { + $rs = "$id"; + if ($data['owner'] == $cPlayer) + $fUpkeep = gameAction('getFleetUpkeep', $cPlayer, $data['gaships'], $data['fighters'], $data['cruisers'], $data['bcruisers']); + else + $fUpkeep = ""; + } + else + { + $rs = ""; + $fUpkeep = ""; + } + + if (is_null($data['location'])) + $mode = is_null($data['move']) ? 3 : 2; + elseif (is_null($data['sale_info'])) + $mode = ($data['attacking'] == 't' ? 1 : 0); + else + $mode = is_null($data['sale']) ? 4 : 5; + + $rs .= "#" . $data['owner'] . '#' . $data['gaships'] . "#" . $data['fighters'] . "#" . $data['cruisers'] . "#" . $data['bcruisers'] . "#" + . ($fPower = gameAction('getFleetPower', $data['owner'], 0, $data['gaships'], $data['fighters'], $data['cruisers'], $data['bcruisers'])) + . "#$mode#$fUpkeep#" . utf8entities($data['name']); + + array_push($lines, $rs); + + // Orbiting fleets + if ($mode < 2) + { + array_push($lines, $data['location'] . "#" . $data['can_move']); + continue; + } + + // Moving fleets + if ($mode == 2) + { + $rs = $data['move']['m_to'] . "#" . $data['move']['m_from'] . "#"; + $rs .= $data['move']['cur'] . "#" . (is_null($data['move']['wait_order']) ? ($data['attacking'] == 't' ? 1 : 0) : 2); + $rs .= "#" . ($data['move']['time_left'] + $data['move']['changed']) . "#" . ($data['move']['hyperspace'] == 't' ? 1 : 0) . "#" . $data['move']['changed']; + array_push($lines, $rs); + + if (!is_null($data['move']['wait_order'])) + { + $q = dbQuery("SELECT * FROM hs_wait WHERE id=".$data['move']['wait_order']); + $data['wait'] = dbFetchHash($q); + $data['wait']['origin'] = $data['move']['m_from']; + $mode = 3; + } + else + continue; + } + + // Fleets standing by / Moving fleets with stand-by orders + if ($mode == 3) + { + $rs = $data['wait']['drop_point'] . "#" . $data['wait']['origin'] . "#"; + $rs .= $data['wait']['time_left'] . "#" . $data['wait']['time_spent'] . "#"; + + if (gameAction('waitCanDestroy', $data['wait']['drop_point'], $data['owner'])) + { + $minLeft = $maxLeft = $fPower; + for ($i=0;$i<$data['wait']['time_left'];$i++) + { + $t = $data['wait']['time_spent'] + $i; + $prob = gameAction('waitGetLossProb', $t); + if ($prob == 0) + continue; + $minLeft -= floor(5 * $minLeft * $prob / 10000); + $maxLeft -= floor(round(10+$prob/10) * $maxLeft * $prob / 10000); + } + $minProb = round((($fPower - $minLeft) / $fPower) * 100); + $maxProb = round((($fPower - $maxLeft) / $fPower) * 100); + $rs .= "$minProb#$maxProb"; + } + else + $rs .= "0#0"; + + $rs .= "#" . ($data['attacking'] == 't' ? 1 : 0); + array_push($lines, $rs); + continue; + } + + // Fleets on sale + if ($mode == 4) + { + $rs = $data['location'] . "#"; + if ($data['owner'] == $cPlayer) + $rs .= $data['sale_info']['sale']['id'] . "#" . (is_null($data['sale_info']['sale']['planet']) ? 0 : 1); + else + $rs .= "#"; + } + // Sold fleets waiting for owner change + else + { + $rs = $data['location'] . "#"; + if ($data['owner'] == $cPlayer) + $rs .= $data['sale_info']['sale']['id'] . "#" . $data['sale_info']['sale']['sold_to'] . "#" + . $data['sale'] . "#" . (is_null($data['sale_info']['sale']['planet']) ? 0 : 1); + else + $rs .= "###"; + } + array_push($lines, $rs); + } + + return array($lines, $players); + } + + function genPlanetLines($allPlanets, $allAllies, $locations) + { + $tags = array(); $result = array(); + foreach ($allPlanets as $id) { + $pl = gameAction('getPlanetById', $id); + array_push($result, "$id#" . $pl['status'] . "#" . $pl['x'] . "#" . $pl['y'] . "#" . ($pl['orbit'] + 1) . "#" . utf8entities($pl['name'])); + if ($pl['status'] != 0) + continue; + + if (!is_null($pl['owner'])) { + $owner = in_array($pl['owner'], $allAllies) ? $pl['owner'] : ''; + if (is_null($tags[$pl['owner']])) { + $i = gameAction('getPlayerInfo', $pl['owner']); + $tag = $tags[$pl['owner']] = utf8entities($i['alliance']); + } else { + $tag = $tags[$pl['owner']]; + } + } else { + $owner = $tag = ""; + } + + if (in_array($id, $locations)) { + $turrets = $pl['turrets']; + $tPower = gameAction('getFleetPower', $pl['owner'], $turrets, 0, 0, 0, 0); + $pop = $pl['pop']; + $vacation = ($pl['vacation'] == 'YES ') ? 1 : 0; + $protection = (!is_null($pl['owner']) + && (gameAction('getProtectionLevel', $pl['owner']) > 0)) ? 1 : 0; + } else { + $turrets = $pop = $tPower = $vacation = $protection = ""; + } + + array_push($result, "$owner#$turrets#$tPower#$pop#$vacation#$protection#$tag"); + } + return $result; + } + + function getFleetsList() + { + // Get related data + list($allAllies, $allyData) = $this->getOfflineAllies($_SESSION[game::sessName()]['player']); + list($locations, $owned, $allyHasPlanets) = $this->getLocations($allAllies, $dest); + list($fleets, $targets, $allyHasFleets) = $this->getRawFleets($locations, $allAllies); + list($fleetLines, $players) = $this->genFleetLines($_SESSION[game::sessName()]['player'], $fleets, $allAllies); + $planetLines = $this->genPlanetLines(array_merge($targets, $locations), $allAllies, $locations); + $result = array(); + + // nAllies # nFleets # nPlanets + array_push($result, count($allAllies) . "#" . count($fleets) . "#" . (count($locations) + count($targets))); + + // listLocations # listMode # fDispMode # allyMode + $lMode = $this->sessData('listMode'); $lLoc = $this->sessData('listLocations'); + $fMode = $this->sessData('fDispMode'); $aMode = $this->sessData('alliesMode'); + array_push($result, "$lLoc#$lMode#$fMode#$aMode"); + + // sType # sText + $sType = $this->sessData('sType'); $sText = $this->sessData('sText'); + array_push($result, "$sType#$sText"); + + // Allies list + $result = array_merge($result, $allyData); + + // Fleets list + $result = array_merge($result, $fleetLines); + + // Planets list + $result = array_merge($result, $planetLines); + + // Players + foreach ($allAllies as $id) { + if (!in_array($id, $players) && ($allyHasPlanets[$id] || $allyHasFleets[$id])) { + array_push($players, $id); + } + } + foreach ($players as $id) + array_push($result, "$id#" . utf8entities(gameAction('getPlayerName', $id))); + + return join("\n", $result); + } + + //-------------------------------------------------------------------------------------------- + // FLEET ACTIONS + + function renameFleets($fleets, $name) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + // Check the new name + $name = preg_replace('/\s+/', ' ', trim($name)); + if ($name == "") + return "ERR#0"; + elseif (strlen($name) < 3) + return "ERR#1"; + elseif (strlen($name) > 64) + return "ERR#2"; + + // Get players + $player = $_SESSION[game::sessName()]['player']; + $players = gameAction('checkPlayerAllies', $player); + array_push($players, $player); + + // Get all fleets + $afl = array(); + foreach ($players as $pid) + $afl = array_merge($afl, array_keys(gameAction('getPlayerFleets', $pid))); + + // Check fleet IDs + $fids = explode('#', $fleets); + foreach ($fids as $fid) + if (!in_array($fid, $afl)) + return "ERR#3"; + + foreach ($fids as $fid) + gameAction('renameFleet', $fid, $name); + + return $this->getFleetsList(); + } + + function mergeFleets($fleets, $name) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + // Check the new name + $name = preg_replace('/\s+/', ' ', trim($name)); + if (strlen($name) < 3 && $name != '') + return "ERR#0"; + elseif (strlen($name) > 64) + return "ERR#1"; + + // Get players + $player = $_SESSION[game::sessName()]['player']; + $players = gameAction('checkPlayerAllies', $player); + array_push($players, $player); + + // Check fleet IDs + $fids = explode('#', $fleets); + $afl = array(); + foreach ($fids as $fid) + array_push($afl, (int)$fid); + + // Do it + gameAction('mergeFleets', array_unique($afl), $players, $name); + + return $this->getFleetsList(); + } + + // Switch fleet status, making sure fleets at the same location are in the same + // mode. NOTE, we assume that fleets at a given location for a player are already + // in the same mode. + function switchStatus($fleets) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + // Get players + $player = $_SESSION[game::sessName()]['player']; + $players = gameAction('checkPlayerAllies', $player); + array_push($players, $player); + + // Check fleet IDs + $fids = explode('#', $fleets); + $afl = array(); + foreach ($fids as $fid) + array_push($afl, (int)$fid); + + // Check for problems and additional fleets to switch + $inspected = array(); + $locations = array(); + foreach ($afl as $fid) + { + // Get fleet and check ownership + $f = gameAction('getFleet', $fid); + if (!($f && in_array($f['owner'], $players))) + { + logText("beta5/fleets/switchStatus: fleet $fid not owned by any allied player (" . join(', ', $players) . ")", LOG_NOTICE); + return "ERR#0"; + } + + // Get owner's other fleets at the same location + if (!is_null($f['location'])) + { + $location = $f['location']; + $key = $f['owner'] . ":" . $location; + if (!in_array($key, $inspected)) { + if (!in_array($location, $locations)) + array_push($locations, $location); + + $fl = array_keys(gameAction('getFleetsAt', $location, $f['owner'])); + foreach ($fl as $nid) + if (!in_array($nid, $afl)) + array_push($afl, $nid); + array_push($inspected, $key); + } + } + elseif (is_null($f['moving'])) + $location = $f['wait']['drop_point']; + else + $location = $f['move']['m_to']; + + // Make sure a player's not attacking himself + $po = gameAction('getPlanetOwner', $location); + if ($po == $f['owner']) + { + logText("beta5/fleets/switchStatus: fleet $fid can't be switched (planet owner == fleet owner for $location)", LOG_NOTICE); + return "ERR#1"; + } + + // If the player's switching to def, make sure he can + if (!is_null($po) && $po != $f['owner'] && !is_null($f['location'])) + { + // Is the fleet owner an enemy of the planet owner? + $isEnemy = gameAction('isPlayerEnemy', $po, $f['owner']); + if (!$isEnemy) + { + // Get fleet owner data + $foi = gameAction('getPlayerInfo', $f['owner']); + // Check for enemy alliance + $isEnemy = (!is_null($foi['aid']) && gameAction('isAllianceEnemy', $po, $foi['aid'])); + } + + if ($isEnemy) + { + logText("beta5/fleets/switchStatus: fleet $fid can't be switched (planet owner has fleet owner on enemy list at $location)", LOG_NOTICE); + return "ERR#2"; + } + } + + // If we are in a round game, and if the player is marked for destruction by the + // Peacekeepers in that system, prevent him from switching + if ($this->game->params['victory'] == 0 && !is_null($f['location'])) { + if (is_null($this->protection)) { + $this->protection = $this->game->getLib('beta5/prot'); + } + if ($this->protection->call('isPlayerMarked', $f['owner'], $f['location'])) { + return "ERR#3"; + } + } + } + + // Switch fleets status + foreach ($afl as $fid) + gameAction('switchFleetStatus', $fid); + + // Update the attacks cache + foreach ($locations as $pid) + gameAction('updateAttackStatus', $pid); + + return $this->getFleetsList(); + } + + function manualSplit($fleet, $name, $amount, $sg, $sf, $sc, $sb) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + // Check the new name + $name = preg_replace('/\s+/', ' ', trim($name)); + if (strlen($name) < 3 && $name != '') + return "ERR#10"; + elseif (strlen($name) > 60) + return "ERR#11"; + + // Get players + $player = $_SESSION[game::sessName()]['player']; + $players = gameAction('checkPlayerAllies', $player); + array_push($players, $player); + + // Get all fleets + $f = gameAction('getFleet', (int)$fleet); + if (!($f && in_array($f['owner'], $players))) + return "ERR#0"; + + // Check parameters + $amount = (int)$amount; + $sg = (int)$sg; $sf = (int)$sf; $sc = (int)$sc; $sb = (int)$sb; + if ($amount <= 0 || $amout > 10 || $sg < 0 || $sf < 0 || $sc < 0 || $sb < 0 || $sg+$sf+$sc+$sb <= 0) + return "ERR#1"; + + $ret = gameAction('splitFleetManually', $fleet, $name, $amount, $sg, $sf, $sc, $sb); + if ($ret) + return "ERR#" . ($ret + 1); + + return $this->getFleetsList(); + } + + function autoSplit($fleet, $name, $amount) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + // Check the new name + $name = preg_replace('/\s+/', ' ', trim($name)); + if (strlen($name) < 3 && $name != '') + return "ERR#10"; + elseif (strlen($name) > 60) + return "ERR#11"; + + // Get players + $player = $_SESSION[game::sessName()]['player']; + $players = gameAction('checkPlayerAllies', $player); + array_push($players, $player); + + // Get all fleets + $f = gameAction('getFleet', (int)$fleet); + if (!($f && in_array($f['owner'], $players))) + return "ERR#0"; + + // Check parameters + $amount = (int)$amount; + if ($amount <= 0 || $amout > 10) + return "ERR#1"; + + $ret = gameAction('splitFleetAuto', $fleet, $name, $amount + 1); + if ($ret) + return "ERR#" . ($ret + 4); + + return $this->getFleetsList(); + } + + function disbandFleets($fleets) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + // Get current player's fleets + $afl = array_keys(gameAction('getPlayerFleets', $_SESSION[game::sessName()]['player'])); + + // Check fleet IDs + $fids = explode('#', $fleets); + foreach ($fids as $fid) + if (!in_array($fid, $afl)) + return "ERR#0"; + + // Disband fleets + foreach ($fids as $fid) + { + $e = gameAction('disbandFleet', $fid); + if ($e != 0) + return "ERR#$e"; + } + + return $this->getFleetsList(); + } + + function setOrders($fleets, $moveTo, $waitTime, $status) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + // Get players + $player = $_SESSION[game::sessName()]['player']; + $players = gameAction('checkPlayerAllies', $player); + array_push($players, $player); + + // Get all fleets + $afl = array(); + foreach ($players as $pid) + $afl = array_merge($afl, array_keys(gameAction('getPlayerFleets', $pid))); + + // Check fleet IDs + $fids = explode('#', $fleets); + foreach ($fids as $fid) + if (!in_array($fid, $afl)) + return "ERR#0"; + + // Check destination + $moveTo = ($moveTo > 0 ? (int)$moveTo : null); + if (!is_null($moveTo)) + { + $d = gameAction('getPlanetById', $moveTo); + if (is_null($d)) + return "ERR#1"; + } + + // Set other parameters + $waitTime = ($waitTime > 0 ? (int)$waitTime : null); + $status = ($status == '1'); + + logText("beta5/fleets/setOrders: move to '$moveTo', wait time '$waitTime', status '".($status?"Att":"Def")."', fleets: " . join(', ', $fids), LOG_DEBUG); + + // Checks the validity of the orders + foreach ($fids as $fid) + { + $fleet = gameAction('getFleet', $fid); + if (is_null($fleet)) // Should never happen + { + logText("beta5/fleets/setOrders: fleet $fid has disappeared", LOG_WARNING); + return "ERR#2"; + } + elseif (!is_null($fleet['sale_info'])) + { + logText("beta5/fleets/setOrders: fleet $fid is for sale", LOG_NOTICE); + return "ERR#3"; + } + elseif ($fleet['can_move'] != 'Y') + { + logText("beta5/fleets/setOrders: fleet $fid is unavailable", LOG_NOTICE); + return "ERR#4"; + } + } + + // Execute orders + $fLib = $this->game->getLib('beta5/fleet'); + foreach ($fids as $fid) { + $fLib->call('setOrders', $fid, $moveTo, $waitTime, $status); + } + $fLib->call('sendMoveMessages'); + + return $this->getFleetsList(); + } + + function sellFleets($data) { + $cPlayer = $_SESSION[game::sessName()]['player']; + $pLib = $this->game->getLib('beta5/player'); + + if ($pLib->call('isOnVacation', $cPlayer)) { + return "ERR#200"; + } elseif ($pLib->call('getProtectionLevel', $cPlayer)) { + return "ERR#201"; + } + + if (gameAction('isPlayerRestrained', $cPlayer) > 0) + return "ERR#-1"; + $pProt = gameAction('getProtectionLevel', $cPlayer); + + // Extract data from the received string + $tmp = explode('*', $data); + $sales = array(); + foreach ($tmp as $str) + { + $tmp2 = explode('#', $str); + if (count($tmp2) != 6) + return "ERR#-1"; + $fleets = array_unique(explode('!', array_pop($tmp2))); + if (!count($fleets)) + return "ERR#-1"; + array_push($tmp2, $fleets); + array_push($sales, $tmp2); + } + + // Check each sale + $remove = array(); + $expOk = array(0, 6, 12, 24, 48, 72, 96, 120); + for ($i=0;$i 3) + return "ERR#-1"; + + // Check target player + if ($sales[$i][1] < 2) + { + $p = gameAction('getPlayer', $sales[$i][4]); + if (is_null($p)) + return "ERR#0#".$sales[$i][0]; + else if ($p == $cPlayer) + return "ERR#6#".$sales[$i][0]; + $pl = gameAction('getProtectionLevel', $p); + if ($pl != $pProt) + return "ERR#1#".$sales[$i][0]; + if (gameAction('isPlayerRestrained', $p) > 0) + return "ERR#7#".$sales[$i][0]; + $sales[$i][4] = $p; + } + else + $sales[$i][4] = null; + + // Check price + if ($sales[$i][1] != 0 && $sales[$i][3] <= 0) + return "ERR#2#".$sales[$i][0]; + + // Check expiration + if (!in_array($sales[$i][2],$expOk)) + return "ERR#-1"; + elseif ($mode == 3 && $sales[$i][2] == 0) + return "ERR#3#".$sales[$i][0]; + + // Check fleets + foreach ($sales[$i][5] as $fid) + { + $f = gameAction('getFleet', (int)$fid); + if (is_null($f) || $f['owner'] != $cPlayer || $f['attacking'] == 't' + || is_null($f['location']) || $f['location'] != $sales[$i][0] + || !is_null($f['sale']) || !is_null($f['sale_info'])) + { + if (!is_array($remove[$sales[$i][0]])) + $remove[$sales[$i][0]] = array(); + array_push($remove[$sales[$i][0]], $fid); + } + } + } + + // If some fleets have to be removed from the list, notify the page + if (count($remove)) + { + $rv = array('ERR', '4'); + foreach ($remove as $sid => $fl) + { + array_push($rv, $sid); + array_push($rv, join('!', $fl)); + } + return join('#', $rv); + } + + // Generate sales + foreach ($sales as $sale) + { + // Merge fleets + if (count($sale[5]) > 1) + { + $fidl = gameAction('mergeFleets', $sale[5], array($cPlayer), ''); + $fid = $fidl[0]; + } + else + $fid = $sale[5][0]; + gameAction('newSale', $cPlayer, ($sale[1]>1), ($sale[1]==3), $sale[2], $sale[3], $sale[4], null, $fid); + } + + return $this->getFleetsList(); + } + + function cancelSales($list) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $cPlayer = $_SESSION[game::sessName()]['player']; + $pinf = gameAction('getPlayerInfo', $cPlayer); + + $offers = array(); + $ids = explode("#", $list); + $sum = 0; + foreach ($ids as $sid) + { + $offer = gameAction('getFleetSale', (int)$sid); + if (is_null($offer) || $offer['player'] != $cPlayer) + return "ERR#-1"; + array_push($offers, $offer); + if (!is_null($offer['finalized'])) + $sum += $offer['price']; + } + + if ($sum > $pinf['cash']) + return "ERR#0"; + + foreach ($offers as $offer) + if (is_null($offer['finalized'])) + gameAction('cancelSale', $cPlayer, $offer['id']); + else + gameAction('cancelTransfer', $cPlayer, $offer['id']); + + return $this->getFleetsList(); + } + + function getSaleDetails($sId) { + $cPlayer = $_SESSION[game::sessName()]['player']; + + $offer = gameAction('getFleetSale', (int)$sId); + if (is_null($offer) || $offer['player'] != $cPlayer) + return ""; + + $rs = "$sId#{$offer['fleet']}#{$offer['expires']}#{$offer['finalized']}#{$offer['sold_to']}#{$offer['tx_time']}#"; + if ($offer['public']) + { + if ($offer['is_auction']) + $rs .= "2#{$offer['price']}#{$offer['max_bid']}#{$offer['bidder']}#{$offer['last_bid']}"; + else + $rs .= "1#{$offer['price']}"; + } + else + $rs .= "0#{$offer['price']}"; + + if (!is_null($offer['sold_to'])) + $rs .= "\n" . gameAction('getPlayerName', $offer['sold_to']); + elseif (!is_null($offer['bidder'])) + $rs .= "\n" . gameAction('getPlayerName', $offer['bidder']); + + return $rs; + } + + //-------------------------------------------------------------------------------------------- + // HANDLER AND HELPER FUNCTIONS + + function setFilter($name, $value) { + $okv = array('listMode','listLocations','fDispMode','alliesMode','sType','sText'); + if (!in_array($name, $okv)) + return; + $v = &$this->sessData($name); + $v = $value; + } + + function getLocation($id) + { + $id = (int)$id; + $p = gameAction('getPlanetById', $id); + if (is_null($p)) + return ""; + return join("\n", $this->genPlanetLines(array($id),array(),array())); + } + + function getMapData($sx, $sy) { + $sx = (int)$sx; $sy = (int)$sy; + $sys = gameAction('getSystemAt', $sx, $sy); + if (is_null($sys)) { + return "$sx#$sy#-1"; + } + + switch ($this->game->params['victory']) { + case 0: + $sys['prot'] = ($sys['prot'] > 0) ? 1 : 0; + break; + case 1: + $sys['prot'] = 0; + break; + case 2: + $sys['prot'] = input::$game->getLib('beta5/ctf')->call('isTarget', $sys['id']) ? 1 : 0; + break; + } + + $rv = array("$sx#$sy#{$sys['nebula']}#{$sys['prot']}"); + $plist = gameAction('getSystemPlanets', $sys['id']); + + $player = $_SESSION[game::sessName()]['player']; + $pinf = gameAction('getPlayerInfo', $player); + + foreach ($plist as $p) + { + $str = $p['id'] . "#" . $p['status'] . "#"; + if ($p['status'] == 0) + { + if ($p['owner'] == $player) + $str .= "2"; + elseif (!is_null($pinf['alliance']) && $p['tag'] == $pinf['alliance']) + $str .= "1"; + else + $str .= "0"; + } + else + $str .= '0'; + $str .= "#" . utf8entities($p['name']); + array_push($rv, $str); + } + return join("\n", $rv); + } + + function moveMapToId($id) + { + $id = (int)$id; + $p = gameAction('getPlanetById', $id); + if (is_null($p)) + return "ERR"; + return $this->getMapData($p['x'], $p['y']); + } + + function moveMapToName($name) + { + $p = gameAction('getPlanetByName', $name); + if (is_null($p)) + return "ERR"; + return $this->getMapData($p['x'], $p['y']); + } + + function getTrajectory($key) + { + list($from, $hc, $owner, $to) = explode(';', $key); + + // Get players + $player = $_SESSION[game::sessName()]['player']; + $players = gameAction('checkPlayerAllies', $player); + array_push($players, $player); + if (!in_array($owner, $players)) + return ""; + + // Test destination and origin + $p = gameAction('getPlanetById', $from); + if (is_null($p)) + return ""; + if ($from == $to) + return $key; + $p = gameAction('getPlanetById', $to); + if (is_null($p)) + return ""; + $hc = ($hc == "1"); + + // Get player capabilities + $rules = gameAction('loadPlayerRules', $owner); + + // Get trajectory + $t = gameAction('getTrajectory', $from, $to, $rules['capital_ship_speed'], $hc); + if (!$t) + return ""; + + // Generate return string + $rv = array($key); + foreach ($t['list'] as $wp) + { + $p = gameAction('getPlanetById', $wp['pid']); + array_push($rv, $p['x'] . "#" . $p['y'] . "#" . ($p['orbit'] + 1) . "#" . $wp['time'] . "#" . utf8entities($p['name'])); + } + + return join("\n", $rv); + } + + function getFleetTrajectory($fid) + { + // Get players + $player = $_SESSION[game::sessName()]['player']; + $players = gameAction('checkPlayerAllies', $player); + array_push($players, $player); + + // Get all fleets + $f = gameAction('getFleet', (int)$fid); + if (!($f && in_array($f['owner'], $players))) + return "ERR#0"; + elseif (is_null($f['move'])) + return "ERR#1"; + + // Start generating output + $s = "$fid#".$f['move']['m_from']."#".$f['move']['m_to']."#".$f['move']['changed']."#" + .$f['move']['time_left']."#".($f['move']['hyperspace'] == 't' ? 1 : 0)."#".(is_null($f['wait'])?"0":$f['wait']['time_left']); + $rv = array($s); + + // Get trajectory + $t = gameAction('getObjectTrajectory', $f['moving']); + foreach ($t as $wp) + { + $s = $wp['id'] . "#" . $wp['eta'] . "#" . $wp['x'] . "," . $wp['y'] . "#" . $wp['orbit'] . "#" . $wp['opacity'] + . "#" . utf8entities($wp['name']); + array_push($rv, $s); + } + + return join("\n", $rv); + } + + function &sessData($name) + { + if (!is_array($_SESSION[game::sessName()]['fleets'])) + $_SESSION[game::sessName()]['fleets'] = array( + 'listMode' => 0, + 'listLocations' => 0, + 'fDispMode' => 0, + 'alliesMode' => 0, + 'sType' => 0, + 'sText' => '' + ); + return $_SESSION[game::sessName()]['fleets'][$name]; + } + + function handle($input) + { + // FIXME: should check for request argument ("Send Fleets" planet command) + if ($input['sto'] != '') + $dest = gameAction('getPlanetById', (int)$input['sto']); + else + $dest = null; + $prefix = is_null($dest) ? '-' : $dest['id']; + $this->data = "$prefix " . $this->getFleetsList(); + $this->output = "fleets"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/forums.inc b/scripts/site/beta5/handlers/forums.inc new file mode 100644 index 0000000..3fc6669 --- /dev/null +++ b/scripts/site/beta5/handlers/forums.inc @@ -0,0 +1,1072 @@ + $cat) + { + if ($cat['type'] != $ctype) + continue; + for ($i=0;is_null($forum)&&$i 100) { + $e = 2; + } elseif (strlen($t) < 3) { + $e = 3; + } else { + $ot = preg_replace('/\r/', '', $t); + $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) { + $e = 4; + } else { + $t = $nt; + $e = 0; + } + } + return $e; + } + + function makeUniqueId() + { + if (!is_array($_SESSION[game::sessName()]['postids'])) + $_SESSION[game::sessName()]['postids'] = array(); + do { + $id = md5(uniqid(rand())); + } while (in_array($id, $_SESSION[game::sessName()]['postids'])); + return $id; + } + + function cleanIds() + { + if (!is_array($_SESSION[game::sessName()]['postids'])) + return; + $a = array(); + $now = time(); + foreach ($_SESSION[game::sessName()]['postids'] as $t => $id) + if ($now - $t < 5) + $a[$t] = $id; + $_SESSION[game::sessName()]['postids'] = $a; + } + + function notDoublePost($id) + { + return !(is_array($_SESSION[game::sessName()]['postids']) && in_array($id, $_SESSION[game::sessName()]['postids'])); + } + + function addPostId($id) + { + if (!is_array($_SESSION[game::sessName()]['postids'])) + return; + $_SESSION[game::sessName()]['postids'][time()] = $id; + } + + function doForumMod(&$forum, $in, &$cats) + { + $sel = $in['msel']; + if (!count($sel)) + return; + if ($in['dt'] != '') + { + if ($forum['ctype'] == 'A') + $ga = 'deleteForumTopic'; + else + $ga = array('main','deleteTopic'); + foreach ($sel as $tid) { + gameAction($ga, $forum['id'], $tid); + $forum['topics'] --; + } + } + else if ($in['st'] != '') + { + if ($forum['ctype'] == 'A') + $ga = 'switchTopicSticky'; + else + $ga = array('main','switchTopicSticky'); + foreach ($sel as $tid) + gameAction($ga, $forum['id'], $tid); + } + else if ($in['mt'] != '') + { + $dest = $in['mdest']; + $f = $this->getForum($forum['ctype'],$dest,$cats); + if (is_null($f) && $forum['ctype'] != 'A') { + $rev = array('V' => 'G', 'G' => 'V'); + $ctype = $rev[$forum['ctype']]; + $f = $this->getForum($ctype,$dest,$cats); + if (is_null($f) || !$f['mod']) { + return; + } + } else if (!$f['mod']) { + return; + } + + if ($forum['ctype'] == 'A') + { + $ga = 'moveForumTopic'; + $pid = $_SESSION[game::sessName()]['player']; + } + else + { + $ga = array('main','moveTopic'); + $pid = $_SESSION['userid']; + } + foreach ($sel as $tid) { + gameAction($ga, $forum['id'], $tid, $f['id'], $pid); + $forum['topics'] --; + } + } + } + + function doDelete($cpid) + { + $cats = gameAction('getCatsAndForums', $_SESSION[game::sessName()]['player']); + list($ctype,$postId) = explode('#',$cpid); + $post = $this->getPost($ctype,$postId); + if (is_null($post)) + { + $this->data = array("sp" => 'postnotfound', "d" => $cats); + return; + } + $forum = $this->getForum($ctype,$post['fid'],$cats); + if (is_null($forum)) + { + $this->data = array("sp" => 'forumnotfound', "d" => $cats); + return; + } + $topic = $this->getTopic($ctype,$post['tid']); + if (is_null($topic)) + { + $this->data = array("sp" => 'topicnotfound', "d" => $cats); + return; + } + + if ( !$forum['mod'] + && ( ($ctype == 'A' && $post['pid'] != $_SESSION[game::sessName()]['player']) + || ($ctype != 'A' && $post['uid'] != $_SESSION['userid']) + || ($post['id'] == $topic['fpid']) + ) + ) + { + $this->data = array("sp" => 'overview', "d" => $cats); + return; + } + + if ($forum['mod'] && $post['id'] == $topic['fpid']) + { + if ($ctype == 'A') + $ga = 'deleteForumTopic'; + else + $ga = array('main','deleteTopic'); + gameAction($ga, $forum['id'], $topic['id']); + $this->pgForum($ctype."#".$forum['id'], '', ''); + } + else + { + if ($ctype == 'A') + $ga = 'deleteSinglePost'; + else + $ga = array('main','deleteSinglePost'); + gameAction($ga, $post['id']); + $this->pgTopic($ctype."#".$topic['id'], '', '', '', ''); + } + } + + function markForum($id, $pg) + { + $cats = gameAction('getCatsAndForums', $_SESSION[game::sessName()]['player']); + + list($ctype,$fid) = explode('#',$id); + $forum = $this->getForum($ctype,$fid,$cats); + if (is_null($forum)) + { + $this->data = array("sp" => 'forumnotfound', "d" => $cats); + return; + } + $forum['ctype'] = $ctype; + + if ($ctype == 'A') + { + $ga = 'markForumAsRead'; + $uid = $_SESSION[game::sessName()]['player']; + } + else + { + $ga = array('main','markForumAsRead'); + $uid = $_SESSION['userid']; + } + gameAction($ga, $fid, $uid); + + $this->pgForum($id, '', $pg); + } + + function markCategory($cid) + { + $cats = gameAction('getCatsAndForums', $_SESSION[game::sessName()]['player']); + if (preg_match('/^V#/', $cid)) + $cid = preg_replace('/^V#/', 'G#', $cid); + if (is_null($cats[$cid])) + { + $this->data = array("sp" => 'catnotfound', "d" => $cats); + return; + } + + $c = $cats[$cid]; + if ($c['type'] == 'A') + { + $ga = 'markForumAsRead'; + $uid = $_SESSION[game::sessName()]['player']; + } + else + { + $ga = array('main','markForumAsRead'); + $uid = $_SESSION['userid']; + } + foreach ($c['forums'] as $f) + gameAction($ga, $f['id'], $uid); + + $this->pgCategory($cid); + } + + function pgOverview() + { + $this->data = array( + "d" => gameAction('getCatsAndForums', $_SESSION[game::sessName()]['player']), + "sp" => 'overview' + ); + } + + function pgCategory($cid) + { + $cats = gameAction('getCatsAndForums', $_SESSION[game::sessName()]['player']); + if (is_null($cats[$cid])) + $this->data = array("sp" => 'catnotfound', "d" => $cats); + else + $this->data = array( + "sp" => "category", + "d" => array( + "all" => $cats, + "id" => $cid + ) + ); + } + + function pgForum($cfid, $tpp, $pg, $in = array()) + { + $cats = gameAction('getCatsAndForums', $_SESSION[game::sessName()]['player']); + + list($ctype,$fid) = explode('#',$cfid); + $forum = $this->getForum($ctype,$fid,$cats); + if (is_null($forum)) + { + $this->data = array("sp" => 'forumnotfound', "d" => $cats); + return; + } + $forum['ctype'] = $ctype; + + if ($forum['mod'] && count($in)) + $this->doForumMod($forum, $in, $cats); + + if (preg_match('/^[1-5]0$/', $tpp)) + prefs::set('main/forums_ntopics', $tpp); + elseif ($prefs['main']['forums_ntopics'] != "") + $tpp = prefs::get('main/forums_ntopics', 20); + else + $tpp = 20; + + $mod = $forum['topics'] % $tpp; + $npg = ($mod ? 1 : 0) + ($forum['topics']-$mod) / $tpp; + + if ($pg != "0" && !preg_match('/^[0-9]+$/', $pg)) + $pg = 0; + else if ($pg * $tpp > $forum['topics']) + $pg = $npg - 1; + + if ($ctype == "A") + { + $get = 'getForumTopics'; + $isRead = 'isTopicRead'; + $uid = $_SESSION[game::sessName()]['player']; + } + else + { + $get = array('main','getTopics'); + $isRead = array('main','isTopicRead'); + $uid = $_SESSION['userid']; + } + $topics = gameAction($get,$forum['id'],$pg*$tpp,$tpp); + for ($i=0;$idata = array( + "sp" => 'forum', + "d" => array( + "cats" => $cats, + "forum" => $forum, + "topics" => $topics, + "details" => array($tpp,$pg,$npg) + ) + ); + } + + function pgTopic($ctid, $mpp, $pg, $fdm, $fmo) + { + $cats = gameAction('getCatsAndForums', $_SESSION[game::sessName()]['player']); + + list($ctype,$tid) = explode('#',$ctid); + $topic = $this->getTopic($ctype, $tid); + if (is_null($topic)) + { + $this->data = array("sp" => 'topicnotfound', "d" => $cats); + return; + } + + $forum = $this->getForum($ctype, $topic['fid'], $cats); + if (is_null($forum)) + { + $this->data = array("sp" => 'forumnotfound', "d" => $cats); + return; + } + $forum['ctype'] = $ctype; + + if (preg_match('/^[1-5]0$/', $mpp)) + prefs::set('main/forums_nitems', $mpp); + elseif ($prefs['main']['forums_nitems'] != "") + $mpp = prefs::get('main/forums_nitems', 20); + else + $mpp = 20; + + $mod = $topic['nitems'] % $mpp; + $npg = ($mod ? 1 : 0) + ($topic['nitems']-$mod) / $mpp; + + if ($pg != "0" && !preg_match('/^[0-9]+$/', $pg)) + $pg = 0; + else if (($pg - 1) * $mpp > $topic['nitems']) + $pg = $npg - 1; + else + $pg --; + + if ($fdm != "") + { + $thr = ($fdm == "1"); + if (prefs::get('main/forums_threaded') != $fdm) + prefs::set('main/forums_threaded', $thr?"1":"0"); + } + else + $thr = (prefs::get('main/forums_threaded') == 1); + if ($fmo != "") + { + $rev = ($fmo == "1"); + if (prefs::get('main/forums_reversed') != $fmo) + prefs::set('main/forums_reversed', $rev?"1":"0"); + } + else + $rev = (prefs::get('main/forums_reversed') == 1); + + if ($ctype == "A") + { + $ga = 'getForumPosts'; + $mr = 'markAsRead'; + $uid = $_SESSION[game::sessName()]['player']; + } + else + { + $ga = array('main', 'getPosts'); + $mr = array('main', 'markAsRead'); + $uid = $_SESSION['userid']; + } + $posts = gameAction($ga, $topic['id'], $thr, $rev, $mpp, $mpp * $pg); + if (gameAction($mr, $topic['id'], $uid)) + { + $forum['unread'] --; + $cid = $ctype."#".$forum[$ctype=='A'?'alliance':'category']; + for ($i=0;$idata = array( + "sp" => 'topic', + "d" => array( + "cats" => $cats, + "forum" => $forum, + "topic" => $topic, + "posts" => $posts, + "details" => array($mpp,$pg,$npg,$thr,$rev) + ) + ); + } + + function pgLatest($cat, $mpp = '', $pg = '') + { + $cats = gameAction('getCatsAndForums', $_SESSION[game::sessName()]['player']); + $aForums = $gForums = array(); + + if ($cat == '') + { + foreach ($cats as $cid => $cd) + { + foreach ($cd['forums'] as $f) + if ($cd['type'] == 'A') + array_push($aForums, $f['id']); + else + $gForums[$f['id']] = $cd['type']; + } + } + else if (is_null($cats[$cat])) + { + $this->data = array("sp" => 'catnotfound', "d" => $cats); + return; + } + else if ($cats[$cat]['type'] == 'A') + foreach ($cats[$cat]['forums'] as $f) + array_push($aForums, $f['id']); + else + foreach ($cats[$cat]['forums'] as $f) + $gForums[$f['id']] = $cats[$cat]['type']; + + if (preg_match('/^[1-5]0$/', $mpp)) + prefs::set('main/forums_nitems', $mpp); + else + $mpp = prefs::get('main/forums_nitems', 20); + + $i = 0; + do { + $list = gameAction('getForumLatest', $aForums, array_keys($gForums), $mpp + 1, (int)$pg * $mpp); + if (!count($list)) + { + $pg --; + $i ++; + } + } while (!count($list) && $pg > -1 && $i < 20); + for ($i=0;$idata = array( + "sp" => 'latest', + "d" => array( + "cats" => $cats, + "cat" => $cat, + "posts" => $list, + "details" => array($mpp,$pg) + ) + ); + } + + function pgNewTopic($cfid,$in) + { + $cats = gameAction('getCatsAndForums', $_SESSION[game::sessName()]['player']); + + list($ctype,$fid) = explode('#',$cfid); + $forum = $this->getForum($ctype,$fid,$cats); + if (is_null($forum)) + { + $this->data = array("sp" => 'forumnotfound', "d" => $cats); + return; + } + + list($action,$smileys,$tags,$subject,$text,$uid,$sticky) = $this->getPostFormData($in); + if ($action == 'cancel') + { + $this->pgForum($cfid, '', ''); + return; + } + else if (!$forum['user_post'] && !$forum['mod']) + { + $this->data = array("sp" => 'forumnopost', "d" => $cats); + return; + } + + $sticky = $sticky && $forum['mod']; + $this->data = array( + 'sp' => 'post', + 'd' => array( + 'cats' => $cats, + 'cmd' => 'n', + 'forum' => $forum, + 'cfid' => $cfid, + 'ref' => $cfid, + 'sub' => '', + 'txt' => '', + 'prev' => '', + 'err' => 0, + 'sst' => $forum['mod'], + 'st' => $sticky + ) + ); + + if ($action == 'none') + { + $this->data['d']['uid'] = $this->makeUniqueId(); + $this->data['d']['sm'] = prefs::get('main/smileys'); + $this->data['d']['fc'] = prefs::get('main/forum_code'); + return; + } + + $this->data['d']['sm'] = $smileys; + $this->data['d']['fc'] = $tags; + $this->data['d']['st'] = $sticky; + $this->data['d']['sub'] = $subject; + $this->data['d']['txt'] = $text; + $this->data['d']['uid'] = $uid; + $this->data['d']['err'] = $err = $this->checkPostData($subject,$text); + + if ($err) + return; + + if ($action == 'preview') + { + $subst = array('main','forumSubstitute'); + $prev = gameAction($subst, $text, $tags, $smileys); + if (prefs::get('main/forums_sig') != "") + $prev .= '
    ' . gameAction( + $subst, prefs::get('main/forums_sig'), prefs::get('main/forum_code'), prefs::get('main/smileys') + ); + $this->data['d']['prev'] = $prev; + $this->data['d']['st'] = $sticky; + return; + } + + if ($this->notDoublePost($uid)) + { + if ($ctype == 'A') + { + $a = $_SESSION[game::sessName()]['player']; + $ga = 'newForumTopic'; + } + else + { + $a = $_SESSION['userid']; + $ga = array('main','newTopic'); + } + gameAction($ga, $a, $fid, $subject, $text, $tags, $smileys, $sticky); + $this->addPostId($uid); + } + + $this->pgForum($cfid, '', ''); + } + + function pgReply($repTo,$in) + { + $cats = gameAction('getCatsAndForums', $_SESSION[game::sessName()]['player']); + list($ctype,$postId) = explode('#',$repTo); + $post = $this->getPost($ctype,$postId); + if (is_null($post)) + { + $this->data = array("sp" => 'postnotfound', "d" => $cats); + return; + } + $forum = $this->getForum($ctype,$post['fid'],$cats); + if (is_null($forum)) + { + $this->data = array("sp" => 'forumnotfound', "d" => $cats); + return; + } + + $list = array($post); + $i = 0; + while ($i<4 && !is_null($post['reply_to'])) + { + $post = $this->getPost($ctype,$post['reply_to']); + if (is_null($post)) + break; + array_push($list, $post); + $i ++; + } + + list($action,$smileys,$tags,$subject,$text,$uid) = $this->getPostFormData($in); + if ($action == 'cancel') + { + $this->pgTopic($ctype."#".$list[0]['tid'], '', 10000, '', ''); + return; + } + + $this->data = array( + 'sp' => 'post', + 'd' => array( + 'cats' => $cats, + 'cmd' => 'R#'.$repTo, + 'forum' => $forum, + 'cfid' => $ctype."#".$forum['id'], + 'ref' => $repTo, + 'posts' => $list, + 'sub' => '', + 'txt' => '', + 'prev' => '', + 'err' => 0 + ) + ); + + if ($action == 'none') + { + $this->data['d']['uid'] = $this->makeUniqueId(); + $this->data['d']['sm'] = prefs::get('main/smileys'); + $this->data['d']['fc'] = prefs::get('main/forum_code'); + $sub = trim($list[0]['title']); + if (!preg_match('/^Re:/', $sub)) + $sub = "Re: $sub"; + $this->data['d']['sub'] = $sub; + if ($in['q'] == 1) + $this->data['d']['txt'] = "[quote=".$list[0]['author']."]".$list[0]['contents']."[/quote]"; + return; + } + + $this->data['d']['sm'] = $smileys; + $this->data['d']['fc'] = $tags; + $this->data['d']['sub'] = $subject; + $this->data['d']['txt'] = $text; + $this->data['d']['uid'] = $uid; + $this->data['d']['err'] = $err = $this->checkPostData($subject,$text); + + if ($err) + return; + + if ($action == 'preview') + { + $subst = array('main','forumSubstitute'); + $prev = gameAction($subst, $text, $tags, $smileys); + if (prefs::get('main/forums_sig') != "") + $prev .= '
    ' . gameAction( + $subst, prefs::get('main/forums_sig'), prefs::get('main/forum_code'), prefs::get('main/smileys') + ); + $this->data['d']['prev'] = $prev; + return; + } + + if ($this->notDoublePost($uid)) + { + if ($ctype == 'A') + { + $a = $_SESSION[game::sessName()]['player']; + $ga = 'postForumReply'; + } + else + { + $a = $_SESSION['userid']; + $ga = array('main','postReply'); + } + gameAction($ga, $a, $list[0], $subject, $text, $tags, $smileys); + $this->addPostId($uid); + } + + $this->pgTopic($ctype."#".$list[0]['tid'], '', 10000, '', ''); + } + + function pgEdit($id,$in) + { + $cats = gameAction('getCatsAndForums', $_SESSION[game::sessName()]['player']); + list($ctype,$postId) = explode('#',$id); + $post = $this->getPost($ctype,$postId); + if (is_null($post)) + { + $this->data = array("sp" => 'postnotfound', "d" => $cats); + return; + } + $forum = $this->getForum($ctype,$post['fid'],$cats); + if (is_null($forum)) + { + $this->data = array("sp" => 'forumnotfound', "d" => $cats); + return; + } + + if ( !$forum['mod'] + && ( ($ctype == 'A' && $post['pid'] != $_SESSION[game::sessName()]['player']) + || ($ctype != 'A' && $post['uid'] != $_SESSION['userid']) + ) + ) + { + $this->data = array("sp" => 'overview', "d" => $cats); + return; + } + + list($action,$smileys,$tags,$subject,$text,$uid) = $this->getPostFormData($in); + if ($action == 'cancel') + { + $this->pgTopic($ctype."#".$post['tid'], '', 10000, '', ''); + return; + } + + $this->data = array( + 'sp' => 'post', + 'd' => array( + 'cats' => $cats, + 'cmd' => 'E#'.$id, + 'forum' => $forum, + 'cfid' => $ctype."#".$forum['id'], + 'ref' => $id, + 'post' => $post, + 'sub' => '', + 'txt' => '', + 'prev' => '', + 'err' => 0 + ) + ); + + if ($action == 'none') + { + $this->data['d']['uid'] = ''; + $this->data['d']['sm'] = ($post['es'] == 't'); + $this->data['d']['fc'] = ($post['ec'] == 't'); + $this->data['d']['sub'] = trim($post['title']); + $this->data['d']['txt'] = $post['contents']; + return; + } + + $this->data['d']['sm'] = $smileys ? 1 : 0; + $this->data['d']['fc'] = $tags ? 1 : 0; + $this->data['d']['sub'] = $subject; + $this->data['d']['txt'] = $text; + $this->data['d']['uid'] = $uid; + $this->data['d']['err'] = $err = $this->checkPostData($subject,$text); + + if ($err) + return; + + if ($action == 'preview') + { + $subst = array('main','forumSubstitute'); + $prev = gameAction($subst, $text, $tags, $smileys); + if (prefs::get('main/forums_sig') != "") + $prev .= '
    ' . gameAction( + $subst, prefs::get('main/forums_sig'), + prefs::get('main/forum_code'), prefs::get('main/smileys') + ); + $this->data['d']['prev'] = $prev; + return; + } + + if ($ctype == 'A') + { + $a = $_SESSION[game::sessName()]['player']; + $ga = 'editForumPost'; + } + else + { + $a = $_SESSION['userid']; + $ga = array('main','postEdit'); + } + gameAction($ga, $a, $post['id'], $subject, $text, $tags, $smileys); + + $this->pgTopic($ctype."#".$post['tid'], '', 10000, '', ''); + } + + function doSearchPosts(&$cats) + { + $gfl = $afl = array(); + if ($_SESSION[game::sessName()]['forumsearch']['fid'] == '') + { + foreach ($cats as $cat) + if ($cat['type'] == 'A') + foreach ($cat['forums'] as $f) array_push($afl, $f['id']); + else + foreach ($cat['forums'] as $f) $gfl[$f['id']] = $cat['type']; + } + else + { + list($ctype,$fid) = explode('#', $_SESSION[game::sessName()]['forumsearch']['fid']); + if ($ctype == 'A') + array_push($afl, $fid); + else + $gfl[$fid] = $ctype; + } + + $txt = '%' . preg_replace('/\*/', '%', addslashes($_SESSION[game::sessName()]['forumsearch']['text'])) . '%'; + $pg = $_SESSION[game::sessName()]['forumsearch']['page']; + $mpp = $_SESSION[game::sessName()]['forumsearch']['perpage']; + $c = $_SESSION[game::sessName()]['forumsearch']['whole']; + $sfs = array('moment','title'); + $stt = $sfs[$_SESSION[game::sessName()]['forumsearch']['stype']]; + $ord = $_SESSION[game::sessName()]['forumsearch']['sord'] ? "DESC" : "ASC"; + $i = 0; + do { + $list = gameAction('forumSearchPosts',$afl,array_keys($gfl),$mpp+1,$pg*$mpp,$txt,$c,$stt,$ord); + if (!count($list)) + { + $pg --; + $i ++; + } + } while (!count($list) && $pg > -1 && $i < 20); + for ($i=0;$idata = array( + "sp" => 'sresposts', + "d" => array( + 'cats' => $cats, + 'sparm' => $_SESSION[game::sessName()]['forumsearch'], + 'posts' => $list + ) + ); + } + + function doSearchTopics(&$cats) + { + $fl = $gfl = $afl = array(); + if ($_SESSION[game::sessName()]['forumsearch']['fid'] == '') + { + foreach ($cats as $cat) + { + if ($cat['type'] == 'A') + foreach ($cat['forums'] as $f) array_push($afl, $f['id']); + else + foreach ($cat['forums'] as $f) $gfl[$f['id']] = $cat['type']; + foreach ($cat['forums'] as $f) + $fl[$cat['type'] . "#" . $f['id']] = array($f['title'],$cat['id'],$cat['title']); + } + } + else + { + list($ctype,$fid) = explode('#', $_SESSION[game::sessName()]['forumsearch']['fid']); + if ($ctype == 'A') + array_push($afl, $fid); + else + $gfl[$fid] = $ctype; + + foreach ($cats as $cat) foreach ($cat['forums'] as $f) + $fl[$cat['type'] . "#" . $f['id']] = array($f['title'],$cat['id'],$cat['title']); + } + + $txt = '%' . preg_replace('/\*/', '%', addslashes($_SESSION[game::sessName()]['forumsearch']['text'])) . '%'; + $pg = $_SESSION[game::sessName()]['forumsearch']['page']; + $mpp = $_SESSION[game::sessName()]['forumsearch']['perpage']; + $c = $_SESSION[game::sessName()]['forumsearch']['whole']; + $sfs = array('moment','title'); + $stt = $sfs[$_SESSION[game::sessName()]['forumsearch']['stype']]; + $ord = $_SESSION[game::sessName()]['forumsearch']['sord'] ? "DESC" : "ASC"; + $i = 0; + do { + $list = gameAction('forumSearchTopics',$afl,array_keys($gfl),$mpp+1,$pg*$mpp,$txt,$c,$stt,$ord); + if (!count($list)) + { + $pg --; + $i ++; + } + } while (!count($list) && $pg > -1 && $i < 20); + $_SESSION[game::sessName()]['forumsearch']['page'] = $pg; + + $pid = $_SESSION[game::sessName()]['player']; + $uid = $_SESSION['userid']; + for ($i=0;$idata = array( + "sp" => 'srestopics', + "d" => array( + 'cats' => $cats, + 'sparm' => $_SESSION[game::sessName()]['forumsearch'], + 'topics' => $list + ) + ); + } + + function pgSearch($in) + { + if (!is_array($_SESSION[game::sessName()]['forumsearch'])) + $_SESSION[game::sessName()]['forumsearch'] = array(); + + $cats = gameAction('getCatsAndForums', $_SESSION[game::sessName()]['player']); + if ((is_null($in['s']) || trim($in['stxt']) == '') && is_null($in['pg'])) + { + $this->data = array( + "sp" => 'search', + "d" => array('cats' => $cats, 'sparm' => $_SESSION[game::sessName()]['forumsearch']) + ); + return; + } + + if (!is_null($in['s'])) + { + $_SESSION[game::sessName()]['forumsearch']['text'] = trim($in['stxt']); + $_SESSION[game::sessName()]['forumsearch']['whole'] = ($in['sin'] == 1); + $sid = $in['sif']; + if ($sid != '') + { + list($ctype,$fid) = explode('#', $sid); + $f = $this->getForum($ctype,$fid,$cats); + if (is_null($f)) + $sid = ''; + } + $_SESSION[game::sessName()]['forumsearch']['fid'] = $sid; + $st = (int)$in['sst']; + if ($st < 0 || $st > 1) + $st = 0; + $_SESSION[game::sessName()]['forumsearch']['stype'] = $st; + $_SESSION[game::sessName()]['forumsearch']['sord'] = ($in['sor'] == 1); + $_SESSION[game::sessName()]['forumsearch']['resd'] = ($in['srp'] == 1); + $_SESSION[game::sessName()]['forumsearch']['page'] = 0; + $_SESSION[game::sessName()]['forumsearch']['perpage'] = prefs::get('main/' . ($in['srp'] == 1 ? 'forums_ntopics' : 'forums_nitems')); + } else { + $_SESSION[game::sessName()]['forumsearch']['page'] = (int)$in['pg']; + if (preg_match('/^[1-5]0$/', $in['mpp'])) + { + $_SESSION[game::sessName()]['forumsearch']['perpage'] = (int)$in['mpp']; + prefs::set("main/" . ($_SESSION[game::sessName()]['forumsearch']['resd'] ? 'forums_ntopics' : 'forums_nitems'), $in['mpp']); + } + } + + if ($_SESSION[game::sessName()]['forumsearch']['resd']) + $this->doSearchTopics($cats); + else + $this->doSearchPosts($cats); + } + + function handle($input) + { + $this->cleanIds(); + $c = $input['cmd']; + switch ($c) + { + case 'o': + $this->pgOverview(); + break; + case 'n': + $this->pgNewTopic(substr($input['f'],2),$input); + break; + case 'l': + $this->pgLatest('', $input['mpp'], $input['pg']); + break; + case 's': + $this->pgSearch($input); + break; + default: + if (substr($c,0,2) == "C#") + $this->pgCategory(substr($c,2)); + else if (substr($c,0,2) == "F#") + $this->pgForum(substr($c,2), $input['tpp'], $input['pg'], $input); + else if (substr($c,0,2) == "T#") + $this->pgTopic(substr($c,2),$input['mpp'],$input['pg'],$input['thr'],$input['ord']); + else if (substr($c,0,2) == "L#") + $this->pgLatest(substr($c,2), $input['mpp'], $input['pg']); + else if (substr($c,0,2) == "R#") + $this->pgReply(substr($c,2), $input); + else if (substr($c,0,2) == "E#") + $this->pgEdit(substr($c,2), $input); + else if (substr($c,0,2) == "D#") + $this->doDelete(substr($c,2), $input); + else if (substr($c,0,3) == "MF#") + $this->markForum(substr($c,3), $input['pg']); + else if (substr($c,0,3) == "MC#") + $this->markCategory(substr($c,3)); + else + $this->pgOverview(); + } + $this->output = "forums"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/manual.inc b/scripts/site/beta5/handlers/manual.inc new file mode 100644 index 0000000..4c5f6d9 --- /dev/null +++ b/scripts/site/beta5/handlers/manual.inc @@ -0,0 +1,85 @@ +subPage = "notfound"; + } + + function getFirstPage() { + $fPage = $this->lib->call('getFirstPage', $this->lang); + if (is_null($fPage)) { + $this->pageNotFound(); + return; + } + + $this->page = $this->lib->call('getPage', $fPage); + if (is_null($this->page)) { + $this->pageNotFound(); + return; + } + + $this->subPage = "page"; + } + + function getPage($name) { + $secId = $this->lib->call('getSectionId', $this->lang, $name); + if (is_null($secId)) { + $this->pageNotFound(); + return; + } + + $pageId = $this->lib->call('getPageId', $secId); + if (is_null($pageId)) { + $this->pageNotFound(); + return; + } + + $this->page = $this->lib->call('getPage', $pageId); + if (is_null($this->page)) { + $this->pageNotFound(); + return; + } + + $this->subPage = "page"; + } + + function getSearchPage($text) { + $this->searchText = $text; + + if (is_array(tracking::$data['man_search']) && tracking::$data['man_search']['text'] != $text) { + tracking::$data['man_search'] = null; + } + if (!is_array(tracking::$data['man_search'])) { + tracking::$data['man_search'] = array( + "text" => $text, + "results" => $this->lib->call('search', $text, $this->lang, $this->version) + ); + } + $this->data = tracking::$data['man_search']; + $this->subPage = "search"; + } + + function handle($input) { + $this->lang = getLanguage(); + $this->lib = $this->game->getLib('main/manual'); + + if ($input['ss'] != '') { + $this->getSearchPage($input['ss']); + } elseif ($input['p'] != '') { + $p = preg_replace('/[^A-Za-z0-9_\-]/', '', $input['p']); + $this->getPage($p); + } else { + $this->getFirstPage(); + } + $this->output = "manual"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/map.inc b/scripts/site/beta5/handlers/map.inc new file mode 100644 index 0000000..6c9734c --- /dev/null +++ b/scripts/site/beta5/handlers/map.inc @@ -0,0 +1,146 @@ + array( + "getMapParams", "updateData", "findName", "getPlayerPlanets" + ), + "method"=> array( + "updateData" => "POST", + ), + "init" => "makeMapTooltips();\nx_getMapParams(mapInit);" + ); + + function getMapParams() + { + $s = $_SESSION[game::sessName()]['map'] . "\n" . $this->findName($_SESSION[game::sessName()]['map_ctr']); + $s2 = $this->getPlayerPlanets(); + if ($s2 != "") + $s .= "\n$s2"; + return $s; + } + + function getPlayerPlanets() + { + $as = array(); + $pl = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + foreach ($pl as $id => $name) + array_push($as, "$id#$name"); + return join("\n", $as); + } + + function findName($n) + { + $n = trim($n); + $a = gameAction('getPlanetByName', $n); + if (is_null($a)) + return "ERR"; + $o = ($a['owner'] == $_SESSION[game::sessName()]['player']) ? 0 : 1; + $i = $o ? $a['name'] : $a['id']; + return $a['x']."#".$a['y']."#$o#$i"; + } + + function updateData($rlist) + { + if ($rlist == "") + return ""; + + $pid = $_SESSION[game::sessName()]['player']; + $pinf = gameAction('getPlayerInfo', $pid); + $tag = $pinf['aid'] ? $pinf['alliance'] : ""; + + $l = explode('#', $rlist); + $rs = array(); + foreach ($l as $rdata) + { + list($xt,$yt,$md5) = explode(',', $rdata); + $x = (int)$xt; $y = (int)$yt; + + $sys = gameAction('getSystemAt', $x, $y); + if (is_null($sys)) { + if ($md5 != "-") { + array_push($rs, "#$x#$y"); + } + continue; + } + + if ($sys['nebula'] > 0) { + $mmd5 = md5(serialize($sys)); + if ($md5 == $mmd5) + continue; + array_push($rs, "{$sys['id']}#$x#$y#$mmd5#{$sys['nebula']}#0"); + $zones = gameAction('getSystemPlanets', $sys['id']); + for ($i = 0; $i < 6; $i++) { + array_push($rs, "{$zones[$i]['id']}#" . ($zones[$i]['status'] - 1) + . "#{$zones[$i]['name']}"); + } + continue; + } + + switch (input::$game->params['victory']) { + case 0: + $sys['prot'] = ($sys['prot'] > 0) ? 1 : 0; + break; + case 1: + $sys['prot'] = 0; + break; + case 2: + $sys['prot'] = input::$game->getLib('beta5/ctf')->call('isTarget', $sys['id']) ? 1 : 0; + break; + } + + $sys['planets'] = gameAction('getSystemPlanets', $sys['id']); + for ($i = 0; $i < 6; $i ++) { + if ($sys['planets'][$i]['owner'] == $pid) + $r = 2; + elseif ($tag != "" && $sys['planets'][$i]['tag'] == $tag) + $r = 1; + else + $r = 0; + $sys['planets'][$i]['relation'] = $r; + } + + $mmd5 = md5(serialize($sys)); + if ($md5 == $mmd5) { + continue; + } + + array_push($rs, "{$sys['id']}#$x#$y#$mmd5#{$sys['nebula']}#{$sys['prot']}"); + for ($i = 0; $i < 6; $i ++) { + $p = $sys['planets'][$i]; + $s = $p['id']."#".$p['status']."#".$p['relation']."#".$p['tag']; + array_push($rs, $s); + array_push($rs, $p['name']); + } + } + + return join("\n", $rs); + } + + function handle($input) + { + $ctrMap = null; + switch ($input['menu']): + case 'p': + $_SESSION[game::sessName()]['map'] = 0; + if ($input['ctr'] != '' && !is_null($mc = gameAction('getPlanetById', (int)$input['ctr']))) + $ctrMap = $mc['name']; + break; + case 'a': $_SESSION[game::sessName()]['map'] = 1; break; + case 'l': $_SESSION[game::sessName()]['map'] = 2; break; + endswitch; + if (is_null($_SESSION[game::sessName()]['map'])) + $_SESSION[game::sessName()]['map'] = 0; + + if (is_null($ctrMap)) + $_SESSION[game::sessName()]['map_ctr'] = gameAction('getFirstPlanet', $_SESSION[game::sessName()]['player']); + else + $_SESSION[game::sessName()]['map_ctr'] = $ctrMap; + + $this->output = "map"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/market.inc b/scripts/site/beta5/handlers/market.inc new file mode 100644 index 0000000..3f3b02e --- /dev/null +++ b/scripts/site/beta5/handlers/market.inc @@ -0,0 +1,463 @@ + array( + 'getPublicOffers', 'moveMap', 'cancelSale', 'placeBid', + 'buyPublic', 'getPrivateOffers', 'buyPrivate', 'declinePrivate', + 'getSentOffers' + ), + 'init' => 'initMarket();' + ); + + //-------------------- + // Public offers + + function findName($n) + { + $n = trim($n); + $a = gameAction('getPlanetByName', $n); + if (is_null($a)) + return null; + $o = ($a['owner'] == $_SESSION[game::sessName()]['player']) ? 0 : 1; + $i = $o ? $a['name'] : $a['id']; + return array($o + 1, $i, $a['x'], $a['y']); + } + + function getPlayerPlanets() + { + $pl = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + $as = array(count($pl)); + foreach ($pl as $id => $name) + array_push($as, "$id#$name"); + return $as; + } + + function getPublicOffers() { + $page = &$this->sessData('page'); $page = 0; + $player = $_SESSION[game::sessName()]['player']; + $pinfo = gameAction('getPlayerInfo', $player); + $result = array(); + + // Get map configuration + $map = &$this->sessData('po_map'); + if (!is_array($map)) { + $pname = gameAction('getFirstPlanet', $player); + list($type,$param,$x,$y) = $this->findName($pname); + $map = array( + 'type' => $type, + 'x' => $x, + 'y' => $y, + 'param' => $param, + 'dist' => 1 + ); + } elseif ($map['type'] == 1) { + $pi = gameAction('getPlanetOwner', $map['param']); + if ($pi['owner'] != $player) { + $pi['type'] = 2; + $pi['param'] = $pi['name']; + } + } + + // Add map data to the output array + array_push($result, $map['type']."#".$map['x']."#".$map['y']."#".$map['dist'].($map['type'] == 0 ? '' : ('#'.utf8entities($map['param'])))); + $result = array_merge($result, $this->getPlayerPlanets()); + + // Get selected system data + $sys = gameAction('getSystemAt', $map['x'], $map['y']); + if (is_null($sys)) { + array_push($result, "-1#0"); + } else { + switch ($this->game->params['victory']) { + case 0: + $sys['prot'] = ($sys['prot'] > 0) ? 1 : 0; + break; + case 1: + $sys['prot'] = 0; + break; + case 2: + $sys['prot'] = input::$game->getLib('beta5/ctf')->call('isTarget', $sys['id']) ? 1 : 0; + break; + } + + $pLev = gameAction('getProtectionLevel', $player); + array_push($result, $sys['nebula']."#".$sys['prot']); + $plist = gameAction('getSystemPlanets', $sys['id']); + foreach ($plist as $pi) { + $s = $pi['id']."#".$pi['status']."#".($pi['owner'] == $player ? '1' : '0') . '#'; + $s .= ($pinfo['aid'] && $pi['owner'] != $player && $pinfo['alliance'] == $pi['tag']) ? '1' : '0'; + array_push($result, $s . "#" . utf8entities($pi['name'])); + } + } + + // Look for sales + $pIds = gameAction('getPlanetsAround', $map['x'], $map['y'], $map['dist']); + if (count($pIds)) + { + $sales = gameAction('getSales', $pIds); + $pSales = $fSales = array(); + for ($i=0;$isessData('po_map'); + + $type = (int)$type; + if ($type < 0 || $type > 2) { + $type = 0; + } + $dist = (int)$dist; + if ($dist < 1 || $dist > 7) { + $dist = 1; + } + $map['dist'] = $dist; + + switch ($type) : + case 0: + list($x,$y) = split('#',$parm); + $map['x'] = (int)$x; + $map['y'] = (int)$y; + $map['type'] = 0; + break; + case 1: + $a = gameAction('getPlanetById', (int)$parm); + if (!is_null($a)) { + $map['x'] = $a['x']; + $map['y'] = $a['y']; + $map['param'] = (int)$parm; + $map['type'] = 1; + } + break; + case 2: + $a = $this->findName($parm); + if (!is_null($a)) { + list($map['type'],$map['param'],$map['x'],$map['y']) = $a; + } + break; + endswitch; + + return $this->getPublicOffers(); + } + + function cancelSale($id) { + $player = $_SESSION[game::sessName()]['player']; + $pLib = $this->game->getLib('beta5/player'); + + if ($pLib->call('isOnVacation', $player)) { + return "ERR#200"; + } elseif ($pLib->call('getProtectionLevel', $player)) { + return "ERR#201"; + } + + gameAction('cancelSale', $_SESSION[game::sessName()]['player'], (int)$id); + return $this->getMarketData(); + } + + function placeBid($id, $bid) { + $player = $_SESSION[game::sessName()]['player']; + $pLib = $this->game->getLib('beta5/player'); + + if ($pLib->call('isOnVacation', $player)) { + return "ERR#200"; + } elseif ($pLib->call('getProtectionLevel', $player)) { + return "ERR#201"; + } + + $r = gameAction('placeBid', $_SESSION[game::sessName()]['player'], (int)$id, (int)$bid); + if ($r != '') + return "ERR#$r"; + return $this->getPublicOffers(); + } + + function buyPublic($offer) { + $player = $_SESSION[game::sessName()]['player']; + $pLib = $this->game->getLib('beta5/player'); + + if ($pLib->call('isOnVacation', $player)) { + return "ERR#200"; + } elseif ($pLib->call('getProtectionLevel', $player)) { + return "ERR#201"; + } + + $r = gameAction('buy', $_SESSION[game::sessName()]['player'], (int)$offer); + if ($r != '') + return "ERR#$r"; + return $this->getPublicOffers(); + } + + + //-------------------- + // Private offers + + function getPrivateOffers() { + $page = &$this->sessData('page'); $page = 1; + $player = $_SESSION[game::sessName()]['player']; + $pinfo = gameAction('getPlayerInfo', $player); + $result = array(); + + // Get offers and history + $offers = gameAction('getDirectSales', $player); + $history = gameAction('getSalesHistoryTo', $player); + $sellers = array(); + array_push($result, count($offers) . "#" . count($history)); + + // Generate offers list + foreach ($offers as $o) + { + $f = $o['fleet']; + $p = $o['planet']; + if (!in_array($o['player'], $sellers)) + array_push($sellers, $o['player']); + + $cs = is_null($p) ? $f : $p; + $rs = array($o['id'], $o['player'], $o['price'], $cs['x'], $cs['y'], $cs['orbit']+1); + array_push($rs, is_null($p) ? '' : $p['id']); + array_push($rs, is_null($f) ? '' : $f['id']); + array_push($rs, $o['started']); array_push($rs, $o['expires']); + array_push($rs, utf8entities(is_null($p) ? $f['pname'] : $p['name'])); + array_push($result, join('#', $rs)); + + if (!is_null($f)) + { + $rs = array($f['sg'], $f['sf'], $f['sc'], $f['sb']); + array_push($result, join('#', $rs)); + } + + if (!is_null($p)) + { + $rs = array($p['pop'], $p['turrets'], $p['fact']); + array_push($result, join('#', $rs)); + } + } + + // Generate history list + foreach ($history as $o) + { + if (!in_array($o['from_player'], $sellers)) + array_push($sellers, $o['from_player']); + $price = is_null($o['sell_price']) ? $o['price'] : $o['sell_price']; + $hf = ($o['is_planet'] == 'f') || ($o['f_gaships'] + $o['f_fighters'] + $o['f_cruisers'] + $o['f_bcruisers'] != 0); + $pinf = gameAction('getPlanetById', $o['p_id']); + $rs = array( + $o['from_player'], $price, $pinf['x'], $pinf['y'], $pinf['orbit']+1, + ($o['is_planet'] == 't' ? 1 : 0), $hf?1:0, + $o['started'], $o['end_mode'], $o['ended'], utf8entities($o['p_name']) + ); + array_push($result, join("#", $rs)); + + if ($hf) + array_push($result, $o['f_gaships']."#".$o['f_fighters']."#".$o['f_cruisers']."#".$o['f_bcruisers']); + if ($o['is_planet'] == 't') + array_push($result, $o['p_pop']."#".$o['p_turrets']."#".$o['p_factories']); + } + + // Player names and status + foreach ($sellers as $sid) + { + $pinf = gameAction('getPlayerInfo', $sid, true); + array_push($result, "$sid#" . $pinf['quit'] . "#" . utf8entities($pinf['name'])); + } + + return join("\n", $result); + } + + function buyPrivate($offer) { + $player = $_SESSION[game::sessName()]['player']; + $pLib = $this->game->getLib('beta5/player'); + + if ($pLib->call('isOnVacation', $player)) { + return "ERR#200"; + } elseif ($pLib->call('getProtectionLevel', $player)) { + return "ERR#201"; + } + + $r = gameAction('buy', $_SESSION[game::sessName()]['player'], (int)$offer); + if ($r != '') + return "ERR#$r"; + return $this->getPrivateOffers(); + } + + function declinePrivate($offer) { + $player = $_SESSION[game::sessName()]['player']; + $pLib = $this->game->getLib('beta5/player'); + + if ($pLib->call('isOnVacation', $player)) { + return "ERR#200"; + } elseif ($pLib->call('getProtectionLevel', $player)) { + return "ERR#201"; + } + + if (!gameAction('isDirectOffer', $_SESSION[game::sessName()]['player'], (int)$offer)) { + return "ERR#0"; + } + gameAction('declinePrivate', (int)$offer); + return $this->getPrivateOffers(); + } + + + //-------------------- + // Sent offers + + function getSentOffers() { + $page = &$this->sessData('page'); $page = 2; + $player = $_SESSION[game::sessName()]['player']; + $pinfo = gameAction('getPlayerInfo', $player); + $result = array(); + + // Get offers and history + $offers = gameAction('getSentOffers', $player); + $history = gameAction('getSalesHistoryFrom', $player); + $buyers = array(); + array_push($result, count($offers) . "#" . count($history)); + + // Generate offers list + foreach ($offers as $o) { + $f = $o['fleet']; + $p = $o['planet']; + if ($o['to_player'] != '' && !in_array($o['to_player'], $buyers)) + array_push($buyers, $o['to_player']); + + $cs = is_null($p) ? $f : $p; + $rs = array($o['id'], $o['mode'], $o['price'], $o['to_player'], $cs['x'], $cs['y'], $cs['orbit'] + 1, is_null($p) ? 0 : 1, is_null($f) ? 0 : 1); + array_push($rs, $o['started']); array_push($rs, $o['expires']); + array_push($rs, utf8entities(is_null($p) ? $f['pname'] : $p['name'])); + array_push($result, join('#', $rs)); + + if (!is_null($f)) { + $rs = array($f['sg'], $f['sf'], $f['sc'], $f['sb']); + array_push($result, join('#', $rs)); + } + + if (!is_null($p)) { + $rs = array($p['pop'], $p['turrets'], $p['fact']); + array_push($result, join('#', $rs)); + } + } + + // Generate history list + foreach ($history as $o) { + if ($o['to_player'] != '' && !in_array($o['to_player'], $buyers)) + array_push($buyers, $o['to_player']); + $price = is_null($o['sell_price']) ? $o['price'] : $o['sell_price']; + $hf = ($o['is_planet'] == 'f') || ($o['f_gaships'] + $o['f_fighters'] + $o['f_cruisers'] + $o['f_bcruisers'] != 0); + $pinf = gameAction('getPlanetById', $o['p_id']); + $rs = array( + $pinf['x'], $pinf['y'], $pinf['orbit'] + 1, $o['mode'], ($o['is_planet'] == 't' ? 1 : 0), + $hf?1:0, $o['to_player'], $price, + $o['started'], $o['end_mode'], $o['ended'], utf8entities($o['p_name']) + ); + array_push($result, join("#", $rs)); + + if ($hf) + array_push($result, $o['f_gaships']."#".$o['f_fighters']."#".$o['f_cruisers']."#".$o['f_bcruisers']); + if ($o['is_planet'] == 't') + array_push($result, $o['p_pop']."#".$o['p_turrets']."#".$o['p_factories']); + } + + // Player names and status + foreach ($buyers as $sid) { + $pinf = gameAction('getPlayerInfo', $sid, true); + array_push($result, "$sid#" . $pinf['quit'] . "#" . utf8entities($pinf['name'])); + } + + return join("\n", $result); + } + + + //-------------------- + // Standard output + + function &sessData($name) + { + if (!is_array($_SESSION[game::sessName()]['market'])) + $_SESSION[game::sessName()]['market'] = array('page' => 0); + if (!isset($_SESSION[game::sessName()]['market'][$name])) + $_SESSION[game::sessName()]['market'][$name] = null; + return $_SESSION[game::sessName()]['market'][$name]; + } + + function getMarketData() + { + switch ($this->sessData('page')) : + case 0: $s = $this->getPublicOffers(); break; + case 1: $s = $this->getPrivateOffers(); break; + case 2: $s = $this->getSentOffers(); break; + default: $s = ""; break; + endswitch; + return $s; + } + + function handle($input) + { + $page = &$this->sessData('page'); + switch ($input['p']) : + case 'p': $page = 0; break; + case 'r': $page = 1; break; + case 's': $page = 2; break; + endswitch; + + $r = gameAction('isPlayerRestrained', $_SESSION[game::sessName()]['player']); + if ($r > 0) + $this->data = $r; + else + $this->data = array( + "page" => $page, + "pdata" => $this->getMarketData() + ); + $this->output = "market"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/message.inc b/scripts/site/beta5/handlers/message.inc new file mode 100644 index 0000000..f8bd6ea --- /dev/null +++ b/scripts/site/beta5/handlers/message.inc @@ -0,0 +1,492 @@ + array( + "sendMessage", "setSortParameters", "setMessagesPerPage", + "getFolderSettings", "getMessageList", "getTargetName", + "getMessageText", "updateFolders", "deleteMessages", + "moveMessages", "renameFolder", "addFolder", "deleteFolders", + "flushFolders", "switchThreaded" + ), + "init" => "makeMessagesTooltips();\ninitMessages();" + ); + + + function getFolderSettings($n, $cid) + { + $nok = array('IN','OUT','INT','CUS'); + if (!in_array($n, $nok)) + return ""; + + $pid = $_SESSION[game::sessName()]['player']; + $fl = gameAction('getCustomFolders', $pid); + if ($n=="CUS" && is_null($fl[$cid])) + return ""; + + $pn = $n; + if ($n == "CUS") + $pn .= "!$cid"; + $rs = ""; + $p = prefs::get("beta5/$pn!nmsg"); + $rs .= ($p == "" ? 20 : $p) . "#"; + $p = prefs::get("beta5/$pn!orderby"); + $rs .= ($p == "" ? "date" : $p) . "#"; + $p = prefs::get("beta5/$pn!direction"); + $rs .= ($p == "" ? 0 : $p) . "#"; + $p = prefs::get("beta5/$pn!threaded"); + $rs .= ($p == "" ? 1 : $p); + + return $rs; + } + + + function getMessageList($n, $cid, $alst) + { + $nok = array('IN','OUT','INT','CUS'); + if (!in_array($n, $nok)) + return ""; + + $pid = $_SESSION[game::sessName()]['player']; + $fl = gameAction('getCustomFolders', $pid); + if ($n=="CUS" && is_null($fl[$cid])) + return ""; + + $list = gameAction('getMessageHeaders', $pid, $n, $cid); + $s = ""; + + $al = array(); + if (trim($alst) != "") + { + $alc = explode('#', $alst); + $cmid = array_keys($list); + foreach ($alc as $mid) + { + if ($mid == "" || (int)$mid != $mid) + continue; + array_push($al, $mid); + if (in_array($mid, $cmid)) + continue; + if ($s != "") + $s .= "\n"; + $s .= "-#$mid"; + } + } + + foreach ($list as $id => $hdr) + { + if (in_array($id, $al)) + continue; + if ($s != "") + $s .= "\n"; + $s .= "+#$id#".$hdr['received']."#".$hdr['status']."#"; + $s .= $hdr['slink']."#".$hdr['rlink'].'#'.$hdr['replink']; + $s .= "#" . $hdr['replyTo'] . "\n"; + $s .= $hdr['from'] . "\n"; + $s .= $hdr['to'] . "\n"; + $s .= $hdr['subject']; + } + + return $s; + } + + + function sendMessage($t, $rec, $sub, $msg, $replyId) + { + $e = false; + $errs = array(0,0,0); + $rec = trim($rec); + $sub = trim($sub); + $msg = trim($msg); + + if ($rec == "") + { + $errs['rec'] = 1; + $e = true; + } + elseif ($t == 0) + { + $rid = gameAction('getPlayer', $rec); + if (is_null($rid)) + { + $errs[0] = 2; + $e = true; + } + else + $sendAction = 'Player'; + } + elseif ($t == 1) + { + $pinf = gameAction('getPlanetByName', $rec); + if (is_null($pinf)) + { + $errs[0] = 2; + $e = true; + } + else + { + $rid = $pinf['id']; + $sendAction = 'Planet'; + } + } + else + { + $rid = gameAction('getAlliance', $rec); + $pinf = gameAction('getPlayerInfo', $_SESSION[game::sessName()]['player']); + if (is_null($rid)) + { + $errs[0] = 2; + $e = true; + } + elseif ($rid == $pinf['aid']) + $sendAction = 'Alliance'; + else + $sendAction = 'Diplomacy'; + } + + if ($sub == "") + { + $errs[1] = 1; + $e = true; + } + elseif (strlen($sub) > 64) + { + $errs[1] = 2; + $e = true; + } + + if ($msg == "") + { + $errs[2] = 1; + $e = true; + } + + if ($replyId == -1) + $replyId = null; + + if (!$e) + gameAction("sendMessage$sendAction", $_SESSION[game::sessName()]['player'], $rid, $sub, $msg, $replyId); + + return $e ? join('#', $errs) : "OK"; + } + + function getFolderParms($i) + { + $cf = -1; + $ftype = null; + switch ($i['f']) + { + case 'I': + $ftype = 'IN'; + break; + case 'O': + $ftype = 'OUT'; + break; + case 'T': + $ftype = 'INT'; + break; + case 'C': + if (is_null($this->cFolders[$i['cf']])) + $ftype = null; + else + { + $ftype = 'CUS'; + $cf = $i['cf']; + } + break; + } + + if (is_null($ftype)) + return "#-1"; + + return "#$ftype#$cf"; + } + + function setSortParameters($n, $cid, $se, $so) + { + $nok = array('IN','OUT','INT','CUS'); + if (!in_array($n, $nok)) + return; + + $pid = $_SESSION[game::sessName()]['player']; + $fl = gameAction('getCustomFolders', $pid); + if ($n=="CUS" && is_null($fl[$cid])) + return; + + $seOk = array('st', 'sub', 'date', 'from', 'to'); + if (!in_array($se, $seOk)) + return; + + $pn = $n; + if ($n == "CUS") + $pn .= "!$cid"; + prefs::set("beta5/$pn!orderby", $se); + prefs::set("beta5/$pn!direction", ($so=="1")?"1":"0"); + } + + function setMessagesPerPage($n, $cid, $mpp) + { + $nok = array('IN','OUT','INT','CUS'); + if (!in_array($n, $nok)) + return; + + $pid = $_SESSION[game::sessName()]['player']; + $fl = gameAction('getCustomFolders', $pid); + if ($n=="CUS" && is_null($fl[$cid])) + return; + + if ($mpp == "" || (int)$mpp != $mpp || $mpp<1 || $mpp>5) + return; + + $pn = $n; + if ($n == "CUS") + $pn .= "!$cid"; + prefs::set("beta5/$pn!nmsg", $mpp * 10); + } + + function switchThreaded($n, $cid) + { + $nok = array('IN','OUT','INT','CUS'); + if (!in_array($n, $nok)) + return; + + $pid = $_SESSION[game::sessName()]['player']; + $fl = gameAction('getCustomFolders', $pid); + if ($n=="CUS" && is_null($fl[$cid])) + return; + + $pn = $n; + if ($n == "CUS") + $pn .= "!$cid"; + $p = prefs::get("beta5/$pn!threaded"); + prefs::set("beta5/$pn!threaded", ($p == "1") ? "0" : "1"); + } + + function getTargetName($type, $id) + { + $type = (int)$type; + $id = (int)$id; + $rv = ""; + switch ($type) : + case 0: + $rv = gameAction('getPlayerName', $id); + break; + case 1: + $pinf = gameAction('getPlanetById', $id); + if (!is_null($pinf)) + $rv = $pinf['name']; + break; + case 2: + $pinf = gameAction('getAllianceInfo', $id); + if (!is_null($pinf)) + $rv = $pinf['tag']; + break; + endswitch; + return $rv; + } + + function getMessageText($mId) + { + $mId = (int)$mId; + $pl = $_SESSION[game::sessName()]['player']; + $msg = gameAction('getCompleteMessage', $mId, $pl); + if (is_null($mId) || $msg['player'] != $pl) + return ""; + $s = $msg['ftype'] . '#' . (is_null($msg['cfid']) ? 0 : $msg['cfid']) . '#' . $msg['id']; + $s .= "\n" . $msg['subject']; + if ($msg['text']) + { + $s .= "\n" . $msg['text']; + gameAction('setMessageRead', $mId); + } + return $s; + } + + function updateFolders() + { + $pl = $_SESSION[game::sessName()]['player']; + $flist = "IN##" . gameAction('getNewMessages', $pl, 'IN'); + $flist .= "#" . gameAction('getAllMessages', $pl, 'IN') . "\n\n"; + $flist .= "INT##" . gameAction('getNewMessages', $pl, 'INT'); + $flist .= "#" . gameAction('getAllMessages', $pl, 'INT') . "\n\n"; + $flist .= "OUT##" . gameAction('getNewMessages', $pl, 'OUT'); + $flist .= "#" . gameAction('getAllMessages', $pl, 'OUT') . "\n"; + $fl = gameAction('getCustomFolders', $pl); + foreach ($fl as $id => $name) + { + $flist .= "\nCUS#$id#" . gameAction('getNewMessages', $pl, 'CUS', $id); + $flist .= "#" . gameAction('getAllMessages', $pl, 'CUS', $id); + $flist .= "\n".utf8entities($name); + } + + return $flist; + } + + function deleteMessages($n, $cid, $dList, $aList) + { + $nok = array('IN','OUT','INT','CUS'); + if (!in_array($n, $nok)) + return; + + $pid = $_SESSION[game::sessName()]['player']; + $fl = gameAction('getCustomFolders', $pid); + if ($n=="CUS" && is_null($fl[$cid])) + return; + + $dl = explode("#", $dList); + foreach ($dl as $mid) + { + if ($mid == "" || (int)$mid != $mid) + continue; + gameAction('deleteMessage', $mid, $pid); + } + return $this->getMessageList($n, $cid, $aList); + } + + function moveMessages($n, $cid, $tn, $tcid, $mList, $aList) + { + $nok = array('IN','OUT','INT','CUS'); + if (!in_array($n, $nok)) + return; + $pid = $_SESSION[game::sessName()]['player']; + $fl = gameAction('getCustomFolders', $pid); + if ($n=="CUS" && is_null($fl[$cid])) + return; + + if ( !in_array($tn, $nok) + || ($tn=="CUS" && is_null($fl[$tcid])) + ) + return $this->getMessageList($n, $cid, $aList); + + $dl = explode("#", $mList); + foreach ($dl as $mid) + { + if ($mid == "" || (int)$mid != $mid) + continue; + gameAction('moveMessage', $mid, $pid, $tn, $tcid); + } + return $this->getMessageList($n, $cid, $aList); + } + + function renameFolder($fid, $name) + { + $pid = $_SESSION[game::sessName()]['player']; + $fl = gameAction('getCustomFolders', $pid); + $name = trim($name); + if (is_null($fl[$fid]) || $name == "" || strlen($name) > 32) + return $this->updateFolders(); + gameAction('renameCustomFolder', $fid, $name); + return $this->updateFolders(); + } + + function addFolder($name) + { + $pid = $_SESSION[game::sessName()]['player']; + $fl = gameAction('getCustomFolders', $pid); + $name = trim($name); + if ($name == "" || strlen($name) > 32 || count($fl) >= 15 || in_array($name, array_values($fl))) + return $this->updateFolders(); + gameAction('createCustomFolder', $pid, $name); + return $this->updateFolders(); + } + + function deleteFolders($lst) + { + $pid = $_SESSION[game::sessName()]['player']; + $fl = gameAction('getCustomFolders', $pid); + $dl = explode('#', $lst); + foreach ($dl as $id) + { + if (is_null($fl[$id])) + continue; + gameAction('deleteCustomFolder', $pid, $id); + } + return $this->updateFolders(); + } + + function flushFolders($lst) + { + $pid = $_SESSION[game::sessName()]['player']; + $fl = gameAction('getCustomFolders', $pid); + $dl = explode('#', $lst); + $nok = array('IN','OUT','INT','CUS'); + foreach ($dl as $idc) + { + list($n, $id) = explode('!', $idc); + if (!in_array($n, $nok) || ($n == "CUS" && is_null($fl[$id]))) + continue; + gameAction('flushFolder', $pid, $n, $id); + } + return $this->updateFolders(); + } + + function handle($input) + { + $pl = $_SESSION[game::sessName()]['player']; + $this->cFolders = gameAction('getCustomFolders', $pl); + + // Generate complete folder list + $flist = "IN##" . gameAction('getNewMessages', $pl, 'IN'); + $flist .= "#" . gameAction('getAllMessages', $pl, 'IN') . "\n\n"; + $flist .= "INT##" . gameAction('getNewMessages', $pl, 'INT'); + $flist .= "#" . gameAction('getAllMessages', $pl, 'INT') . "\n\n"; + $flist .= "OUT##" . gameAction('getNewMessages', $pl, 'OUT'); + $flist .= "#" . gameAction('getAllMessages', $pl, 'OUT') . "\n"; + foreach ($this->cFolders as $id => $name) + { + $flist .= "\nCUS#$id#" . gameAction('getNewMessages', $pl, 'CUS', $id); + $flist .= "#" . gameAction('getAllMessages', $pl, 'CUS', $id); + $flist .= "\n".utf8entities($name); + } + + switch ($input['a']) : + case 'c': + $dInit = "Compose"; + $type = $input['ct']; + $id = $input['id']; + if ($type != '' && $id != '') + $dInit .= "#$type#$id#"; + break; + case 'f': + $dInit = "Browse"; + $dInit .= $this->getFolderParms($input); + break; + case 'mf': + $dInit = "Folders"; + break; + default: + $dInit = "Compose"; + break; + endswitch; + $this->data = array( + "dinit" => $dInit, + "flist" => $flist + ); + $this->output = "message"; + } + + function redirect($input) { + if (is_null($_SESSION['userid'])) { + return "message"; + } + + $this->msgs = $this->game->getLib('beta5/msg'); + $player = $_SESSION[game::sessName()]['player']; + + if ($this->msgs->call('getNew', $player, 'IN')) { + return "message?a=f&f=I"; + } elseif ($this->msgs->call('getNew', $player, 'INT')) { + return "message?a=f&f=T"; + } + l::FIXME("check messages in other folders"); + return "message"; + } +} + + +?> diff --git a/scripts/site/beta5/handlers/money.inc b/scripts/site/beta5/handlers/money.inc new file mode 100644 index 0000000..52c2433 --- /dev/null +++ b/scripts/site/beta5/handlers/money.inc @@ -0,0 +1,131 @@ + array( + "getCash", "getCashDetails", "transferFunds" + ), + "init" => "makeMoneyTooltips();\nx_getCash(displayCash); x_getCashDetails(displayPage);" + ); + + function getCash() { + $pi = gameAction('getPlayerInfo', $_SESSION[game::sessName()]['player']); + return $pi['cash']; + } + + function getCashDetails() { + $pr = gameAction('isPlayerRestrained', $_SESSION[game::sessName()]['player']); + $str = "$pr"; + $pinf = gameAction('getPlayerInfo', $_SESSION[game::sessName()]['player']); + $str .= "#" . (gameAction('isOnVacation', $_SESSION[game::sessName()]['player']) ? 1 : 0); + $income = $upkeep = 0; + + $ppl = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + $str .= "#" . count($ppl); + $strp = ""; + foreach ($ppl as $id => $name) + { + $info = gameAction('getPlanetById', $id); + if ($strp != "") + $strp .= "\n"; + $strp .= "$name\n$id#"; + $m = gameAction('getPlanetIncome', $info['owner'], $info['pop'], $info['happiness'], $info['ifact'], $info['mfact'], $info['turrets'], $info['corruption']); + $income += $m[0]; + $strp .= join('#', $m); + $strp .= '#' . $info['ifact']; + } + + $pfl = gameAction('getPlayerFleets', $_SESSION[game::sessName()]['player']); + $str .= "#" . count($pfl); + $strf = ""; + foreach ($pfl as $id => $name) + { + $info = gameAction('getFleet', $id); + if ($strf != "") + $strf .= "\n"; + $strf .= "$name\n"; + if (is_null($info['move']) && is_null($info['wait'])) + { + $pinf = gameAction('getPlanetById', $info['location']); + $strf .= $pinf['name'] . "\n$id#0#0"; + } + elseif (is_null($info['move'])) + { + $pinf = gameAction('getPlanetById', $info['wait']['drop_point']); + $strf .= $pinf['name'] . "\n$id#0#" . $info['wait']['time_left']; + } + else + { + $pinf = gameAction('getPlanetById', $info['move']['m_to']); + $strf .= $pinf['name'] . "\n$id#" . $info['move']['time_to_arrival'] . "#"; + $strf .= (is_null($info['wait']) ? 0 : $info['wait']['time_left']); + } + $u = gameAction('getFleetUpkeep', $_SESSION[game::sessName()]['player'], + $info['gaships'], $info['fighters'], $info['cruisers'], $info['bcruisers']); + $upkeep += $u; + $strf .= "#$u"; + } + + $profit = $income - $upkeep; + $str .= "#$income#$upkeep#$profit\n$strp\n$strf"; + return $str; + } + + public function transferFunds($target, $amount) { + if ((int)$amount != $amount || $amount <= 0) { + return 1; + } + + if ($target == "") { + return 2; + } + + $sid = $_SESSION[game::sessName()]['player']; + $pLib = $this->game->getLib('beta5/player'); + $tid = $pLib->call('getPlayerId', $target); + if (is_null($tid)) { + return 3; + } + if ($sid == $tid) { + return 4; + } + + if ($pLib->call('isRestrained', $tid)) { + return 5; + } + if ($pLib->call('isRestrained', $sid)) { + return 6; + } + + $sourcePLevel = $pLib->call('getProtectionLevel', $sid); + if ($sourcePLevel) { + return 7; + } + $targetPLevel = $pLib->call('getProtectionLevel', $tid); + if ($targetPLevel) { + return 8; + } + + $p = gameAction('getPlayerInfo', $sid); + if ($p['cash'] < $amount) { + return 9; + } + + $vac = $this->game->getLib('beta5/player'); + if ($vac->call('isOnVacation', $sid) || $vac->call('isOnVacation', $tid)) { + return 10; + } + + gameAction('transferFunds', $sid, $tid, $amount); + return 0; + } + + function handle($input) + { + $this->output = "money"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/norealloc.inc b/scripts/site/beta5/handlers/norealloc.inc new file mode 100644 index 0000000..01be34d --- /dev/null +++ b/scripts/site/beta5/handlers/norealloc.inc @@ -0,0 +1,13 @@ +output = "norealloc"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/nplanet.inc b/scripts/site/beta5/handlers/nplanet.inc new file mode 100644 index 0000000..6fac8e3 --- /dev/null +++ b/scripts/site/beta5/handlers/nplanet.inc @@ -0,0 +1,59 @@ + 15) + return 1; + if (preg_match('/[^A-Za-z0-9_\.\-\+@\/'."'".' ]/', $n)) + return 2; + if ( preg_match('/^\s/', $n) + || preg_match('/\s$/', $n) + ) + return 3; + if (preg_match('/\s\s+/', $n)) + return 4; + if (strlen($n) < 2) + return 5; + return 0; + } + + function handle($input) { + if ($this->game->params['norealloc'] == 1) { + $this->output = 'norealloc'; + return; + } + + $this->output = 'nplanet'; + $pl = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + if (count($pl)) + { + $this->data = false; + return; + } + + if ($input['name'] == '') + { + $this->data = array('ok' => false, 'err' => 0, 'name' => ''); + return; + } + $pErr = $this->checkName($input['name']); + if (!$pErr) + { + $p = gameAction('getPlanetByName', $input['name']); + if (!is_null($p)) + $pErr = 6; + } + if ($pErr) + { + $this->data = array('ok' => false, 'err' => $pErr, 'name' => $input['name']); + return; + } + + $nplanet = gameAction('reassignPlanet', $_SESSION[game::sessName()]['player'], $input['name']); + $this->data = array('ok' => true, 'name' => $input['name'], 'id' => $nplanet); + } +} diff --git a/scripts/site/beta5/handlers/overview.inc b/scripts/site/beta5/handlers/overview.inc new file mode 100644 index 0000000..1c13894 --- /dev/null +++ b/scripts/site/beta5/handlers/overview.inc @@ -0,0 +1,87 @@ + array('getOverview', 'switchOvMode', 'breakProtection'), + 'init' => "makeOverviewTooltips();\ninitPage();" + ); + + public function getOverview() { + $overview = $this->game->action('getOverview', $_SESSION[game::sessName()]['player'], + getLanguage()); + $result = array($_SESSION[game::sessName()]['ov_complete'] ? "1" : "0"); + + // Protection level + array_push($result, $overview['protection']); + + // Communications overview + $data = $overview['comms']; + array_push($result, count($data['folders']['CUS']) . "#" . count($data['forums']['general']) + . "#" . count($data['forums']['alliance']) . "#" . $data['forums']['allianceID']); + + // 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)); + } + + // Empire overview + $data = $overview['empire']; + array_push($result, join('#', $data['planetStats'])); + array_push($result, "{$data['fleetStats']['power']}#{$data['fleetStats']['fleets']}" + . "#{$data['fleetStats']['battle']}"); + array_push($result, "{$data['income']}#{$data['fleetStats']['upkeep']}"); + array_push($result, $data['techStats']['new']); + + // Universe overview + $data = $overview['universe']; + array_push($result, $data['summary'][0] . "#" . $data['summary'][2] . "#" . $data['summary'][0]); + array_push($result, join("#", $data['rankings'])); + array_push($result, time()); + foreach ($data['ticks'] as $tick) { + array_push($result, join('#', $tick)); + } + + return join("\n", $result); + } + + public function breakProtection() { + $pLib = $this->game->getLib('beta5/player'); + $pLib->call('breakProtection', $_SESSION[game::sessName()]['player'], 'BRK'); + return $this->getOverview(); + } + + public function switchOvMode() { + $_SESSION[game::sessName()]['ov_complete'] = ! $_SESSION[game::sessName()]['ov_complete']; + } + + public function handle($input) { + $this->data = $this->getOverview();; + $this->output = "overview"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/planet.inc b/scripts/site/beta5/handlers/planet.inc new file mode 100644 index 0000000..fa74281 --- /dev/null +++ b/scripts/site/beta5/handlers/planet.inc @@ -0,0 +1,670 @@ + array( + "getPlanetList", "getPlanetData", "addToQueue", + "factoryAction", "replaceItems", "flushQueue", + "moveItems", "deleteItems", "rename", + "planetSale", "cancelSale", "abandon", + "cancelAbandon", "blowItUp", "cancelDestruction", + "getSellableFleets", "destroyTurrets" + ), + "init" => "initPlanetPage();" + ); + + function getPlanetList() { + $l = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + $str = count($l); + foreach ($l as $id => $name) + $str .= "\n$id#$name"; + + return $str; + } + + function getPlanetData($id) { + // Get planet data + $id = (int)$id; + $info = gameAction('getPlanetById', $id); + if (is_null($info)) { + return ""; + } + $pid = $_SESSION[game::sessName()]['player']; + + // Build result array + $result = array( + "{$info['status']}#$id#{$info['x']}#{$info['y']}#" . ($info['orbit']+1) + . "#" . utf8entities($info['name']) + ); + + // Get probe report + $probeData = null; // FIXME + + // Build fleets summary + $ownFleets = array_keys(gameAction('getFleetsAt', $id, $pid)); + $allFleets = array_keys(gameAction('getFleetsAt', $id)); + $ownSum = 0; + if (count($ownFleets) || $info['owner'] == $pid) + { + $attSum = 0; + $defSum = 0; + $ownMode = ($info['owner'] == $pid ? 1 : null); + foreach ($allFleets as $fid) + { + $finf = gameAction('getFleet', $fid); + if ($finf['owner'] == $pid) + { + if (is_null($ownMode)) + $ownMode = ($finf['attacking'] == 't') ? 2 : 1; + $ownSum += gameAction('getFleetPower', $finf['owner'], 0, $finf['gaships'], $finf['fighters'], $finf['cruisers'], $finf['bcruisers']); + } + elseif ($finf['attacking'] == 't') + $attSum += gameAction('getFleetPower', $finf['owner'], 0, $finf['gaships'], $finf['fighters'], $finf['cruisers'], $finf['bcruisers']); + else + $defSum += gameAction('getFleetPower', $finf['owner'], 0, $finf['gaships'], $finf['fighters'], $finf['cruisers'], $finf['bcruisers']); + } + if (is_null($ownMode)) + $ownMode = 0; + array_push($result, "$ownMode#$ownSum#$attSum#$defSum"); + } + elseif (!is_null($probeData)) + { + // FIXME: get fleets data from probe report + } + else + array_push($result, "0###"); + + // If the planet has been destroyed or is a nebula, we're done + if ($info['status'] != 0) + return join("\n", $result); + + // Get owner and player data + $isOwn = ($info['owner'] == $pid); + $owner = is_null($info['owner']) ? null : gameAction('getPlayerInfo', $info['owner']); + $player = $isOwn ? $owner : gameAction('getPlayerInfo', $pid); + $protected = is_null($owner) ? false : ( + $this->game->getLib('beta5/player')->call('getProtectionLevel', $info['owner']) > 0 + ); + $plPriv = gameAction('getAlliancePrivileges', $pid); + $vacation = ($isOwn && $info['vacation'] != 'NO ') + || (!$isOwn && $info['vacation'] == 'YES '); + + // Build planet summary: + // - isOwner + $s = ($isOwn ? 1 : 0) . "#" . ($vacation ? 1 : 0) . "#" . ($protected ? 1 : 0) . "#"; + + // - turrets + if ($isOwn || $ownSum || ($player['aid'] == $owner['aid'] && $plPriv['list_access'] == 3)) + $s .= $info['turrets']; + elseif (!is_null($probeData)) + $s .= ""; //FIXME: use probe report + $s .= "#"; + // - population + if ($isOwn || $ownSum) + $s .= $info['pop']; + elseif (!is_null($probeData)) + $s .= ""; //FIXME: use probe report + else + $s .= ""; + $s .= "#"; + // - factories + if (!$isOwn && $player['aid'] == $owner['aid'] && $plPriv['list_access'] == 3) + $s .= ((int)$info['ifact'] + (int)$info['mfact']); + elseif (!is_null($probeData)) // FIXME: don't include it if the probe got the exact amounts + $s .= ""; // FIXME: use probe report + $s .= "#"; + if ($isOwn) + $s .= $info['ifact'] . "#" . $info['mfact']; + elseif (!is_null($probeData)) // FIXME: only include it if the probe got the exact amounts + $s .= "#"; // FIXME: use probe report + else + $s .= "#"; + $s .= "#"; + // - happiness + if ($isOwn) + $s .= $info['happiness']; + elseif (!is_null($probeData)) + $s .= ""; // FIXME: use probe report + $s .= "#"; + // - corruption + if ($isOwn) + $s .= round($info['corruption'] / 320); + elseif (!is_null($probeData)) + $s .= ""; // FIXME: use probe report + $s .= "#"; + // - profit + if ($isOwn) { + $m = gameAction('getPlanetIncome', $info['owner'], $info['pop'], $info['happiness'], $info['ifact'], $info['mfact'], $info['turrets'], $info['corruption']); + $s .= $m[0]; + } + $s .= "#"; + // - alliance tag + $s .= $owner['alliance']; + array_push($result, $s); + + // We're done if we're not the owner + if (!$isOwn) + return join("\n", $result); + $rules = gameAction('loadPlayerRules', $pid); + + // Current action + if (!is_null($info['bh_prep'])) + $s = "2#".($info['bh_prep'] + 1); + elseif (!is_null($info['abandon'])) + $s = "3#".($info['abandon']); + else + { + $offer = gameAction('getPlanetSale', $id); + if (is_null($offer)) + { + $restr = gameAction('isPlayerRestrained', $pid); + $np = gameAction('getRealPlanetCount', $pid); + $canSell = ($restr == 0) && ($np > 1); + $q = dbQuery("SELECT bh_unhappiness FROM player WHERE id=$pid"); + list($bhu) = dbFetchArray($q); + $q = dbQuery("SELECT COUNT(*) FROM planet WHERE owner=$pid AND bh_prep IS NOT NULL"); + list($bhp) = dbFetchArray($q); + $canBlow = ($np > 1) && ($rules['planet_destruction'] > 0) && ($bhu < 20) && ($bhp < 4); + if ($canBlow && $this->game->params['victory'] == 2) { + // Prevent planets from being destroyed in CTF games + $ctfLib = $this->game->getLib('beta5/ctf'); + $canBlow = !($ctfLib->call('isTarget', $info['system'])); + } + + $s = "0#" . $info['rename'] . "#" . ($canSell ? 1 : 0) . "#" . ($canBlow ? 1 : 0) . "#" . ($np > 1 ? 1 : 0); + } + else + { + $s = "1#" . (is_null($offer['finalized']) ? 0 : ($info['sale'] + 1)); + $s .= "#" . $offer['sold_to'] . "#"; + if (!is_null($offer['sold_to'])) + $s .= utf8entities(gameAction('getPlayerName', $offer['sold_to'])); + } + } + array_push($result, $s); + + // Capabilities and prices + $s = $rules['military_level'] . "#" . $rules['if_cost'] . "#" . $rules['mf_cost'] . "#" . $rules['bh_cost'] . "#"; + $s .= $rules['build_cost_turret'] . "#" . $rules['build_cost_gaship'] . "#" . $rules['build_cost_fighter']; + $s .= "#" . $rules['build_cost_cruiser'] . "#" . $rules['build_cost_bcruiser']; + array_push($result, $s); + + // Build queue + $bq = gameAction('getBuildQueue', $id); + foreach ($bq as $bqi) + array_push($result, $bqi["type"] . "#" . $bqi['quantity'] . "#" . $bqi['time'] . "#" . $bqi['ctime']); + + return join("\n", $result); + } + + function factoryAction($pid, $act, $nb, $type) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + if ((int)$nb != $nb || $nb <= 0) { + return "ERR#3"; + } + + $t = ($type == '1') ? "m" : "i"; + + $plp = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + $ids = array_keys($plp); + if (!in_array($pid, $ids)) { + return "ERR#-1"; + } + + if ($act != "1") { + $planets = $this->game->getLib('beta5/planet'); + $err = $planets->call('checkBuildFactories', $pid, $nb, $t); + if ($err) { + return "ERR#" . ($err + 23); + } + } else { + $err = gameAction('checkDestroyFactories', $pid, $nb, $t); + if ($err) { + return "ERR#" . ($err + 7); + } + } + + gameAction(($act == "1") ? "destroyFactories" : "buildFactories", $pid, $nb, $t); + return $this->getPlanetData($pid); + } + + function addToQueue($pid, $nb, $type) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + if ((int)$nb != $nb || $nb <= 0) { + return "ERR#2"; + } + + $r = gameAction('loadPlayerRules', $_SESSION[game::sessName()]['player']); + $type = (int)$type; + if ($type < 0 || $type > $r['military_level'] + 2) + return "ERR#1"; + + $plp = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + $ids = array_keys($plp); + if (!in_array($pid, $ids)) + return "ERR#-1"; + + $types = array('turret', 'gaship', 'fighter', 'cruiser', 'bcruiser'); + $cost = $r['build_cost_'.$types[$type]] * $nb; + $pinf = gameAction('getPlayerInfo', $_SESSION[game::sessName()]['player']); + if ($pinf['cash'] < $cost) + return "ERR#0"; + gameAction('addToBuildQueue', $pid, $nb, $type); + return $this->getPlanetData($pid); + } + + function replaceItems($pid, $items, $nb, $type) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + if ((int)$nb != $nb || $nb <= 0) + return "ERR#2"; + $r = gameAction('loadPlayerRules', $_SESSION[game::sessName()]['player']); + if ($type < 0 || $type > $r['military_level'] + 2) + return "ERR#1"; + + $plp = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + $ids = array_keys($plp); + if (!in_array($pid, $ids)) + return "ERR#-1"; + + $lst = explode('#', $items); + if (!count($lst)) + return "ERR#5"; + foreach ($lst as $it) + if (!preg_match('/^[0-9]+$/', $it)) + return "ERR#-2"; + + $types = array('turret', 'gaship', 'fighter', 'cruiser', 'bcruiser'); + $icost = $r['build_cost_'.$types[$type]] * $nb; + $sum = gameAction('getReplacementCost', $pid, $lst, $icost); + $pinf = gameAction('getPlayerInfo', $_SESSION[game::sessName()]['player']); + if ($pinf['cash'] < $sum) + return "ERR#0"; + gameAction('replaceItems', $pid, $lst, $nb, $type); + return $this->getPlanetData($pid); + } + + function moveItems($pid, $items, $dir) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $plp = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + $ids = array_keys($plp); + if (!in_array($pid, $ids)) + return "ERR#-1"; + + $lst = explode('#', $items); + if (!count($lst)) + return 5; + foreach ($lst as $li) + if (!preg_match('/^[0-9]+$/', $li)) + return "ERR#-2"; + + sort($lst); + if ($dir != '1') + { + $bql = gameAction('getQueueLength', $pid); + if ($lst[0] >= $bql - 1) + return "ERR#8"; + $lst = array_reverse($lst); + } + elseif ($lst[0] == 0) + return "ERR#7"; + + $ga = ($dir == '1') ? "Up" : "Down"; + foreach ($lst as $it) + gameAction('moveItem' . $ga, $pid, $it); + + return $this->getPlanetData($pid); + } + + function deleteItems($pid, $sel) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $plp = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + $ids = array_keys($plp); + if (!in_array($pid, $ids)) + return "ERR#-1"; + + $items = explode('#', $sel); + if (!count($items)) + return "ERR#5"; + foreach ($items as $i) + { + if (!preg_match('/^[0-9]+$/', $i)) + continue; + gameAction('deleteBuildQueueItem', $pid, $i); + } + gameAction('reorderBuildQueue', $pid); + + return $this->getPlanetData($pid); + } + + function flushQueue($pid) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $plp = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + $ids = array_keys($plp); + if (!in_array($pid, $ids)) + return "ERR#-1"; + gameAction('flushBuildQueue', $pid); + return $this->getPlanetData($pid); + } + + function rename($pid, $name) { + $player = $_SESSION[game::sessName()]['player']; + $playerLib = $this->game->getLib('beta5/player'); + + if ($playerLib->call('isOnVacation', $player)) { + return "ERR#200"; + } + + $plp = $playerLib->call('getPlanets', $player); + $ids = array_keys($plp); + if (!in_array($pid, $ids)) { + return "ERR#-1"; + } + + $n = trim($name); + $err = $this->game->getLib()->call('checkPlanetName', $n); + if ($err) { + if ($err == 6) { + return "ERR#38"; + } + return "ERR#". ($err + 12); + } + + $planetLib = $this->game->getLib('beta5/planet'); + $salesLib = $this->game->getLib('beta5/sale'); + + $info = $planetLib->call('byId', $pid); + $offer = $salesLib->call('getPlanetSale', $pid); + if (is_null($info['abandon']) && is_null($info['bh_prep']) && $info['rename'] && is_null($offer)) { + $planetLib->call('rename', $pid, $n); + } + return $this->getPlanetData($pid); + } + + function planetSale($pid, $mode, $player, $price, $expires, $fleets) { + $cPlayer = $_SESSION[game::sessName()]['player']; + if (gameAction('isOnVacation', $cPlayer)) { + return "ERR#200"; + } elseif (gameAction('getProtectionLevel', $cPlayer)) { + return "ERR#201"; + } + + $ids = array_keys(gameAction('getPlayerPlanets', $cPlayer)); + if (!in_array($pid, $ids)) + return "ERR#-1"; + $info = gameAction('getPlanetById', $pid); + $offer = gameAction('getPlanetSale', $pid); + if (!(is_null($info['abandon']) && is_null($info['bh_prep']) && is_null($offer))) + return "ERR#-1"; + $restr = gameAction('isPlayerRestrained', $cPlayer); + $np = gameAction('getRealPlanetCount', $cPlayer); + if ($restr > 0 || $np <= 1) + return "ERR#-1"; + + $mode = (int)$mode; + if ($mode < 0 || $mode > 3) + return "ERR#-1"; + + if ($mode < 2) + { + $player = trim($player); + if ($player == '') + return "ERR#1"; + $tPid = gameAction('getPlayer', $player); + if (is_null($tPid)) + return "ERR#4"; + elseif ($tPid == $cPlayer) { + return "ERR#6"; + } elseif (gameAction('getProtectionLevel', $tPid)) { + return "ERR#5"; + } + } + else + $tPid = null; + + if ($mode != 0) + { + $price = (int)$price; + if ($price <= 0) + return "ERR#2"; + } + else + $price = 0; + + $expOk = array(0, 6, 12, 24, 48, 72, 96, 120); + $expires = (int)$expires; + if (!in_array($expires,$expOk)) + return "ERR#-1"; + elseif ($mode == 3 && $expires == 0) + return "ERR#3"; + + // Check fleets + if ($fleets != "") + { + $sFleets = split('#', $fleets); + $flist = array_keys(gameAction('getFleetsAt', $pid, $cPlayer)); + foreach ($sFleets as $fid) + { + if (!in_array($fid, $flist)) + return "ERR#7"; + $f = gameAction('getFleet', $fid); + if ($f['can_move'] != 'Y' || !is_null($fleet['sale_info']) || !is_null($fleet['sale'])) + return "ERR#8"; + } + } + else + $sFleets = array(); + + // Merge fleets + if (count($sFleets) > 1) + { + $tmp = gameAction('mergeFleets', $sFleets, array($cPlayer), ''); + $fleet = $tmp[0]; + } + elseif (count($sFleets)) + $fleet = $sFleets[0]; + else + $fleet = null; + + gameAction('newSale', $cPlayer, ($mode>1), ($mode == 3), $expires, $price, $tPid, $pid, $fleet); + + return $this->getPlanetData($pid); + } + + function abandon($pid) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $cPlayer = $_SESSION[game::sessName()]['player']; + $ids = array_keys(gameAction('getPlayerPlanets', $cPlayer)); + if (!in_array($pid, $ids)) + return "ERR#-1"; + $info = gameAction('getPlanetById', $pid); + $offer = gameAction('getPlanetSale', $pid); + if (!(is_null($info['abandon']) && is_null($info['bh_prep']) && is_null($offer))) + return "ERR#-1"; + $np = gameAction('getRealPlanetCount', $cPlayer); + if ($np <= 1) + return "ERR#-1"; + + gameAction('setPlanetAbandon', $pid); + return $this->getPlanetData($pid); + } + + function cancelAbandon($pid) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $cPlayer = $_SESSION[game::sessName()]['player']; + $ids = array_keys(gameAction('getPlayerPlanets', $cPlayer)); + if (!in_array($pid, $ids)) + return "ERR#-1"; + $info = gameAction('getPlanetById', $pid); + if (is_null($info['abandon'])) + return "ERR#-1"; + + gameAction('setPlanetAbandon', $pid, false); + return $this->getPlanetData($pid); + } + + function cancelSale($pid) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $cPlayer = $_SESSION[game::sessName()]['player']; + $ids = array_keys(gameAction('getPlayerPlanets', $cPlayer)); + if (!in_array($pid, $ids)) + return "ERR#-1"; + $offer = gameAction('getPlanetSale', $pid); + if (is_null($offer)) + return "ERR#-1"; + + if (is_null($offer['finalized'])) + gameAction('cancelSale', $cPlayer, $offer['id']); + elseif (!gameAction('cancelTransfer', $cPlayer, $offer['id'])) + return "ERR#18"; + return $this->getPlanetData($pid); + } + + function blowItUp($pid) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $cPlayer = $_SESSION[game::sessName()]['player']; + $ids = array_keys(gameAction('getPlayerPlanets', $cPlayer)); + if (!in_array($pid, $ids)) { + return "ERR#-1"; + } + + $info = gameAction('getPlanetById', $pid); + $offer = gameAction('getPlanetSale', $pid); + if (!(is_null($info['abandon']) && is_null($info['bh_prep']) && is_null($offer))) { + return "ERR#-1"; + } + + $rules = gameAction('loadPlayerRules', $cPlayer); + $np = gameAction('getRealPlanetCount', $cPlayer); + + $q = dbQuery("SELECT bh_unhappiness FROM player WHERE id=$cPlayer"); + list($bhu) = dbFetchArray($q); + $q = dbQuery("SELECT COUNT(*) FROM planet WHERE owner=$pid AND bh_prep IS NOT NULL"); + list($bhp) = dbFetchArray($q); + + $canBlow = ($np > 1) && ($rules['planet_destruction'] > 0) && ($bhu < 20) && ($bhp < 4); + if ($canBlow && $this->game->params['victory'] == 2) { + // Prevent planets from being destroyed in CTF games + $ctfLib = $this->game->getLib('beta5/ctf'); + $canBlow = !($ctfLib->call('isTarget', $info['system'])); + } + + if (! $canBlow) { + return "ERR#-1"; + } + + gameAction('setPlanetBoom', $pid); + return $this->getPlanetData($pid); + } + + function cancelDestruction($pid) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $cPlayer = $_SESSION[game::sessName()]['player']; + $ids = array_keys(gameAction('getPlayerPlanets', $cPlayer)); + if (!in_array($pid, $ids)) + return "ERR#-1"; + $info = gameAction('getPlanetById', $pid); + if (is_null($info['bh_prep'])) + return "ERR#-1"; + + gameAction('setPlanetBoom', $pid, false); + return $this->getPlanetData($pid); + } + + function getSellableFleets($pid) + { + $cPlayer = $_SESSION[game::sessName()]['player']; + $ids = array_keys(gameAction('getPlayerPlanets', $cPlayer)); + if (!in_array($pid, $ids)) + return "ERR#-1"; + $fleets = array_keys(gameAction('getFleetsAt', $pid, $cPlayer)); + $rv = array(); + foreach ($fleets as $fid) + { + $f = gameAction('getFleet', $fid); + if ($f['can_move'] != 'Y' || !is_null($fleet['sale_info']) || !is_null($fleet['sale'])) + continue; + $s = "$fid#" . $f['gaships'] . "#" . $f['fighters'] . "#" . $f['cruisers'] . "#" . $f['bcruisers'] . "#"; + $s .= gameAction('getFleetPower', $cPlayer, 0, $f['gaships'], $f['fighters'], $f['cruisers'], $f['bcruisers']) . "#"; + $s .= utf8entities($f['name']); + array_push($rv, $s); + } + return join("\n", $rv); + } + + function destroyTurrets($pid, $turrets) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $cPlayer = $_SESSION[game::sessName()]['player']; + $ids = array_keys(gameAction('getPlayerPlanets', $cPlayer)); + if (!in_array($pid, $ids)) + return "ERR#-1"; + $turrets = (int)$turrets; + if ($turrets <= 0) + return "ERR#19"; + $err = gameAction('destroyTurrets', $pid, $turrets); + if ($err > 0) + return "ERR#" . ($err + 19); + return $this->getPlanetData($pid); + } + + function handle($input) + { + if (!preg_match('/^[0-9]+$/', $input['id'])) { + logText("****** BUG? Planet not found: '{$input['id']}' (from {$_SERVER['HTTP_REFERER']})", LOG_DEBUG); + $this->output = "planetnf"; + return; + } + $info = gameAction('getPlanetById', $input['id']); + if (is_null($info)) + { + $this->output = "planetnf"; + return; + } + + $this->data = array( + 'id' => $input['id'], + 'plist' => $this->getPlanetList(), + 'pdata' => $this->getPlanetData($input['id']) + ); + $this->output = "planet"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/planetnf.inc b/scripts/site/beta5/handlers/planetnf.inc new file mode 100644 index 0000000..1bcd102 --- /dev/null +++ b/scripts/site/beta5/handlers/planetnf.inc @@ -0,0 +1,13 @@ +output = "planetnf"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/planets.inc b/scripts/site/beta5/handlers/planets.inc new file mode 100644 index 0000000..a12705d --- /dev/null +++ b/scripts/site/beta5/handlers/planets.inc @@ -0,0 +1,311 @@ + array( + "getInitVals", "getPlanetList", "setMode", + "setCumulative", "getMilitaryLevel", + "flushQueues", "addToQueues", "factoryAction", + "deleteItems", "moveItems", "replaceItems" + ), + "init" => "makePlanetsTooltips();\nx_getInitVals(initGetMode);" + ); + + function getPlanetList() { + $str = ""; + $l = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + foreach ($l as $id => $name) { + $info = gameAction('getPlanetById', $id); + if ($str != "") + $str .= "\n"; + $str .= "$id#" . $info["x"] . "#" . $info["y"] . "#" . ($info['orbit'] + 1); + $str .= "#" . $info["pop"] . '#' . $info['happiness']; + $str .= "#" . $info["ifact"] . '#' . $info['turrets']; + $str .= "#" . $info["mfact"] . '#'; + $m = gameAction('getPlanetIncome', $info['owner'], $info['pop'], $info['happiness'], $info['ifact'], $info['mfact'], $info['turrets'], $info['corruption']); + $str .= $m[0] . "#" . round($info['corruption'] / 320); + $str .= "\n$name\n"; + + $bq = gameAction('getBuildQueue', $id); + $bqs = ''; + foreach ($bq as $bqi) + { + if ($bqs != "") + $bqs .= "#"; + $bqs .= $bqi["type"] . "#" . $bqi['quantity'] . "#" . $bqi['time'] . "#" . $bqi['ctime']; + } + $str .= $bqs; + } + + return $str; + } + + function getInitVals() { + $vac = gameAction('isOnVacation', $_SESSION[game::sessName()]['player']); + + if (is_null($_SESSION[game::sessName()]['quick_builder']) || $vac) { + $_SESSION[game::sessName()]['quick_builder'] = false; + } + if (is_null($_SESSION[game::sessName()]['plist_cumulative'])) { + $_SESSION[game::sessName()]['plist_cumulative'] = true; + } + return ($_SESSION[game::sessName()]['quick_builder'] ? "1" : "0") . '#' . ($_SESSION[game::sessName()]['plist_cumulative'] ? "1" : "0") + . "#" . ($vac ? 1 : 0); + } + + function setMode($val) { + $vac = gameAction('isOnVacation', $_SESSION[game::sessName()]['player']); + $_SESSION[game::sessName()]['quick_builder'] = ($val == "1" && !$vac); + } + + function setCumulative($val) { + $_SESSION[game::sessName()]['plist_cumulative'] = ($val == "1"); + } + + function getMilitaryLevel() { + $r = gameAction('loadPlayerRules', $_SESSION[game::sessName()]['player']); + if (is_null($r)) + return "0"; + return $r['military_level']; + } + + function flushQueues($planets) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return 13; + } + + $pl = explode('#', $planets); + $plp = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + $ids = array_keys($plp); + foreach ($pl as $pid) { + if (!in_array($pid, $ids)) { + continue; + } + gameAction('flushBuildQueue', $pid); + } + return "OK"; + } + + function addToQueues($planets, $nb, $type) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return 13; + } + + if ((int)$nb != $nb || $nb <= 0) { + return 2; + } + + $r = gameAction('loadPlayerRules', $_SESSION[game::sessName()]['player']); + if ($type < 0 || $type > $r['military_level'] + 2) { + return -1; + } + + $pl = explode('#', $planets); + $plp = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + $ids = array_keys($plp); + foreach ($pl as $pid) { + if (!in_array($pid, $ids)) { + return -1; + } + } + + $types = array('turret', 'gaship', 'fighter', 'cruiser', 'bcruiser'); + $cost = $r['build_cost_'.$types[$type]] * $nb * count($pl); + $pinf = gameAction('getPlayerInfo', $_SESSION[game::sessName()]['player']); + if ($pinf['cash'] < $cost) { + return 4; + } + + foreach ($pl as $pid) { + gameAction('addToBuildQueue', $pid, $nb, $type); + } + + return "OK"; + } + + function factoryAction($planets, $act, $nb, $type) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return 13; + } + + if ((int)$nb != $nb || $nb <= 0) { + return 3; + } + + $t = ($type == '1') ? "m" : "i"; + $r = gameAction('loadPlayerRules', $_SESSION[game::sessName()]['player']); + + $pl = explode('#', $planets); + $plp = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + $ids = array_keys($plp); + foreach ($pl as $pid) { + if (!in_array($pid, $ids)) { + return -1; + } + } + + if ($act != "1") { + // Check cash first + $cost = $nb * $r[$t . 'f_cost'] * count($pl) ; + $pinf = gameAction('getPlayerInfo', $_SESSION[game::sessName()]['player']); + if ($pinf['cash'] < $cost) { + return 4; + } + + // Now check other planet details + $planets = $this->game->getLib('beta5/planet'); + foreach ($pl as $pid) { + $err = $planets->call('checkBuildFactories', $pid, $nb, $t); + if ($err) { + return $err + 13; + } + } + } else { + foreach ($pl as $pid) { + $err = gameAction('checkDestroyFactories', $pid, $nb, $t); + if ($err) { + return $err + 4; + } + } + } + + $ga = ($act == "1") ? "destroyFactories" : "buildFactories"; + foreach ($pl as $pid) { + gameAction($ga, $pid, $nb, $t); + } + + return "OK"; + } + + function deleteItems($items) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return 13; + } + + $lst = explode('#', $items); + $items = array(); + foreach ($lst as $i) { + array_push($items, explode('-', $i)); + } + $plp = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + $ids = array_keys($plp); + + $pll = array(); + foreach ($items as $i) + { + list($pl, $it) = $i; + if (!(in_array($pl, $ids) && preg_match('/^[0-9]+$/', $it))) + continue; + if (!in_array($pl, $pll)) + array_push($pll, $pl); + gameAction('deleteBuildQueueItem', $pl, $it); + } + foreach ($pll as $pid) + gameAction('reorderBuildQueue', $pid); + + return "OK"; + } + + function replaceItems($items, $nb, $type) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return 13; + } + + if ((int)$nb != $nb || $nb <= 0) + return 2; + $r = gameAction('loadPlayerRules', $_SESSION[game::sessName()]['player']); + if ($type < 0 || $type > $r['military_level'] + 2) + return -1; + + $lst = explode('#', $items); + $rList = array(); + $plp = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + $ids = array_keys($plp); + foreach ($lst as $li) + { + list($pl,$it) = explode('-', $li); + if (!is_array($rList[$pl])) + { + if (!in_array($pl, $ids)) + return -1; + $rList[$pl] = array(); + } + if (!preg_match('/^[0-9]+$/', $it)) + return -1; + array_push($rList[$pl], $it); + } + + $types = array('turret', 'gaship', 'fighter', 'cruiser', 'bcruiser'); + $icost = $r['build_cost_'.$types[$type]] * $nb; + + $ids = array_keys($rList); + $sum = 0; + foreach ($ids as $pl) + $sum += gameAction('getReplacementCost', $pl, $rList[$pl], $icost); + + $pinf = gameAction('getPlayerInfo', $_SESSION[game::sessName()]['player']); + if ($pinf['cash'] < $sum) + return 4; + + foreach ($ids as $pl) + gameAction('replaceItems', $pl, $rList[$pl], $nb, $type); + + return "OK"; + } + + function moveItems($items, $dir) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return 13; + } + + $lst = explode('#', $items); + $rList = array(); + $plp = gameAction('getPlayerPlanets', $_SESSION[game::sessName()]['player']); + $ids = array_keys($plp); + foreach ($lst as $li) + { + list($pl,$it) = explode('-', $li); + if (!is_array($rList[$pl])) + { + if (!in_array($pl, $ids)) + return -1; + $rList[$pl] = array(); + } + if (!preg_match('/^[0-9]+$/', $it)) + return -1; + array_push($rList[$pl], $it); + } + + $ids = array_keys($rList); + foreach ($ids as $pl) + { + sort($rList[$pl]); + if ($dir != '1') + { + $bql = gameAction('getQueueLength', $pl); + if ($rList[$pl][0] >= $bql - 1) + return 12; + $rList[$pl] = array_reverse($rList[$pl]); + } + elseif ($rList[$pl][0] == 0) + return 11; + } + + $ga = ($dir == '1') ? "Up" : "Down"; + foreach ($ids as $pl) + { + foreach ($rList[$pl] as $it) + gameAction('moveItem' . $ga, $pl, $it); + } + + return "OK"; + } + + function handle($input) { + $this->output = "planets"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/play.inc b/scripts/site/beta5/handlers/play.inc new file mode 100644 index 0000000..2bba37a --- /dev/null +++ b/scripts/site/beta5/handlers/play.inc @@ -0,0 +1,245 @@ +rankings->call('getType', $type); + $r = $this->rankings->call('get', $rt, $name); + return $r; + } + + + private function player($id) { + $pinf = $this->players->call('get', $id); + + // Basic player data + $data = new data_node('Player'); + $data->setAttribute('id', $id); + $data->setAttribute('name', $pinf['name']); + $data->setAttribute('cash', $pinf['cash']); + + // Alliance + if ($pinf['aid'] || $pinf['arid']) { + $aData = new data_leaf('Alliance'); + if ($pinf['aid']) { + $aData->setAttribute('inalliance', 1); + $tag = $pinf['alliance']; + } else { + $aData->setAttribute('inalliance', 0); + $tag = $pinf['alliance_req']; + } + $aData->setAttribute('tag', $tag); + $data->addContents($aData); + } + + // Rankings + $data->addContents($rankings = new data_node('Rankings')); + $rankings->setAttribute('players', $this->beta5->call('getPlayerCount')); + $rkTypes = array( + 'p_general' => 'General', + 'p_civ' => 'Civilian', + 'p_financial' => 'Financial', + 'p_military' => 'Military', + 'p_round' => 'Overall Round', + 'p_idr' => 'Inflicted Damage' + ); + foreach ($rkTypes as $type => $name) { + $r = $this->getRanking($type, $pinf['name']); + if (!$r) { + continue; + } + $rt = new data_leaf('Ranking', "$name Ranking"); + $rt->setAttribute('points', $r['points']); + $rt->setAttribute('rank', $r['ranking']); + $rankings->addContents($rt); + } + + return $data; + } + + + private function empire($pid) { + $empire = new data_node('Empire'); + + // Planet statistics + $empire->addContents($planets = new data_node('Planets')); + $pld = $this->planets->call('getStats', $pid); + $planets->setAttribute('count', $pld[0]); + $planets->setAttribute('avgHap', $pld[1]); + $planets->setAttribute('totPop', $pld[2]); + $planets->setAttribute('avgPop', $pld[3]); + $planets->setAttribute('totFac', $pld[4]); + $planets->setAttribute('avgFac', $pld[5]); + $planets->setAttribute('totTur', $pld[6]); + $planets->setAttribute('avgTur', $pld[7]); + $planets->setAttribute('siege', $pld[8]); + $planets->setAttribute('avgCor', $pld[9]); + + // Planet list and income + $income = 0; + if ($pld[0] > 0) { + $planets->addContents($plist = new data_node('List')); + $l = $this->players->call('getPlanets', $pid); + foreach ($l as $id => $name) { + $p = new data_leaf('Planet', $name); + $p->setAttribute('id', $id); + $plist->addContents($p); + + $info = $this->planets->call('byId', $id); + $m = $this->planets->call('getIncome', $pid, $info['pop'], $info['happ'], $info['ifact'], $info['mfact'], $info['turrets'], $info['corruption']); + $income += $m[0]; + } + } + + // Fleets + $empire->addContents($fleets = new data_node('Fleets')); + $fs = $this->fleets->call('getStats', $pid); + $fleets->setAttribute('count', $fs['fleets']); + $fleets->setAttribute('inBattle', $fs['battle']); + $copy = array('power', 'gaships', 'fighters', 'cruisers', 'bcruisers'); + foreach ($copy as $name) { + $fleets->setAttribute($name, $fs[$name]); + } + + // Income and upkeep + $empire->addContents($budget = new data_node('Budget')); + $budget->setAttribute('income', $income); + $budget->setAttribute('upkeep', $fs['upkeep']); + $budget->setAttribute('profit', $income - $fs['upkeep']); + + // Research + $empire->addContents($research = new data_node('Research')); + $research->setAttribute('points', $this->techs->call('getPoints', $pid)); + $research->setAttribute('new', count($this->techs->call('getTopics', $pid, 0))); + $research->setAttribute('foreseen', count($this->techs->call('getTopics', $pid, -1)) / 2); + + // Research budget + $research->addContents($rBudget = new data_node('RBudget')); + $rb = $this->techs->call('getBudget', $pid); + $attrs = array('fundamental', 'military', 'civilian'); + for ($i=0;$isetAttribute($attrs[$i], $rb[$i]); + } + + return $empire; + } + + + private function ticks() { + $lib = $this->game->getLib('main'); + $tinf = $lib->call('getTicks', getLanguage()); + + $ticks = new data_node('Ticks'); + foreach ($tinf as $tid => $td) { + if (!$td['game']) { + continue; + } + + $ticks->addContents($tick = new data_leaf('Tick', $td['name'])); + $tick->setAttribute('first', $td['first']); + $tick->setAttribute('interval', $td['interval']); + if ($td['last']) { + $tick->setAttribute('last', $td['last']); + } + } + + return $ticks; + } + + + private function communications($pid) { + $comms = new data_node('Communications'); + $comms->addContents($messages = new data_node('Messages')); + $comms->addContents($forums = new data_node('Forums')); + + // Get custom folders and forum structure + $cfold = $this->msgs->call('getCustomFolders', $pid); + $cats = $this->forums->call('getStructure', $pid); + + // Messages in default folders + $dfld = array('IN', 'INT', 'OUT'); + foreach ($dfld as $f) { + $messages->addContents($node = new data_leaf('DefaultFolder')); + $node->setAttribute('id', $f); + $node->setAttribute('all', $this->msgs->call('getAll', $pid, $f)); + $node->setAttribute('new', $this->msgs->call('getNew', $pid, $f)); + } + + // Custom folders + foreach ($cfold as $cfid => $cfn) { + $messages->addContents($node = new data_leaf('CustomFolder', utf8_encode($cfn))); + $node->setAttribute('id', $cfid); + $node->setAttribute('all', $this->msgs->call('getAll', $pid, 'CUS', $cfid)); + $node->setAttribute('new', $this->msgs->call('getNew', $pid, 'CUS', $cfid)); + } + + // Forums + foreach ($cats as $c) { + if (!count($c['forums'])) { + continue; + } + + if ($c['type'] == 'A') { + $forums->addContents($category = new data_node('AllianceForums')); + } else { + $forums->addContents($category = new data_node('GeneralForums')); + $category->addContents($cdesc = new data_leaf('Description', utf8_encode($c['title']))); + $cdesc->setAttribute('id', $c['id']); + $cdesc->setAttribute('type', $c['type']); + } + foreach ($c['forums'] as $f) { + $category->addContents($forum = new data_leaf('Forum', utf8_encode($f['title']))); + $forum->setAttribute('id', $f['id']); + $forum->setAttribute('topics', $f['topics']); + $forum->setAttribute('unread', $f['unread']); + } + } + + return $comms; + } + + + function xml($input) { + if (!$_SESSION['authok']) { + return null; + } + + $pid = $_SESSION[game::sessName()]['player']; + + $this->beta5 = $this->game->getLib('beta5'); + $this->forums = $this->game->getLib('beta5/forums'); + $this->fleets = $this->game->getLib('beta5/fleet'); + $this->msgs = $this->game->getLib('beta5/msg'); + $this->planets = $this->game->getLib('beta5/planet'); + $this->players = $this->game->getLib('beta5/player'); + $this->rankings = $this->game->getLib('main/rankings'); + $this->techs = $this->game->getLib('beta5/tech'); + + $data = new data_node('Overview'); + $data->setAttribute('serverTime', time()); + + $data->addContents($this->player($pid)); + $data->addContents($this->empire($pid)); + $data->addContents($this->ticks()); + $data->addContents($this->communications($pid)); + + return $data; + } + + + function redirect($input) { + $this->accounts = $this->game->getLib('main/account'); + + if (!is_null($_SESSION['userid'])) { + $isAdmin = $this->accounts->call('isAdmin', $_SESSION['userid']); + } else { + $isAdmin = false; + } + return $isAdmin ? "admin" : "overview"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/preferences.inc b/scripts/site/beta5/handlers/preferences.inc new file mode 100644 index 0000000..dbf88a9 --- /dev/null +++ b/scripts/site/beta5/handlers/preferences.inc @@ -0,0 +1,142 @@ +passError = 1; + return; + } + list($rop) = dbFetchArray($q); + if ($rop != $op) + $this->passError = 2; + elseif ($np != $cp) + $this->passError = 3; + elseif (strlen($np) < 4) + $this->passError = 4; + elseif (strlen($np) > 64) + $this->passError = 5; + elseif ($np == $_SESSION['login']) + $this->passError = 6; + else + { + $p = addslashes($np); + $op = addslashes($rop); + dbQuery( + "INSERT INTO pass_change (account, old_pass, new_pass) " + . "VALUES({$_SESSION['userid']}, '$op', '$p')" + ); + $q = dbQuery("UPDATE account SET password='$p' WHERE id=".$_SESSION['userid']); + if (!$q) { + $this->passError = 1; + } + } + } + + function checkFormData($input) + { + $pLang = array('fr', 'en'); + if (in_array($input['lang'], $pLang)) + prefs::set('main/language', $input['lang']); + + $pCol = array('red','green','blue','yellow','grey','purple'); + if (in_array($input['col'], $pCol)) + prefs::set('main/colour', $input['col']); + + $themes = array('default','invert','classic','cripes'); + if (in_array($input['thm'], $themes)) { + prefs::set('beta5/theme', $input['thm']); + if ($input['thm'] == 'cripes') { + $input['tt'] = 0; + } + } + + if (preg_match('/^[0-4]$/', $input['fs'])) + prefs::set('main/font_size', $input['fs']); + + if ($this->checkMail($input['mail'])) + dbQuery("UPDATE account SET email='".$input['mail']."' WHERE id=".$_SESSION['userid']); + else + $this->mailError = preg_replace('/"/', '"', $input['mail']); + + $tt = (int)$input['tt']; + if ($tt >= 0 && $tt <= 6) + prefs::set('main/tooltips', $tt); + + if (preg_match('/^[1-5]0$/', $input['tpp'])) + prefs::set('main/forums_ntopics', $input['tpp']); + if (preg_match('/^[1-5]0$/', $input['mpp'])) + prefs::set('main/forums_nitems', $input['mpp']); + prefs::set('main/smileys', ($input['gsm'] == "1")?"1":"0"); + prefs::set('main/forum_code', ($input['gft'] == "1")?"1":"0"); + prefs::set('main/forums_threaded', ($input['fdm'] == "1")?"1":"0"); + prefs::set('main/forums_reversed', ($input['fmo'] == "1")?"1":"0"); + + $fsig = preg_replace('/ +/', ' ', trim($input['fsig'])); + if (strlen($fsig) <= 255) + prefs::set('main/forums_sig', $input['fsig']); + + if ($input['opass'] != "") + $this->checkPassword($input['opass'], $input['npass'], $input['cpass']); + } + + function handleQuit() { + if (gameAction('isFinished')) { + return; + } + $pid = $_SESSION[game::sessName()]['player']; + $pinf = gameAction('getPlayerInfo', $pid); + if (!is_null($pinf['qts'])) + gameAction('cancelPlayerQuit', $pid); + else + gameAction('setPlayerQuit', $pid); + } + + function handle($input) + { + if (!is_null($input['quit'])) + $this->handleQuit(); + elseif ($input['col'] != "") + $this->checkFormData($input); + + $q = dbQuery("SELECT email FROM account WHERE id=".$_SESSION['userid']); + list($email) = dbFetchArray($q); + $pinf = gameAction('getPlayerInfo', $_SESSION[game::sessName()]['player']); + + $this->data = array( + "lang" => getLanguage(), + "mail" => $email, + "col" => prefs::get('main/colour', 'red'), + "fs" => prefs::get('main/font_size', 2), + "err1" => $this->mailError, + "err2" => $this->passError, + "tpp" => prefs::get('main/forums_ntopics', 20), + "mpp" => prefs::get('main/forums_nitems', 20), + "gsm" => (prefs::get('main/smileys', 1) == 1), + "gft" => (prefs::get('main/forum_code', 1) == 1), + "fdm" => (prefs::get('main/forums_threaded', 1) == 1), + "fmo" => (prefs::get('main/forums_reversed', 1) == 1), + "fsig" => prefs::get('main/forums_sig', ""), + "tt" => prefs::get('main/tooltips', 2), + "thm" => prefs::get('beta5/theme', 'default'), + "quit" => $pinf['qts'], + "name" => $this->game->text, + "lok" => !(gameAction('isFinished') || $this->game->params['victory']) + ); + + $this->output = "preferences"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/probes.inc b/scripts/site/beta5/handlers/probes.inc new file mode 100644 index 0000000..5aadf04 --- /dev/null +++ b/scripts/site/beta5/handlers/probes.inc @@ -0,0 +1,260 @@ + array( + 'getPageData', +// 'setEmpirePolicy', 'togglePlanetPolicy', 'setPlanetPolicy', + 'upgradeBeacon' + ), + "init" => "initPage();" + ); + + + //------------------------------------------------------------------------------ + // Probes policy control + + function setEmpirePolicy($element, $value) + { + $element = (int)$element; $value = (int)$value; + if ($element < 0 || $value < 0 || $element > 3 || $value > 2) + return "ERR#1"; + + // Get empire policy + $player = $_SESSION[game::sessName()]['player']; + $ePolicy = gameAction('getPlayerPolicy', $player); + if (is_null($ePolicy)) + return "ERR#0"; + + // Set new value + $ePolicy{$element} = $value; + gameAction('setPlayerPolicy', $player, $ePolicy); + + return "1\n$ePolicy"; + } + + function togglePlanetPolicy($planet) + { + // Get planet information + $player = $_SESSION[game::sessName()]['player']; + $planet = (int)$planet; + $pinf = gameAction('getPlanetById', $planet); + if (is_null($pinf)) + return "ERR#1"; + elseif ($pinf['owner'] != $player) + return "ERR#2"; + + // Get planet policy + $pPolicy = gameAction('getPlanetPolicy', $planet); + if (is_null($pPolicy)) + { + $ePolicy = gameAction('getPlayerPolicy', $player); + if (is_null($ePolicy)) + return "ERR#0"; + $pPolicy = $ePolicy; + } + else + $pPolicy = null; + gameAction('setPlanetPolicy', $planet, $pPolicy); + + return "2\n$planet#$pPolicy"; + } + + function setPlanetPolicy($planet, $element, $value) + { + // Check parameters + $element = (int)$element; $value = (int)$value; + if ($element < 0 || $value < 0 || $element > 3 || $value > 2) + return "ERR#1"; + + // Get planet information + $player = $_SESSION[game::sessName()]['player']; + $planet = (int)$planet; + $pinf = gameAction('getPlanetById', $planet); + if (is_null($pinf)) + return "ERR#1"; + elseif ($pinf['owner'] != $player) + return "ERR#2"; + + // Get planet policy + $pPolicy = gameAction('getPlanetPolicy', $planet); + if (is_null($pPolicy)) + return "ERR#1"; + $pPolicy{$element} = $value; + gameAction('setPlanetPolicy', $planet, $pPolicy); + + return "2\n$planet#$pPolicy"; + } + + + //------------------------------------------------------------------------------ + // Beacon management functions + + function getPlanetBeaconData($pid, $maxTech) + { + // Get the planet's data + $pinf = gameAction('getPlanetById', $pid); + $pinf['orbit'] ++; + $mainData = "$pid#{$pinf['x']}#{$pinf['y']}#{$pinf['orbit']}#{$pinf['beacon']}#"; + + // Compute the price to upgrade if that applies + if ($pinf['beacon'] < $maxTech) + $price = $this->bcnCost * pow($this->bcnPow, $pinf['beacon']); + else + $price = ""; + $mainData .= "$price#" . ($pinf['built_probe'] == 't' ? 1 : 0) . "#"; + + // If the beacon can spot fleets standing by in Hyperspace, check for them + $reqLevel = $this->game->params['fakebeacons'] ? 1 : 2; + $hsFleets = array(); + if ($pinf['beacon'] >= $reqLevel) { + $hsFleetsRaw = $this->game->getLib('beta5/planet')->call('getDetectedFleets', $pid); + foreach ($hsFleetsRaw as $fleet) { + $str = "{$fleet['i_level']}#{$fleet['fl_size']}#{$fleet['fl_owner']}#{$fleet['owner']}"; + array_push($hsFleets, $str); + } + } + + // Generate the return value + $mainData .= count($hsFleets) . "#" . count($mvFleets) . "#" . utf8entities($pinf['name']); + $rv = array($mainData); + if (count($hsFleets)) { + $rv = array_merge($rv, $hsFleets); + } + + return $rv; + } + + function upgradeBeacon($pid) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $player = $_SESSION[game::sessName()]['player']; + $pid = (int)$pid; + + // Check the planet's existance, ownership and beacon/probe status + $pinf = gameAction('getPlanetById', $pid); + if (is_null($pinf) || $pinf['built_probe'] == 't') + return "ERR#0"; + else if ($pinf['owner'] != $player) + return "ERR#1"; + + // Check the player's tech level + $rules = gameAction('loadPlayerRules', $player); + if ($pinf['beacon'] == $rules['hs_beacon_level']) + return "ERR#0"; + + // Check the player's cash + $plinf = gameAction('getPlayerInfo', $player); + $price = $this->bcnCost * pow($this->bcnPow, $pinf['beacon']); + if ($plinf['cash'] < $price) + return "ERR#2"; + + // Upgrade the beacon + dbQuery("UPDATE planet SET beacon=beacon+1,built_probe=".dbBool(1)." WHERE id=$pid"); + dbQuery("UPDATE player SET cash=cash-$price WHERE id=$player"); + + // Generate the return value + $rv = $this->getPlanetBeaconData($pid, $rules['hs_beacon_level']); + array_unshift($rv, 1); + return join("\n", $rv); + } + + + //------------------------------------------------------------------------------ + // Complete data generation for sub-pages + + function getPageData($name) + { + $page = $this->setPage($name); + switch ($page) + { + //case "policy": return $this->getPolicyData(); + case "beacons": return $this->getBeaconsData(); + //case "build": return $this->getBuildData(); + //case "data": return $this->getProbesData(); + } + } + + function getPolicyData() + { + $player = $_SESSION[game::sessName()]['player']; + + // Get empire policy + $ePolicy = gameAction('getPlayerPolicy', $player); + if (is_null($ePolicy)) + return "ERR#0"; + + // Get the player's planet-specific policies + $pPolicies = gameAction('getPlayerPlanets', $player); + foreach ($pPolicies as $id => $junk) + { + $pinf = gameAction('getPlanetById', $id); + $pPolicies[$id] = array( + $pinf['x'], $pinf['y'], $pinf['orbit'] + 1, + gameAction('getPlanetPolicy', $id), + utf8entities($pinf['name']) + ); + } + + // Generate output + $rv = array(0, count($pPolicies) . "#" . $ePolicy); + if (count($pPolicies)) + { + foreach ($pPolicies as $id => $pPolicy) + array_push($rv, "$id#" . join("#", $pPolicy)); + } + + return join("\n", $rv); + } + + function getBeaconsData() + { + $player = $_SESSION[game::sessName()]['player']; + + // Get the player's beacon technology level + $rules = gameAction('loadPlayerRules', $player); + $beaconTech = $rules['hs_beacon_level']; + + // Get the data regarding all of the player's planets + $pl = gameAction('getPlayerPlanets', $player); + $rv = array(0, "$beaconTech#" . count($pl)); + foreach ($pl as $id => $name) { + $rv = array_merge($rv, $this->getPlanetBeaconData($id, $beaconTech)); + } + + return join("\n", $rv); + } + + //------------------------------------------------------------------------------ + // Sub-pages and session + + function setPage($name) + { + //$okPages = array('policy', 'beacons', 'build', 'data'); + $okPages = array('beacons'); + if (!in_array($name, $okPages)) + $name = $okPages[0]; + return ($_SESSION[game::sessName()]['probe_page'] = $name); + } + + function getPage() + { + if (is_null($_SESSION[game::sessName()]['probe_page'])) + $_SESSION[game::sessName()]['probe_page'] = 'beacons'; + return $_SESSION[game::sessName()]['probe_page']; + } + + function handle($input) + { + $this->data = $this->getPage(); + $this->output = "probes"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/rank.inc b/scripts/site/beta5/handlers/rank.inc new file mode 100644 index 0000000..46dbfdc --- /dev/null +++ b/scripts/site/beta5/handlers/rank.inc @@ -0,0 +1,266 @@ + array( + 'setPage', 'getPlayerData', + 'getGeneralRankings', 'getDetailedRankings', + 'getAllianceRankings', 'getRoundRankings', + 'getDamageRankings' + ), + 'init' => "makeRanksTooltips();\ninitPage();" + ); + + function getRanking($type,$name) { + if (! $this->rkLib) { + $this->rkLib = input::$game->getLib('main/rankings'); + } + $rt = $this->rkLib->call('getType', $type); + $r = $this->rkLib->call('get', $rt, $name); + if (!$r) { + return array('',''); + } + return $r; + } + + function getPlayerData($md5) + { + $pname = gameAction('getPlayerName', $_SESSION[game::sessName()]['player']); + $gr = $this->getRanking('p_general', $pname); + $cr = $this->getRanking('p_civ', $pname); + $mr = $this->getRanking('p_military', $pname); + $fr = $this->getRanking('p_financial', $pname); + $or = $this->getRanking('p_round', $pname); + $ir = $this->getRanking('p_idr', $pname); + $a = array( + $gr['points'], $gr['ranking'], $cr['points'], $cr['ranking'], + $mr['points'], $mr['ranking'], $fr['points'], $fr['ranking'], + $or['points'], $or['ranking'], $ir['points'], $ir['ranking'] + ); + $nmd5 = md5(serialize($a)); + if ($md5 == $nmd5) + return "KEEP"; + array_unshift($a, $nmd5); + return join('#', $a); + } + + function setPage($page) + { + $pok = array('Summary','General','Details','Alliance','Round', 'Damage'); + if (in_array($page,$pok)) + $_SESSION[game::sessName()]['rkpage'] = $page; + return $_SESSION[game::sessName()]['rkpage']; + } + + function getGeneralRankings($param, $md5) { + // Listing configuration + $conf = array( + "perPage" => array(5, 10, 15, 20, 25), + "sortable" => array( + "player" => SORT_STRING, + "rank" => SORT_NUMERIC, + ), + "searchModes" => array("player"), + "output" => array( + "isMe", "rank", "points", "player_real" + ) + ); + + // Get the data + $players = array(); + $myName = gameAction('getPlayerName', $_SESSION[game::sessName()]['player']); + if (! $this->rkLib) { + $this->rkLib = input::$game->getLib('main/rankings'); + } + $rt = $this->rkLib->call('getType', "p_general"); + $rl = $this->rkLib->call('getAll', $rt); + foreach ($rl as $r) + array_push($players, array( + "player" => strtolower($r['id']), + "player_real" => $r['id'], + "rank" => $r['ranking'], + "points" => $r['points'], + "isMe" => ($r['id'] == $myName) ? 1 : 0 + )); + + return gameAction('generateListing', $players, $conf, $param, $md5); + } + + function getDetailedRankings($param, $md5) { + // Listing configuration + $conf = array( + "perPage" => array(5, 10, 15, 20, 25), + "sortable" => array( + "player" => SORT_STRING, + "civRank" => SORT_NUMERIC, + "milRank" => SORT_NUMERIC, + "finRank" => SORT_NUMERIC, + ), + "searchModes" => array("player"), + "output" => array( + "isMe", "civRank", "civPoints", + "milRank", "milPoints", "finRank", + "finPoints", "player_real" + ) + ); + + // Get the data + $players = array(); + $myName = gameAction('getPlayerName', $_SESSION[game::sessName()]['player']); + if (! $this->rkLib) { + $this->rkLib = input::$game->getLib('main/rankings'); + } + $rt = $this->rkLib->call('getType', "p_civ"); + $rl = $this->rkLib->call('getAll', $rt); + foreach ($rl as $r) + $players[$r['id']] = array( + "player" => strtolower($r['id']), + "player_real" => $r['id'], + "civRank" => $r['ranking'], + "civPoints" => $r['points'], + "isMe" => ($r['id'] == $myName) ? 1 : 0 + ); + + $rt = $this->rkLib->call('getType', "p_military"); + $rl = $this->rkLib->call('getAll', $rt); + foreach ($rl as $r) { + $players[$r['id']]['milRank'] = $r['ranking']; + $players[$r['id']]['milPoints'] = $r['points']; + } + + $rt = $this->rkLib->call('getType', "p_financial"); + $rl = $this->rkLib->call('getAll', $rt); + foreach ($rl as $r) { + $players[$r['id']]['finRank'] = $r['ranking']; + $players[$r['id']]['finPoints'] = $r['points']; + } + + $data = array(); + foreach ($players as $id => $d) + array_push($data, $d); + + return gameAction('generateListing', $data, $conf, $param, $md5); + } + + function getAllianceRankings($param, $md5) { + // Listing configuration + $conf = array( + "perPage" => array(5, 10, 15, 20, 25), + "sortable" => array( + "alliance" => SORT_STRING, + "rank" => SORT_NUMERIC, + ), + "searchModes" => array("alliance"), + "output" => array( + "rank", "points", "alliance_real" + ) + ); + + // Get the data + $data = array(); + if (! $this->rkLib) { + $this->rkLib = input::$game->getLib('main/rankings'); + } + $rt = $this->rkLib->call('getType', "a_general"); + $rl = $this->rkLib->call('getAll', $rt); + foreach ($rl as $r) + array_push($data, array( + "alliance" => strtolower($r['id']), + "alliance_real" => $r['id'], + "rank" => $r['ranking'], + "points" => $r['points'] + )); + + return gameAction('generateListing', $data, $conf, $param, $md5); + } + + function getRoundRankings($param, $md5) { + // Listing configuration + $conf = array( + "perPage" => array(5, 10, 15, 20, 25), + "sortable" => array( + "player" => SORT_STRING, + "rank" => SORT_NUMERIC, + ), + "searchModes" => array("player"), + "output" => array( + "isMe", "rank", "points", "player_real" + ) + ); + + // Get the data + $players = array(); + $myName = gameAction('getPlayerName', $_SESSION[game::sessName()]['player']); + if (! $this->rkLib) { + $this->rkLib = input::$game->getLib('main/rankings'); + } + $rt = $this->rkLib->call('getType', "p_round"); + $rl = $this->rkLib->call('getAll', $rt); + foreach ($rl as $r) + array_push($players, array( + "player" => strtolower($r['id']), + "player_real" => $r['id'], + "rank" => $r['ranking'], + "points" => $r['points'], + "isMe" => ($r['id'] == $myName) ? 1 : 0 + )); + + return gameAction('generateListing', $players, $conf, $param, $md5); + } + + function getDamageRankings($param, $md5) { + // Listing configuration + $conf = array( + "perPage" => array(5, 10, 15, 20, 25), + "sortable" => array( + "player" => SORT_STRING, + "rank" => SORT_NUMERIC, + ), + "searchModes" => array("player"), + "output" => array( + "isMe", "rank", "points", "player_real" + ) + ); + + // Get the data + $players = array(); + $myName = gameAction('getPlayerName', $_SESSION[game::sessName()]['player']); + if (! $this->rkLib) { + $this->rkLib = input::$game->getLib('main/rankings'); + } + $rt = $this->rkLib->call('getType', "p_idr"); + $rl = $this->rkLib->call('getAll', $rt); + foreach ($rl as $r) + array_push($players, array( + "player" => strtolower($r['id']), + "player_real" => $r['id'], + "rank" => $r['ranking'], + "points" => $r['points'], + "isMe" => ($r['id'] == $myName) ? 1 : 0 + )); + + return gameAction('generateListing', $players, $conf, $param, $md5); + } + + function handle($input) + { + switch ($input['p']): + case 's': $_SESSION[game::sessName()]['rkpage'] = 'Summary'; break; + case 'g': $_SESSION[game::sessName()]['rkpage'] = 'General'; break; + case 'd': $_SESSION[game::sessName()]['rkpage'] = 'Details'; break; + case 'a': $_SESSION[game::sessName()]['rkpage'] = 'Alliance'; break; + case 'o': $_SESSION[game::sessName()]['rkpage'] = 'Round'; break; + case 'i': $_SESSION[game::sessName()]['rkpage'] = 'Damage'; break; + default: + if (is_null($_SESSION[game::sessName()]['rkpage'])) + $_SESSION[game::sessName()]['rkpage'] = 'Summary'; + break; + endswitch; + $this->data = $_SESSION[game::sessName()]['rkpage']; + $this->output = "rank"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/rat.inc b/scripts/site/beta5/handlers/rat.inc new file mode 100644 index 0000000..698e988 --- /dev/null +++ b/scripts/site/beta5/handlers/rat.inc @@ -0,0 +1,11 @@ +output = "rat"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/research.inc b/scripts/site/beta5/handlers/research.inc new file mode 100644 index 0000000..cd3d908 --- /dev/null +++ b/scripts/site/beta5/handlers/research.inc @@ -0,0 +1,272 @@ + array( + 'getMode', 'setMode', 'getDescriptions', + 'getTopicsData', 'getLawsData', 'getAttributionsData', + 'implementTechnology', 'switchLaw', 'validateBudget', + 'getDiplomacyData', 'sendOffer', 'acceptOffer', + 'declineOffer' + ), + "init" => "makeResearchTooltips(); x_getMode(initResearchPage);" + ); + + function guessResearchMode($i) + { + switch ($i['p']) : + case 't': $_SESSION[game::sessName()]['respage_mode'] = 'Topics'; break; + case 'l': $_SESSION[game::sessName()]['respage_mode'] = 'Laws'; break; + case 'b': $_SESSION[game::sessName()]['respage_mode'] = 'Attributions'; break; + case 'd': $_SESSION[game::sessName()]['respage_mode'] = 'Diplomacy'; break; + endswitch; + if (is_null($_SESSION[game::sessName()]['respage_mode'])) + $_SESSION[game::sessName()]['respage_mode'] = 'Topics'; + return $_SESSION[game::sessName()]['respage_mode']; + } + + function getMode() + { + if (is_null($_SESSION[game::sessName()]['respage_mode'])) + return $this->guessResearchMode(array()); + return $_SESSION[game::sessName()]['respage_mode']; + } + + function setMode($nm) + { + $pm = array('Topics', 'Laws', 'Attributions', 'Diplomacy'); + if (!in_array($nm, $pm)) + return $this->guessResearchMode(array()); + + $_SESSION[game::sessName()]['respage_mode'] = $nm; + return $nm; + } + + function getDescriptions($lst) + { + $l = getLanguage(); + $a = split('#', $lst); + $s = ""; + foreach ($a as $id) + { + if ((int)$id != $id) + continue; + $ns = gameAction('getResearchData', $l, $id); + if ($ns == "") + continue; + if ($s != "") + $s .= "\n"; + $s .= $ns; + } + return $s; + } + + function getTopicsData() + { + $pl = $_SESSION[game::sessName()]['player']; + $impl = gameAction('getResearchTopics', $pl, 1); + $disp = gameAction('getResearchTopics', $pl, 0); + $brkt = gameAction('getResearchTopics', $pl, -1); + return join("#", $impl) . "!" . join("#", $disp) . "!" . join("#", $brkt); + } + + function implementTechnology($id) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + if ((int)$id != $id) { + logText("(CHEAT) research/implement received weird ID from user {$_SESSION['userid']}"); + return "ERR#1"; + } + return (gameAction('implementTechnology', $_SESSION[game::sessName()]['player'], $id) ? "OK" : "ERR#0"); + } + + function getLawsData() { + return join('#', gameAction('getLaws', $_SESSION[game::sessName()]['player'])); + } + + function switchLaw($id) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + if ((int)$id != $id) { + return "ERR#1"; + } + return (gameAction('switchLaw', $_SESSION[game::sessName()]['player'], $id) ? "OK" : "ERR#0"); + } + + function getAttributionsData() { + $pl = $_SESSION[game::sessName()]['player']; + $a = array(gameAction('getResearchPoints', $pl)); + $b = gameAction('getResearchBudget', $pl); + $a = array_merge($a, $b); + return join('#', $a); + } + + function validateBudget($b) { + if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { + return "ERR#200"; + } + + $a = split('#', $b); + if (count($a) != 3) { + return "ERR#0"; + } + $s = 0; + for ($i=0;$i 100) { + return "ERR#0"; + } + } + gameAction('setResearchBudget', $_SESSION[game::sessName()]['player'], $a); + return "OK"; + } + + function getDiplomacyData() + { + $pl = $_SESSION[game::sessName()]['player']; + $pr = gameAction('isPlayerRestrained', $pl); + if ($pr > 0) + return $pr; + + $impl = gameAction('getResearchTopics', $pl, 1); + $impl = array_merge($impl, gameAction('getResearchTopics', $pl, 0)); + $time = $this->game->ticks['day']->interval - $this->game->ticks['hour']->interval * 2; + $s1 = $time . "\n" . join('#',$impl); + $laws = gameAction('getLaws', $pl); + for ($i=0;$igame->getLib('beta5/player'); + $cpid = $_SESSION[game::sessName()]['player']; + + if ($pLib->call('isOnVacation', $cpid)) { + return "ERR#200"; + } elseif ($pLib->call('getProtectionLevel', $cpid)) { + return "ERR#201"; + } + + $pname = trim($pname); + if ($pname == "") { + return "ERR#2"; + } + + $tpid = $pLib->call('getPlayerId', $pname); + if (is_null($tpid)) { + return "ERR#4"; + } elseif ($tpid == $cpid) { + return "ERR#6"; + } + + $tLib = $this->game->getLib('beta5/tech'); + if ($ty == 0) { + $tid = null; + } elseif ($ty == 1) { + $tid = (int)$tid; + if (!$tLib->call('has', $cpid, $tid)) { + logText("*** CHEAT? research::sendOffer: player $cpid trying to send tech $tid"); + return "ERR#5"; + } + } else { + logText("*** CHEAT? research::sendOffer: player $cpid, received invalid status '$ty'"); + return "ERR#8"; + } + + if ("$pr" != (int)$pr || $pr < 0) { + return "ERR#3"; + } + if ($tLib->call('checkOffer', $cpid)) { + logText("*** CHEAT? research::sendOffer: player $cpid has already sent an offer"); + return "ERR#9"; + } + + if ($pLib->call('isOnVacation', $tpid)) { + return "ERR#7"; + } elseif ($pLib->call('getProtectionLevel', $tpid)) { + return "ERR#10"; + } + + $tLib->call('makeOffer', $cpid, $tpid, $tid, $pr); + + return $this->getDiplomacyData(); + } + + function acceptOffer($oid) { + $pLib = $this->game->getLib('beta5/player'); + $pid = $_SESSION[game::sessName()]['player']; + + if ($pLib->call('isOnVacation', $pid)) { + return "ERR#200"; + } elseif ($pLib->call('getProtectionLevel', $pid)) { + return "ERR#201"; + } + + $oid = (int)$oid; + $a = gameAction('acceptResearchOffer', $pid, $oid); + if ($a) + return "ERR#$a"; + return $this->getDiplomacyData(); + } + + function declineOffer($oid) { + $pLib = $this->game->getLib('beta5/player'); + $pid = $_SESSION[game::sessName()]['player']; + + if ($pLib->call('isOnVacation', $pid)) { + return "ERR#200"; + } elseif ($pLib->call('getProtectionLevel', $pid)) { + return "ERR#201"; + } + + $oid = (int)$oid; + $a = gameAction('declineResearchOffer', $pid, $oid); + if ($a) + return "ERR#$a"; + return $this->getDiplomacyData(); + } + + function handle($input) + { + switch ($input['mode']) : + case 't': + $this->setMode('Topics'); + break; + case 'l': + $this->setMode('Laws'); + break; + case 'b': + $this->setMode('Attributions'); + break; + case 'd': + $this->setMode('Diplomacy'); + break; + default: + $this->guessResearchMode($input); + endswitch; + $this->output = "research"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/techtrade.inc b/scripts/site/beta5/handlers/techtrade.inc new file mode 100644 index 0000000..1cd5b24 --- /dev/null +++ b/scripts/site/beta5/handlers/techtrade.inc @@ -0,0 +1,490 @@ + array( + // General information + "getGeneralInfo", "getTechList", + // Main page + "getSubmitPage", "submitList", "sendOffer", + // Requests page + "getRequestsPage", "submitRequests", + // Alliance-wide listing + "getAllianceList", "setListOptions", + // Viewing orders + "getCurrentOrders", + // Changing orders + "getChangeOrdersData", "submitOrders" + ), + "init" => "new TechTrade(document.getElementById('init-data').value)" + ); + + /* Valid pages */ + static private $subPages = array( + "Submit", "Req", "ViewList", "SOrders", "VOrders" + ); + + /* Libraries */ + private $pLib; + private $aLib; + private $rLib; + + /* Player information */ + private $player; + private $pInfo; + private $vacation; + private $alliance; + private $hasRequests; + private $accessLevel; + + /** This function initialises the libraries + * and checks the current player's privileges + * with regards to technology trading. + */ + private function init() { + // Get libraries + $this->pLib = $this->game->getLib('beta5/player'); + $this->aLib = $this->game->getLib('beta5/alliance'); + $this->rLib = $this->game->getLib('beta5/tech'); + + // Get player information + $this->player = $_SESSION[game::sessName()]['player']; + $this->pInfo = $this->pLib->call('get', $this->player); + $this->vacation = $this->pLib->call('isOnVacation', $this->player); + $this->alliance = $this->pInfo['aid']; + $alInfo = $this->aLib->call('get', $this->alliance); + $this->hasRequests = ($alInfo['enable_tt'] == 'R'); + if (is_null($this->alliance)) { + $this->accessLevel = 0; + } else { + $privileges = $this->aLib->call('getPrivileges', $this->player); + $this->accessLevel = $privileges['tech_trade']; + } + } + + + /** This method tries to set the current sub-page being viewed, accounting for + * the user's privileges. + */ + private function currentPage($page = null) { + // If we're not trying to change page or if we're trying to set an invalid page, + // fetch the current page from the session. + if (is_null($page) || !in_array($page, self::$subPages)) { + $page = $_SESSION[game::sessName()]['tt_page']; + } + + // Check privileges + switch ($page) { + case 'Submit': $ok = ($this->accessLevel > 0); break; + case 'Req': $ok = ($this->accessLevel > 1 && $this->hasRequests); break; + case 'ViewList': $ok = ($this->accessLevel > 2); break; + case 'SOrders': $ok = ($this->accessLevel > 3); break; + case 'VOrders': $ok = ($this->accessLevel > 3); break; + default: $ok = false; break; + } + + // If the privileges are invalid, set the page + if (!$ok) { + if ($this->accessLevel == 0) { + $page = 'NoAccess'; + } else { + $page = 'Submit'; + } + } + + return ($_SESSION[game::sessName()]['tt_page'] = $page); + } + + + /*************************************************************************************** + * General information retrieval * + ***************************************************************************************/ + + /** This AJAX function reads the information about the player and returns + * it. It allows the page to "know" what to display. + */ + public function getGeneralInfo() { + // Initialise the handler + $this->init(); + + // Get the current page + $page = $this->currentPage(); + + $requests = ($this->hasRequests ? 1 : 0); + return "$page#{$this->accessLevel}#$requests#{$this->alliance}#" . ($this->vacation ? 1 : 0); + } + + + /** This AJAX function returns the complete list of technologies as pairs + * of ID / Name lines. + */ + public function getTechList() { + // Prevent people from loading the list just for the sake of it + if ($_SESSION[game::sessName()]['tt_list']) { + return ""; + } + $_SESSION[game::sessName()]['tt_list'] = true; + + // Initialise the handler and get the language + $this->init(); + $language = getLanguage(); + + // Get the list + $techList = $this->rLib->call('getAll', $language); + + // Create the result + $result = array(); + foreach ($techList as $id => $data) { + $line = array($id, count($data['deps'])); + $line = array_merge($line, $data['deps']); + array_push($line, utf8entities($data['name'])); + array_push($result, join('#', $line)); + } + + return join("\n", $result); + } + + + /*************************************************************************************** + * Submission / view orders page * + ***************************************************************************************/ + + /** This is the actual method that generates the output for the submission page. + */ + private function __submitPage() { + $result = array(); + + // Get last submission date + list($canSubmit, $lastSubmission, $nextSubmission) = $this->aLib->call( + 'getTechSubmission', $this->player); + array_push($result, ($canSubmit ? 1 : 0) . "#$lastSubmission#$nextSubmission"); + + // Get orders + list($sendWhat, $toWhom, $sDate, $oDate) = $this->aLib->call('getTechOrder', $this->player, true); + if ($toWhom) { + $toName = $this->pLib->call('getName', $toWhom); + if (!$oDate) { + $canSend = !($this->vacation || $this->rLib->call('checkOffer', $this->player)) ? 1 : 0; + } else { + $canSend = 0; + } + array_push($result, "$sDate#$oDate#$canSend#$sendWhat#$toWhom#" . utf8entities($toName)); + } else { + array_push($result, "-"); + } + + list($getWhat, $fromWhom, $sDate, $oDate) = $this->aLib->call('getTechOrder', $this->player, false); + if ($fromWhom) { + $fromName = $this->pLib->call('getName', $fromWhom); + array_push($result, "$sDate#$oDate#$getWhat#$fromWhom#" . utf8entities($fromName)); + } else { + array_push($result, "-"); + } + + return join("\n", $result); + } + + + /** This AJAX function returns the contents for the submission page. + */ + public function getSubmitPage() { + // Initialise page + $this->init(); + $page = $this->currentPage('Submit'); + if ($page != 'Submit') { + return "FU"; + } + + return $this->__submitPage(); + } + + + /** This AJAX function submits a player's technology list to his alliance + */ + public function submitList() { + // Initialise page + $this->init(); + $page = $this->currentPage('Submit'); + if ($page != 'Submit') { + return "FU"; + } + $result = array(); + + // Check if the player can submit + list($canSubmit, $lastSubmission, $nextSubmission) = $this->aLib->call( + 'getTechSubmission', $this->player); + if ($this->vacation || ! $canSubmit) { + return $this->__submitPage(); + } + + // Submit the list + $this->aLib->call('submitTechList', $this->player); + return $this->__submitPage(); + } + + + /** This AJAX function causes a player to obey his tech trading order. + */ + public function sendOffer() { + // Initialise page + $this->init(); + $page = $this->currentPage('Submit'); + if ($page != 'Submit') { + return "FU"; + } + $result = array(); + + // Check if the player can submit + list($sendWhat, $toWhom, $sDate, $oDate) = $this->aLib->call('getTechOrder', $this->player, true); + if ($toWhom) { + $canSend = !($oDate || $this->vacation || $this->rLib->call('checkOffer', $this->player)); + if ($canSend) { + $this->rLib->call('makeOffer', $this->player, $toWhom, $sendWhat, 0); + $this->aLib->call('obeyOrder', $this->player); + } + } + + return $this->__submitPage(); + } + + + /*************************************************************************************** + * Player requests page * + ***************************************************************************************/ + + /** This method generates the actual requests page data. + */ + private function __requestsPage() { + // Get requests + $requests = $this->aLib->call('getTechRequests', $this->player); + + // Get techs the player could request + $requestable = $this->rLib->call('getAvailableTechs', $this->player); + + return join('#', $requests) . "\n" . join('#', $requestable); + } + + + /** This AJAX function returns the contents for the requests page. + */ + public function getRequestsPage() { + // Initialise page + $this->init(); + $page = $this->currentPage('Req'); + if ($page != 'Req') { + return "FU"; + } + + // Make sure the requests have been updated + $this->aLib->call('updateRequests', $this->player); + return $this->__requestsPage(); + } + + /** This AJAX function updates a player's technology requests. + */ + public function submitRequests($requests = "") { + // Initialise page + $this->init(); + $page = $this->currentPage('Req'); + if ($page != 'Req') { + return "FU"; + } + + // Parse the list + $requestable = $this->rLib->call('getAvailableTechs', $this->player); + $rawList = explode('#', $requests); + $list = array(); + foreach ($rawList as $techID) { + $techID = (int) $techID; + if (in_array($techID, $list) || !in_array($techID, $requestable)) { + continue; + } + array_push($list, $techID); + } + + // Make sure the requests have been updated + $this->aLib->call('setTechRequests', $this->player, $list); + return $this->__requestsPage(); + } + + + /*************************************************************************************** + * Alliance tech list page * + ***************************************************************************************/ + + public function __allianceList() { + // Get the list itself + $aList = $this->aLib->call('getTechList', $this->alliance, $this->hasRequests); + + // Generate the output string + $result = array(count($aList)); + foreach ($aList as $player => $techData) { + array_push($result, "$player#{$techData['vacation']}#{$techData['submitted']}#" + . "{$techData['restrict']}#" . count($techData['list']) . "#" + . utf8entities($this->pLib->call('getName', $player))); + + foreach ($techData['list'] as $tech => $status) { + array_push($result, "$tech#$status"); + } + + if (is_null($techData['requests'])) { + array_push($result, ""); + } else { + array_push($result, join('#', $techData['requests'])); + } + } + + return join("\n", $result); + } + + public function getAllianceList($oldMD5 = "") { + // Initialise page + $this->init(); + $page = $this->currentPage('ViewList'); + if ($page != 'ViewList') { + return "FU"; + } + + $rVal = $this->__allianceList(); + $newMD5 = md5($rVal); + $nTechs = prefs::get("beta5/ttTechs", 10); + $nPlayers = prefs::get("beta5/ttPlayers", 3); + return ($oldMD5 == $newMD5) ? "NC" : "$newMD5\n$nTechs#$nPlayers\n$rVal"; + } + + public function setListOptions($playersPerPage, $techsPerPage) { + // Initialise page + $this->init(); + $page = $this->currentPage('ViewList'); + if ($page != 'ViewList') { + return; + } + + $ppp = min(10, max(2, (int) $playersPerPage)); + prefs::set("beta5/ttPlayers", $ppp); + + $tpp = min(8, max(1, floor((int) $techsPerPage / 10))) * 10; + prefs::set("beta5/ttTechs", $tpp); + } + + + /*************************************************************************************** + * Order viewing and management * + ***************************************************************************************/ + + private function __getOrders() { + // Get list of orders + $orders = $this->aLib->call('getTechOrders', $this->alliance, $this->hasRequests); + + // Generate output + $result = array(); + foreach ($orders as $player => $pRecord) { + $pName = $this->pLib->call('getName', $player); + array_push($result, "$player#{$pRecord['sendTo']}#{$pRecord['sendTech']}" + ."#{$pRecord['sendSub']}#{$pRecord['sendDone']}" + . "#{$pRecord['recvFrom']}#{$pRecord['recvTech']}" + . "#{$pRecord['recvSub']}#{$pRecord['recvDone']}#" . utf8entities($pName)); + } + + return join("\n", $result); + } + + public function getCurrentOrders($oldMD5 = "") { + // Initialise page + $this->init(); + $page = $this->currentPage('VOrders'); + if ($page != 'VOrders') { + return; + } + + $rVal = $this->__getOrders(); + $newMD5 = md5($rVal); + return ($oldMD5 == $newMD5) ? "NC" : "$newMD5\n$rVal"; + } + + + public function getChangeOrdersData($oldMD5 = "") { + // Initialise page + $this->init(); + $page = $this->currentPage('SOrders'); + if ($page != 'SOrders') { + return "FU"; + } + + list($lastSet, $nextSet) = $this->aLib->call('getLatestTechOrders', $this->alliance); + $rVal = "$lastSet#$nextSet"; + if ($nextSet == 0) { + $rVal .= "\n" . $this->__allianceList(); + } + + $newMD5 = md5($rVal); + return ($oldMD5 == $newMD5) ? "NC" : "$newMD5\n$rVal"; + } + + + public function submitOrders($orderString) { + // Initialise page + $this->init(); + $page = $this->currentPage('SOrders'); + if ($page != 'SOrders') { + return "FU"; + } + + // If the order string is not empty, extract orders + $pfx = ''; + if ($orderString != '') { + $oList = explode('!', $orderString); + $orders = array(); + foreach ($oList as $oText) { + $order = explode('#', $oText); + if (count($order) != 3) { + $pfx = "ERR"; + break; + } + + $order[0] = (int)$order[0]; + $order[1] = (int)$order[1]; + $order[2] = (int)$order[2]; + + $id = array_shift($order); + if (array_key_exists($id, $orders)) { + $pfx = "ERR"; + break; + } + $orders[$id] = $order; + } + if ($pfx == '') { + $pfx = $this->aLib->call('submitTechOrders', $this->alliance, $orders) ? '' : 'ERR'; + } + } else { + $pfx = 'ERR'; + } + + list($lastSet, $nextSet) = $this->aLib->call('getLatestTechOrders', $this->alliance); + $rVal = "$lastSet#$nextSet"; + if ($nextSet == 0) { + $rVal .= "\n" . $this->__allianceList(); + } + $pfx .= md5($rVal); + return "$pfx\n$rVal"; + } + + + /*************************************************************************************** + * Main page handler * + ***************************************************************************************/ + + public function handle($input) { + $_SESSION[game::sessName()]['tt_list'] = false; + $this->output = "techtrade"; + $this->data = $this->getGeneralInfo(); + } + +} + + +?> diff --git a/scripts/site/beta5/handlers/ticks.inc b/scripts/site/beta5/handlers/ticks.inc new file mode 100644 index 0000000..914d68a --- /dev/null +++ b/scripts/site/beta5/handlers/ticks.inc @@ -0,0 +1,32 @@ + 'readInitString()', + 'func' => array('updateTicks') + ); + + function updateTicks() + { + $tickLib = $this->game->getLib('main'); + $tinf = $tickLib->call('getTicks', getLanguage()); + $now = time(); + $dt = array($now); + foreach ($ticks as $tid => $td) + if ($td['game']) + array_push($dt, "$tid#" . $td['last']); + return join('#', $dt); + } + + function handle($input) + { + $lang = getLanguage(); + $tickLib = $this->game->getLib('main'); + $this->data = $tickLib->call('getTicks', getLanguage()); + $this->output = "ticks"; + } +} + +?> diff --git a/scripts/site/beta5/handlers/universe.inc b/scripts/site/beta5/handlers/universe.inc new file mode 100644 index 0000000..088765d --- /dev/null +++ b/scripts/site/beta5/handlers/universe.inc @@ -0,0 +1,31 @@ + array('getInformation'), + 'init' => 'initPage();' + ); + + public function getInformation() { + $universe = $this->game->action('getUniverseOverview', + $_SESSION[game::sessName()]['player'], getLanguage() + ); + + $ticks = array(); + foreach ($universe['ticks'] as $tick) { + array_push($ticks, join('#', $tick)); + } + + $main = array(join('#', $universe['summary']), join('#', $universe['rankings']), time()); + + return join("\n", array_merge($main, $ticks)); + } + + public function handle($input) { + $this->data = $this->getInformation(); + $this->output = "universe"; + } +} + +?> diff --git a/scripts/site/beta5/layout/classic/ajax.inc b/scripts/site/beta5/layout/classic/ajax.inc new file mode 100644 index 0000000..dcbebb0 --- /dev/null +++ b/scripts/site/beta5/layout/classic/ajax.inc @@ -0,0 +1,19 @@ +getLib('beta5/player'); + $mLib = input::$game->getLib('beta5/msg'); + $player = $_SESSION[game::sessName()]['player']; + + $pi = $pLib->call('get', $player); + $newMsg = ($mLib->call('getNew', $player) > 0) ? 1 : 0; + return time() . "#{$pi['name']}#{$pi['cash']}#$newMsg#{$pi['alliance']}"; +} + + +return array( + "func" => array('getHeaderData'), + "init" => "thmcls_writeHeader(document.getElementById('thm-hdr-init').value);" +); + +?> diff --git a/scripts/site/beta5/layout/classic/footer.en.inc b/scripts/site/beta5/layout/classic/footer.en.inc new file mode 100644 index 0000000..e69de29 diff --git a/scripts/site/beta5/layout/classic/header.en.inc b/scripts/site/beta5/layout/classic/header.en.inc new file mode 100644 index 0000000..cdbbfac --- /dev/null +++ b/scripts/site/beta5/layout/classic/header.en.inc @@ -0,0 +1,72 @@ +getLib('main/account'); +$players = $game->getLib('beta5/player'); +$player = $_SESSION[game::sessName()]['player']; + +?> +
    + + + + +
    + + + +
    >Player call('isAdmin', $_SESSION['userid'])) { + echo "Administration"; +} else { + echo " "; +} + +?>>Server time:
    + + + + + + + + + + + + + + + + + + + + + + +

    Overview

    Fleets

    Alliance

    Messages

    Planets

    Research

    Marketplace

    Forums

    Money

    Beacons

    Maps

    Rankings

    Enemies

    Allies

    Preferences

    Log out

    + + + +
    >Current funds: call('get', $player); +if ($accounts->call('getQuitCountdown', $_SESSION['userid'])) { + echo "CLOSING ACCOUNT"; +} else if (!is_null($pinf['qts'])) { + echo "LEAVING GAME"; +} else if ($players->call('isOnVacation', $player)) { + echo "ON VACATION"; +} else if ($players->call('getProtectionLevel', $player)) { + echo "UNDER PROTECTION"; +} else { + echo " "; +} + +?>Game: text?>
    diff --git a/scripts/site/beta5/layout/cripes/ajax.inc b/scripts/site/beta5/layout/cripes/ajax.inc new file mode 100644 index 0000000..86648b8 --- /dev/null +++ b/scripts/site/beta5/layout/cripes/ajax.inc @@ -0,0 +1,19 @@ +getLib('beta5/player'); + $mLib = input::$game->getLib('beta5/msg'); + $player = $_SESSION[game::sessName()]['player']; + + $pi = $pLib->call('get', $player); + $newMsg = ($mLib->call('getNew', $player) > 0) ? 1 : 0; + return time() . "#{$pi['name']}#{$pi['cash']}#$newMsg#{$pi['alliance']}"; +} + + +return array( + "func" => array('getHeaderData'), + "init" => "thmcrp_writeHeader(document.getElementById('thm-hdr-init').value);" +); + +?> diff --git a/scripts/site/beta5/layout/cripes/footer.en.inc b/scripts/site/beta5/layout/cripes/footer.en.inc new file mode 100644 index 0000000..79fc534 --- /dev/null +++ b/scripts/site/beta5/layout/cripes/footer.en.inc @@ -0,0 +1 @@ +
    [...] if you think this game is a 'copy' of any other game, then you are mistaken, and you should seek psycological help [...] -- El Christoph
    diff --git a/scripts/site/beta5/layout/cripes/header.en.inc b/scripts/site/beta5/layout/cripes/header.en.inc new file mode 100644 index 0000000..4c78495 --- /dev/null +++ b/scripts/site/beta5/layout/cripes/header.en.inc @@ -0,0 +1,102 @@ +output; + return " 1 ? " colspan='$colspan'" : "") . ($class == "" ? "" : " class='$class'") + . ($id == "" ? "" : " id='$id'") + . " style='width: $width' onclick='location.href=\"$name\"'>$title\n"; +} + +$game = input::$game; +$accounts = $game->getLib('main/account'); +$players = $game->getLib('beta5/player'); +$player = $_SESSION[game::sessName()]['player']; + +?> +
    +
    + + + + +call('isAdmin', $_SESSION['userid'])) { +?> + + + + + + + + + + + + + + + + + + + + + + + + + + ", 2, '180px', 'crpmsg', 'msgmenu')?> + ", 2, '180px', 'crpcash')?> + + + + + + + + +
    Research   Server time:
    call('get', $player); +if ($accounts->call('getQuitCountdown', $_SESSION['userid'])) { + echo "CLOSING ACCOUNT"; +} else if (!is_null($pinf['qts'])) { + echo "LEAVING GAME"; +} else if ($players->call('isOnVacation', $player)) { + echo "ON VACATION"; +} else if ($players->call('getProtectionLevel', $player)) { + echo "UNDER PROTECTION"; +} else { + echo " "; +} + +?>Account - text?>
    +
    diff --git a/scripts/site/beta5/layout/default/ajax.inc b/scripts/site/beta5/layout/default/ajax.inc new file mode 100644 index 0000000..0f0d9cb --- /dev/null +++ b/scripts/site/beta5/layout/default/ajax.inc @@ -0,0 +1,40 @@ +getLib('beta5/player'); + $mLib = input::$game->getLib('beta5/msg'); + $player = $_SESSION[game::sessName()]['player']; + + $pi = $pLib->call('get', $player); + $newMsg = ($mLib->call('getNew', $player) > 0) ? 1 : 0; + return time() . "#{$pi['name']}#{$pi['cash']}#$newMsg#{$pi['alliance']}"; +} + +function thm_getHeaderPList() { + $pLib = input::$game->getLib('beta5/player'); + $pl = $pLib->call('getPlanets', $_SESSION[game::sessName()]['player']); + + $s = ""; + foreach ($pl as $id => $n) { + $s .= ($s==""?"":"\n")."$id#$n"; + } + return $s; +} + +function thm_getHeaderFolders() { + $mLib = input::$game->getLib('beta5/msg'); + $fl = $mLib->call('getCustomFolders', $_SESSION[game::sessName()]['player']); + $s = ""; + foreach ($fl as $id => $n) { + $s .= ($s == "" ? "" : "\n") . "$id#$n"; + } + return $s; +} + + +return array( + "func" => array('getHeaderData','getHeaderPList','getHeaderFolders'), + "init" => "thmdef_writeHeader(document.getElementById('thm-hdr-init').value);thmdef_writePlanets(document.getElementById('thm-plist-init').value);thmdef_initFolders();" +); + +?> diff --git a/scripts/site/beta5/layout/default/footer.en.inc b/scripts/site/beta5/layout/default/footer.en.inc new file mode 100644 index 0000000..e69de29 diff --git a/scripts/site/beta5/layout/default/header.en.inc b/scripts/site/beta5/layout/default/header.en.inc new file mode 100644 index 0000000..312466f --- /dev/null +++ b/scripts/site/beta5/layout/default/header.en.inc @@ -0,0 +1,239 @@ +
    • $t
        \n"; + $b5menu ++; +} + +function menuTopLevelEnd() { + echo "
    \n"; +} + +function menuTopLevelEntry($t, $l, $tt = null) { + $te = is_string($tt) ? tooltip($tt) : ""; + $t = preg_replace('/ /', ' ', $t); + echo "\n"; +} + +function menuEntry($t, $l, $tt = null) { + $te = is_string($tt) ? tooltip($tt) : ""; + echo "
  • $t
  • \n"; +} + +function menuSubBegin($t, $l, $tt = null, $sid = null) { + $te = is_string($tt) ? tooltip($tt) : ""; + $id = ($sid == "") ? "" : " id='$sid'"; + $t = preg_replace('/ /', ' ', $t); + echo "
  • $t
      \n"; +} + +function menuSubEnd() { + echo "
  • \n"; +} + +function drawIcon($n, $d) { + $src = getStatic("beta5/pics/icons/$n." . (input::$IE ? 'gif' : 'png')); + echo "\"".'; +} + +function drawTitle() { + $n = handler::$h->output; + $c = prefs::get('main/colour', 'purple'); + $src = getStatic("beta5/pics/ttl/def/en/$c/$n.gif"); + if (!is_null($src)) { + echo "\"$n\""; + } +} + + +$game = input::$game; +$accounts = $game->getLib('main/account'); +$players = $game->getLib('beta5/player'); +$alliance = $game->getLib('beta5/alliance'); + +$player = $_SESSION[game::sessName()]['player']; +$pInfo = $players->call('get', $player); +if ($pInfo['aid']) { + $privileges = $alliance->call('getPrivileges', $player); + $techTrade = ($privileges['tech_trade'] > 0); +} else { + $techTrade = false; +} +$protected = $players->call('getProtectionLevel', $player); + + +?> +
    + + + +
    + + + +
    >Player >Current funds: >Server Time:
    + + + +
    +call('isRestrained', $player)) { + menuEntry('Marketplace', 'market', 'Sell stuff to other players and buy other stuff from them.'); + } + menuEntry('Enemies', 'enemylist', 'Manage your enemy list.'); + menuEntry('Trusted Allies', 'allies', 'Manage your trusted allies list.'); + menuTopLevelEnd(); + + menuTopLevelBegin('Universe', 'universe', 'Overview of the game universe.'); + if (input::$IE) { + menuEntry('Maps', 'map', 'View the maps of the game universe.'); + menuEntry('Rankings', 'rank', 'View the rankings of players and alliances.'); + } else { + menuSubBegin('Maps', 'map', 'View the maps of the game universe.'); + menuEntry('Planets', 'map?menu=p', 'View the planets in the current universe.'); + menuEntry('Alliances', 'map?menu=a', 'View planets belonging to alliances in the current universe.'); + menuEntry('Listing', 'map?menu=l', 'View a listing of the planets in the game universe.'); + menuSubEnd(); + menuSubBegin('Rankings', 'rank', 'View the rankings of players and alliances.'); + menuEntry('Summary', 'rank?p=s', 'Overview of your current ranking.'); + menuEntry('General', 'rank?p=g', 'Display general player rankings.'); + menuEntry('Detailed', 'rank?p=d', 'Display detailed player rankings.'); + menuEntry('Alliance', 'rank?p=a', 'Display alliance rankings.'); + menuEntry('Overall', 'rank?p=o', 'Display overall player rankings.'); + menuEntry('Damage', 'rank?p=i', 'Display inflicted damage rankings.'); + menuSubEnd(); + } + menuEntry('Ticks', 'ticks', 'Display details about the ticks.'); + menuEntry('Manual', 'manual', 'The manual. Newbies, please read it. Seriously.'); + menuTopLevelEnd(); + + menuTopLevelBegin('Communications', 'comms', 'An overview of your communications with other players'); + menuEntry('Compose', 'message?a=c', 'Compose a new private message.'); + menuEntry('Inbox', 'message?a=f&f=I', 'View the contents of your inbox.'); + if (input::$IE) { + menuEntry('Transmissions', 'message?a=f&f=T', 'View internal transmissions'); + menuEntry('Folders', 'message?a=mf', 'Manage your custom folders.'); + } else { + menuSubBegin('Folders', 'message?a=mf', 'Manage your custom folders.', 'jsfmenu'); + menuEntry('Transmissions', 'message?a=f&f=T', 'View internal transmissions'); + menuEntry('Outbox', 'message?a=f&f=O', 'View the messages you sent.'); + menuSubEnd(); + } + menuEntry('Forums', 'forums?cmd=o', 'Access the forums.'); + menuTopLevelEnd(); + + if ($accounts->call('isLeech', $_SESSION['userid'])) { + menuTopLevelEntry('Contribute!', makeLink('contrib', 'main'), 'Contribute to LegacyWorlds!'); + } + if ($accounts->call('isAdmin', $_SESSION['userid'])) { + menuTopLevelEntry('Admin', 'admin', 'Administrative tools'); + } + +?> +
     >>>>>
    +
    +
    Game: text?>
    + + +call('get', $player); +if ($accounts->call('getQuitCountdown', $_SESSION['userid'])) { +?> + + + + + + + +call('isOnVacation', $player)) { +?> + + + + + + + + + + + +
    CLOSING ACCOUNTCLOSING ACCOUNTLEAVING GAMELEAVING GAMEON VACATIONON VACATIONUNDER PROTECTIONUNDER PROTECTION
    +
    +
    diff --git a/scripts/site/beta5/layout/invert/ajax.inc b/scripts/site/beta5/layout/invert/ajax.inc new file mode 100644 index 0000000..5cfc069 --- /dev/null +++ b/scripts/site/beta5/layout/invert/ajax.inc @@ -0,0 +1,40 @@ +getLib('beta5/player'); + $mLib = input::$game->getLib('beta5/msg'); + $player = $_SESSION[game::sessName()]['player']; + + $pi = $pLib->call('get', $player); + $newMsg = ($mLib->call('getNew', $player) > 0) ? 1 : 0; + return time() . "#{$pi['name']}#{$pi['cash']}#$newMsg#{$pi['alliance']}"; +} + +function thm_getHeaderPList() { + $pLib = input::$game->getLib('beta5/player'); + $pl = $pLib->call('getPlanets', $_SESSION[game::sessName()]['player']); + + $s = ""; + foreach ($pl as $id => $n) { + $s .= ($s==""?"":"\n")."$id#$n"; + } + return $s; +} + +function thm_getHeaderFolders() { + $mLib = input::$game->getLib('beta5/msg'); + $fl = $mLib->call('getCustomFolders', $_SESSION[game::sessName()]['player']); + $s = ""; + foreach ($fl as $id => $n) { + $s .= ($s == "" ? "" : "\n") . "$id#$n"; + } + return $s; +} + + +return array( + "func" => array('getHeaderData','getHeaderPList','getHeaderFolders'), + "init" => "thminv_writeHeader(document.getElementById('thm-hdr-init').value);thminv_writePlanets(document.getElementById('thm-plist-init').value);thminv_initFolders();" +); + +?> diff --git a/scripts/site/beta5/layout/invert/footer.en.inc b/scripts/site/beta5/layout/invert/footer.en.inc new file mode 100644 index 0000000..e69de29 diff --git a/scripts/site/beta5/layout/invert/header.en.inc b/scripts/site/beta5/layout/invert/header.en.inc new file mode 100644 index 0000000..c1f0ba7 --- /dev/null +++ b/scripts/site/beta5/layout/invert/header.en.inc @@ -0,0 +1,239 @@ +
    • $t
        \n"; + $b5menu ++; +} + +function menuTopLevelEnd() { + echo "
    \n"; +} + +function menuTopLevelEntry($t, $l, $tt = null) { + $te = is_string($tt) ? tooltip($tt) : ""; + $t = preg_replace('/ /', ' ', $t); + echo "\n"; +} + +function menuEntry($t, $l, $tt = null) { + $te = is_string($tt) ? tooltip($tt) : ""; + echo "
  • $t
  • \n"; +} + +function menuSubBegin($t, $l, $tt = null, $sid = null) { + $te = is_string($tt) ? tooltip($tt) : ""; + $id = ($sid == "") ? "" : " id='$sid'"; + $t = preg_replace('/ /', ' ', $t); + echo "
  • $t
      \n"; +} + +function menuSubEnd() { + echo "
  • \n"; +} + +function drawIcon($n, $d) { + $src = getStatic("beta5/pics/icons/$n." . (input::$IE ? 'gif' : 'png')); + echo "\"".'; +} + + +$game = input::$game; +$accounts = $game->getLib('main/account'); +$players = $game->getLib('beta5/player'); +$alliance = $game->getLib('beta5/alliance'); + +$player = $_SESSION[game::sessName()]['player']; +$pInfo = $players->call('get', $player); +if ($pInfo['aid']) { + $privileges = $alliance->call('getPrivileges', $player); + $techTrade = ($privileges['tech_trade'] > 0); +} else { + $techTrade = false; +} +$protected = $players->call('getProtectionLevel', $player); + + +?> +
    + + + +
    + + + +
    +call('isRestrained', $player)) { + menuEntry('Marketplace', 'market', 'Sell stuff to other players and buy other stuff from them.'); + } + menuEntry('Enemies', 'enemylist', 'Manage your enemy list.'); + menuEntry('Trusted Allies', 'allies', 'Manage your trusted allies list.'); + menuTopLevelEnd(); + + menuTopLevelBegin('Universe', 'universe', 'Overview of the game universe.'); + if (input::$IE) { + menuEntry('Maps', 'map', 'View the maps of the game universe.'); + menuEntry('Rankings', 'rank', 'View the rankings of players and alliances.'); + } else { + menuSubBegin('Maps', 'map', 'View the maps of the game universe.'); + menuEntry('Planets', 'map?menu=p', 'View the planets in the current universe.'); + menuEntry('Alliances', 'map?menu=a', 'View planets belonging to alliances in the current universe.'); + menuEntry('Listing', 'map?menu=l', 'View a listing of the planets in the game universe.'); + menuSubEnd(); + menuSubBegin('Rankings', 'rank', 'View the rankings of players and alliances.'); + menuEntry('Summary', 'rank?p=s', 'Overview of your current ranking.'); + menuEntry('General', 'rank?p=g', 'Display general player rankings.'); + menuEntry('Detailed', 'rank?p=d', 'Display detailed player rankings.'); + menuEntry('Alliance', 'rank?p=a', 'Display alliance rankings.'); + menuEntry('Overall', 'rank?p=o', 'Display overall player rankings.'); + menuEntry('Damage', 'rank?p=i', 'Display inflicted damage rankings.'); + menuSubEnd(); + } + menuEntry('Ticks', 'ticks', 'Display details about the ticks.'); + menuEntry('Manual', 'manual', 'The manual. Newbies, please read it. Seriously.'); + menuTopLevelEnd(); + + menuTopLevelBegin('Communications', 'comms', 'An overview of your communications with other players'); + menuEntry('Compose', 'message?a=c', 'Compose a new private message.'); + menuEntry('Inbox', 'message?a=f&f=I', 'View the contents of your inbox.'); + if (input::$IE) { + menuEntry('Transmissions', 'message?a=f&f=T', 'View internal transmissions'); + menuEntry('Folders', 'message?a=mf', 'Manage your custom folders.'); + } else { + menuSubBegin('Folders', 'message?a=mf', 'Manage your custom folders.', 'jsfmenu'); + menuEntry('Transmissions', 'message?a=f&f=T', 'View internal transmissions'); + menuEntry('Outbox', 'message?a=f&f=O', 'View the messages you sent.'); + menuSubEnd(); + } + menuEntry('Forums', 'forums?cmd=o', 'Access the forums.'); + menuTopLevelEnd(); + + if ($accounts->call('isLeech', $_SESSION['userid'])) { + menuTopLevelEntry('Contribute!', makeLink('contrib', 'main'), 'Contribute to LegacyWorlds!'); + } + if ($accounts->call('isAdmin', $_SESSION['userid'])) { + menuTopLevelEntry('Admin', 'admin', 'Administrative tools'); + } + +?> +
     >>>>>
    + + + +
    >Player >Current funds: >Server Time:
    +
    +
    Game: text?>
    + + +output; + $c = prefs::get('main/colour', 'red'); + $src = getStatic("beta5/pics/ttl/def/en/$c/$n.gif"); + if (!is_null($src)) { + echo "\"$n\""; + } +} + +$pinf = $players->call('get', $player); +if ($accounts->call('getQuitCountdown', $_SESSION['userid'])) { +?> + + + + + + + +call('isOnVacation', $player)) { +?> + + + + + + + + + + + +
    CLOSING ACCOUNTCLOSING ACCOUNTLEAVING GAMELEAVING GAMEON VACATIONON VACATIONUNDER PROTECTIONUNDER PROTECTION
    +
    +
    diff --git a/scripts/site/beta5/output/admin.en.inc b/scripts/site/beta5/output/admin.en.inc new file mode 100644 index 0000000..a6657af --- /dev/null +++ b/scripts/site/beta5/output/admin.en.inc @@ -0,0 +1,8 @@ +

    Legacy Worlds Administrative Tools

    + diff --git a/scripts/site/beta5/output/admin/en/acmgmt.inc b/scripts/site/beta5/output/admin/en/acmgmt.inc new file mode 100644 index 0000000..16d59f6 --- /dev/null +++ b/scripts/site/beta5/output/admin/en/acmgmt.inc @@ -0,0 +1,61 @@ +
    + +

    Select an account

    +

    + Name of the account to manage: + + +Account not found."; +} elseif ($data['admin']) { +?> +
    Administrative account, no changes allowed. + +

    +

    Manage account

    +

    + Account status: +

    +

    + Account email address: + + +Address changed"; break; + case 1: print "
    Invalid address"; break; + case 2: print "
    Address already used by account " + . utf8entities($data['oacc']) . ""; break; + case 3: print "
    Database error"; break; +endswitch; +?> +

    +

    + Account password: + + +Password updated"; break; + case 1: print "
    Password too short (min 4 characters)"; break; + case 2: print "
    Database error"; break; +endswitch; +} + +?> +

    + +

    + Confirmation code: +

    + +
    diff --git a/scripts/site/beta5/output/admin/en/kicklst.inc b/scripts/site/beta5/output/admin/en/kicklst.inc new file mode 100644 index 0000000..a084f45 --- /dev/null +++ b/scripts/site/beta5/output/admin/en/kicklst.inc @@ -0,0 +1,87 @@ +

    Pending kick requests

    + + + + + + + + +"; +} else { + foreach ($data['lists'][0] as $r) { + $pn = handler::$h->accounts->call('getUser', $r['to_kick']); + $an = handler::$h->accounts->call('getUser', $r['requested_by']); + echo "\n"; + } +} + +?> +
    Account nameRequested byDateReasonActions
    No pending kick requests
    " . utf8entities($pn['name']) . "" . utf8entities($an['name']) + . "" . gmstrftime("%Y-%m-%d %H:%M:%S", $r['requested_at']) + . "" . utf8entities($r['reason']) . ""; + if ($r['requested_by'] == $_SESSION['userid']) { + echo "Cancel"; + } else { + echo "Reject - Kick player"; + } + echo "
    + +

    Accepted kick requests

    + + + + + + + + +"; +} else { + foreach ($data['lists'][1] as $r) { + $pn = handler::$h->accounts->call('getUser', $r['to_kick']); + $an = handler::$h->accounts->call('getUser', $r['requested_by']); + $vn = handler::$h->accounts->call('getUser', $r['examined_by']); + echo "\n"; + } +} + +?> +
    Account nameRequested byDateReasonValidated by
    No accepted kick requests
    " . utf8entities($pn['name']) . "" . utf8entities($an['name']) + . "" . gmstrftime("%Y-%m-%d %H:%M:%S", $r['requested_at']) + . "" . utf8entities($r['reason']) . "" + . utf8entities($vn['name']) . "
    + +

    Rejected kick requests

    + + + + + + + + +"; +} else { + foreach ($data['lists'][2] as $r) { + $pn = handler::$h->accounts->call('getUser', $r['to_kick']); + $an = handler::$h->accounts->call('getUser', $r['requested_by']); + $vn = handler::$h->accounts->call('getUser', $r['examined_by']); + echo "\n"; + } +} + +?> +
    Account nameRequested byDateReasonRejected by
    No rejected requests
    " . utf8entities($pn['name']) . "" . utf8entities($an['name']) + . "" . gmstrftime("%Y-%m-%d %H:%M:%S", $r['requested_at']) + . "" . utf8entities($r['reason']) . "" + . utf8entities($vn['name']) . "
    diff --git a/scripts/site/beta5/output/admin/en/kickreq.inc b/scripts/site/beta5/output/admin/en/kickreq.inc new file mode 100644 index 0000000..138307b --- /dev/null +++ b/scripts/site/beta5/output/admin/en/kickreq.inc @@ -0,0 +1,49 @@ +

    Request a kick

    +

    + Please indicate the name of the player to be kicked as well as the reason why the player should be kicked in the form below.
    + The player will only be kicked after another administrator confirms it. +

    +"; + switch($data['error']) : + case 1: + echo "Account not found."; + break; + case 2: + echo "Please specify a reason (>10 characters)."; + break; + case 3: + echo "This player is already on the list of pending kicks."; + break; + default: + echo "Unknown error."; + break; + endswitch; + echo "

    "; +} + +?> +
    + + + + + + + + + + + + + + + + +
    Player to kick:" />
    Reason:
     
      + + +
    +
    diff --git a/scripts/site/beta5/output/admin/en/lkcat.inc b/scripts/site/beta5/output/admin/en/lkcat.inc new file mode 100644 index 0000000..9c7fe64 --- /dev/null +++ b/scripts/site/beta5/output/admin/en/lkcat.inc @@ -0,0 +1,47 @@ +

    category

    +
    + + + + + + + + + + + +\n"; +} +?> + + + + +\n"; +} +?> + + + +
    Name:
     "; + switch ($data['error']) : + case 1: echo "This title is too short"; break; + case 2: echo "This title is too long"; break; + case 3: echo "There is already such a category"; break; + endswitch; + echo "
    Description:
     This description is too short.
    + category" style="color:white;border-color:white;background-color:green" /> + + +
    + +
    diff --git a/scripts/site/beta5/output/admin/en/lklist.inc b/scripts/site/beta5/output/admin/en/lklist.inc new file mode 100644 index 0000000..16ea088 --- /dev/null +++ b/scripts/site/beta5/output/admin/en/lklist.inc @@ -0,0 +1,42 @@ +Links and categories"; +if (!count($data)) { + echo "

    There are no categories. Click here to add one.

    "; + return; +} + +$hasAccount = (is_array($_SESSION) && !is_null($_SESSION['userid'])); +for ($i=0;$i" . utf8entities($data[$i]['title']) . ""; + if (!is_null($data[$i]['description'])) { + echo "
    " . preg_replace('/\n/', '
    ', utf8entities($data[$i]['description'])); + } + echo "
    Add link - Edit"; + echo " - Delete"; + if ($i > 0) { + echo " - Move up"; + } + if ($i < count($data) - 1) { + echo " - Move down"; + } + echo "

    "; + + echo "
      "; + if (!count($data[$i]['links'])) { + echo "
    • No links in this category.
    • "; + } else { + foreach ($data[$i]['links'] as $l) { + echo "
    • " . utf8entities($l['title']) . ""; + if (!is_null($l['description'])) { + echo "
      " . preg_replace('/\n/', '
      ', utf8entities($l['description'])); + } + echo "
      Edit - Delete"; + echo "
       
    • "; + } + } + echo "
    "; +} +echo "

    Add category

    "; + +?> diff --git a/scripts/site/beta5/output/admin/en/lklk.inc b/scripts/site/beta5/output/admin/en/lklk.inc new file mode 100644 index 0000000..1fd1b39 --- /dev/null +++ b/scripts/site/beta5/output/admin/en/lklk.inc @@ -0,0 +1,65 @@ +

    link

    +
    + + + + + + + + + + + + + +\n"; +} +?> + + + + += 3 && $data['error'] <= 5) { + echo "\n"; +} +?> + + + + +\n"; +} +?> + + + +
    Name:
     "; + switch ($data['error']) : + case 1: echo "This title is too short"; break; + case 2: echo "This title is too long"; break; + endswitch; + echo "
    URL:
     "; + switch ($data['error']) : + case 3: echo "This URL is invalid"; break; + case 4: echo "The server could not be found"; break; + case 5: echo "The URL is already in the DB"; break; + endswitch; + echo "
    Description:
     This description is too short.
    + link" style="color:white;border-color:white;background-color:green" /> + + +
    + +
    diff --git a/scripts/site/beta5/output/admin/en/lkrep.inc b/scripts/site/beta5/output/admin/en/lkrep.inc new file mode 100644 index 0000000..6db150f --- /dev/null +++ b/scripts/site/beta5/output/admin/en/lkrep.inc @@ -0,0 +1,19 @@ +Broken links report"; +if (!count($data)) { + echo "

    No broken links have been reported.

    "; + return; +} + +echo ""; + +?> diff --git a/scripts/site/beta5/output/admin/en/lksadd.inc b/scripts/site/beta5/output/admin/en/lksadd.inc new file mode 100644 index 0000000..10317bb --- /dev/null +++ b/scripts/site/beta5/output/admin/en/lksadd.inc @@ -0,0 +1,31 @@ +

    Accept submission

    +

    + You are about to accept the following submission: +

    +
      +
    • Title:
    • +
    • URL:
    • +Description: " . utf8entities($data['sub']['description']) . "\n"; +} +?> +
    +
    + + + +

    + Select the category into which the link should be added: +
    + + +

    +
    diff --git a/scripts/site/beta5/output/admin/en/lksub.inc b/scripts/site/beta5/output/admin/en/lksub.inc new file mode 100644 index 0000000..312c048 --- /dev/null +++ b/scripts/site/beta5/output/admin/en/lksub.inc @@ -0,0 +1,25 @@ +Submitted links"; +if (!count($data)) { + echo "

    No links have been submitted.

    "; + return; +} + +echo "
      "; +foreach ($data as $link => $entries) { + echo "
    • Link to " . utf8entities($link) . ":
        "; + foreach ($entries as $entry) { + echo "
      • Submitted by {$entry['submitter']} as {$entry['title']}
        "; + if ($entry['description'] != '') { + echo "Description: {$entry['description']}"; + } else { + echo "No description"; + } + echo "
        Accept entry
      • "; + } + echo "
      • Delete entry
    • "; +} +echo "
    "; + +?> diff --git a/scripts/site/beta5/output/admin/en/main.inc b/scripts/site/beta5/output/admin/en/main.inc new file mode 100644 index 0000000..a26dbd2 --- /dev/null +++ b/scripts/site/beta5/output/admin/en/main.inc @@ -0,0 +1,30 @@ +

    Main administrative tools

    +

    + Manual update
    + Tool that updates the manual from the XML files on the server.
    + Warning: this tool is very resource-consuming; please don't run it unless you have a good reason to. +

    +

    + Account management
    + This tool allows operations such as changing an account's email address or password. +

    +

    + Request player kick
    + Request that a player be banned from the game. Another admin will have to validate your request.
    + Manage kicks
    + Display the list of pending, accepted and rejected kick requests, and validate pending requests. +

    +

    + Link management: Links & categories - Reports - Submissions
    + Different tools to manage the main page's links. +

    + +

    Beta 5 administrative tools

    +

    + Planet names
    + Planet names moderation tools to spot planets with insulting or "politically incorrect" names. +

    +

    + Admin spam
    + Sends a message to all players. +

    diff --git a/scripts/site/beta5/output/admin/en/pnlist.inc b/scripts/site/beta5/output/admin/en/pnlist.inc new file mode 100644 index 0000000..ff33dbb --- /dev/null +++ b/scripts/site/beta5/output/admin/en/pnlist.inc @@ -0,0 +1,98 @@ +" . utf8entities($text) . ""; +} + +function printPrompt($id) { + $prompts = array( + "You are about to send this planet a warning regarding its name.", + "You are about to validate this planet name.", + "You are about to reset this planet." + ); + return " onclick='return confirm(\"{$prompts[$id]}\\nPlease confirm.\")'"; +} + +?> +

    Planet names

    +
    + + + + + + + + + +No planets in this list.\n"; +} + +?> + +
    List: "; + for ($i=0;$i<$data['pages'];$i++) { + echo printOption($i, $i+1, $data['page']); + } + echo " / {$data['pages']}"; +} + +?>
    + + + + + + + + + + + + +
    Planet nameOwnerActions
    " . utf8entities($planet['oname']) . ""; + } +?> + Centre mapReset\n"; + } else { + echo " - Send warning\n"; + } + if ($data['mode'] == 'r') { + echo " - Validate\n"; + } + } elseif ($data['mode'] != 'p') { + echo " - Reset\n"; + if ($data['mode'] == 'w') { + echo " - Validate\n"; + } + } +?> +
    +
    diff --git a/scripts/site/beta5/output/admin/en/spam.inc b/scripts/site/beta5/output/admin/en/spam.inc new file mode 100644 index 0000000..839c6f0 --- /dev/null +++ b/scripts/site/beta5/output/admin/en/spam.inc @@ -0,0 +1,55 @@ +

    Send admin spam

    +

    + Type the spam in the form below. You can use forum tags inside the spam.
    + Use the "Preview" button to check on your spam before sending it.
    + If you need to send the spam to all players within all active games, check the "Send in all games" box. +

    +
    + + + + + +"; +} + +?> + + + + + + + + + + + + + + + + + +\n"; + echo " \n"; + echo " \n"; +} + +?> +
     
     " . $errs[$data['err']-1] + . " 
    Options: + /> +
     
      + + + +
       
    Preview - " . utf8entities($data['sub']) . "
    " . $data['prev'] . "
    +
    diff --git a/scripts/site/beta5/output/alliance.en.inc b/scripts/site/beta5/output/alliance.en.inc new file mode 100644 index 0000000..d72f2df --- /dev/null +++ b/scripts/site/beta5/output/alliance.en.inc @@ -0,0 +1,2 @@ + +
    diff --git a/scripts/site/beta5/output/allies.en.inc b/scripts/site/beta5/output/allies.en.inc new file mode 100644 index 0000000..f571845 --- /dev/null +++ b/scripts/site/beta5/output/allies.en.inc @@ -0,0 +1,21 @@ +
    + + + + + + + + + + + + +
       +

    Players who trust you

    +
     
    +
    Help
     
      +

    Trusted Allies blacklist

    +
     
    +
     
    + diff --git a/scripts/site/beta5/output/comms.en.inc b/scripts/site/beta5/output/comms.en.inc new file mode 100644 index 0000000..1d32780 --- /dev/null +++ b/scripts/site/beta5/output/comms.en.inc @@ -0,0 +1,25 @@ + + +
    +

    Private Messages

    +

    >Compose Message

    +

    Default Folders

    +

    + >Inbox:
    + >Internal Transmissions:
    + >Outbox: +

    +

    Custom Folders

    +

    +
    + >Manage Custom Folders +

    +
    +

    Forums

    +

    General forums

    +

    +
    +
    + Help +
    +
    diff --git a/scripts/site/beta5/output/diplomacy.en.inc b/scripts/site/beta5/output/diplomacy.en.inc new file mode 100644 index 0000000..6c144e4 --- /dev/null +++ b/scripts/site/beta5/output/diplomacy.en.inc @@ -0,0 +1,25 @@ + + + + + + +
    +

    Alliance

    +
    +
    +

    Scientific Assistance

    +

     

    +

    Allies & Enemies

    +

    +  
    + >Manage allies - >Manage enemies +

    +

    Private Messages

    +

    + >Messages:   (  new)
    + >Internal Transmissions:   (  new)
    + >Compose a message +

    +
    Help
    + diff --git a/scripts/site/beta5/output/empire.en.inc b/scripts/site/beta5/output/empire.en.inc new file mode 100644 index 0000000..f40ce79 --- /dev/null +++ b/scripts/site/beta5/output/empire.en.inc @@ -0,0 +1,73 @@ +
    + + + + + + +
    +

    Planets

    +

    Overview

    +

    + Planets owned:
    + Average happiness:
    + Average corruption:
    + Total population:
    + Average population:
    + Total factories:
    + Average factories:
    + Total turrets:
    + Average turrets:
    + + >Planet list +

    +

    Quick links

    +

    +

    Research

    +

    Budget

    +

    + Fundamental research: ( points/day)
    + Military research: ( points/day)
    + Civilian research: ( points/day)
    + >Manage research +

    +
    +
    +

    Money

    +

    + Total income: € 
    + Daily profits: € 
    + >Details ... +

    Fleets

    +

    Overview

    +

    + Total fleet power:
    + Fleet upkeep: €
    + Number of fleets:
    + Fleets engaged in battle:
    + >View fleets +

    +

    Fleets at home

    +

    + Number of fleets:
    + Fleets engaged in battle: +

    +

    Other fleets

    +

    + Fleets on foreign planets:
    + Fleets engaged in battle:
    + Moving fleets:
    + Fleets waiting in Hyperspace: +

    +

    Statistics

    +

    + GA ships:
    + Fighters:
    + Cruisers:
    + Battle cruisers:
    + Total ships: +

    +
    + Help +
    + diff --git a/scripts/site/beta5/output/enemylist.en.inc b/scripts/site/beta5/output/enemylist.en.inc new file mode 100644 index 0000000..e1d1a78 --- /dev/null +++ b/scripts/site/beta5/output/enemylist.en.inc @@ -0,0 +1,25 @@ +
    + + + + + + +
      +

    Player Enemy List

    +

    + New enemy: + name="nepl" id="nepl" value="" size="16" maxlength="15" /> + name="addpl" value="Add" onClick="addPlayer();return false" /> +

    +
    +
    +

    Alliance Enemy List

    +

    + New enemy: + name="neal" id="neal" value="" size="6" maxlength="5" /> + name="addal" value="Add" onClick="addAlliance();return false" /> +

    +
    +
    Help
    + diff --git a/scripts/site/beta5/output/fleets.en.inc b/scripts/site/beta5/output/fleets.en.inc new file mode 100644 index 0000000..33bcc60 --- /dev/null +++ b/scripts/site/beta5/output/fleets.en.inc @@ -0,0 +1,2 @@ + +
     
    diff --git a/scripts/site/beta5/output/forums.en.inc b/scripts/site/beta5/output/forums.en.inc new file mode 100644 index 0000000..926e752 --- /dev/null +++ b/scripts/site/beta5/output/forums.en.inc @@ -0,0 +1,72 @@ + + + + + + + + + + + + $cat) + { + $sel1 = ($mode == "C#$cid"); + $selL = ($mode == "L#$cid"); + $sel = $sel1 || $selL; + if (!$sel && substr($mode,0,4) == "F#".$cat['type']."#") + { + for ($i=0;!$sel&&$i' : ''; $ceb = $au ? " ($au)" : ''; + + echo ''; + echo "\n"; + + if ($sel) + foreach ($cat['forums'] as $f) + { + $au = $f['unread']; + $cb = $au ? '' : ''; $ceb = $au ? " ($au)" : ''; + $fid = "F#".$cat['type']."#".$f['id']; + $selF = ($mode == $fid); + echo ""; + echo " "; + echo "\n"; + } + + echo ''; + echo "\n"; + echo "\n"; + } + +?> + + + + + + +
     
    Forums
    Help
     >":""?>Overview":""?>
    >":""?>Latest messages":""?>
     
     $cb".($sel1?"":"").utf8entities($cat['title']); + echo ($sel1?"":"")."$ceb
     $cb".($selF?"":"").utf8entities($f['title']).($selF?"":"")."$ceb
     ".($selL?"":"").'Latest messages'.($selL?"":"")."
     
     >":""?>Search forums":""?>
     
    >Messages
    + diff --git a/scripts/site/beta5/output/forums/en/category.inc b/scripts/site/beta5/output/forums/en/category.inc new file mode 100644 index 0000000..50ccfe9 --- /dev/null +++ b/scripts/site/beta5/output/forums/en/category.inc @@ -0,0 +1,67 @@ + + + + + + +
    + + + +

    " >Mark forums as read
    +

    ', utf8entities($cat['desc'])); +else if ($cat['type'] == 'A') + echo "

    These are the forums of the alliance called ".utf8entities($cat['title'])."

    "; +else + echo " "; + +echo "

    \n"; + +if (count($cat['forums'])) +{ + echo "\n"; + echo ""; + echo "\n"; + for ($i=0;$i"; + $pic = config::$main['staticurl'] . "/beta5/pics/" . ($cat['forums'][$i]['unread'] > 0 ? 'un' : '') . 'read.gif'; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo "\n\n"; + } + echo "
     Forum nameTopicsPostsLast post
     FIXME"; + echo utf8entities($cat['forums'][$i]['title']) . "" . $cat['forums'][$i]['topics'] . "" . $cat['forums'][$i]['posts'] . " "; + $l = $cat['forums'][$i]['last']; + if (is_null($l)) + echo "No Posts"; + else + { + echo "Last post by " . utf8entities($l['author']) . "
    "; + echo preg_replace('/ /', ' ', gmstrftime("%H:%M:%S on %d/%m/%Y", $l['moment'])); + } + echo "

    "; + if ($cat['forums'][$i]['description'] != '') + echo preg_replace('/\n/', '
    ', utf8entities($cat['forums'][$i]['description'])); + else + echo " "; + echo "

    "; +} +else + echo "

    There are no forums in this category.

    "; + +?> +
    + diff --git a/scripts/site/beta5/output/forums/en/catnotfound.inc b/scripts/site/beta5/output/forums/en/catnotfound.inc new file mode 100644 index 0000000..e235cbf --- /dev/null +++ b/scripts/site/beta5/output/forums/en/catnotfound.inc @@ -0,0 +1,11 @@ + + + + + +
    +

    Category Not Found

    +

    The category you wish to view no longer exists.

    +
    diff --git a/scripts/site/beta5/output/forums/en/forum.inc b/scripts/site/beta5/output/forums/en/forum.inc new file mode 100644 index 0000000..baf5ab5 --- /dev/null +++ b/scripts/site/beta5/output/forums/en/forum.inc @@ -0,0 +1,146 @@ + + + + + + +
    + + + +

    &pg=" >Mark topics as read
    +" . preg_replace('/\n/', '
    ', utf8entities($f['description'])) . "

    \n"; +?> +
    + + + + + + + +
    New topic"; +else + echo "Only moderators can create topics"; + + ?> + Display > topics per page + >Page "; + for ($i=0;$i<$nPages;$i++) + echo ""; + echo " / $nPages"; +} + + ?>
    + + +\n"; +else +{ + echo ""; + if ($mod) + echo ""; + echo ""; + echo "\n"; + + $topics = $args['topics']; + for ($i=0;$i"; + if ($mod) + echo ""; + $pic = config::$main['staticurl'] . "/beta5/pics/" . ($topics[$i]['read'] ? '' : 'un') . 'read'; + if ($topics[$i]['sticky']) + $pic .= '_sticky'; + $pic .= '.gif'; + $text = ($topics[$i]['read'] ? 'Read' : 'Unread') . ($topics[$i]['sticky'] ? " sticky" : "") . " topic"; + echo ""; + echo ""; + echo ""; + echo ""; + } +} + +?> +
    This forum is empty.
      TopicRepliesFirst PostLast Post
    $text"; + // FIXME: poll icon + echo utf8entities($topics[$i]['title']) . "" . $topics[$i]['posts'] . "" . gmstrftime('%H:%M:%S on %d/%m/%Y', $topics[$i]['moment']) . " by "; + if ($topics[$i]['author_id'] != '') + echo "".utf8entities($topics[$i]['author']).""; + else + echo "".utf8entities($topics[$i]['author']).""; + echo "" . gmstrftime('%H:%M:%S on %d/%m/%Y', $topics[$i]['last_moment']) . " by "; + if ($topics[$i]['last_author_id'] != '') + echo "".utf8entities($topics[$i]['last_author']).""; + else + echo "".utf8entities($topics[$i]['last_author']).""; + echo "
    + + + + +
    + type="submit" name="dt" value="Delete" onClick="return confirmDelete()" /> + type="submit" name="st" value="Switch sticky" onClick="return confirmSticky()" /> + + $cat) { + if ($cat['type'] == 'A' && $f['ctype'] != 'A' || $cat['type'] != 'A' && $f['ctype'] == 'A') + continue; + foreach ($cat['forums'] as $cf) + if ($cf['id'] != $f['id'] && $cf['mod']) + array_push($mf, $cf); + } + + if (!count($mf)) + echo " "; + else + { + echo " to forum "; + echo""; + } +?>
    + + +
    +
    diff --git a/scripts/site/beta5/output/forums/en/forumnopost.inc b/scripts/site/beta5/output/forums/en/forumnopost.inc new file mode 100644 index 0000000..210585e --- /dev/null +++ b/scripts/site/beta5/output/forums/en/forumnopost.inc @@ -0,0 +1,11 @@ + + + + + +
    +

    Permission Denied

    +

    You are not allowed to post in this forum; only its moderators can do so.

    +
    diff --git a/scripts/site/beta5/output/forums/en/forumnotfound.inc b/scripts/site/beta5/output/forums/en/forumnotfound.inc new file mode 100644 index 0000000..e5d714d --- /dev/null +++ b/scripts/site/beta5/output/forums/en/forumnotfound.inc @@ -0,0 +1,11 @@ + + + + + +
    +

    Forum Not Found

    +

    The forum you wish to browse no longer exists.

    +
    diff --git a/scripts/site/beta5/output/forums/en/latest.inc b/scripts/site/beta5/output/forums/en/latest.inc new file mode 100644 index 0000000..7684999 --- /dev/null +++ b/scripts/site/beta5/output/forums/en/latest.inc @@ -0,0 +1,98 @@ + + + + + + +
    +

    +
    + + + + + + + + +
    0) ? ("") : ""?><- Previous page 0) ? "" : ""?>Display > posts per page"):""?>Next page ->":""?>
    +
    + +No posts were found."; +else +{ + for ($i=0;$i<$nbp;$i++) + { + echo ""; + echo ""; + echo "\n"; + echo "\n"; + + echo ""; + echo "\n"; + + echo "\n"; + + if ($p[$i]['edited'] != 0) + { + echo ""; + } + + echo "
    "; + if ($args['cat'] == '') + echo utf8entities($p[$i]['cname'] . " > "); + echo utf8entities($p[$i]['fname']) . "View forum
    "; + echo "View topic
    "; + echo "Reply - "; + echo "Quote\n"; + echo "
    " . utf8entities($p[$i]['title']) . "
    Posted " . gmstrftime("%H:%M:%S on %d/%m/%Y", $p[$i]['moment']); + echo " by ".(is_null($p[$i]['pid'])?'':('')); + echo $p[$i]['author'].(is_null($p[$i]['pid'])?'':'')."
    " . $p[$i]["contents"] . "
    Edited by "; + echo (is_null($p[$i]['edit_id']) ? '' : ('')) . utf8entities($p[$i]['edited_by']); + echo (is_null($p[$i]['edit_id']) ? '' : ''). " at " . gmstrftime("%H:%M:%S on %d/%m/%Y", $p[$i]['edited']) . "
    \n"; + } +?> +
    + + + + + + + + +
    0) ? ("") : ""?><- Previous page 0) ? "" : ""?>Display > posts per page"):""?>Next page ->":""?>
    +
    + +
    diff --git a/scripts/site/beta5/output/forums/en/overview.inc b/scripts/site/beta5/output/forums/en/overview.inc new file mode 100644 index 0000000..2e3ae04 --- /dev/null +++ b/scripts/site/beta5/output/forums/en/overview.inc @@ -0,0 +1,61 @@ + + + + + +
    +

    Forums Overview

    + $cat) +{ + echo "\n"; + echo "\n"; + echo "\n"; + + if (count($cat['forums'])) + { + echo ""; + } + + echo "
    "; + echo utf8entities($cat['title']) . "(" . ($cat['type'] == "A" ? "alliance" : "general") . ")
    "; + if ($cat['desc'] != "") + echo "

    " . preg_replace('/\n/', '
    ', utf8entities($cat['desc'])) . "

    \n"; + else + echo " "; + echo "
    \n"; + echo ""; + echo "\n"; + for ($i=0;$i 0 ? 'un' : '') . 'read.gif'; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo "\n\n"; + } + echo "
     Forum nameTopicsPostsLast post
    FIXME"; + echo utf8entities($cat['forums'][$i]['title']) . "" . $cat['forums'][$i]['topics'] . "" . $cat['forums'][$i]['posts'] . " "; + $l = $cat['forums'][$i]['last']; + if (is_null($l)) + echo "No Posts"; + else + { + echo "Last post by " . utf8entities($l['author']) . "
    "; + echo preg_replace('/ /', ' ', gmstrftime("%H:%M:%S on %d/%m/%Y", $l['moment'])); + } + echo "

    "; + if ($cat['forums'][$i]['description'] != '') + echo preg_replace('/\n/', '
    ', utf8entities($cat['forums'][$i]['description'])); + else + echo " "; + echo "

    \n"; +} + +?> +
    diff --git a/scripts/site/beta5/output/forums/en/post.inc b/scripts/site/beta5/output/forums/en/post.inc new file mode 100644 index 0000000..766d1c1 --- /dev/null +++ b/scripts/site/beta5/output/forums/en/post.inc @@ -0,0 +1,118 @@ + + + + + \n"; +} +else if (is_array($args['post'])) +{ + echo " \n"; + echo " \n"; + echo " \n"; +} + +?> +
    +

    +
    + ' /> + ' /> + ' /> + ' /> + + + + +"; + +?> + + + + + + + + + + + + + + + + + + + + + +\n"; + echo " \n"; + echo " \n"; +} + +if (is_array($args['posts'])) +{ + echo " \n"; + echo " \n"; + echo " \n"; + echo ""; + echo "\n"; + echo "\n"; + echo "
     
     " . $errs[$args['err']-1] . " 
    type='text' id="sub" name='sub' value="" maxlength="100" size='60' />
    > 
    Options: + type='checkbox' name='sm' id="esm" value='1' /> + type='checkbox' name='fc' id="efc" value='1' /> +
    Sticky topic: + type='checkbox' name='st' id="est" value='1' /> +
     
      + type='submit' name='e' value='Submit' accesskey="g" /> + type='submit' name='p' value='Preview' accesskey="p" /> + type='submit' name='c' value='Cancel' accesskey="c" /> +
       
    Preview - " . utf8entities($args['sub']) . "
    " . $args['prev'] . "
       
    Replying to ...
    "; + $p = $args['posts']; + for ($i=0;$i"; + echo "
    " . utf8entities($p[$i]['title']) . "
    Posted " . gmstrftime("%H:%M:%S on %d/%m/%Y", $p[$i]['moment']); + echo " by ".$p[$i]['author']."
    " . $p[$i]["html"] . "
    \n"; + } + echo "
       
    Original post
    "; + $p = $args['post']; + echo ""; + echo "\n"; + echo ""; + echo "\n"; + echo "\n"; + echo "
    " . utf8entities($p['title']) . "
    Posted " . gmstrftime("%H:%M:%S on %d/%m/%Y", $p['moment']); + echo " by ".$p['author']."
    " . $p["html"] . "
    \n"; + echo "
    + + + + diff --git a/scripts/site/beta5/output/forums/en/postnotfound.inc b/scripts/site/beta5/output/forums/en/postnotfound.inc new file mode 100644 index 0000000..4aa4f43 --- /dev/null +++ b/scripts/site/beta5/output/forums/en/postnotfound.inc @@ -0,0 +1,11 @@ + + + + + +
    +

    Post Not Found

    +

    The post you wish to edit no longer exists.

    +
    diff --git a/scripts/site/beta5/output/forums/en/search.inc b/scripts/site/beta5/output/forums/en/search.inc new file mode 100644 index 0000000..0b5df5a --- /dev/null +++ b/scripts/site/beta5/output/forums/en/search.inc @@ -0,0 +1,76 @@ + + + + + +
    +

    Search the Forums

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     
    Text: type="text" name="stxt" value="" size='40' />
     Note: use the '*' character for partial matches
     
    Search in: + type="radio" name="sin" value="0" id="sin0" /> + type="radio" name="sin" value="1" id="sin1" /> +
    Forum: >
     
    Sort by: >
      + type="radio" name="sor" value="0" id="sor0" /> + type="radio" name="sor" value="1" id="sor1" /> +
    Display results: + type="radio" name="srp" value="0" id="srp0" /> + type="radio" name="srp" value="1" id="srp1" /> +
     
      type="submit" name="s" value="Search" />
    +
    +
    diff --git a/scripts/site/beta5/output/forums/en/sresposts.inc b/scripts/site/beta5/output/forums/en/sresposts.inc new file mode 100644 index 0000000..73129e3 --- /dev/null +++ b/scripts/site/beta5/output/forums/en/sresposts.inc @@ -0,0 +1,86 @@ + + + + + + +
    +

    Search results

    +
    + + + + + + + + +
    0) ? ("") : ""?><- Previous page 0) ? "" : ""?>Display > posts per page"):""?>Next page ->":""?>
    +
    + +No posts were found."; +else +{ + for ($i=0;$i<$nbp;$i++) + { + echo ""; + echo ""; + echo "\n"; + echo "\n"; + + echo ""; + echo "\n"; + + echo "\n"; + + if ($p[$i]['edited'] != 0) + { + echo ""; + } + + echo "
    " . utf8entities($p[$i]['cname'] . " > " . $p[$i]['fname']) . "View forum
    "; + echo "View topic
    "; + echo "Reply - "; + echo "Quote\n"; + echo "
    " . utf8entities($p[$i]['title']) . "
    Posted " . gmstrftime("%H:%M:%S on %d/%m/%Y", $p[$i]['moment']); + echo " by ".(is_null($p[$i]['pid'])?'':('')); + echo $p[$i]['author'].(is_null($p[$i]['pid'])?'':'')."
    " . $p[$i]["contents"] . "
    Edited by "; + echo (is_null($p[$i]['edit_id']) ? '' : ('')) . utf8entities($p[$i]['edited_by']); + echo (is_null($p[$i]['edit_id']) ? '' : ''). " at " . gmstrftime("%H:%M:%S on %d/%m/%Y", $p[$i]['edited']) . "
    \n"; + } +?> +
    + + + + + + + + +
    0) ? ("") : ""?><- Previous page 0) ? "" : ""?>Display posts per page"):""?>Next page ->":""?>
    +
    + +
    diff --git a/scripts/site/beta5/output/forums/en/srestopics.inc b/scripts/site/beta5/output/forums/en/srestopics.inc new file mode 100644 index 0000000..2f332e7 --- /dev/null +++ b/scripts/site/beta5/output/forums/en/srestopics.inc @@ -0,0 +1,92 @@ + + + + + + +
    +

    Search results

    +
    + + + + + + + + +
    0) ? ("") : ""?><- Previous page 0) ? "" : ""?>Display> topics per page"):""?>Next page ->":""?>
    +
    + +No posts were found."; +else +{ + echo ''; + echo ""; + echo ""; + echo "\n"; + for ($i=0;$i"; + $pic = config::$main['staticurl'] . "/beta5/pics/" . ($p[$i]['read'] ? '' : 'un') . 'read'; + if ($p[$i]['sticky']) { + $pic .= '_sticky'; + } + $pic .= '.gif'; + $text = ($p[$i]['read'] ? 'Read' : 'Unread') . ($p[$i]['sticky'] ? " sticky" : "") . " topic"; + $id = $p[$i]['rctype'] . "#" . $p[$i]['id']; + echo ""; + echo ""; + echo ""; + echo ""; + } +?> + + + +
     TopicRepliesFirst PostLast Post
    $text"; + // FIXME: poll icon + echo utf8entities($p[$i]['title']) . "
    Forum: "; + echo "" . utf8entities($p[$i]['catname']) . " > "; + echo "" . utf8entities($p[$i]['fname']) . "
    " . ($p[$i]['nitems'] - 1) . "" . gmstrftime('%H:%M:%S on %d/%m/%Y', $p[$i]['moment']) . " by "; + if ($p[$i]['author_id'] != '') + echo "".utf8entities($p[$i]['author']).""; + else + echo "".utf8entities($p[$i]['author']).""; + echo "" . gmstrftime('%H:%M:%S on %d/%m/%Y', $p[$i]['last_moment']) . " by "; + if ($p[$i]['last_author_id'] != '') + echo "".utf8entities($p[$i]['last_author']).""; + else + echo "".utf8entities($p[$i]['last_author']).""; + echo "
    + + + + + +
    0) ? (" ") : ""?><- Previous page 0) ? "" : ""?>Display > topics per page"):""?>Next page ->":""?>
    + + +
    diff --git a/scripts/site/beta5/output/forums/en/topic.inc b/scripts/site/beta5/output/forums/en/topic.inc new file mode 100644 index 0000000..69da2c2 --- /dev/null +++ b/scripts/site/beta5/output/forums/en/topic.inc @@ -0,0 +1,102 @@ + + + + +
    +

    +
    + ' /> + + + + + +
    Display > posts per page 1) +{ + echo "Page / " . $nPages; +} +else + echo "Page 1 / 1"; + +?>
    + +
     \n"; + + echo ""; + echo ""; + echo ""; + + echo ""; + echo "\n"; + + echo "\n"; + + if ($p[$i]['edited'] != 0) + { + echo ""; + } + + echo "
    " . utf8entities($p[$i]['title']) . ""; + echo "Reply - "; + echo "Quote\n"; + if ($f['mod'] || $p[$i]['mine']) + { + echo "
    "; + echo "Edit"; + if ($p[$i]['id'] != $t['fpid'] || $f['mod']) + { + echo " - Delete\n"; + } + } + echo "
    Posted " . gmstrftime("%H:%M:%S on %d/%m/%Y", $p[$i]['moment']); + echo " by ".(is_null($p[$i]['pid'])?'':('')); + echo $p[$i]['author'].(is_null($p[$i]['pid'])?'':'')."
    " . $p[$i]["contents"] . "
    Edited by "; + echo (is_null($p[$i]['edit_id']) ? '' : ('')) . utf8entities($p[$i]['edited_by']); + echo (is_null($p[$i]['edit_id']) ? '' : ''). " at " . gmstrftime("%H:%M:%S on %d/%m/%Y", $p[$i]['edited']) . "
    \n"; + if ($d) + echo "
    \n"; +} + +?> + + + + + +
    Display mode: >Messages order: >
    + + + + diff --git a/scripts/site/beta5/output/forums/en/topicnotfound.inc b/scripts/site/beta5/output/forums/en/topicnotfound.inc new file mode 100644 index 0000000..f42db75 --- /dev/null +++ b/scripts/site/beta5/output/forums/en/topicnotfound.inc @@ -0,0 +1,11 @@ + + + + + +
    +

    Topic Not Found

    +

    The topic you wish to view no longer exists.

    +
    diff --git a/scripts/site/beta5/output/manual.en.inc b/scripts/site/beta5/output/manual.en.inc new file mode 100644 index 0000000..68c999c --- /dev/null +++ b/scripts/site/beta5/output/manual.en.inc @@ -0,0 +1,14 @@ + + + + + +
    + + +subPage . ".inc"); +?> +
    diff --git a/scripts/site/beta5/output/manual/en/header.inc b/scripts/site/beta5/output/manual/en/header.inc new file mode 100644 index 0000000..f4b271b --- /dev/null +++ b/scripts/site/beta5/output/manual/en/header.inc @@ -0,0 +1,68 @@ +
    +lib->call('getStructure', 'en'); +$txt = array('Top', 'Up', 'Previous', 'Next'); +if (!is_null(handler::$h->page)) { + $pid = handler::$h->page['id']; + $navLinks = handler::$h->lib->call('getNavLinks', $pid); + for ($i=0;$i<4;$i++) { + $t = is_null($navLinks[$i]) ? "" : ""; + $t .= $txt[$i] . (is_null($navLinks[$i]) ? "" : ""); + $navLinks[$i] = $t; + } +} else { + $pid = null; + $navLinks = $txt; +} + + $size = 15 + prefs::get('main/font_size', 2) * 3; +?> +
    + + + + + + + + + + + + + + + + + + +
      
     
    searchText)?>" style="width:90%" />
     

    Contents

    +
    + $sd) { + if ($br) { + echo "
    "; + } else { + $br = true; + } + if ($depth > 0) { + echo str_repeat(' ', $depth * 2); + } + echo " " . utf8entities($sd['title']) . ""; + if (count($sd['subs'])) { + echo "
    "; + dumpManualStructure($sd['subs'], $depth + 1); + } + } +} + +dumpManualStructure($manual, 0); + +?> +
    +
    +
    diff --git a/scripts/site/beta5/output/manual/en/notfound.inc b/scripts/site/beta5/output/manual/en/notfound.inc new file mode 100644 index 0000000..22d4912 --- /dev/null +++ b/scripts/site/beta5/output/manual/en/notfound.inc @@ -0,0 +1,5 @@ +

    Page not found!

    +

    + The page you were looking for could not be found in the manual. Maybe the manual + has been updated in the meantime. +

    diff --git a/scripts/site/beta5/output/manual/en/page.inc b/scripts/site/beta5/output/manual/en/page.inc new file mode 100644 index 0000000..efaffe6 --- /dev/null +++ b/scripts/site/beta5/output/manual/en/page.inc @@ -0,0 +1,108 @@ +{$list[$k]['title']}"; + if (count($list[$k]['subsections'])) { + echo "
      "; + drawContents($list[$k]['subsections']); + echo "
    "; + } + echo ""; + } +} + + +function displayLinks($text) { + $l = explode('(.*\n)*.*/', '\1', $t); + $toName = preg_replace('/\s/', '', $toName); + $t = preg_replace('/^[A-Za-z0-9_\-]+[\'"]>/', '', $t); + + $secId = handler::$h->lib->call('getSectionId', handler::$h->lang, $toName); + $link = ""; + if (!is_null($secId)) { + $pageId = handler::$h->lib->call('getPageId', $secId); + if (!is_null($pageId)) { + $lt = handler::$h->lib->call('readSectionRecord', $secId); + if ($pageId == handler::$h->page['id']) { + $link = ""; + } else { + $pg = handler::$h->lib->call('readSectionRecord', $pageId); + $link = ""; + } + } + } + + $nText .= $link . preg_replace('/^(.*)<\/mlink>(.*\n)*.*/', '\1', $t) . ($link != '' ? "" : "") + . preg_replace('/^.*<\/mlink>/', '', $t); + } + + return $nText; +} + + +function drawSecTitle(&$section, $depth) { + $pgLink = ""; + if ($section['linkto'] != "") { + $pageId = handler::$h->lib->call('getPageId', $section['linkto']); + if (!is_null($pageId)) { + $lt = handler::$h->lib->call('readSectionRecord', $section['linkto']); + if ($pageId == handler::$h->page['id']) { + $pgLink = " (view)"; + } else { + $pg = handler::$h->lib->call('readSectionRecord', $pageId); + $pgLink = " (view)"; + } + } + } + + $mDepth = ($depth - 2) * 10; + echo "
    {$section['title']}$pgLink" + . "Top
    "; +} + +function drawSections (&$list, $depth = 2) { + $kl = array_keys($list); + foreach ($kl as $k) { + if ($depth == 2) { + echo "
    "; + } + drawSecTitle($list[$k], $depth); + if ($list[$k]['contents'] != '') { + echo "
    " . displayLinks($list[$k]['contents']) . "
    "; + } + if (count($list[$k]['subsections'])) { + drawSections($list[$k]['subsections'], $depth + 1); + } + } +} + + +if (is_null(handler::$h->page)) { + include('manual_notfound.en.inc'); + return; +} + +?> + +

    page['title']?>

    +page['subsections'])) { + $st = input::$IE ? "margin:0px 0px 0px 30px" : "margin:0px 0px 0px -15px"; + echo "
    Contents:
      "; + drawContents(handler::$h->page['subsections']); + echo "
    "; + drawSections(handler::$h->page['subsections']); +} + +?> diff --git a/scripts/site/beta5/output/manual/en/search.inc b/scripts/site/beta5/output/manual/en/search.inc new file mode 100644 index 0000000..c7edfaa --- /dev/null +++ b/scripts/site/beta5/output/manual/en/search.inc @@ -0,0 +1,24 @@ +

    Search results for ''

    +Sorry, no results were found.

    \n"; +} else { + $c = count($args['results']); + echo "

    $c result" . ($c > 1 ? "s were" : " was") . " found:

    "; + echo "
      "; + $error = false; + foreach ($args['results'] as $p) { + $pData = handler::$h->lib->call('readSectionRecord', $p); + if (is_null($pData)) { + $error = true; + continue; + } + echo "
    1. {$pData['title']}
    2. "; + } + echo "
    "; + if ($error) { + echo "Some results could not be displayed because of a database error."; + } + echo "
    "; +} +?> diff --git a/scripts/site/beta5/output/map.en.inc b/scripts/site/beta5/output/map.en.inc new file mode 100644 index 0000000..cabedca --- /dev/null +++ b/scripts/site/beta5/output/map.en.inc @@ -0,0 +1,3 @@ + +
    +
    diff --git a/scripts/site/beta5/output/market.en.inc b/scripts/site/beta5/output/market.en.inc new file mode 100644 index 0000000..11340be --- /dev/null +++ b/scripts/site/beta5/output/market.en.inc @@ -0,0 +1,19 @@ + +
    + + + + + +
     Help
    +   +
    + + +
    Your account has been created too recently for you to use this feature.
    You must wait for $args more day".($args>1?"s":"")."."; +} +?> diff --git a/scripts/site/beta5/output/message.en.inc b/scripts/site/beta5/output/message.en.inc new file mode 100644 index 0000000..a318ecc --- /dev/null +++ b/scripts/site/beta5/output/message.en.inc @@ -0,0 +1,4 @@ + + +
    +
    diff --git a/scripts/site/beta5/output/money.en.inc b/scripts/site/beta5/output/money.en.inc new file mode 100644 index 0000000..f36a529 --- /dev/null +++ b/scripts/site/beta5/output/money.en.inc @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + +
    +

    Current Funds

    +

    Your empire has .

    +
    +

    Daily Profits

    +

    + >Planet Income:
    + >Fleet Upkeep:
    + Daily Profits: € +

    +
    Help
    +
     
    +

    Planet Profit and Loss


    Fleet Upkeep Expenses

    diff --git a/scripts/site/beta5/output/norealloc.en.inc b/scripts/site/beta5/output/norealloc.en.inc new file mode 100644 index 0000000..bd365b2 --- /dev/null +++ b/scripts/site/beta5/output/norealloc.en.inc @@ -0,0 +1,6 @@ +

    Getting a new planet

    +

    + In this specific game, it is just impossible to get a new planet this way.
    + You either need to take a planet using your fleets (provided you have any left), + or to get someone to sell you or give you a planet. +

    diff --git a/scripts/site/beta5/output/nplanet.en.inc b/scripts/site/beta5/output/nplanet.en.inc new file mode 100644 index 0000000..edbe15e --- /dev/null +++ b/scripts/site/beta5/output/nplanet.en.inc @@ -0,0 +1,45 @@ + +

    You Evil, Evil Man

    +

    + You already have a planet, or many of these. Now begone! +

    + +

    New Planet Assigned!

    +

    + Your new planet, '>, has been assigned to your empire!
    +
    + View the Planet List +

    +Get a New Planet"; + echo "

    "; + echo "Please type the name of your new planet in the field below, then click OK to get the planet.

    "; + if ($args['err']) + { + echo ""; + switch ($args['err']) : + case 1: echo "This planet name is too long (maximum 15 characters)"; break; + case 2: echo "This planet name is incorrect (letters, numbers, spaces and _.@-+'/ only)"; break; + case 3: echo "Spaces are not allowed at the beginning or at the end of the planet's name"; break; + case 4: echo "Multiple spaces are not allowed"; break; + case 5: echo "This planet name is too short (minimum 2 characters)"; break; + case 6: echo "A planet by that name already exists"; break; + endswitch; + echo ".
    "; + } + echo "Name of your new planet: "; + echo "

    "; + echo "Please note that getting a new planet will disband all of your fleets and place you under protection from the Peacekeepers."; + echo "

    "; +} diff --git a/scripts/site/beta5/output/overview.en.inc b/scripts/site/beta5/output/overview.en.inc new file mode 100644 index 0000000..101b936 --- /dev/null +++ b/scripts/site/beta5/output/overview.en.inc @@ -0,0 +1,2 @@ +
    +
    diff --git a/scripts/site/beta5/output/planet.en.inc b/scripts/site/beta5/output/planet.en.inc new file mode 100644 index 0000000..41f3e85 --- /dev/null +++ b/scripts/site/beta5/output/planet.en.inc @@ -0,0 +1,18 @@ + + + + + + + +
    + + + +
     

     

    + ' /> + Select planet: - + Planet list - + Help +
     
      +  
    diff --git a/scripts/site/beta5/output/planetnf.en.inc b/scripts/site/beta5/output/planetnf.en.inc new file mode 100644 index 0000000..3f59775 --- /dev/null +++ b/scripts/site/beta5/output/planetnf.en.inc @@ -0,0 +1,5 @@ +

    Planet not found

    +

    + The planet you are looking for could not be found.
    + Insert witty pun here. +

    diff --git a/scripts/site/beta5/output/planets.en.inc b/scripts/site/beta5/output/planets.en.inc new file mode 100644 index 0000000..8ab8fc4 --- /dev/null +++ b/scripts/site/beta5/output/planets.en.inc @@ -0,0 +1,2 @@ +
    +
    diff --git a/scripts/site/beta5/output/preferences.en.inc b/scripts/site/beta5/output/preferences.en.inc new file mode 100644 index 0000000..1d7cd58 --- /dev/null +++ b/scripts/site/beta5/output/preferences.en.inc @@ -0,0 +1,201 @@ +"; + return $t; +} + +?> +
    + + + + + + + + + + + + + + +
      + + +\n"; +?> + + + + + + + + + + + + + + + + + +

    Account

    The address you entered is invalid, please correct it.
    E-mail address: class='txt' value="" size="35" maxlength="128" />
    "; + switch ($args['err2']) : + case 1: echo "A database access error has occured"; break; + case 2: echo "The current password is incorrect"; break; + case 3: echo "The new password and its confirmation are different"; break; + case 4: echo "The new password is too short (minimum 4 characters)"; break; + case 5: echo "The new password is too long (maximum 64 characters)"; break; + case 6: echo "The new password must be different from your user name"; break; + endswitch; +} +else + echo " class='note'>Please leave these fields empty if you do not intend to change your password."; +?>
    Current password:/>
    New password:/>
    Confirm new password:/>
    +
    + + + + + + + + + + + + + + + + + + +
    + + + +

    Display

    Help
    +
    Language:>
    Font size:>
    Tooltips:>
    Theme:>
    Colour scheme:>
    +
     

    + + + + + + + + + + + + + + + + + + + + + + + + +

    Forums

    Topics/page:>Messages/page:>
    Graphical smileys:>Forum tags:>
    Display mode:>Messages order:>
    Signature:>
    +

    + /> + /> +

    +

    Leave Game

    +

    + + Click the button bellow to leave this game. Please note that it will not close your account; it will simply quit the current game. You will have 24 hours after you click the button to cancel your action; after that, your planets will be made neutral, your fleets will be destroyed, your private messages erased, etc...

    + You will still be able to access the other games you're playing, and you'll be able to start playing any other game from the Account page.

    name='quit' value='Leave ' /> + 0) + echo "$h hour" . ($h>1 ? 's' : ''); + $tl -= $h * 3600; + $m = ($tl - $tl % 60) / 60; + if ($m > 0) + echo ($h != 0 ? ' and ' : '') . "$m minute" . ($m>1 ? 's' : ''); + $tl -= $m * 60; + ?> + unless you decide to cancel this action by clicking the button below.

    value='Do NOT leave ' /> + +

    +
    +
    diff --git a/scripts/site/beta5/output/probes.en.inc b/scripts/site/beta5/output/probes.en.inc new file mode 100644 index 0000000..07d9e2d --- /dev/null +++ b/scripts/site/beta5/output/probes.en.inc @@ -0,0 +1,10 @@ + +
    + + + + + +
     Help
    +   +
    diff --git a/scripts/site/beta5/output/rank.en.inc b/scripts/site/beta5/output/rank.en.inc new file mode 100644 index 0000000..269fd3d --- /dev/null +++ b/scripts/site/beta5/output/rank.en.inc @@ -0,0 +1,10 @@ + + + + + + +
     Help
    +   +
    + diff --git a/scripts/site/beta5/output/rat.en.inc b/scripts/site/beta5/output/rat.en.inc new file mode 100644 index 0000000..302144c --- /dev/null +++ b/scripts/site/beta5/output/rat.en.inc @@ -0,0 +1,10 @@ +

    Fool!

    +

    + Begone! +

    +

    + Now! +

    +

    + You are not wanted here! +

    diff --git a/scripts/site/beta5/output/research.en.inc b/scripts/site/beta5/output/research.en.inc new file mode 100644 index 0000000..2f4d57d --- /dev/null +++ b/scripts/site/beta5/output/research.en.inc @@ -0,0 +1,9 @@ + + + + + + +
     Help
    +   +
    diff --git a/scripts/site/beta5/output/techtrade.inc b/scripts/site/beta5/output/techtrade.inc new file mode 100644 index 0000000..80fd6d0 --- /dev/null +++ b/scripts/site/beta5/output/techtrade.inc @@ -0,0 +1 @@ +
     
    diff --git a/scripts/site/beta5/output/ticks.en.inc b/scripts/site/beta5/output/ticks.en.inc new file mode 100644 index 0000000..14adc85 --- /dev/null +++ b/scripts/site/beta5/output/ticks.en.inc @@ -0,0 +1,24 @@ + + + + + +
    + $td) +{ + if (!$td['game']) + continue; + echo "

    " . utf8entities($td['name']) . "

    "; + echo "

    " . utf8entities($td['description']) . "

    "; + echo "
     
    "; + echo "

     

    "; + array_push($dt, "$tid#" . $td['first'] . "#" . $td['interval'] . "#" . $td['last']); +} + +?> +
    Help
    + diff --git a/scripts/site/beta5/output/universe.en.inc b/scripts/site/beta5/output/universe.en.inc new file mode 100644 index 0000000..40192a4 --- /dev/null +++ b/scripts/site/beta5/output/universe.en.inc @@ -0,0 +1,38 @@ +
    + + + + + + +
    +

    Universe Information

    +

    Galaxy

    +

    + Planets:
    + Neutral planets:
    + Systems occupied by nebulas:
    + Average turrets/planet:
    + Average factories/planet: +

    +

    >View maps

    +
    +

    Ticks

    +

    +   +
    + >More details... +

    +

    Rankings

    +

    + You are ranked with points.
    + Your financial ranking is ( points).
    + Your civilization ranking is ( points).
    + Your military ranking is ( points).
    + Your inflicted damage ranking is ( points).
    +
    + There are players in the rankings.
    +
    + >More details... +

    +
    Help
    diff --git a/scripts/site/beta5/page.inc b/scripts/site/beta5/page.inc new file mode 100644 index 0000000..7148cc2 --- /dev/null +++ b/scripts/site/beta5/page.inc @@ -0,0 +1,172 @@ +dir = config::$main['scriptdir'] . "/site/beta5"; + $this->static = config::$main['staticdir'] . "/beta5"; + } + + + function header($pg, $lg) { + // Output the HTML header + echo "\n" + . "Legacy Worlds " . input::$game->text . "" + . ""; + + // Read the user preferences + $tt = prefs::get('main/tooltips', 2); + $fs = prefs::get('main/font_size', 2); + $col = prefs::get('main/colour', 'purple'); + $thm = prefs::get('beta5/theme', "default"); + + // Generate CSS and JS resources + $this->generateStylesheets($pg, $thm, $fs, $col); + if (is_null(handler::$h->customJS)) { + $this->generateJS($pg, $thm, $col, $lg, $tt, $fs); + } else { + $this->jsRes = handler::$h->customJS; + } + + if (!is_null($this->cssRes)) { + echo "cssRes}' />"; + } + if (!is_null($this->jsRes)) { + echo ""; + } + + // Generate HTML body tag + echo "customJS)) { + if (ajax::$init != "" || $tt) { + echo " onload='lwAutoInit();'"; + } + if ($tt) { + echo " onunload='tt_Disable()'"; + } + echo ">\n"; + } else { + if (!is_null(handler::$h->customJS['load'])) { + echo " onload='" . handler::$h->customJS['load'] . "'"; + } + if (!is_null(handler::$h->customJS['unload'])) { + echo " onunload='" . handler::$h->customJS['unload'] ."'"; + } + } + + // Load the header + if (is_null(handler::$h->customHeader)) { + $hdr = "header"; + } else { + $hdr = handler::$h->customHeader; + } + $this->includeFile("{$this->dir}/layout/$thm/$hdr.$lg.inc"); + } + + + function footer($pg, $lg) { + $tt = prefs::get('main/tooltips', 2); + $col = prefs::get('main/colour', 'purple'); + $thm = prefs::get('beta5/theme', "default"); + + if (is_null(handler::$h->customFooter)) { + $ftr = "footer"; + } else { + $ftr = handler::$h->customFooter; + } + $this->includeFile("{$this->dir}/layout/$thm/$ftr.$lg.inc"); + + // Tooltips + if ($tt && is_null(handler::$h->customJS)) { + echo "
    "; + $f = getStatic("beta5/js/tt_{$thm}_$col.js"); + if (!is_null($f)) { + echo ""; + } + $f = getStatic("beta5/js/tooltips.js"); + if (!is_null($f)) { + echo ""; + } + } + + echo ""; + } + + + function generateStylesheets($page, $thm, $fs, $col) { + $css = array("main"); + array_push($css, "thm_$thm"); + array_push($css, "pg_$page"); + array_push($css, "fonts$fs"); + array_push($css, "$col"); + foreach ($css as $fn) { + $rf = "/css/$fn" . (input::$IE ? "-ie" : ""); + $r = addFileResource("css", "{$this->static}/$rf.css"); + if (input::$IE && !$r) { + $r = addFileResource("css", "{$this->static}/css/$fn.css"); + } + } + + $this->cssRes = storeResource("css", 345600); + } + + + function generateJS($pg, $thm, $col, $lg, $tt, $fs) { + // JavaScript variables (static access URL, color, etc...) + $jsConf = "var staticurl=\"".config::$main['staticurl'] + . "\";\nvar color=\"$col\";\nvar ttFontSize = '" . ($fs + 9) . "px';\n" + . "var ttDelay = " . ($tt * 500) . ";\n"; + + // AJAX-specified initialization code + if (ajax::$init != "" || $tt) { + $jsConf .= "function lwAutoInit()\n{\n"; + if (is_array(ajax::$fHandler)) { + foreach (ajax::$fHandler as $f) { + $jsConf .= "\tnew rpc_Function('$f','" . ajax::$method[$f] . "');\n"; + } + } + if (is_array(ajax::$fTheme)) { + foreach (ajax::$fTheme as $f) { + $jsConf .= "\tnew rpc_Function('$f','" . ajax::$method[$f] . "');\n"; + } + } + if (ajax::$init != "") { + $jsConf .= "\t" . ajax::$init . "\n"; + } + if ($tt) { + $jsConf .= "\ttt_Init();\n"; + } + $jsConf .= "}\n"; + } + addRawResource("js", $jsConf); + + // Common JavaScript functions + addFileResource("js", "{$this->static}/js/main.js"); + addFileResource("js", "{$this->static}/js/main-$lg.js"); + + // AJAX script and initialisation + if (count(ajax::$fHandler) || count(ajax::$fTheme)) { + addRawResource("js", "var rpc_pageURI='" . makeLink($pg, input::$game->name, 'rpc') . "';\n"); + addFileResource("js", "{$this->static}/js/rpc.js"); + addFileResource("js", "{$this->static}/js/rpc-$lg.js"); + } + + // Theme-specific JavaScript + addFileResource("js", "{$this->static}/js/thm_$thm.js"); + addFileResource("js", "{$this->static}/js/thm_$thm-$lg.js"); + + // Page-specific JavaScript + addFileResource("js", "{$this->static}/js/pg_$pg.js"); + addFileResource("js", "{$this->static}/js/pg_$pg-$lg.js"); + + // Add JS resource + $this->jsRes = storeResource("js", 345600); + } + + + function includeFile($file, $args = null) { + include($file); + } +} + +?> diff --git a/scripts/site/main/handlers/about.inc b/scripts/site/main/handlers/about.inc new file mode 100644 index 0000000..9a9fe35 --- /dev/null +++ b/scripts/site/main/handlers/about.inc @@ -0,0 +1,11 @@ +output = "about"; + } + +} + +?> diff --git a/scripts/site/main/handlers/b6pp.inc b/scripts/site/main/handlers/b6pp.inc new file mode 100644 index 0000000..84aca8d --- /dev/null +++ b/scripts/site/main/handlers/b6pp.inc @@ -0,0 +1,158 @@ +db->query('SELECT COUNT(*) FROM b6_planet_pics')); + list($rated) = dbFetchArray($this->db->query("SELECT COUNT(*) FROM b6_planet_votes WHERE account = {$_SESSION['userid']}")); + $this->data['total'] = $total; + $this->data['rated'] = $rated; + } + + private function getRandomUnrated() { + $user = $_SESSION['userid']; + $q = $this->db->query( + "SELECT * FROM b6_planet_pics WHERE id NOT IN (SELECT picture FROM b6_planet_votes WHERE account = $user) ORDER BY RANDOM()" + ); + if (! dbCount($q)) { + return 0; + } + + $data = dbFetchHash($q); + return $data; + } + + private function storeRating($id, $rating) { + if ($rating < 1 || $rating > 5) { + return; + } + + $q = $this->db->query("SELECT id FROM b6_planet_pics WHERE id = $id"); + if (! dbCount($q)) { + return; + } + + $user = $_SESSION['userid']; + + $q = $this->db->query("SELECT account FROM b6_planet_votes WHERE picture = $id AND account = $user"); + if (dbCount($q)) { + return; + } + + $this->db->query("INSERT INTO b6_planet_votes (account, picture, vote) VALUES ($user, $id, $rating)"); + $this->db->query("UPDATE credits SET credits_obtained = credits_obtained + 120 WHERE account = $user"); + } + + private function doRatings() { + $pd = $this->getRandomUnrated(); + if (! $pd) { + $this->data = array( + 'page' => 'nu' + ); + } else { + $this->data = array( + 'page' => 'vp', + 'pic' => $pd, + 'cr' => null, + 'ar' => null, + 'nv' => null + ); + } + } + + private function getPlanet($id) { + $q = $this->db->query("SELECT * FROM b6_planet_pics WHERE id = $id"); + if (! dbCount($q)) { + $this->doRatings(); + return; + } + $pd = dbFetchHash($q); + + $user = $_SESSION['userid']; + $q = $this->db->query("SELECT vote FROM b6_planet_votes WHERE picture = $id AND account = $user"); + if (! dbCount($q)) { + $this->data = array( + 'page' => 'vp', + 'pic' => $pd, + 'cr' => null, + 'ar' => null, + 'nv' => null + ); + return; + } + list($cr) = dbFetchArray($q); + + $q = $this->db->query("SELECT AVG(vote), COUNT(*) FROM b6_planet_votes WHERE picture = $id"); + list($ar, $nv) = dbFetchArray($q); + $this->data = array( + 'page' => 'vp', + 'pic' => $pd, + 'cr' => $cr, + 'ar' => sprintf("%.2f", $ar), + 'nv' => $nv + ); + } + + private function topRatings() { + $q = $this->db->query("SELECT COUNT(*) AS votes FROM b6_planet_votes GROUP BY picture"); + if (!dbCount($q)) { + $this->data = array( + 'page' => 'nt' + ); + return; + } + + $sum = 0; + while ($r = dbFetchArray($q)) { + $sum += $r[0]; + } + $sum /= dbCount($q); + + $q = $this->db->query("SELECT picture, AVG(vote) AS rating, COUNT(*) AS votes FROM b6_planet_votes " + . "GROUP BY picture ORDER BY rating DESC, votes DESC, picture"); + if (!dbCount($q)) { + $this->data = array( + 'page' => 'nt' + ); + } else { + $this->data = array( + 'page' => 'tt', + 'pics' => array() + ); + while ($r = dbFetchHash($q)) { + if ($r['votes'] >= $sum) { + array_push($this->data['pics'], $r); + } + if (count($this->data['pics']) >= 50) { + break; + } + } + } + } + + public function handle($input) { + $this->db = $this->game->getDBAccess(); + $this->output = 'b6pp'; + + $command = $input['c']; + if ($command == 'v') { + $id = (int) $input['id']; + $this->getPlanet($id); + } elseif ($command == 'r') { + $id = (int) $input['id']; + $r = (int) $input['r']; + $this->storeRating($id, $r); + $this->doRatings(); + } elseif ($command == 't') { + $this->topRatings(); + } else { + $this->doRatings(); + } + + $this->getCount(); + } +} + +?> diff --git a/scripts/site/main/handlers/confirm.inc b/scripts/site/main/handlers/confirm.inc new file mode 100644 index 0000000..ac1d156 --- /dev/null +++ b/scripts/site/main/handlers/confirm.inc @@ -0,0 +1,12 @@ +output = "confirm"; + } +} + +?> diff --git a/scripts/site/main/handlers/contrib.inc b/scripts/site/main/handlers/contrib.inc new file mode 100644 index 0000000..126a288 --- /dev/null +++ b/scripts/site/main/handlers/contrib.inc @@ -0,0 +1,17 @@ +game->getDBAccess()->query("SELECT resources_used, credits_obtained FROM credits WHERE account = {$_SESSION['userid']}")); + } + + public function handle($input) { + $this->output = "contrib"; + $this->data = $this->getCredits(); + } +} + +?> diff --git a/scripts/site/main/handlers/create.inc b/scripts/site/main/handlers/create.inc new file mode 100644 index 0000000..dca22f6 --- /dev/null +++ b/scripts/site/main/handlers/create.inc @@ -0,0 +1,128 @@ +data['username'] = $n; + if (strlen($n) > 15) { + return 1; + } + if (preg_match('/[^A-Za-z0-9_\.\-\+@\/'."'".' ]/', $n)) { + return 2; + } + if (trim($n) != $n) { + return 3; + } + if (preg_match('/\s\s+/', $n)) { + return 4; + } + if (strlen($n) < 2) { + return 5; + } + if (!preg_match('/[A-Za-z]/', $n)) { + return 6; + } + $q = dbQuery("SELECT id FROM account WHERE LOWER(name)='" . addslashes(strtolower($n)) . "'"); + if (dbCount($q)) { + return 7; + } + return 0; + } + + function checkMailAddr($a) { + return preg_match( + '/^[A-Za-z0-9_\.\-\+]+@([A-Za-z0-9_\.\-\+]+)+\.[A-Za-z]{2,6}/', + $a + ); + } + + function checkMail($a1, $a2) { + $this->data['mail'] = $a1; + if ($a1 != $a2) + return 1; + if (!$this->checkMailAddr($a1)) + return 2; + $this->data['mail2'] = $a1; + + $q = dbQuery("SELECT id,status FROM account WHERE LOWER(email) = LOWER('$a1')"); + if (!dbCount($q)) { + return 0; + } + list($id,$status) = dbFetchArray($q); + if ($status == 'KICKED') { + dbQuery("INSERT INTO banned_attempt (ip_addr) VALUES ('{$_SERVER['REMOTE_ADDR']}')"); + tracking::$data['bat'] = true; + tracking::$data['uid'] = $id; + return -1; + } else { + return 3; + } + return 0; + } + + function checkPassword($np, $cp) { + if ($np != $cp) + return 1; + elseif (strlen($np) < 4) + return 2; + elseif (strlen($np) > 64) + return 3; + elseif (strtolower($np) == strtolower($this->data['username'])) + return 4; + return 0; + } + + function checkLanguage($l) { + $pLang = array('en'); + if (!in_array($l, $pLang)) { + $l = 'en'; + } + $this->data['lang'] = $l; + tracking::$data['language'] = $l; + } + + private function checkPlanetName($name) { + $game = config::getDefaultGame(); + $this->data['planetname'] = $name; + return $game->getLib()->call('checkPlanetName', $name); + } + + function checkData($in) { + $this->data = array(); + $this->data['err1'] = $this->checkUsername($in['username']); + $this->data['err2'] = $this->checkMail($in['email'], $in['email2']); + $this->data['err3'] = $this->checkPassword($in['password'], $in['password2']); + $this->data['err4'] = $this->checkPlanetName($in['planet']); + $this->checkLanguage($in['lang']); + return (!($this->data['err1']||$this->data['err2']||$this->data['err3']||$this->data['err4'])); + } + + function handle($input) { + if ($_SESSION['authok']) { + $this->output = "index"; + } elseif (!tracking::$data['readDisclaimer']) { + tracking::$data['readDisclaimer'] = true; + $this->output = "disclaimer"; + $this->data = true; + } elseif (tracking::$data['bat']) { + $this->output = "kicked"; + } elseif ($input['create'] == "") { + $this->output = "create"; + $this->data = array(); + } elseif (!$this->checkData($input)) { + if (tracking::$data['bat']) { + $this->output = "kicked"; + } else { + $this->output = "create"; + } + } else { + $vLib = $this->game->getLib('main/account'); + $this->data['success'] = $vLib->call('createAccount', $this->data['username'], + $input['password'], strtolower($this->data['mail']), $this->data['lang'], + $this->data['planetname']); + $this->output = "created"; + } + } +} + +?> diff --git a/scripts/site/main/handlers/credits.inc b/scripts/site/main/handlers/credits.inc new file mode 100644 index 0000000..37cad51 --- /dev/null +++ b/scripts/site/main/handlers/credits.inc @@ -0,0 +1,11 @@ +output = "credits"; + } + +} + +?> diff --git a/scripts/site/main/handlers/css.inc b/scripts/site/main/handlers/css.inc new file mode 100644 index 0000000..bbcd4f2 --- /dev/null +++ b/scripts/site/main/handlers/css.inc @@ -0,0 +1,12 @@ +output = "index"; + } +} + +?> diff --git a/scripts/site/main/handlers/disclaimer.inc b/scripts/site/main/handlers/disclaimer.inc new file mode 100644 index 0000000..b540108 --- /dev/null +++ b/scripts/site/main/handlers/disclaimer.inc @@ -0,0 +1,13 @@ +output = "disclaimer"; + $this->data = false; + } + +} + +?> diff --git a/scripts/site/main/handlers/donate.inc b/scripts/site/main/handlers/donate.inc new file mode 100644 index 0000000..51f08dd --- /dev/null +++ b/scripts/site/main/handlers/donate.inc @@ -0,0 +1,26 @@ +lib = $this->game->getLib("main/paypal"); + + $this->data = array(); + if ($input['doit'] == 1) { + $this->data['pid'] = $this->lib->call('newTicket', $_SESSION['userid']); + logText("PAYPAL: generated ticket {$this->data['pid']} for account #{$_SESSION['userid']}", LOG_INFO); + $this->data['doit'] = true; + } else { + $this->data['selfContrib'] = $this->lib->call('getUserContributions', $_SESSION['userid']); + $this->data['hist'] = $this->lib->call('getUserHistory', $_SESSION['userid']); + $this->data['totalContrib'] = $this->lib->call('getTotalContributions'); + $this->data['totalMonth'] = $this->lib->call('getMonthContributions'); + } + + $this->output = "donate"; + } +} + +?> diff --git a/scripts/site/main/handlers/index.inc b/scripts/site/main/handlers/index.inc new file mode 100644 index 0000000..f8ded7b --- /dev/null +++ b/scripts/site/main/handlers/index.inc @@ -0,0 +1,242 @@ +name == 'main') { + continue; + } + + $status = $game->status(); + if ($status == 'FINISHED') { + continue; + } + + $lib = $game->getLib(); + $pid = $lib->call('doesUserPlay', $_SESSION['userid']); + if (!is_null($pid)) { + $a2[$game->name] = $lib->call('getPlayerStatus', $pid); + array_unshift($a2[$game->name], $game->text); + array_push($a2[$game->name], $status); + if ($status == 'ENDING') { + array_push($a2[$game->name], $game->lastTick()); + } elseif ($status == 'READY') { + array_push($a2[$game->name], $game->firstTick()); + } + } elseif ($status != 'PRE') { + $a1[$game->name] = array( + $game->text, $lib->call('getPlayerCount'), + $status, + $status != 'VICTORY' && $lib->call('canJoin') + ); + if ($status == 'ENDING') { + array_push($a1[$game->name], $game->lastTick()); + } elseif ($status == 'READY') { + array_push($a1[$game->name], $game->firstTick()); + } + } + } + + if ($input['sw'] != "") { + $_SESSION['show_unregistered'] = !$_SESSION['show_unregistered']; + } + + // Get the quit timestamp + $quit = $this->aLib->call('getQuitCountdown', $_SESSION['userid']); + + // Get data regarding vacation mode + if (is_null($quit)) { + $vacation = $this->vLib->call('getStatus', $_SESSION['userid']); + if (is_null($vacation)) { + $vacation = array( + "status" => 'VAC', + "vac_start" => null, + "vac_credits" => 1 + ); + } + $vacation['can_set'] = ($vacation['status'] != 'VAC') && is_null($vacation['vac_start']) + && $this->vLib->call('canSet', $_SESSION['userid']); + } else { + $vacation = null; + } + + $this->data = array( + "other" => $a1, + "play" => $a2, + "vac" => $vacation, + "quit" => $quit, + "leech" => $this->aLib->call('isLeech', $_SESSION['userid']) + ); + $this->output = "account"; + } + + function exitVacation(&$input) { + if ($this->vLib->call('isOnVacation', $_SESSION['userid'])) { + $this->output = "vac_leave"; + } else { + $this->accountPage($input); + } + } + + function actualExitVacation(&$input) { + if (!$input['cancel'] && $this->vLib->call('isOnVacation', $_SESSION['userid'])) { + $this->vLib->call('leave', $_SESSION['userid']); + } + $this->accountPage($input); + } + + function startVacation(&$input) { + if ($this->vLib->call('canSet', $_SESSION['userid'])) { + $this->output = "vac_start"; + } else { + $this->accountPage($input); + } + } + + function actualStartVacation(&$input) { + if (!$input['cancel'] && $this->vLib->call('canSet', $_SESSION['userid'])) { + $this->vLib->call('setStart', $_SESSION['userid']); + } + $this->accountPage($input); + } + + function cancelStart(&$input) { + $vacation = $this->vLib->call('getStatus', $_SESSION['userid']); + if (is_null($vacation['vac_start'])) { + $this->accountPage($input); + } else { + $this->output = "vac_cancel"; + } + } + + function actualCancelStart(&$input) { + if (!$input['cancel']) { + $vacation = $this->vLib->call('getStatus', $_SESSION['userid']); + if (!is_null($vacation['vac_start'])) { + $this->vLib->call('resetStart', $_SESSION['userid']); + } + } + $this->accountPage($input); + } + + function closeAccount($input) { + $quit = $this->aLib->call('getQuitCountdown', $_SESSION['userid']); + if (!is_null($quit)) { + $this->accountPage($input); + } else { + $this->data = array(); + $this->output = "quit_confirm"; + } + } + + function actualCloseAccount($input) { + // FIXME: SQL query in handler + $q = dbQuery("SELECT password FROM account WHERE id={$_SESSION['userid']} AND quit_ts IS NULL"); + if ($input['cancel'] || !($q && dbCount($q) == 1)) { + $this->accountPage($input); + } else { + list($rPass) = dbFetchArray($q); + + $this->data = array( + "ePass" => ($input['q_pass'] != $rPass), + ); + if ($this->data['ePass']) { + $this->data['reason'] = $input['q_reason']; + $this->output = "quit_confirm"; + logText("main/confirm_quit: Account {$_SESSION['userid']} provided wrong password", LOG_WARNING); + } else { + $this->aLib->call('setQuitCountdown', $_SESSION['userid'], $input['q_reason']); + $this->accountPage($input); + } + } + } + + function cancelClose($input) { + $quit = $this->aLib->call('getQuitCountdown', $_SESSION['userid']); + if (is_null($quit)) { + $this->accountPage($input); + } else { + $this->data = array(); + $this->output = "back_confirm"; + } + } + + function actualCancelClose($input) { + $quit = $this->aLib->call('getQuitCountdown', $_SESSION['userid']); + if (!$input['cancel'] && !is_null($quit)) { + $this->aLib->call('cancelQuitCountdown', $_SESSION['userid']); + } + $this->accountPage($input); + } + + function loggedIn(&$input) { + $this->main = $this->game->getLib(); + $this->vLib = $this->game->getLib("main/vacation"); + $this->aLib = $this->game->getLib("main/account"); + + if ($input['evm'] == 1) { + $this->exitVacation($input); + } else if ($input['evmc'] == 1) { + $this->actualExitVacation($input); + } else if ($input['svm'] == 1) { + $this->startVacation($input); + } else if ($input['svmc'] == 1) { + $this->actualStartVacation($input); + } else if ($input['cvms'] == 1) { + $this->cancelStart($input); + } else if ($input['cvmsc'] == 1) { + $this->actualCancelStart($input); + } else if ($input['rq'] == 1) { + $this->closeAccount($input); + } else if ($input['rqc'] == 1) { + $this->actualCloseAccount($input); + } else if ($input['crq'] == 1) { + $this->cancelClose($input); + } else if ($input['crqc'] == 1) { + $this->actualCancelClose($input); + } else { + $this->accountPage($input); + } + } + + + function xml($input) { + if (!$_SESSION['authok']) { + return null; + } + + $data = new data_node('Games'); + + foreach (config::getGames() as $game) { + if ($game->name == 'main') { + continue; + } + + $lib = $game->getLib(); + $pid = $lib->call('doesUserPlay', $_SESSION['userid']); + if (!is_null($pid)) { + $node = new data_leaf('Game', utf8_encode($game->text)); + $node->setAttribute('version', $game->version->id); + $node->setAttribute('path', $game->name); + $data->addContents($node); + } + } + + return $data; + } + + + function handle($input) { + if ($_SESSION['authok']) { + $this->loggedIn($input); + } else { + $this->output = "index"; + } + } +} + +?> diff --git a/scripts/site/main/handlers/js.inc b/scripts/site/main/handlers/js.inc new file mode 100644 index 0000000..bbcd4f2 --- /dev/null +++ b/scripts/site/main/handlers/js.inc @@ -0,0 +1,12 @@ +output = "index"; + } +} + +?> diff --git a/scripts/site/main/handlers/links.inc b/scripts/site/main/handlers/links.inc new file mode 100644 index 0000000..138c85c --- /dev/null +++ b/scripts/site/main/handlers/links.inc @@ -0,0 +1,172 @@ +lib->call('getCategories'); + for ($i=0;$ilib->call('getLinks', $categories[$i]['id']); + } + + $this->data = array( + "mode" => 0, + "data" => $categories + ); + } + + + function handleBrokenReport($account, $link, $confirm) { + // Get existing reports + $reports = $this->lib->call('getBrokenReports'); + $myReports = array(); + foreach ($reports as $r) { + if ($r['reported_by'] == $account) { + array_push($myReports, $r['link']); + } + } + + if (is_null($link) || $link == "") { + // No link reported yet, get the list + $links = array(); + $categories = $this->lib->call('getCategories'); + foreach ($categories as $cat) { + $clinks = $this->lib->call('getLinks', $cat['id']); + foreach ($clinks as $lnk) { + if (in_array($lnk['id'], $myReports)) { + continue; + } + $links[$lnk['id']] = utf8entities($cat['title'] . " -> " . $lnk['title']); + } + } + + $this->data = array( + "mode" => 1, + "data" => $links + ); + } elseif ($confirm == '') { + // A link has been reported, but the report hasn't been confirmed yet + $lk = $this->lib->call('getLink', (int)$link); + if (is_null($lk) || in_array((int)$link, $myReports)) { + return; + } + $cat = $this->lib->call('getCategory', $lk['category']); + $lk['long_title'] = utf8entities($cat['title'] . " -> " . $lk['title']); + $this->data = array( + "mode" => 2, + "data" => $lk + ); + } else { + // A link has been reported and the user confirmed + $lk = $this->lib->call('getLink', (int)$link); + if (is_null($lk) || in_array((int)$link, $myReports)) { + return; + } + $cat = $this->lib->call('getCategory', $lk['category']); + $lk['long_title'] = utf8entities($cat['title'] . " -> " . $lk['title']); + + $this->lib->call('reportBroken', (int)$link, $account); + + $this->data = array( + "mode" => 3, + "data" => $lk + ); + } + } + + + function handleSubmission(&$input) { + $sl = (int)$input['sl']; + if ($sl == 1) { + // Initialise the form + $this->data = array( + "mode" => 4, + "data" => array( + "url" => "http://", + "title" => "", + "desc" => "", + "err" => 0 + ) + ); + } elseif ($sl == 2) { + // Check submitted data + $title = preg_replace('/\s+/', ' ', trim($input['title'])); + $desc = preg_replace('/\s+/', ' ', trim($input['desc'])); + $url = trim($input['url']); + + // Check title + if (strlen($title) < 5) { + $error = 1; + } elseif (strlen($title) > 64) { + $error = 2; + // Check description + } elseif ($desc != "" && strlen($desc) < 10) { + $error = 3; + // Check URL + } elseif (!preg_match('/^(http|https):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?(\/.*)?$/i', $url, $m)) { + $error = 4; + } else { + list($junk, $proto, $hostname, $port) = $m; + if (!preg_match('/^\d+\.\d+\.\d+\.\d+$/', $hostname)) { + $ip = gethostbyname($hostname); + if ($ip === $hostname) { + $error = 5; + } else { + $error = 0; + } + } else { + $error = 0; + } + } + + // If there was no error, check whether this user already submitted the link + if ($error == 0 && !$this->lib->call('checkSubmission', $url, $_SESSION['userid'])) { + $error = 6; + } + + // Generate output + if ($error) { + $this->data = array( + "mode" => 4, + "data" => array( + "url" => $url, + "title" => $title, + "desc" => $desc, + "err" => $error + ) + ); + } else { + $this->lib->call('submitLink', $url, $title, $desc == "" ? null : $desc, $_SESSION['userid']); + $this->data = array( + "mode" => 5, + "data" => $url + ); + } + } + } + + + function handle($input) { + $this->lib = $this->game->getLib('main/links'); + + if (is_array($_SESSION) && !is_null($_SESSION['userid'])) { + $account = $_SESSION['userid']; + + if (!is_null($input['rbl']) && is_null($input['cancel'])) { + $this->handleBrokenReport($account, $input['id'], $input['confirm']); + } elseif (!is_null($input['sl']) && is_null($input['cancel'])) { + $this->handleSubmission($input); + } + } + + if (is_null($this->data)) { + $this->makeLinksList(); + } + + $this->output = "links"; + } +} + +?> diff --git a/scripts/site/main/handlers/login.inc b/scripts/site/main/handlers/login.inc new file mode 100644 index 0000000..1e49cd0 --- /dev/null +++ b/scripts/site/main/handlers/login.inc @@ -0,0 +1,12 @@ +output = "login"; + } +} + +?> diff --git a/scripts/site/main/handlers/logout.inc b/scripts/site/main/handlers/logout.inc new file mode 100644 index 0000000..82c21ab --- /dev/null +++ b/scripts/site/main/handlers/logout.inc @@ -0,0 +1,17 @@ +output = "logout"; + } +} + +?> diff --git a/scripts/site/main/handlers/lostpass.inc b/scripts/site/main/handlers/lostpass.inc new file mode 100644 index 0000000..083d578 --- /dev/null +++ b/scripts/site/main/handlers/lostpass.inc @@ -0,0 +1,18 @@ +output = "index"; + return; + } + + $this->output = "lostpass"; + $this->data = $this->game->action('lostPassword', $input['u'], $input['m'], $input['c']); + } +} + +?> diff --git a/scripts/site/main/handlers/macwidget.inc b/scripts/site/main/handlers/macwidget.inc new file mode 100644 index 0000000..522ce2e --- /dev/null +++ b/scripts/site/main/handlers/macwidget.inc @@ -0,0 +1,18 @@ +setAttribute('latest', config::getParam('latestWidget')); + $data->setAttribute('oldestOk', config::getParam('oldestWidget')); + + return $data; + } + +} + +?> diff --git a/scripts/site/main/handlers/manual.inc b/scripts/site/main/handlers/manual.inc new file mode 100644 index 0000000..7e0404d --- /dev/null +++ b/scripts/site/main/handlers/manual.inc @@ -0,0 +1,95 @@ +page = null; + $this->output = "manual"; + } + + function getFirstPage() { + $fPage = $this->lib->call('getFirstPage', $this->lang); + if (is_null($fPage)) { + $this->pageNotFound(); + return; + } + + $this->page = $this->lib->call('getPage', $fPage); + if (is_null($this->page)) { + $this->pageNotFound(); + return; + } + + $this->output = "manual"; + } + + function getPage($name) { + $secId = $this->lib->call('getSectionId', $this->lang, $name); + if (is_null($secId)) { + $this->pageNotFound(); + return; + } + + $pageId = $this->lib->call('getPageId', $secId); + if (is_null($pageId)) { + $this->pageNotFound(); + return; + } + + $this->page = $this->lib->call('getPage', $pageId); + if (is_null($this->page)) { + $this->pageNotFound(); + return; + } + + $this->output = "manual"; + } + + function getSearchPage($text) { + $this->searchText = $text; + + if (is_array(tracking::$data['man_search']) && tracking::$data['man_search']['text'] != $text) { + tracking::$data['man_search'] = null; + } + if (!is_array(tracking::$data['man_search'])) { + tracking::$data['man_search'] = array( + "text" => $text, + "results" => $this->lib->call('search', $text, $this->lang) + ); + } + $this->data = tracking::$data['man_search']; + $this->output = "manual_search"; + } + + function handle($input) { + $game = config::getDefaultGame(); + $this->lib = $game->getLib('main/manual'); + $this->lang = getLanguage(); + + if ($input['ss'] != '') { + $this->getSearchPage($input['ss']); + } else { + if ($input['p'] != '') { + $p = preg_replace('/[^A-Za-z0-9_\-]/', '', $input['p']); + $this->getPage($p); + } else { + $this->getFirstPage(); + } + if (is_array(tracking::$data['man_search'])) { + $this->searchText = tracking::$data['man_search']['text']; + } else { + $this->searchText = ""; + } + } + } +} + +?> diff --git a/scripts/site/main/handlers/notfound.inc b/scripts/site/main/handlers/notfound.inc new file mode 100644 index 0000000..d39441c --- /dev/null +++ b/scripts/site/main/handlers/notfound.inc @@ -0,0 +1,12 @@ +output = "notfound"; + } +} + +?> diff --git a/scripts/site/main/handlers/pcheck.inc b/scripts/site/main/handlers/pcheck.inc new file mode 100644 index 0000000..f6283d0 --- /dev/null +++ b/scripts/site/main/handlers/pcheck.inc @@ -0,0 +1,18 @@ +data = "X"; + } else { + $this->data = "Key is $key"; + } + $this->output = "pcheck"; + } +} + +?> diff --git a/scripts/site/main/handlers/play.inc b/scripts/site/main/handlers/play.inc new file mode 100644 index 0000000..73e5add --- /dev/null +++ b/scripts/site/main/handlers/play.inc @@ -0,0 +1,12 @@ +output = "play"; + $this->data = $this->game->action('joinGame', $_SESSION['userid'], $input['g'], is_null($input['c']), $input['p'], $input['n']); + } +} + +?> diff --git a/scripts/site/main/handlers/ppipn.inc b/scripts/site/main/handlers/ppipn.inc new file mode 100644 index 0000000..30b81b2 --- /dev/null +++ b/scripts/site/main/handlers/ppipn.inc @@ -0,0 +1,45 @@ +lib->call('logIPN', $input); + + $ticket = $input['item_number']; + if ($input['payment_status'] == 'Completed') { + if ($input['mc_currency'] == 'EUR') { + $cash = $input['mc_gross']; + } else { + $cash = $input['settle_amount']; + } + $cash = (float) $cash; + + if ($cash == 0) { + logText("PAYPAL: could not retrieve the amount (log ID #$lid)", LOG_WARNING); + return; + } + + $this->lib->call('addDonation', $ticket, $cash); + logText("PAYPAL: accepted donation ($cash euros, log ID #$lid)", LOG_INFO); + } elseif ($input['payment_status'] == 'Failed' || $input['payment_status'] == 'Denied') { + $this->lib->call('cancelDonation', $ticket); + logText("PAYPAL: received cancelled donation :( (log ID #$lid)", LOG_INFO); + } else { + logText("PAYPAL: ignoring IPN with status '{$input['payment_status']}' (log ID #$lid)", LOG_INFO); + } + } + + function handle($input) { + $this->lib = $this->game->getLib("main/paypal"); + + logText("PAYPAL: handling incoming IPN from {$_SERVER['REMOTE_ADDR']}", LOG_INFO); + if ($this->lib->call('checkIPN')) { + $this->handleIPN($input); + } + + $this->output = "ppipn"; + } +} + +?> diff --git a/scripts/site/main/handlers/rankings.inc b/scripts/site/main/handlers/rankings.inc new file mode 100644 index 0000000..37c0598 --- /dev/null +++ b/scripts/site/main/handlers/rankings.inc @@ -0,0 +1,54 @@ + $d) { + if ($id == 'main' || $d->status() == 'PRE') { + continue; + } + $gTexts[$id] = $d->text; + array_push($gList, $id); + } + + if ($input['t'] != '') { + tracking::$data['rkGame'] = $input['g']; + } + if (is_null(tracking::$data['rkGame']) || !in_array(tracking::$data['rkGame'], $gList)) { + tracking::$data['rkGame'] = $gList[0]; + } + + $game = config::getGame(tracking::$data['rkGame']); + $lib = $game->getLib('main/rankings'); + $rkTypes = array_values($lib->call('getTypes')); + $lang = getLanguage(); + $rkText = array(); + + foreach ($rkTypes as $id) { + $rkText[$id] = $lib->call('getText', $id, $lang); + } + + if ($input['t'] != '') { + tracking::$data['rkType'] = $input['t']; + } + if (is_null(tracking::$data['rkType']) || !in_array(tracking::$data['rkType'], $rkTypes)) { + tracking::$data['rkType'] = $rkTypes[0]; + } + $cType = tracking::$data['rkType']; + + $this->output = "rankings"; + $this->data = array( + "games" => $gTexts, + "cGame" => tracking::$data['rkGame'], + "types" => $rkText, + "cType" => $cType, + "rankings" => $lib->call('getAll', $cType) + ); + } +} + +?> diff --git a/scripts/site/main/handlers/restart.inc b/scripts/site/main/handlers/restart.inc new file mode 100644 index 0000000..1e49cd0 --- /dev/null +++ b/scripts/site/main/handlers/restart.inc @@ -0,0 +1,12 @@ +output = "login"; + } +} + +?> diff --git a/scripts/site/main/handlers/screenshots.inc b/scripts/site/main/handlers/screenshots.inc new file mode 100644 index 0000000..89c22bc --- /dev/null +++ b/scripts/site/main/handlers/screenshots.inc @@ -0,0 +1,55 @@ + array( + 'title' => 'Beta 5', + 'pics' => array( + 'ov' => 'Overview', + 'planets' => 'Planet list', + 'planet' => 'Individual planet page', + 'fleets' => 'Fleets management', + 'research' => 'Research', + 'money' => 'Money', + 'map' => 'Maps', + 'messages' => 'Messages', + 'ranking' => 'Rankings', + 'allies' => 'Trusted allies', + 'market' => 'Marketplace', + 'manual' => 'In-game manual' + ) + ), + "b4" => array( + 'title' => 'Beta 4', + 'pics' => array( + 'ov' => 'Overview', + 'planets' => 'Planet list', + 'fleets' => 'Fleets management', + 'money' => 'Money', + 'map' => 'Maps', + 'ticks' => 'Ticks', + 'ranking' => 'Rankings' + ) + ), + ); + + + public function handle($input) { + $this->data = array( + 'list' => self::$screenshots + ); + if ($input['c'] && array_key_exists($input['c'], self::$screenshots)) { + $cat = self::$screenshots[$input['c']]; + $this->data['category'] = $input['c']; + if ($input['s'] && array_key_exists($input['s'], $cat['pics'])) { + $this->data['picture'] = $input['s']; + } + } + + $this->output = "screenshots"; + } + +} + +?> diff --git a/scripts/site/main/handlers/settings.inc b/scripts/site/main/handlers/settings.inc new file mode 100644 index 0000000..c609b8d --- /dev/null +++ b/scripts/site/main/handlers/settings.inc @@ -0,0 +1,115 @@ +passError = 1; + return; + } + list($rop) = dbFetchArray($q); + if ($rop != $op) { + $this->passError = 2; + } elseif ($np != $cp) { + $this->passError = 3; + } elseif (strlen($np) < 4) { + $this->passError = 4; + } elseif (strlen($np) > 64) { + $this->passError = 5; + } elseif ($np == $_SESSION['login']) { + $this->passError = 6; + } else { + $p = addslashes($np); + $op = addslashes($rop); + $q = dbQuery("UPDATE account SET password='$p' WHERE id=".$_SESSION['userid']); + dbQuery( + "INSERT INTO pass_change (account, old_pass, new_pass) " + . "VALUES({$_SESSION['userid']}, '$op', '$p')" + ); + if (!$q) { + $this->passError = 1; + } + } + } + + function checkFormData($input) { + $pLang = array('fr', 'en'); + if (in_array($input['lang'], $pLang)) { + prefs::set('main/language', $input['lang']); + } + + $pCol = array('red','green','blue','yellow','grey','purple'); + if (in_array($input['col'], $pCol)) { + prefs::set('main/colour', $input['col']); + } + + if (preg_match('/^[0-4]$/', $input['fs'])) { + prefs::set('main/font_size', $input['fs']); + } + + if ($this->checkMail($input['mail'])) { + dbQuery("UPDATE account SET email='".$input['mail']."' WHERE id=".$_SESSION['userid']); + } else { + $this->mailError = preg_replace('/"/', '"', $input['mail']); + } + + if (preg_match('/^[1-5]0$/', $input['tpp'])) { + prefs::set('main/forums_ntopics', $input['tpp']); + } + if (preg_match('/^[1-5]0$/', $input['mpp'])) { + prefs::set('main/forums_nitems', $input['mpp']); + } + prefs::set('main/smileys', ($input['gsm'] == "1")?"1":"0"); + prefs::set('main/forum_code', ($input['gft'] == "1")?"1":"0"); + prefs::set('main/forums_threaded', ($input['fdm'] == "1")?"1":"0"); + prefs::set('main/forums_reversed', ($input['fmo'] == "1")?"1":"0"); + prefs::set('main/forums_sig', $input['fsig']); + + if ($input['opass'] != "") { + $this->checkPassword($input['opass'], $input['npass'], $input['cpass']); + } + } + + + function handle($input) { + if ($input['col'] != "") { + $this->checkFormData($input); + } + + $q = dbQuery("SELECT email FROM account WHERE id=".$_SESSION['userid']); + list($email) = dbFetchArray($q); + $fs = prefs::get('main/font_size', 2); + $col = prefs::get('main/colour', 'red'); + $tpp = prefs::get('main/forums_ntopics', 20); + $mpp = prefs::get('main/forums_nitems', 20); + + $this->data = array( + "lang" => getLanguage(), + "mail" => $email, + "col" => $col, + "fs" => $fs, + "err1" => $this->mailError, + "err2" => $this->passError, + "tpp" => $tpp, + "mpp" => $mpp, + "gsm" => (prefs::get('main/smileys', 1) == 1), + "gft" => (prefs::get('main/forum_code', 1) == 1), + "fdm" => (prefs::get('main/forums_threaded', 1) == 1), + "fmo" => (prefs::get('main/forums_reversed', 1) == 1), + "fsig" => prefs::get('main/forums_sig', "") + ); + + $this->output = "settings"; + } +} + +?> diff --git a/scripts/site/main/layout/actual-header.en.inc b/scripts/site/main/layout/actual-header.en.inc new file mode 100644 index 0000000..3bc84a6 --- /dev/null +++ b/scripts/site/main/layout/actual-header.en.inc @@ -0,0 +1,21 @@ + Legacy Worlds<?= is_null($this->title) ? "" : " - {$this->title}" ?> +cssRes) ) : ?> + ?id=cssRes ?>' /> + +jsRes) ) : ?> + + + + + + +
    +
    + Legacy Worlds +
    + current version: - + engine v - + revision +
    +
    +
    diff --git a/scripts/site/main/layout/footer.inc b/scripts/site/main/layout/footer.inc new file mode 100644 index 0000000..0888875 --- /dev/null +++ b/scripts/site/main/layout/footer.inc @@ -0,0 +1,8 @@ +
    + +
    + + diff --git a/scripts/site/main/layout/header.inc b/scripts/site/main/layout/header.inc new file mode 100644 index 0000000..bc3553f --- /dev/null +++ b/scripts/site/main/layout/header.inc @@ -0,0 +1,4 @@ + + + + diff --git a/scripts/site/main/layout/lbox.en.inc b/scripts/site/main/layout/lbox.en.inc new file mode 100644 index 0000000..f52bce8 --- /dev/null +++ b/scripts/site/main/layout/lbox.en.inc @@ -0,0 +1,20 @@ + + Welcome,
    + + Vacation mode activated + +
    + Preferences
    + Log out + + +
    + +
    + + Username:
    + Password:
    + Forgot your password? + +
    + diff --git a/scripts/site/main/layout/players.en.inc b/scripts/site/main/layout/players.en.inc new file mode 100644 index 0000000..aa52092 --- /dev/null +++ b/scripts/site/main/layout/players.en.inc @@ -0,0 +1,2 @@ + player 1 ? "s" : "" ?>, + online diff --git a/scripts/site/main/maintenance.inc b/scripts/site/main/maintenance.inc new file mode 100644 index 0000000..415d6b5 --- /dev/null +++ b/scripts/site/main/maintenance.inc @@ -0,0 +1,79 @@ + + + + Legacy Worlds - Server under maintenance + + + + +
    +
    + Legacy Worlds +
    + current version: - + engine v - + revision +
    +
    +

    Server under maintenance

    +

    + Reason:
    +
    + It will be available again around + .
    + Current server time: . +

    +
    +
    + +
    + + diff --git a/scripts/site/main/output/about.en.inc b/scripts/site/main/output/about.en.inc new file mode 100644 index 0000000..4a20d0a --- /dev/null +++ b/scripts/site/main/output/about.en.inc @@ -0,0 +1,42 @@ +title = "About Legacy Worlds"; +$this->addStylesheet('text'); +$this->startContents(); +?> +

    About LegacyWorlds

    +

    + 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. +

    +

    + DeepClone Development is proud to introduce this new version, + Legacy Worlds Public Beta 5. It has been rewritten from scratch since the previous version and keeps evolving as bugs + are fixed and features are added. +

    + +

    Online Games Directories

    +

    + Legacy Worlds Beta 5 is listed on the following online games directories: +

    + +

    + Please take the time to vote for Legacy Worlds on these sites, as it means more players, and therefore more fun... +

    + +

    Other DeepClone Development games

    +

    + GP Manager Pro is DeepClone's latest project, a game + in which you play as the manager of a Grand Prix team. +

    +endContents(); ?> diff --git a/scripts/site/main/output/account.en.inc b/scripts/site/main/output/account.en.inc new file mode 100644 index 0000000..7ef8ee9 --- /dev/null +++ b/scripts/site/main/output/account.en.inc @@ -0,0 +1,277 @@ +title = "My account"; +$this->addScript("account"); +$this->addStylesheet("account"); +$this->startContents(); + +function displayUnregistered($a) { + static $status = array( + 'READY' => 'Open for registration', + 'RUNNING' => 'Running', + 'VICTORY' => 'Victory', + 'ENDING' => 'Ending' + ); +?> + + + $c) { + echo ""; + echo ""; + echo "\n"; + if ($c[2] == "READY") { + print ""; + } elseif ($c[2] == "ENDING") { + print ""; + } + } +?> +
    Game namePlayersStatus
    "; + if ($c[3]) { + echo "{$c[0]}"; + } else { + echo "{$c[0]} (closed)"; + } + echo "{$c[1]}".$status[$c[2]]."
     Starting at " + . gmstrftime('%H:%M:%S on %Y-%m-%d', $c[4]) . "
     Ending at " + . gmstrftime('%H:%M:%S on %Y-%m-%d', $c[4]) . "
    + 'Hidden', + 'READY' => 'Open', + 'RUNNING' => 'Running', + 'VICTORY' => 'Victory', + 'ENDING' => 'Ending' + ); +?> + + + $c) { + echo ""; + echo ""; + echo "\n"; + if ($c[4] == "READY") { + print ""; + } elseif ($c[4] == "ENDING") { + print ""; + } + } +?> +
    Game namePlanetsCashGame status
    {$c[0]}{$c[1]}€" + . number_format($c[2]) . "" . $status[$c[4]] . "
     Starting at " + . gmstrftime('%H:%M:%S on %Y-%m-%d', $c[5]) . "
     Ending at " + . gmstrftime('%H:%M:%S on %Y-%m-%d', $c[5]) . "
    + + +
    +

    Welcome to Legacy Worlds, !

    +You are currently playing the following games:

    \n"; + displayRegistered($args['play']); + if (count($args['other'])) { + if ($onVacation) { + echo "

    Other games are available, but you must exit vacation mode to see them.

    \n"; + } elseif (!$_SESSION['show_unregistered']) { + echo "

    You may also register to the following games:"; + echo " (hide):

    \n"; + displayUnregistered($args['other']); + } else { + echo "

    Show all games

    \n"; + } + } else { + echo "

    There are currently no other available games.

    \n"; + } +} else if (!$onVacation) { +?> +

    + You are currently not registered to any game!
    + You may register to any of the games in the list below by clicking its name: +

    + +

    + You are currently not registered to any game!
    + You will have to leave vacation mode before selecting a game to play. +

    + 

    \n"; + +if ($args['leech']) { +?> + +

    Contribute to LegacyWorlds!

    +

    + Learn about how you can help in the '>Contributions page. +

    + +
    + + +
    +

    Vacation mode

    +

    + 0) { +?> + You have vacation credit 1 ? "s" : ""?>, which translates to + 0) { + echo "$crDays day" . ($crDays > 1 ? "s" : "") . " "; + if ($crHours > 0) { + echo "and "; + } + } + if ($crHours > 0) { + echo "$crHours hours"; + } + echo "."; + } else { + echo "You don't have any vacation credit left."; + } + +?> +

    + +

    + You are currently on vacation. +

    +

    + You can choose to leave vacation mode by clicking the button below. + However, if you do so, you will not be able to enter vacation mode again for the next 7 days. +

    +
    + + +
    + +

    + Vacation mode allows you to take a break from the actual game. You can activate + it by clicking the button below. +

    +
    + + +
    + +

    + Your account will enter vacation mode at %H:%M:%S (%Y-%m-%d)", $startDate)?> Server Time. +

    +

    + Clicking the button below will allow you to prevent your account from entering vacation mode. +

    +
    + + +
    + +

    + It is therefore impossible for you to enter vacation mode. You will earn a new credit at the next day tick. +

    + +

    + You exited vacation mode less than a week ago, it is therefore impossible for you to re-enter vacation mode + before (on ). +

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

    Quitting Legacy Worlds

    + +

    + Had enough of Legacy Worlds? Well, you may close your account if you wish to do so. +
    + Closing your account will cause you to disappear from all of the games you're + currently playing on Legacy Worlds; it will also disable your account.
    + You will be able to use your account again at a later time if you wish to; more + details will be provided later. +

    +

    + Click the button below if you wish to close your account. +

    +
    + + +
    + +

    + Your account will be closed at %H:%M on the %Y-%m-%d", $quitDate)?> (Server Time). +

    +

    + Until then, you can choose to cancel this action by clicking the button below. +

    +
    + + +
    + +
    +
    diff --git a/scripts/site/main/output/annoy.en.inc b/scripts/site/main/output/annoy.en.inc new file mode 100644 index 0000000..282165e --- /dev/null +++ b/scripts/site/main/output/annoy.en.inc @@ -0,0 +1,17 @@ +title = "Please contribute"; +$this->addStylesheet('annoy'); +$this->addScript('annoy'); +?> +
    +

    Please contribute to LegacyWorlds

    +

    + You are seeing this page because you have used quite a few server resources. +

    +

    + Contributing doesn't mean you have to pay (although it is an option); you can help + us by reporting bugs or helping us with the Beta 6 development process... +

    +

    You will be redirected to the Contributions page in

    +

    s

    +
    diff --git a/scripts/site/main/output/b6pp.en.inc b/scripts/site/main/output/b6pp.en.inc new file mode 100644 index 0000000..f346734 --- /dev/null +++ b/scripts/site/main/output/b6pp.en.inc @@ -0,0 +1,116 @@ +title = 'Beta 6 planets'; +$this->addStylesheet('b6pp'); + +$envType = array( + "0" => "excellent", + "1" => "good", + "2" => "not so great", + "3" => "bad", + "4" => "infernal", + "5" => "not so great", + "6" => "bad", + "7" => "good" +); +$pType = array( +// "0" => array( "Earth-like planet", "this planet is very similar to Earth"), + "0" => array( "temperate planet", "this planet is very similar to Earth, but the temperature is more constant - the planet has no icecaps and no dry deserts"), + "1" => array( "planetary ocean", "this planet's surface is covered by an ocean; it should be quite cloudy, but not to the point of hiding the surface"), + "2" => array( "Mars-like planet", "this planet is very similar to Mars; it has a relatively thin atmosphere, but isn't too friendly" ), + "3" => array( "dead world", "this very rocky planet has almost no atmosphere"), + "4" => array( "lava world", "this planet's volcanism is downright crazy; it has an atmosphere, but you sure as hell wish it didn't." ), + "5" => array( "desert world", "this planet is a sand desert, with a very clear atmosphere." ), + "6" => array( "ice world", "this planet is very cold, covered in snow; even the surface of its ocean is frozen." ), + "7" => array( "jungle world", "this planet is covered in forests and jungles; it doesn't really have seas and oceans, but rather gigantic swamps." ) +); + +$this->startContents(); +?> +

    Beta 6 planet pictures

    +

    Rate a picture - Top ratings - You rated out of pictures.

    +

     

    + +

    There are no more pictures for you to rate

    +

    + It would seem that you have rated all of the currently available pictures. +

    +

    + Please come back later! +

    + + + + + + + + + + +
    + small + + medium + +

    Planet details

    +

    Planet size: / 10

    +

    Environment:

    +

    Type:

    +

    Description:

    + +

     

    +

    Rate this picture

    +
    +
    +

    + + + (worst)   + +     +  (best) +

    +
    +
    +

     

    +

    Each planet you rate grants you 120 '>contribution credits.

    + +

    Ratings

    +

    Your rating: / 5

    +

    Average rating:

    +

    Total votes:

    + +
    + large +
    + +

    Top 50 planet pictures

    + + + + + + + + +
    + planet + + vote 1 ? "s" : "" ?> + + / 5.00 +
    + +

    No planets have been rated at the moment

    +

    ... which kind of explains why this page is blank.

    + +endContents(); ?> diff --git a/scripts/site/main/output/back_confirm.en.inc b/scripts/site/main/output/back_confirm.en.inc new file mode 100644 index 0000000..e3b54a8 --- /dev/null +++ b/scripts/site/main/output/back_confirm.en.inc @@ -0,0 +1,20 @@ +title = "Cancel account deletion"; +$this->addStylesheet("account"); +$this->startContents(); +?> +
     
    +
    +

    Do you want to keep your account?

    +
    + +

    + Please choose whether you want to cancel the countdown for your account's deletion or keep it going. +

    + + + +
    +
    +endContents(); ?> diff --git a/scripts/site/main/output/confirm.en.inc b/scripts/site/main/output/confirm.en.inc new file mode 100644 index 0000000..fbb5575 --- /dev/null +++ b/scripts/site/main/output/confirm.en.inc @@ -0,0 +1,20 @@ +title = "Account confirmation"; +$this->startContents(); +?> +
    +

    Account confirmation

    +

    + Please type in the confirmation code that was sent to the email address you specified. +

    +

     

    + Invalid confirmation code

    ":""?> +

    + Confirmation code: +

    +

    + +

    +
    +endContents(); ?> diff --git a/scripts/site/main/output/contrib.en.inc b/scripts/site/main/output/contrib.en.inc new file mode 100644 index 0000000..45e3f21 --- /dev/null +++ b/scripts/site/main/output/contrib.en.inc @@ -0,0 +1,33 @@ +title = 'Contributions'; +$this->addStylesheet('text'); +$this->startContents(); +?> +

    Help us keep Legacy Worlds alive!

    +

    + There are three ways you can help us at the moment. +

    +

    + You can choose to '>donate some money; while we certainly don't expect + to make a living out of it, the server costs quite a lot of money, and any amount you donate helps. +

    +

    + You can help us with the new version by '>rating planet pictures for the + new version of Legacy Worlds or commenting on the Beta 6 blog. +

    +

    + Finally, if you report a bug that no-one knew about, you will be credited for it. +

    +

    Your current status

    +

    + The figures below are an estimate of how much of the server's resources you used while playing the game along with + an estimate of your contributions to Legacy Worlds (all players start with 9,000 in this field). +

    +

    Resources used:

    +

    Contribution:

    +

    + Note: if the resources you used are higher than the estimate of your contributions, you will start getting an + annoying message every time you log on to LegacyWorlds. +

    +endContents(); ?> diff --git a/scripts/site/main/output/create.en.inc b/scripts/site/main/output/create.en.inc new file mode 100644 index 0000000..4b1bff2 --- /dev/null +++ b/scripts/site/main/output/create.en.inc @@ -0,0 +1,135 @@ +title = "Create an account"; +$this->addStylesheet("create"); +$this->startContents(); + +?> +
    +

    Legacy Worlds account creation

    +
    +Use letters, numbers, spaces and _.@-+'/ only).", + "Spaces are not allowed at the beginning or at the end of the username.", + "Multiple spaces are not allowed.", + "Username is too short (minimum 2 characters).", + "Username must contain at least one letter.", + "This username is not available." +); +if ($args['err1']) { + $aClass = " error"; +} else { + $aClass = ""; +} +echo "
    {$eAccount[(int) $args["err1"]]}
    "; + +?> +
    +
    + Username: + +
    +
    + Language: + +
    +
    +
    +MUST exist.", + "E-mail fields do not match, please verify.", + "Invalid address.", + "You already have a Legacy Worlds account." +); +if ($__err2) { + $aClass = " error"; +} else { + $aClass = ""; +} + +?> +
    +
    +
    +
    + E-mail: + +
    +
    + Confirm e-mail: + +
    +
    +
    + +
    +
    +
    +
    + Password: + +
    +
    + Confirm password: + +
    +
    +
    +Use letters, numbers, spaces and _.@-+'/ only).", + "The planet name must not contain sequences of spaces.", + "The planet name is too short.", + "The planet name must contain at least one letter.", + "This planet name is unavailable.", + "The planet name must not start or end with spaces." +); +if ($__err4) { + $aClass = " error"; +} else { + $aClass = ""; +} +?> +
    +
    +
    +
    + Planet name: + +
    +
    +
    +
    +
    +
    +
    +endContents(); ?> diff --git a/scripts/site/main/output/created.en.inc b/scripts/site/main/output/created.en.inc new file mode 100644 index 0000000..56b7e65 --- /dev/null +++ b/scripts/site/main/output/created.en.inc @@ -0,0 +1,25 @@ +title = "Account creation"; +$this->addStylesheet('text'); +$this->startContents(); +if ($args['success']) { +?> +

    Your Legacy Worlds account has been created!

    +

    + An e-mail has been sent to you with your account's details as well as + the validation procedure that will confirm your account's creation. +

    + +

    An error has occured!

    +

    + Something went wrong in the process of creating your account. This should never have + happened, please contact our staff to + report this. +

    +endContents(); +?> diff --git a/scripts/site/main/output/credits.en.inc b/scripts/site/main/output/credits.en.inc new file mode 100644 index 0000000..73aba00 --- /dev/null +++ b/scripts/site/main/output/credits.en.inc @@ -0,0 +1,40 @@ +title = "Credits"; +$this->addStylesheet("credits"); +$this->startContents(); +?> + +
    Legacy Worlds
    + + +
    Original concept
    +
    El Christoph
    + +
    Programming & design
    +
    TSeeker
    + +
    Additional programming
    +
    Ju
    +
    Lord_Omega
    + +
    Manual
    +
    Ju
    +
    Sycophant
    + +
    Administration
    +
    Gilgamesh
    +
    Ju
    +
    Lord_Omega
    +
    Sycophant
    +
    TSeeker
    + +
    Tools
    + + + + +
    Thanks to everyone who contributed to the game with various ideas or by reporting bugs...
    +
    Many bottles of beer were harmed during the making of this game.
    + +endContents(); ?> diff --git a/scripts/site/main/output/disclaimer.en.inc b/scripts/site/main/output/disclaimer.en.inc new file mode 100644 index 0000000..e0f1775 --- /dev/null +++ b/scripts/site/main/output/disclaimer.en.inc @@ -0,0 +1,31 @@ +title = "Disclaimer"; +$this->addStylesheet('text'); +$this->startContents(); +?> +

    Terms of use

    +

    Hey! Welcome to Legacy Worlds.

    +

    + The email address you supply when registering to Legacy Worlds is for + internal use only; it will not be given to any third party under any + circumstances, and will only be used to send the first time authentication + code, lost passwords and very occasional informations. +

    +

    + Please do not use a password for your account that you use for bank, email, + or other accounts. Although every attempt is used to keep the data secure + there is always the possibility of hackers, etc... Also, don't forget that + we can see the information! +

    +

    + ONE ACCOUNT PER PERSON: although this is just a test, + people need to play fairly or neither the fun aspect nor the balancing + aspect of the game can be measured. +

    +

    + You will find more informations regarding Legacy World's terms of use in + the manual. +

    + 

    Continue to account creation

    ":""?> +endContents(); ?> diff --git a/scripts/site/main/output/donate.en.inc b/scripts/site/main/output/donate.en.inc new file mode 100644 index 0000000..98847cb --- /dev/null +++ b/scripts/site/main/output/donate.en.inc @@ -0,0 +1,105 @@ + + +
    + + + + + + + + + + + + +

    Please wait ...

    +

    + You are being redirected to the PayPal website. If nothing happens after about 10 seconds, please click the button below:
    + +

    +
    + +title = "Donate"; +$this->addStylesheet('text'); +$this->startContents(); +?> + +

    Donating to Legacy Worlds

    + +

    Why donate?

    +

    + As you know, the current version is a Beta; therefore we don't want to force people to pay as we feel it + would be unethical. There are '>other ways you can contribute... +

    +

    + However, if you really like Legacy Worlds, you are encouraged to contribute a little money. This money will + be used to pay for the hosting and server. +

    +

    Please read this if you want to make a donation:

    +
      +
    • Donations won't give you an in-game advantage.
    • +
    • You will not get refunds.
    • +
    • The amount you donate is free for you to determine.
    • +
    • One euro donated will earn you to 10,000 '>contribution credits.
    • +
    • Donations are made through PayPal; we, at LegacyWorlds, only receive the money, but have + no access to your personal data (credit card number, etc...) in any case.
    • +
    +

    + If you agree with this, you can proceed to making a donation. +

    + +

    Current status

    +

    + €" . number_format($args['selfContrib']) . " to LegacyWorlds! Thanks!"; +} +?>

    + €" . number_format($args['totalContrib']) . " has been contributed to LegacyWorlds ("; + if ($args['totalMonth'] == 0) { + echo "no contributions for the past 30 days"; + } else { + echo "€" . number_format($args['totalMonth']) . " in the past 30 days"; + } + echo ")."; +} +?>

    +

    + NOTE: if you just made a contribution, it may not have been accounted for yet. +

    + +

    History

    +No donations have been accounted for at this time.

    "; +} else { +?> + + + + + +\n"; + } + echo "
    Date & timeAmount
    {$d['time']}€{$d['amount']}
    \n"; +} +$this->endContents(); +?> diff --git a/scripts/site/main/output/index.en.inc b/scripts/site/main/output/index.en.inc new file mode 100644 index 0000000..5d6fff0 --- /dev/null +++ b/scripts/site/main/output/index.en.inc @@ -0,0 +1,10 @@ +addStylesheet('home'); ?> + +
    + Legacy Worlds is a free, massively multiplayer, browser-based strategy game.
    + You don't need any plugins to play it, nor do you need to download anything.
    + This game works best with modern browsers such as + Firefox 2 + or Internet Explorer 7.
    + JavaScript must be enabled to play! +
    diff --git a/scripts/site/main/output/kicked.en.inc b/scripts/site/main/output/kicked.en.inc new file mode 100644 index 0000000..869e867 --- /dev/null +++ b/scripts/site/main/output/kicked.en.inc @@ -0,0 +1,24 @@ +title = "Banned"; +$this->addStylesheet('text'); +$this->startContents(); +?> +

    Banned!

    +

    + You have been banned from the game. +

    +

    + Don't even think about coming back. +

    +Reason: $reason

    "; +} + +$this->endContents(); +?> diff --git a/scripts/site/main/output/links.en.inc b/scripts/site/main/output/links.en.inc new file mode 100644 index 0000000..c910abb --- /dev/null +++ b/scripts/site/main/output/links.en.inc @@ -0,0 +1,189 @@ +Submit link - Report broken link

    "; + } +} + +function displayLinks($categories) { + echo "

    Sites related to Legacy Worlds

    "; + if (!count($categories)) { + echo "

    No sites are currently registered.

    "; + displaySubmitLink(); + return; + } + + $hasAccount = (is_array($_SESSION) && !is_null($_SESSION['userid'])); + for ($i=0;$i" . utf8entities($categories[$i]['title']) . ""; + if (!is_null($categories[$i]['description'])) { + echo "
    " . preg_replace('/\n/', '
    ', utf8entities($categories[$i]['description'])); + } + echo "

    "; + + echo "
      "; + if (!count($categories[$i]['links'])) { + echo "
    • No links in this category.
    • "; + } else { + foreach ($categories[$i]['links'] as $l) { + echo "
    • " . utf8entities($l['title']) . ""; + if (!is_null($l['description'])) { + echo "
      " . preg_replace('/\n/', '
      ', utf8entities($l['description'])); + } + echo "
       
    • "; + } + } + echo "
    "; + } + + displaySubmitLink(); +} + + +function displayReportList($list) { +?> +

    Report a broken link

    +
    + +

    + Please select the link to report from the list below.
    + NOTE: users who repeatedly report links as broken for no good reason will be banned from the game. If you made a mistake, please send an in-game private message to TSeeker or send an e-mail to the staff.
    +
    +
    +
    + + +

    +
    + +

    Report a broken link

    +
    + + +

    + You are about to report the following link as being broken:
    +
    + Link name:
    + URL:
    +
    + WARNING: users who repeatedly report links as broken for no good reason will be banned from the game. If you make a mistake, please send an in-game private message to TSeeker or send an e-mail to the staff.
    +
    + + +

    +
    + +

    Link reported

    +

    + Your report stating that the link is broken has been registered.
    + The link will be removed after the staff verifies your report.
    +
    + Back to the list +

    + +

    Submit a new link

    +
    + +

    + Please fill in the form below to submit a new website to be added to the links page.
    +
    + NOTE: the links will be reviewed by the staff before they appear anywhere on the links page. Users who repeatedly submit unrelated websites (and especially commercial, pornographic or illegal sites) will be banned from the game. +

    + + + + + +\n"; + } +?> + + + + +\n"; + } elseif ($data['err'] == 5) { + echo "\n"; + } elseif ($data['err'] == 6) { + echo "\n"; + } +?> + + + + +\n"; + } +?> + + + + +
    Link title:
     This title is too "; + echo ($data['err'] == 1 ? "short" : "long"); + echo "
    URL:
     This URL is invalid.
     This server does not exist.
     You have already submitted this URL.
    Description (optional):
     Please type in a longer description.
      + + +
    +
    + +

    Link submitted

    +

    + The link to has been submitted.
    + It will be added after the staff verifies it.
    +
    + Back to the list +

    +title = "Links"; +$this->startContents(); +switch($args['mode']) : + case 1: displayReportList($args['data']); break; + case 2: displayReportConfirm($args['data']); break; + case 3: displayReportDone($args['data']); break; + case 4: displaySubmitForm($args['data']); break; + case 5: displaySubmitDone($args['data']); break; + default: displayLinks($args['data']); break; +endswitch; +$this->endContents(); + + +?> diff --git a/scripts/site/main/output/login.en.inc b/scripts/site/main/output/login.en.inc new file mode 100644 index 0000000..b80c749 --- /dev/null +++ b/scripts/site/main/output/login.en.inc @@ -0,0 +1,20 @@ +addStylesheet('text'); +$this->startContents(); +$this->title = "Please log in"; +if ($args['error']) { +?> +

    Invalid credentials

    +

    The username or password you entered are invalid.

    +

    Please try typing them again.

    + +

    Please log in

    +

    The page you are trying to view requires you to be a registered LegacyWorlds player.

    +

    Please type in your username and password into the login form at the top right corner of this page.

    +endContents(); +?> diff --git a/scripts/site/main/output/logout.en.inc b/scripts/site/main/output/logout.en.inc new file mode 100644 index 0000000..50de867 --- /dev/null +++ b/scripts/site/main/output/logout.en.inc @@ -0,0 +1,20 @@ +addStylesheet('text'); +$this->title = "Logged out"; +$this->startContents(); +?> +

    Logged out

    +

    + You are now logged out of Legacy Worlds. +

    +

    + Thanks for playing! +

    +

    + In the meantime, feel free to visit our other games. +

    +

    + See you soon! +

    +endContents(); ?> diff --git a/scripts/site/main/output/lostpass.en.inc b/scripts/site/main/output/lostpass.en.inc new file mode 100644 index 0000000..8900b2b --- /dev/null +++ b/scripts/site/main/output/lostpass.en.inc @@ -0,0 +1,114 @@ +title = "Password recovery"; +$this->startContents(); +?> + +
    +

    Forgot your password?

    +

    + This page allows you to have the server change your password to a new, random password. In order to do that, you will first have to enter your username as well as the email address you used when you created your account. You will then receive a confirmation code. +

    +

     

    + + + + + + + + + + + + + + + + + +
    Username:" size="17" maxlength="16" class="input" />
    E-mail address:" style="width:80%" class="input" />
     Invalid or unknown username or mail address.
     
     
    +
    + +

    Your password has been changed!

    +

    + You will receive an email containing your new password in a few minutes.
    + Thank you! +

    + +

    Unable to send the confirmation mail

    +

    + The server was unable to send the email containing the confirmation code.
    + It's possible the server is too loaded at the moment; please try again later.
    + Sorry for the inconvenience. +

    + +

    Confirmation of the request

    +

    + An email has been sent to the address associated with your account; it contains a confirmation code.
    + Please copy this confirmation code in the box below in order to confirm that you want your password to be reset to a random value which will be mailed to you. +

    +

     

    +
    + " class="input" /> + " class="input" /> + + + + + + + + + + + + + +
    Confirmation code:" size="17" maxlength="16" class="input" />
     Invalid confirmation code.
     
     
    +
    + +

    Unable to send the new password

    +

    + The server was unable to send the email containing the new password; your password was therefore kept unmodified.
    + It's possible the server is too loaded at the moment; please try again later.
    + Sorry for the inconvenience. +

    + +

    Something unexpected happened ...

    +

    + Apparently, this script smoked too much Space Weed and just had a fleeting moment of randomness. Of course, what we mean is the this is a bug. Please contact the Legacy Worlds staff regarding this error and give them the following value: . +

    + +endContents(); ?> diff --git a/scripts/site/main/output/manual-box.en.inc b/scripts/site/main/output/manual-box.en.inc new file mode 100644 index 0000000..c87056d --- /dev/null +++ b/scripts/site/main/output/manual-box.en.inc @@ -0,0 +1,71 @@ +getLib('main/manual'); + +// We're in the manual, display manual controls +$manual = $man->call('getStructure', 'en'); +$txt = array('Top', 'Up', 'Previous', 'Next'); +if (!is_null(handler::$h->page)) { + $pid = handler::$h->page['id']; + $navLinks = $man->call('getNavLinks', $pid); + for ($i=0;$i<4;$i++) { + $t = is_null($navLinks[$i]) ? "" : ""; + $t .= $txt[$i] . (is_null($navLinks[$i]) ? "" : ""); + $navLinks[$i] = $t; + } +} else { + $pid = null; + $navLinks = $txt; +} + +ob_start(); +?> +
    +
      +
    • +
    • +
    • +
    • +
    +
    +
    + searchText) ?>" class="input" id="msb-stext" /> + +
    +
    + Contents +
    + + diff --git a/scripts/site/main/output/manual.en.inc b/scripts/site/main/output/manual.en.inc new file mode 100644 index 0000000..fae3885 --- /dev/null +++ b/scripts/site/main/output/manual.en.inc @@ -0,0 +1,135 @@ +{$list[$k]['title']}"; + if (count($list[$k]['subsections'])) { + echo "
      "; + drawContents($list[$k]['subsections']); + echo "
    "; + } + echo ""; + } +} + + +function displayLinks($text) { + $l = explode('(.*\n)*.*/', '\1', $t); + $toName = preg_replace('/\s/', '', $toName); + $t = preg_replace('/^[A-Za-z0-9_\-]+[\'"]>/', '', $t); + + $secId = handler::$h->lib->call('getSectionId', handler::$h->lang, $toName); + $link = ""; + if (!is_null($secId)) { + $pageId = handler::$h->lib->call('getPageId', $secId); + if (!is_null($pageId)) { + $lt = handler::$h->lib->call('readSectionRecord', $secId); + if ($pageId == handler::$h->page['id']) { + $link = ""; + } else { + $pg = handler::$h->lib->call('readSectionRecord', $pageId); + $link = ""; + } + } + } + + $nText .= $link . preg_replace('/^(.*)<\/mlink>(.*\n)*.*/', '\1', $t) . ($link != '' ? "" : "") + . preg_replace('/^.*<\/mlink>/', '', $t); + } + + return $nText; +} + + +function drawTitle(&$section, $depth) { + $pgLink = ""; + if ($section['linkto'] != "") { + $pageId = handler::$h->lib->call('getPageId', $section['linkto']); + if (!is_null($pageId)) { + $lt = handler::$h->lib->call('readSectionRecord', $section['linkto']); + if ($pageId == handler::$h->page['id']) { + $pgLink = ""; + } else { + $pg = handler::$h->lib->call('readSectionRecord', $pageId); + $pgLink = ""; + } + } + } + + $mDepth = ($depth - 2) * 10; + $tMargin = ($pgLink == "") ? "5px $mDepth 15px 0px" : "5px $mDepth 5px 0px"; + +?> + +'>Top + + id='ac-'>> + +-> Main article +"; + } +*/ +?> + +
    + +
    + +" . displayLinks($list[$k]['contents']) . "
    "; + } + if (count($list[$k]['subsections'])) { + drawSections($list[$k]['subsections'], $depth + 1); + } +?> +
    +title = "Manual"; +$this->addStylesheet("manual"); +$this->addScript("manual"); +if (is_null(handler::$h->page)) { + include('manual_notfound.en.inc'); +} else { +?> +
    +

    page['title']?>

    +page['subsections'])) : ?> +
    +
    + Page contents + [ HideShow ] +
    +
    +
      +page['subsections']); ?> +
    +
    +
    +page['subsections']); ?> + +
    +endContents(); +?> diff --git a/scripts/site/main/output/manual_notfound.en.inc b/scripts/site/main/output/manual_notfound.en.inc new file mode 100644 index 0000000..98aa43d --- /dev/null +++ b/scripts/site/main/output/manual_notfound.en.inc @@ -0,0 +1,5 @@ +

    Page not found!

    +

    + The page you were looking for could not be found in the manual. Maybe the manual + has been updated in the meantime. +

    diff --git a/scripts/site/main/output/manual_search.en.inc b/scripts/site/main/output/manual_search.en.inc new file mode 100644 index 0000000..1961220 --- /dev/null +++ b/scripts/site/main/output/manual_search.en.inc @@ -0,0 +1,35 @@ +title = "Manual"; +$this->addStylesheet("manual"); +$this->addScript("manual"); +?> +
    +

    Search results for ''

    +Sorry, no results were found.

    \n"; +} else { + $c = count($args['results']); + echo "

    $c result" . ($c > 1 ? "s were" : " was") . " found:

    "; + echo "
      "; + $error = false; + foreach ($args['results'] as $p) { + $pData = handler::$h->lib->call('readSectionRecord', $p); + if (is_null($pData)) { + $error = true; + continue; + } + echo "
    1. {$pData['title']}
    2. "; + } + echo "
    "; + if ($error) { + echo "Some results could not be displayed because of a database error."; + } + echo "
    "; +} + +?> +
    +
    diff --git a/scripts/site/main/output/menu.en.inc b/scripts/site/main/output/menu.en.inc new file mode 100644 index 0000000..b009d32 --- /dev/null +++ b/scripts/site/main/output/menu.en.inc @@ -0,0 +1,12 @@ + + My account + Contributions + + Create an account + About this game + +Screenshots +Manual +Rankings +Links +Credits diff --git a/scripts/site/main/output/notfound.en.inc b/scripts/site/main/output/notfound.en.inc new file mode 100644 index 0000000..e3602e7 --- /dev/null +++ b/scripts/site/main/output/notfound.en.inc @@ -0,0 +1,14 @@ +title = "Page not found"; +$this->addStylesheet('text'); +$this->startContents(); +?> +

    Page not found

    +

    + The page you requested could not be found on this server. +

    +

    + Sorry :-\ +

    +endContents(); ?> diff --git a/scripts/site/main/output/notregistered.en.inc b/scripts/site/main/output/notregistered.en.inc new file mode 100644 index 0000000..6fd93cb --- /dev/null +++ b/scripts/site/main/output/notregistered.en.inc @@ -0,0 +1,14 @@ +title = "Registration required"; +$this->addStylesheet('text'); +$this->startContents(); +?> +

    +

    + You are not registered to this game. +

    +

    + Before you can access the game, you have to ?g='>register. +

    +endContents(); ?> diff --git a/scripts/site/main/output/pcheck.inc b/scripts/site/main/output/pcheck.inc new file mode 100644 index 0000000..4bc9d90 --- /dev/null +++ b/scripts/site/main/output/pcheck.inc @@ -0,0 +1 @@ + diff --git a/scripts/site/main/output/play.en.inc b/scripts/site/main/output/play.en.inc new file mode 100644 index 0000000..d72833c --- /dev/null +++ b/scripts/site/main/output/play.en.inc @@ -0,0 +1,120 @@ +title = "Registration"; +$this->startContents(); +?> + +

    Please wait ...

    +

    + You should be redirected to the game's main page shortly.
    + If nothing happens after 10 seconds, please click here.

    + +account page and enter the game from there."; + break; + case 2: + $title = "Registration failed :-("; + $descr = "An internal error prevented you from joining the game.
    Please contact the staff " + . "so that we can help you with it."; + break; + default: + $title = "Unknown error"; + $descr = "An unknown error has occured.
    Please contact the staff."; + break; + endswitch; + + echo "

    $title

    \n

    $descr

    "; +} + + + +function displayForm($args) { +?> +
    + ' /> +

    You are about to join

    +

    + +"; + } +?> + + + + +"; + } +?> + + + + + + + +
    "; + switch ($args['planetError']) : + case 1: echo "This planet name is too long (maximum 15 characters)"; break; + case 2: echo "This planet name is incorrect (letters, numbers, spaces and _.@-+'/ only)"; break; + case 3: echo "Multiple spaces are not allowed"; break; + case 4: echo "This planet name is too short (minimum 2 characters)"; break; + case 5: echo "Planet names must contain at least a letter"; break; + case 6: echo "A planet by that name already exists."; break; + case 7: echo "Spaces are not allowed at the beginning or at the end of the planet's name"; break; + endswitch; + echo ".
    Name of your first planet:" size='16' maxlength='15' class="input" />
    "; + switch ($args['playerError']) : + case 1: echo "This player name is too long (maximum 15 characters)"; break; + case 2: echo "This player name is incorrect (letters, numbers, spaces and _.@-+'/ only)"; break; + case 3: echo "Spaces are not allowed at the beginning or at the end of the player name"; break; + case 4: echo "Multiple spaces are not allowed"; break; + case 5: echo "This player name is too short (minimum 2 characters)"; break; + case 6: echo "A player by that name already exists."; break; + endswitch; + echo ".
    Choose a player name:
     
     
    +
    +addStylesheet('text'); + displayError($args['error']); + } +} else { + $this->addStylesheet('text'); + redirect($args['registered']); +} + +?> +endContents(); ?> diff --git a/scripts/site/main/output/ppipn.en.inc b/scripts/site/main/output/ppipn.en.inc new file mode 100644 index 0000000..459c965 --- /dev/null +++ b/scripts/site/main/output/ppipn.en.inc @@ -0,0 +1 @@ +Paypal IPN page diff --git a/scripts/site/main/output/quit_confirm.en.inc b/scripts/site/main/output/quit_confirm.en.inc new file mode 100644 index 0000000..4f1497c --- /dev/null +++ b/scripts/site/main/output/quit_confirm.en.inc @@ -0,0 +1,60 @@ +addStylesheet("account"); +$this->title = "Close account"; +$this->startContents(); +?> +
     
    +
    +

    You are about to close your account

    +
    + + +

    + In order to close your account, you must provide the following details to make sure it really is you: +

    + + + + + + + + + + + +
    Password:
      + The password you provided is incorrect.
    + +

    + In addition, you may provide us with a reason why you're quitting the game, as it could allow us to make it better. But that is of course optional.
    +

    + + + + + + +
    Why are you leaving?
    + +

    + + +

    +
    +
    +endContents(); ?> diff --git a/scripts/site/main/output/rankings.en.inc b/scripts/site/main/output/rankings.en.inc new file mode 100644 index 0000000..ff4f446 --- /dev/null +++ b/scripts/site/main/output/rankings.en.inc @@ -0,0 +1,48 @@ +addStylesheet('rankings'); +$this->title = "Rankings"; +$this->startContents(); +?> +
    +
    + / +
    +
    +
    + +
    + + + + + + + +\n \n \n \n \n"; +?> +
    RankNamePoints
    #" + . $data['ranking'] . "" . utf8entities($data['id']) + . "" + . number_format($data['points']) . "
    +endContents(); ?> diff --git a/scripts/site/main/output/restart.en.inc b/scripts/site/main/output/restart.en.inc new file mode 100644 index 0000000..7bdf97f --- /dev/null +++ b/scripts/site/main/output/restart.en.inc @@ -0,0 +1,31 @@ +addStylesheet('text'); +$this->title = "Account re-activation"; +$this->startContents(); +?> +
    +

    Welcome back!

    +

    + Since you had left the game, you will need to confirm that you want to + re-activate this account. +

    +

    + Your email address is ; a confirmation code + will be sent to that address after you click the button below. +

    +

    + Once you have received the email containing the confirmation code, you + will need to log in again and type in the code. +

    +

    + +

    +

    + Note: if your email address has changed and you still want to + keep this account, please send an email to the + staff; you must indicate your + user name and password in the email. +

    +
    +endContents(); ?> diff --git a/scripts/site/main/output/screenshots.en.inc b/scripts/site/main/output/screenshots.en.inc new file mode 100644 index 0000000..7ee7603 --- /dev/null +++ b/scripts/site/main/output/screenshots.en.inc @@ -0,0 +1,67 @@ +title = "Screenshots"; +$this->addStylesheet("screenshots"); +$this->startContents(); + +if (is_null($__category)) { +?> +
    +
    Screenshots
    +
    Select a category
    +
    + $data) { +?> +
    +
    + +
    +
    + pictures +
    +
    + +
    +
    Screenshots -
    + +
    + $title) { +?> +
    +
    <?= utf8entities($title) ?>
    +
    +
    + +
    +
    + +
    +
    + <?= utf8entities($title) ?> +
    +endContents(); + +?> diff --git a/scripts/site/main/output/settings.en.inc b/scripts/site/main/output/settings.en.inc new file mode 100644 index 0000000..cb96213 --- /dev/null +++ b/scripts/site/main/output/settings.en.inc @@ -0,0 +1,136 @@ +title = "Preferences"; +$this->startContents(); +?> +"; + return $t; +} + +?> +
    +

    's general preferences

    +

     

    + +\n"; +?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +\n"; +} +?> + + + + + + + + + + + + + + + + +
    The address you entered is invalid, please correct it.
    E-mail address:Language:
    Colour scheme:Font size:
     
    Forums
    +
    Topics/page:Messages/page:
    Graphical smileys:Forum tags:
    Display mode:Messages order:
    Signature:
     
    Password modification
    + Please leave theses fields empty if you do not intend to change your password. +
    "; + switch ($args['err2']) : + case 1: echo "A database access error has occured"; break; + case 2: echo "The current password is incorrect"; break; + case 3: echo "The new password and its confirmation are different"; break; + case 4: echo "The new password is too short (minimum 4 characters)"; break; + case 5: echo "The new password is too long (maximum 64 characters)"; break; + case 6: echo "The new password must be different from your user name"; break; + endswitch; + echo ".
     
    Current password:
    New password:
    Confirm new password:
     
    +
    +endContents(); ?> diff --git a/scripts/site/main/output/side-box.en.inc b/scripts/site/main/output/side-box.en.inc new file mode 100644 index 0000000..da98280 --- /dev/null +++ b/scripts/site/main/output/side-box.en.inc @@ -0,0 +1,21 @@ +addStylesheet("sidebox"); +$this->addScript("sidebox"); +?> +
    +
      +
    • + Manual +
      + +
      +
    • +
    • + Menu +
      + +
      +
    • +
    +
    +
    diff --git a/scripts/site/main/output/vac_cancel.en.inc b/scripts/site/main/output/vac_cancel.en.inc new file mode 100644 index 0000000..401110c --- /dev/null +++ b/scripts/site/main/output/vac_cancel.en.inc @@ -0,0 +1,22 @@ +title = "Cancel vacation mode"; +$this->addStylesheet("account"); +$this->startContents(); +?> +
     
    +
    +

    You are about to cancel your request for vacation mode

    +

    + Your account will no longer enter vacation mode at the indicated time and re-entering vacation mode after that will take another 24 to 30 hours. +

    +

    + Are you sure you want to do that? +

    +
    + + + +
    +
    +endContents(); ?> diff --git a/scripts/site/main/output/vac_leave.en.inc b/scripts/site/main/output/vac_leave.en.inc new file mode 100644 index 0000000..8cd4720 --- /dev/null +++ b/scripts/site/main/output/vac_leave.en.inc @@ -0,0 +1,23 @@ +title = "Leave vacation mode"; +$this->addStylesheet("account"); +$this->startContents(); +?> +
     
    +
    +

    You are about to leave vacation mode

    +

    + You will be able to play the game again.
    + However, you will not be able to enter vacation mode for the next seven days. +

    +

    + Are you sure you want to do that? +

    +
    + + + +
    +
    +endContents(); ?> diff --git a/scripts/site/main/output/vac_start.en.inc b/scripts/site/main/output/vac_start.en.inc new file mode 100644 index 0000000..33e4fdf --- /dev/null +++ b/scripts/site/main/output/vac_start.en.inc @@ -0,0 +1,28 @@ +title = "Enter vacation mode"; +$this->addStylesheet("account"); +$this->startContents(); +?> +
     
    +
    +

    You are about to enter vacation mode

    +

    + For the first 30h or so, the account will still be accessible normally and vacation mode rules will not apply in the games you are playing. +

    +

    + After this delay, vacation mode will be enabled. You will no longer be able to play the games or register to new games, but you will still be + able to access your private messages and the games' forums. +

    +

    + Vacation mode will end either when you decide to exit vacation mode, or when you run out of vacation credits; whichever comes first. +

    + Are you sure you want to do that? +

    +
    + + + +
    +
    +endContents(); ?> diff --git a/scripts/site/main/page.inc b/scripts/site/main/page.inc new file mode 100644 index 0000000..8327b62 --- /dev/null +++ b/scripts/site/main/page.inc @@ -0,0 +1,85 @@ +dir = config::$main['scriptdir'] . "/site/main"; + $this->static = config::$main['staticdir'] . "/main"; + } + + private function _include($file, $args = array()) { + foreach ($args as $k => $v) { + $nk = "__$k"; + $$nk = $v; + } + include($file); + } + + private function addStylesheet($name) { + addFileResource('css', "{$this->static}/css/$name.css"); + } + + private function addScript($name) { + addFileResource('js', "{$this->static}/js/$name.js"); + } + + private function startContents() { + $this->addStylesheet("content"); + echo "
    "; + } + + private function endContents() { + echo "
    "; + } + + public function header($pg, $lg) { + if ($pg == "ppipn" || $pg == "pcheck") { + return; + } + + // Output the HTML header + echo "\n"; + $this->_include("{$this->dir}/layout/header.inc", array("lang" => $lg)); + $this->addStylesheet('main'); + $this->addScript('jquery'); + $this->addScript('adapt'); + } + + public function includeFile($file, $args = array()) { + ob_start(); + $this->_include($file, is_array($args) ? $args : array($args)); + $this->pageContents = ob_get_contents(); + ob_end_clean(); + } + + public function footer($pg, $lg) { + if ($pg == "ppipn" || $pg == "pcheck") { + echo $this->pageContents; + return; + } + + $this->cssRes = storeResource("css", 345600); + $this->jsRes = storeResource("js", 345600); + + $aLib = config::getGame('main')->getLib('main/account'); + list($tAcc, $oAcc) = $aLib->call('getAccounts'); + + if ($_SESSION['authok']) { + $vacation = config::getGame('main')->getLib('main/vacation')->call( + 'isOnVacation', $_SESSION['userid']); + } else { + $vacation = false; + } + $this->_include("{$this->dir}/layout/actual-header.$lg.inc", array( + "accounts" => $tAcc, + "online" => $oAcc, + "vacation" => $vacation + )); + + echo $this->pageContents; + $this->_include("{$this->dir}/layout/footer.inc"); + } + +} + +?> diff --git a/scripts/ticks.php b/scripts/ticks.php new file mode 100644 index 0000000..c66d3e2 --- /dev/null +++ b/scripts/ticks.php @@ -0,0 +1,113 @@ +$it"; + } + + $bt = debug_backtrace(); + array_shift($bt); // Remove this function + array_shift($bt); // Remove eval in lib/log.inc + + $errorText .= "
    Backtrace to the error:
    ";
    +		$base = dirname(config::$main['scriptdir']);
    +		foreach ($bt as $data) {
    +			$str = "... " . str_repeat(' ', strlen($data['class']) > 30 ? 1 : (31 - strlen($data['class'])))
    +				. $data['class'] . " :: " . $data['function'];
    +			if (!is_null($data['file'])) {
    +				$str .= str_repeat(' ', strlen($data['function']) > 25 ? 1 : (26 - strlen($data['function'])));
    +				$fn = preg_replace("#^$base/#", "", $data['file']);
    +				$str .= "  (line {$data['line']}, file '$fn')";
    +			}
    +			$errorText .= "$str\n";
    +		}
    +		$errorText .= "
    "; + + throw new Exception($errorText); + } + + l::setFatalHandler('__adminFatalError'); + try { + dbConnect(); + + $game = config::getGame($gName); + if (is_null($game)) { + throw new Exception("Game '$gName' not found"); + } + + l::notice("administration script executing tick {$gName}::{$tName}"); + $game->getDBAccess(); + $game->runTick($tName, true); + l::notice("tick {$gName}::{$tName} executed"); + + dbClose(); + } catch (Exception $e) { + $argh = $e->getMessage(); + } + chdir($oldDir); + return; +} else { + l::setSyslogPrefix("lwTicks"); + if (count($argv) > 1) { + /* Checks for command line arguments */ + if ($argv[1] == "-r" && count($argv) == 4) { + $game = $argv[2]; + $tick = $argv[3]; + + dbConnect(); + + $game = config::getGame($argv[2]); + if (is_null($game)) { + die("Error: game {$argv[2]} not found"); + } + l::notice("manually executing {$argv[2]}::{$argv[3]}"); + $game->getDBAccess(); + $game->runTick($argv[3], true); + l::notice("{$argv[2]}::{$argv[3]} executed"); + + dbClose(); + exit(0); + } elseif ($argv[1] != "-d") { + die("Syntax: {$argv[0]}\n\t -> to run as a daemon\n\t{$argv[0]} -d\n\t -> to run in debugging mode\n\t{$argv[0]} -r \n\t -> to run a tick manually\n"); + } + } else { + /* Starts the main thread */ + new tick_manager($argv[1] == "-d"); + } + +} + +?> diff --git a/site/beta4/Rank.html b/site/beta4/Rank.html new file mode 100644 index 0000000..72029ea --- /dev/null +++ b/site/beta4/Rank.html @@ -0,0 +1,15 @@ + + + + Legacy Rankings + + + + + + <body> + Your browser does not accept frames, darn. + </body> + + + diff --git a/site/beta4/Rankings.php b/site/beta4/Rankings.php new file mode 100644 index 0000000..0ebbcaa --- /dev/null +++ b/site/beta4/Rankings.php @@ -0,0 +1,35 @@ + + + + Legacy: Player Rankings + + + + + +
    +Legacy Player Rankings"; +echo ""; +while ($PlayerList = mysql_fetch_array($GetPlayerList)){ + $Name = $PlayerList['playerName']; + $Rank ++; + $Points = $PlayerList['rank']; + echo ""; + +} +echo "
    RankPlayerPoints
    $Rank $Name$Points
    "; + + +?> + + + + diff --git a/site/beta4/Rankswitch.html b/site/beta4/Rankswitch.html new file mode 100644 index 0000000..3e1460a --- /dev/null +++ b/site/beta4/Rankswitch.html @@ -0,0 +1,14 @@ + + + Legacy: Player Rankings + + + + + + + + + +
    Current rankingFinancial/Military RankingsAlliance RankingsOverall Round Rankings
    + diff --git a/site/beta4/background.jpg b/site/beta4/background.jpg new file mode 100644 index 0000000..c6488e6 Binary files /dev/null and b/site/beta4/background.jpg differ diff --git a/site/beta4/images/Legacy.jpg b/site/beta4/images/Legacy.jpg new file mode 100644 index 0000000..2ab9642 Binary files /dev/null and b/site/beta4/images/Legacy.jpg differ diff --git a/site/beta4/images/background.jpg b/site/beta4/images/background.jpg new file mode 100644 index 0000000..c6488e6 Binary files /dev/null and b/site/beta4/images/background.jpg differ diff --git a/site/beta4/legacyfront.gif b/site/beta4/legacyfront.gif new file mode 100644 index 0000000..fcd5cd6 Binary files /dev/null and b/site/beta4/legacyfront.gif differ diff --git a/site/beta4/legacyfront.jpg b/site/beta4/legacyfront.jpg new file mode 100644 index 0000000..f2b1058 Binary files /dev/null and b/site/beta4/legacyfront.jpg differ diff --git a/site/beta4/red.css b/site/beta4/red.css new file mode 100644 index 0000000..258a7d9 --- /dev/null +++ b/site/beta4/red.css @@ -0,0 +1,102 @@ +body {background-color="#000000"; + background-image="images/background.jpg"; + font-family: Bookman Old Style; + color:#C0C0C0} +A:link{color: #CC3300} + +A:visited{color: #CC3300} + +h1 {font-size: 14pt; + font-family: Bookman Old Style; + color: #CC3300; + font-style: normal; + line-height: normal; + font-weight: bold; + font-variant: normal; + text-transform: none; + text-decoration: none; + margin-bottom:0px; + margin-top:0px } +h2 {font-size: 14pt; + font-family: Bookman Old Style; + color: #CC3300; + font-style: italic; + line-height: normal; + font-weight: bold; + font-variant: normal; + text-transform: none; + text-decoration: none; + margin-top:0px; + margin-bottom:0px} +h3 {font-size: 14pt; + font-family: Bookman Old Style; + color: #CC3300; + font-style: italic} +h4 {font-size: 12pt; + font-family: Bookman Old Style; + color: #CC3300; + font-style: italic; + line-height: normal; + font-weight: bold; + font-variant: normal; + text-transform: none; + text-decoration: none; + margin-bottom:0px; + margin-top:0px } +h6 {font-size:12pt; + color: #FFFFFF; + font-weight: bold + text-decoration: underline; + margin-bottom:0px; + margin-top:0px} + +em {color: #FFFFFF; + font-style: normal} +em.i{font-style: italic} + +em.green {color: #33CC33; + font-style: normal; + font-weight: bold} + +em.red {color: #FF3300; + font-style: normal; + font-weight: bold} + +em.blue {color: #6666FF; + font-style: normal; + font-weight: bold} + +p {margin-bottom:0px; + margin-top:0px;} + +p.green{color: #33CC33; + font-weight: bold} + +p.blue{color: #6666FF; + font-weight: bold} + +p.red {color: #FF3300; + font-weight: bold} + +p.darkend {color: #808080; + font-weight: normal} + +td {font-size: 12pt; + font-family: Bookman Old Style; + color: #C0C0C0; + font-style: normal; + line-height: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + text-decoration: none} + +th {font-size: 12pt; + font-family: Bookman Old Style; + color: #CC3300; + font-style: italic; + line-height: normal; + font-weight: bold; + font-variant: normal; + text-transform: none; + text-decoration: none} diff --git a/site/downloads/LegacyWorlds-Dashboard-latest.zip b/site/downloads/LegacyWorlds-Dashboard-latest.zip new file mode 100644 index 0000000..3be22e6 Binary files /dev/null and b/site/downloads/LegacyWorlds-Dashboard-latest.zip differ diff --git a/site/index.php b/site/index.php new file mode 100644 index 0000000..a255e98 --- /dev/null +++ b/site/index.php @@ -0,0 +1,35 @@ + diff --git a/site/static/beta5/css/blue.css b/site/static/beta5/css/blue.css new file mode 100644 index 0000000..d29cc80 --- /dev/null +++ b/site/static/beta5/css/blue.css @@ -0,0 +1,55 @@ +/** GENERAL ELEMENTS **/ + +body, p, td, th, h1, h2, h3, h4, h5, h6, a { color: #C0C0C0; } +h1, h2, h3 { color: #1F7FFF; } +h4, h5, h6, th { color: #5F9FFF; } +a, a:visited { color: white; } +b { color: white; } +table.list a { color: #C0C0C0; } +table.list tr:hover td, table.list tr:hover td > * { color: white; } + + +/** STATUS AND MENU BARS **/ + +table.topframe { + background-color: #1F1F7F; + border: 1px solid #3F3F7F; +} +td.topmenu { + background-image: url('__STATICURL__/beta5/pics/ttl/def/blue/menubg.png'); + background-repeat: repeat-x; +} +ul ul.tmenu li.tmenu { background-color: #1F1F7F; } +ul.tmenu li:hover { background-color: #3F3F7F; } +a.tmenu:hover { background-color: black; } +ul.tmenu li.tmenu:hover ul.tmenu { border: 1px solid #3F7FCF; } +td.player, td.funds { color: #66CCFF; } +td.player b, td.funds b { color: white; } +td.stime { color: #66CCFF; } + + +/** FORMS **/ +input[type=text],input[type=password],input[type=submit],input[type=reset],input[type=button],select,textarea { + border: 1px solid #1F1F7F; + color: #5F9FFF; + background-color: black; +} + +input[disabled] { color: #3F3F3F; border: 1px solid #3F3F3F; } + + + +/** MAP COLORS **/ + +table.map li:hover, table.map li:hover a { color: white; } + +.mcown { color: #33CC33; } +a.mcown:visited { color: #33CC33; } +.mcally { color: #6666FF; } +a.mcally:visited { color: #6666FF; } +.mcprot { color: #AFAFAF; background-color: black; } +a.mcprot:visited { background-color: black; } +.mcpally { color: #6666FF; background-color: black; } +a.mcpally:visited { color: #6666FF; } +.mcpown { color: #33CC33; background-color: black; } +a.mcpown:visited { color: #33CC33; } diff --git a/site/static/beta5/css/fonts0.css b/site/static/beta5/css/fonts0.css new file mode 100644 index 0000000..096a0b2 --- /dev/null +++ b/site/static/beta5/css/fonts0.css @@ -0,0 +1,7 @@ +table.crpmenu h1 { font-size: 9px } +table.crpmenu td.stime { font-size: 8px } +h1 { font-size: 13pt; } +h2 { font-size: 11pt; } +h3 { font-size: 9pt; } +body,p,input,td,th,div { font-size: 7pt; } +select,option,input,textarea { font-size: 7pt; } diff --git a/site/static/beta5/css/fonts1.css b/site/static/beta5/css/fonts1.css new file mode 100644 index 0000000..6d44152 --- /dev/null +++ b/site/static/beta5/css/fonts1.css @@ -0,0 +1,7 @@ +table.crpmenu h1 { font-size: 10px } +table.crpmenu td.stime { font-size: 9px } +h1 { font-size: 15pt; } +h2 { font-size: 13pt; } +h3 { font-size: 11pt; } +body,p,input,td,th,div { font-size: 9pt; } +select,option,input,textarea { font-size: 9pt; } diff --git a/site/static/beta5/css/fonts2.css b/site/static/beta5/css/fonts2.css new file mode 100644 index 0000000..a623ac6 --- /dev/null +++ b/site/static/beta5/css/fonts2.css @@ -0,0 +1,7 @@ +table.crpmenu h1 { font-size: 11px } +table.crpmenu td.stime { font-size: 10px } +h1 { font-size: 17pt; } +h2 { font-size: 15pt; } +h3 { font-size: 13pt; } +body,p,input,td,th,div { font-size: 11pt; } +select,option,input,textarea { font-size: 11pt; } diff --git a/site/static/beta5/css/fonts3.css b/site/static/beta5/css/fonts3.css new file mode 100644 index 0000000..19554fb --- /dev/null +++ b/site/static/beta5/css/fonts3.css @@ -0,0 +1,7 @@ +table.crpmenu h1 { font-size: 12px } +table.crpmenu td.stime { font-size: 11px } +h1 { font-size: 19pt; } +h2 { font-size: 17pt; } +h3 { font-size: 15pt; } +body,p,input,td,th,div { font-size: 13pt; } +select,option,input,textarea { font-size: 13pt; } diff --git a/site/static/beta5/css/fonts4.css b/site/static/beta5/css/fonts4.css new file mode 100644 index 0000000..8e1cf4a --- /dev/null +++ b/site/static/beta5/css/fonts4.css @@ -0,0 +1,7 @@ +table.crpmenu h1 { font-size: 13px } +table.crpmenu td.stime { font-size: 12px } +h1 { font-size: 21pt; } +h2 { font-size: 19pt; } +h3 { font-size: 17pt; } +body,p,input,td,th,div { font-size: 15pt; } +select,option,input,textarea { font-size: 15pt; } diff --git a/site/static/beta5/css/green.css b/site/static/beta5/css/green.css new file mode 100644 index 0000000..6e4ee5b --- /dev/null +++ b/site/static/beta5/css/green.css @@ -0,0 +1,53 @@ +/** GENERAL ELEMENTS **/ + +body, p, td { color: #C0C0C0; } +h1, h2, h3 { color: #1FCF1F; } +h4, h5, h6, th { color: #3FAF3F; } +a, a:visited { color: white; } +b { color: white; } +table.list a { color: #C0C0C0; } +table.list tr:hover td, table.list tr:hover td > * { color: white; } + +/** STATUS AND MENU BARS **/ + +table.topframe { + background-color: #1F7F1F; + border: 1px solid #3F7F3F; +} +td.topmenu { + background-image: url('__STATICURL__/beta5/pics/ttl/def/green/menubg.png'); + background-repeat: repeat-x; +} +ul ul.tmenu li.tmenu { background-color: #1F7F1F; } +ul.tmenu li:hover { background-color: #3F9F4F; } +table.topmenu a.tmenu:hover { background-color: black; } +ul.tmenu li.tmenu:hover ul.tmenu { border: 1px solid #7FCF3F; } +td.player, td.funds { color: #8FFFAF; } +td.player b, td.funds b { color: white; } +td.stime { color: #E0E0E0; } + + +/** FORMS **/ +input[type=text],input[type=password],input[type=submit],input[type=reset],input[type=button],select,textarea { + border: 1px solid #1F7F1F; + color: #3FAF3F; + background-color: black; +} + +input[disabled] { color: #3F3F3F; border: 1px solid #3F3F3F; } + + +/** MAP COLORS **/ + +table.sys td:hover, table.sys td:hover a { color: white; } + +.mcown { color: #33CC33; } +a.mcown:visited { color: #33CC33; } +.mcally { color: #6666FF; } +a.mcally:visited { color: #6666FF; } +.mcprot { color: #AFAFAF; background-color: black; } +a.mcprot:visited { background-color: black; } +.mcpally { color: #6666FF; background-color: black; } +a.mcpally:visited { color: #6666FF; } +.mcpown { color: #33CC33; background-color: black; } +a.mcpown:visited { color: #33CC33; } diff --git a/site/static/beta5/css/grey.css b/site/static/beta5/css/grey.css new file mode 100644 index 0000000..ffe9aca --- /dev/null +++ b/site/static/beta5/css/grey.css @@ -0,0 +1,54 @@ +/** GENERAL ELEMENTS **/ + +body, p, td, th, h1, h2, h3, h4, h5, h6, a { color: #C0C0C0; } +h1, h2, h3 { color: #CFCFCF; } +h4, h5, h6, th { color: #9F9F9F; } +a, a:visited { color: white; } +b { color: white; } +table.list a { color: #C0C0C0; } +table.list tr:hover td, table.list tr:hover td > * { color: white; } + + +/** STATUS AND MENU BARS **/ + +table.topframe { + background-color: #3F3F3F; + border: 1px solid #7F7F7F; +} +td.topmenu { + background-image: url('__STATICURL__/beta5/pics/ttl/def/grey/menubg.png'); + background-repeat: repeat-x; +} +ul ul.tmenu li.tmenu { background-color: #5F5F5F; } +ul.tmenu li:hover { background-color: #7F7F7F; } +a.tmenu:hover { background-color: black; } +ul.tmenu li.tmenu:hover ul.tmenu { border: 1px solid #3F3F3F; } +td.player, td.funds { color: #9F9F9F; } +td.player b, td.funds b { color: white; } +td.stime { color: #FFFFFF; } + + +/** FORMS **/ +input[type=text],input[type=password],input[type=submit],input[type=reset],input[type=button],select,textarea { + border: 1px solid #3F3F3F; + color: #7F7F7F; + background-color: black; +} + +input[disabled] { color: #3F3F3F; border: 1px solid #3F3F3F; } + + +/** MAP COLORS **/ + +table.map li:hover, table.map li:hover a { color: white; } + +.mcown { color: #33CC33; } +a.mcown:visited { color: #33CC33; } +.mcally { color: #6666FF; } +a.mcally:visited { color: #6666FF; } +.mcprot { color: #AFAFAF; background-color: black; } +a.mcprot:visited { background-color: black; } +.mcpally { color: #6666FF; background-color: black; } +a.mcpally:visited { color: #6666FF; } +.mcpown { color: #33CC33; background-color: black; } +a.mcpown:visited { color: #33CC33; } diff --git a/site/static/beta5/css/main.css b/site/static/beta5/css/main.css new file mode 100644 index 0000000..d1bc379 --- /dev/null +++ b/site/static/beta5/css/main.css @@ -0,0 +1,124 @@ +body { + background-image: url("__STATICURL__/beta5/pics/background.jpg"); + border: 0px; + margin: 0px; + padding: 0px; +} + +* { + font-family: Arial, sans-serif; +} + +pre { + font-family: monospace; +} + +body, p, td, h6, h4 { font-weight: normal; } +th, h5, h3, h2, h1 { font-weight: bold; } +th, h1, h2, h3, h4, h5, h6 { font-style: italic; } +body, p, td { font-style: normal; } +body, p, td, h6, h5, h4, h3, h2, h1 { text-decoration: none; } +body, p, td, th, h6, h5, h4, h3, h2, h1 { margin: 0px; } + +h1 { padding: 0px 0px 5px 10px; } +h2 { padding: 0px 0px 3px 15px; } +h3 { padding: 0px 0px 2px 20px; } +h4 { padding: 0px 0px 2px 22px; } +h5 { padding: 0px 0px 1px 24px; } +h6 { padding: 0px 0px 0px 25px; } +p { padding: 0px 0px 10px 30px; } + +a { + text-decoration: underline; + font-weight: normal; + font-style: italic; +} + +img { + padding: 0px; + margin: 0px; + border: 0px +} +img.icon { + height: 32px; + width: 32px; +} +img.fleet { + height: 30px; + width: 60px; +} +img.planetsmall { + height: 30px; + width: 30px; +} +img.planetlarge { + height: 80px; + width: 80px; +} + +table { + border: 0px; + margin: 0px; + padding: 0px; + width: 100%; +} + +table.list { + width: 98%; + margin: 0 auto 0 auto; +} + +table.list a { + text-decoration: none; + font-weight: bold; + font-style: italic; +} +table.list a:hover { + text-decoration: underline; +} + +td { vertical-align: top; } +th { vertical-align: middle; } +table.list th { vertical-align: bottom; } +table.list td { vertical-align: middle; } + +td.fbutton { + text-align: center; + padding: 5px 0px 0px 0px; +} +td.flink { + padding: 0px 5px 0px 0px; + text-align: right; +} + +.pc5 { width: 5%; } +.pc10 { width: 10%; } +.pc15 { width: 15%; } +.pc20 { width: 20%; } +.pc25 { width: 25%; } +.pc30 { width: 30%; } +.pc35 { width: 35%; } +.pc40 { width: 40%; } +.pc45 { width: 45%; } +.pc50 { width: 50%; } +.pc55 { width: 55%; } +.pc60 { width: 60%; } +.pc65 { width: 65%; } +.pc70 { width: 70%; } +.pc75 { width: 75%; } +.pc80 { width: 80%; } +.pc85 { width: 85%; } +.pc90 { width: 90%; } +.pc95 { width: 95%; } + +.div2 { width: 50%; } +.div3 { width: 33%; } +.div4 { width: 25%; } +.div5 { width: 20%; } +.div6 { width: 16%; } +.div8 { width: 12%; } +.div10 { width: 10%; } +.div16 { width: 6%; } +.div20 { width: 5%; } + + diff --git a/site/static/beta5/css/pg_alliance.css b/site/static/beta5/css/pg_alliance.css new file mode 100644 index 0000000..d41fd22 --- /dev/null +++ b/site/static/beta5/css/pg_alliance.css @@ -0,0 +1,97 @@ +table.calliance { width: auto; } +table.calliance td { width: auto; } +table.calliance td.brd { width: 30px; } +table.calliance th { text-align: left; width: 140px; } +table.calliance td.but { padding: 0px 0px 0px 70px; } +.noleader { color: red; } + +td#alpmenu { text-align: center; } + +table.prequests { + width: 400px; +} +.prblk { width: 30px; } +.prsel { width: 32px; text-align: center; } +.prplayer { width: 338px; text-align: left; } +.prbut { + padding: 4px 0px 0px 0px; + border-width: 1px 0px 0px 0px; + border-style: solid; + border-color: white; +} +th.prsel, th.prplayer { + padding: 0px 0px 2px 0px; + border-width: 0px 0px 1px 0px; + border-style: solid; + border-color: white; +} + +table.list#candlist { margin: 0px 5% 0px 5%; width: 89%; } +table.list#candlist td { vertical-align: middle; } +table.list#candlist th { vertical-align: bottom; cursor: pointer; } +.lesel { width: 5%; } +.lecname { text-align: left; width: 70%;} +.lecvotes { text-align: center; width: 25%;} + +td#lsmsel { text-align: right; } +td.lspsel1 { width: 50%; text-align: center; } +td.lspsearch { text-align: center; } +.picon { width: 32px; } +.pname { width: auto; text-align: left; } +.oname { width: 100px; text-align: left; } +.pcoord { width: 120px; text-align: center; } +table.list#plnlist { margin: 0px 5% 0px 5%; width: 89%; } +table.list#plnlist td { vertical-align: middle; } +table.list#plnlist th { cursor: pointer; } +tr.attack td { color: red; } +tr.attack td a { color: red; } +tr.defence td { color: #7FFF7F; } +tr.defence td a { color: #7FFF7F; } +tr.attack td.afriendly, tr.defence td.afriendly, tr td.afriendly { color: #5F9FFF; text-align: center; } +tr.attack td.aenemy, tr.defence td.aenemy, tr td.aenemy { color: red; text-align: center; } +table.list tr.deflist td, table.list tr.deflist:hover td { color: #5F9FFF; } +table.list tr.attlist td, table.list tr.attlist:hover td { color: red; } +table.list#mbrlist { margin: 0px 5% 0px 5%; width: 89%; } +table.list#mbrlist th { cursor: pointer; width: 23%; text-align: left; } +table.list#mbrlist td { vertical-align: middle } +table.list#mbrlist td.mselect { text-align: right; vertical-align: middle } +table.list#mbrlist tr td.mbleader { font-weight: bold; color: #7FFF7F; } +table.list#mbrlist tr:hover td.mbleader { font-weight: bold; color: white; } +table.list#mbrlist tr td.mbonline { font-weight: bold; color: #7FFF7F; } +table.list#mbrlist tr:hover td.mbonline { font-weight: bold; color: white; } +table.list#mbrlist tr td.mboffline { color: #DFFF00; } +table.list#mbrlist tr:hover td.mboffline { color: white; } +table.list#mbrlist tr td.mbonvac { font-weight: bold; color: #FF7F7F; } +table.list#mbrlist tr:hover td.mbonvac { font-weight: bold; color: white } + +td#crforum { text-align: right; padding: 0px 10px 0px 0px; } +table.list#fatbl { margin: 0px 5% 0px 5%; width: 89%; border-collapse: collapse; } +table.list#fatbl td { vertical-align: top; padding: 5px 0px 5px 0px; border-width: 1px 0px 1px 0px; border-style: solid; border-color: white; } +table.list#fatbl th { padding: 0px 0px 10px 0px; } +.faname { width: 80%; text-align: left; } +.fauserpost { width: 20%; text-align: center; } +table.list#fatbl tr:hover p { color: white; } +table#faeacl { margin: 0px 5% 0px 5%; width: 89%; } +table#faeacl td { text-align: left; width: 30%; } +table#faeacl td.faaepsel { text-align: center; width: 3%; } +table#faeacl th { text-align: left; padding: 0px 0px 5px 4%; } +table#faeacl td#faeab0, table#faeacl td#faeab1, table#faeacl td#faeab2 { text-align: left; padding: 5px 0px 0px 4%; } +table#feditor th.edheader { width: 150px; text-align: right; padding: 0px 5px 0px 0px; vertical-align: top; } +table#feditor td#febut { text-align: left; padding: 10px 0px 10px 110px; } + + +table.list#ratbl { margin: 0px 5% 0px 5%; width: 89% } +table.list#ratbl .rkshow { text-align: center; vertical-align: top; width: 64px; } +table.list#ratbl .rkname { text-align: left; vertical-align: top; width: auto; } +table.list#ratbl .rkact { text-align: left; vertical-align: top; width: 128px; } +table.list#ratbl .rkmembers { text-align: center; vertical-align: top; width: 64px; } +table.list#ratbl tr:hover p { color: white; } +table#rankedit th { width: 230px; text-align: right; padding: 1px 5px 1px 0px; vertical-align: top; } +table#rankedit h1 { padding: 0px 0px 10px 30px; } +table#rankedit h3 { padding: 10px 0px 5px 30px; } +table#rankedit td { padding: 1px 0px 1px 0px; } +table#rankedit td.rebut { text-align: left; padding: 10px 0px 0px 190px; } +table#rankedit table.rerlist { width: auto; margin: 0px 0px 0px 20px; } +table#rankedit table.rerlist td { padding: 1px 20px 1px 1px; width: auto; } +table#rankedit table.reflist { width: auto; } +table#rankedit table.reflist td { padding: 1px 20px 1px 1px; width: auto; } diff --git a/site/static/beta5/css/pg_allies.css b/site/static/beta5/css/pg_allies.css new file mode 100644 index 0000000..6cae518 --- /dev/null +++ b/site/static/beta5/css/pg_allies.css @@ -0,0 +1,10 @@ +table.lsallies { width: auto; } +.spacer { width: 30px; } +table.lsallies td.al0 { color: #00FF00; } +table.lsallies td.al1 { color: #3FDF3F; } +table.lsallies td.al2 { color: #6FBF6F; } +table.lsallies td.al3 { color: #9F9F9F; } +table.lsallies td.al4 { color: #CF7FCF; } +.raname { text-align: left; width: auto; } +.ralevel { text-align: center; width: 25%; } +.rasel { text-align: center; width: 32px; } diff --git a/site/static/beta5/css/pg_empire.css b/site/static/beta5/css/pg_empire.css new file mode 100644 index 0000000..26927c7 --- /dev/null +++ b/site/static/beta5/css/pg_empire.css @@ -0,0 +1,5 @@ +b.phapok { color: #33CC33; } +b.phapmed { color: #337FFF; } +b.phapdgr { color: #CCCC33; } +b.phapbad { color: #CC3333; } + diff --git a/site/static/beta5/css/pg_enemylist.css b/site/static/beta5/css/pg_enemylist.css new file mode 100644 index 0000000..45e26a0 --- /dev/null +++ b/site/static/beta5/css/pg_enemylist.css @@ -0,0 +1,4 @@ +table.enlist { width: auto; } +table.enlist td { width: 150px; } +table.enlist td.spacer { width: 30px; } +table.enlist td.lsbutton { text-align: center; } diff --git a/site/static/beta5/css/pg_fleets.css b/site/static/beta5/css/pg_fleets.css new file mode 100644 index 0000000..779326b --- /dev/null +++ b/site/static/beta5/css/pg_fleets.css @@ -0,0 +1,235 @@ +table.fctrl { width: 89%; padding: 5px 5% 0px 5%; } +table.fsearch { width: 89%; padding: 0px 5% 5px 5%; } +table.fctrl td { width: 16%; text-align: center; } +table.fctrl select { width: 98%; } +table.fsearch td { width: 100%; text-align: center; } +div#fmain { margin: 0px 0px 34px 0px; } + +div.factions { + position: fixed; + background-color: black; + width: 100%; height: 32px; + bottom: 0px; + padding: 0; margin: 0; +} +table.factions { height: 100%; } +table.factions th { width: 10%; vertical-align: middle; } +table.factions td { vertical-align: middle; } + +table.planet { + width: 95%; + margin: 0px 2% 20px 2%; + padding: 0px 0px 0px 0px; + border: 1px solid #3F3F3F; + border-collapse: collapse; +} +table.planet td { + padding: 0px 0px 0px 0px; + border: 1px solid #3F3F3F; + border-collapse: collapse; +} +tr.phdr td.name { + font-size: larger; + vertical-align: middle; +} +tr.phdr td.pinf { + width: 15%; + text-align: center; + vertical-align: middle; +} +tr.phdr td.pimg { width: 21px; height: 21px; vertical-align: middle; } +table.planet tr td.noflt { + text-align: center; + padding: 12px 0px 12px 0px; +} +img.pimg { width: 20px; height: 21px; border: 0px; } + +table.fltl, table.mfltl, table.wfltl { + width: 100%; +} +table.fltl, table.fltl td, table.fltl th, table.mfltl, table.mfltl td, table.mfltl th, table.wfltl, table.wfltl td, table.wfltl th { + border: 1px solid #3F3F3F; + border-collapse: collapse; + padding: 0px 0px 0px 0px; + margin: 0px 0px 0px 0px; +} +table.fltl td, table.mfltl td, table.wfltl td { vertical-align: middle; } + +table.fltl .fsel { width: 20px; text-align: center; } +table.fltl .fown { width: 15%; text-align: left; } +table.fltl .fname { width: auto; text-align: left; } +table.fltl .fhs { width: 5%; text-align: center; } +table.fltl .fshp { width: 8%; text-align: center; } +table.fltl .fpwr { width: 8%; text-align: center; } +table.fltl .fstat { width: 10%; text-align: right; } +table.fltl .fdest { width: 10%; text-align: center; } +table.fltl .fstd { width: 6%; text-align: center; } + +table.mfltl .fsel { width: 20px; text-align: center; } +table.mfltl .fown { width: 15%; text-align: left; } +table.mfltl .fname { width: auto; text-align: left; } +table.mfltl .fhs { width: 5%; text-align: center; } +table.mfltl .fshp { width: 8%; text-align: center; } +table.mfltl .fpwr { width: 8%; text-align: center; } +table.mfltl .fstat { width: 10%; text-align: center; } +table.mfltl .fstd, table.mfltl .feta, table.mfltl .floc, table.mfltl .fdest { text-align: center; } + +table.wfltl .fsel { width: 20px; text-align: center; } +table.wfltl .fown { width: 15%; text-align: left; } +table.wfltl .fname { width: auto; text-align: left; } +table.wfltl .fhs { width: 5%; text-align: center; } +table.wfltl .fshp { width: 8%; text-align: center; } +table.wfltl .fpwr { width: 8%; text-align: center; } +table.wfltl .fstat { width: 10%; text-align: center; } +table.wfltl .floc, table.wfltl .ftime, table.wfltl .floss { text-align: center; } + +table.wfltl:hover td a, table.wfltl:hover td, table.mfltl:hover td a, table.mfltl:hover td, table.fltl tr:hover td, table.fltl tr:hover td a { color: white; } +tr.flown td, tr.flown td a, b.own { color: #33CC33; } +tr.flally td, tr.flally td a { color: #6666FF; } +tr.flenemy td, tr.flenemy td a, b.enemy { color: #FF3300; } + +table.scmd { + margin: 0% 2% 0% 2%; + width: 95%; +} +table.scmd th { + width: 150px; + text-align: left; +} +table.scmd td.sbut { + padding: 0px 0px 0px 90px; +} + +table.oself, table.oavaf { + margin: 10px 20px 10px 20px; + width: 96%; +} +table.oavaf, table.oavaf td, table.oavaf th, table.oself, table.oself td, table.oself th { + border: 1px solid #3F3F3F; + border-collapse: collapse; + padding: 0px 0px 0px 0px; +} +table.oavaf tr:hover td, table.oself tr:hover td { color: white; } +table.oself td { vertical-align: middle; } + +table.oself .fown { width: 15%; text-align: left; } +table.oself .fname { width: auto; text-align: left; } +table.oself .fhs { width: 5%; text-align: center; } +table.oself .fshp { width: 25%; text-align: center; } +table.oself .fpwr { width: 8%; text-align: center; } +table.oself .ftraj { width: 30%; text-align: center; } + +table.oavaf .fown { width: 15%; text-align: left; } +table.oavaf .fname { width: auto; text-align: left; } +table.oavaf .fhs { width: 5%; text-align: center; } +table.oavaf .fshp { width: 25%; text-align: center; } +table.oavaf .fpwr { width: 8%; text-align: center; } +table.oavaf .fco { width: 25%; text-align: left; } + +td#chord { width: 80%; text-align: left; } +td#chordc { + width: 20%; + text-align: right; + vertical-align: top; + padding: 0px 20px 0px 0px; +} + +td#sdmap { + width: 270px; + padding: 10px 5px 0px 5px; + border-style: solid; + border-color: white; + border-width: 0px 0px 0px 1px; +} +p.sdbut { padding: 20px 0px 0px 40px; } + +span.prot { + border-style: none; + border-width: 0px; + padding: 0px; + margin: 0px +} +span.prot table { + background-color: #00274f +} + +table.smap { + border: 1px solid white; + width: 194px; + height: 194px; + margin: 0px auto 0px auto; +} +table.smap td { vertical-align: middle; } +p.smapi { + text-align: center; + padding: 10px 0px 0px 0px; +} + +table.gmap { + width: 260px; + height: 260px; + margin: 0px auto 0px auto; +} +table.gmap, table.gmap td { + border-collapse: collapse; + border: 1px solid white; +} +table.gmmap, table.gmmap td { border: 0; } +table.gmmap td { vertical-align: middle; } +td.gmcor { width: 32px; height: 32px; } +td.gmvm, td.gmhm { vertical-align: middle; text-align: center; } +td.gmvm { height: 32px; } +td.gmhm { width: 32px; } +td.gmun { height: 32px; text-align: center; } +table#mapctr { + width: auto; + margin: 0px auto 0px auto; +} +p.mapbt { + + text-align: center; + padding: 20px 0px 0px 0px; +} + +td.sys1 { background-image: url("__STATICURL__/beta5/pics/nebula1.png"); } +td.sys2 { background-image: url("__STATICURL__/beta5/pics/nebula2.png"); } +td.sys3 { background-image: url("__STATICURL__/beta5/pics/nebula3.png"); } +td.sys4 { background-image: url("__STATICURL__/beta5/pics/nebula4.png"); } +td.mimg { height: 32px; width: 32px; padding: 0; margin: 0; } +img.mimg { height: 32px; width: 32px; border: 0; } +td.ptype1 { color: #6666FF; font-weight: bold; } +td.ptype2 { color: #33CC33; font-weight: bold; } +td.ptype3 { color: #AFAF1F; font-style: italic; } +td.ptype4 { color: #AF1F1F; height: 32px; text-align: center; } +td.ptype5 { color: #DF3F3F; height: 32px; text-align: center; } +td.ptype6 { color: #EF7F7F; height: 32px; text-align: center; } +td.ptype7 { color: #FF9F9F; height: 32px; text-align: center; } + +table.ftraj { + margin: 10px 20px 10px 20px; + width: 96%; +} +table.ftraj tr:hover td { color: white; } +table.ftraj .tname { text-align: left; width: 20%; } +table.ftraj .ttype { text-align: center; width: 20%; } +table.ftraj .tstat { text-align: left; } +span.ot1 { color: #AFAF1F; } +span.ot2 { color: #AF1F1F; } +span.ot3 { color: #DF3F3F; } +span.ot4 { color: #EF7F7F; } +span.ot5 { color: #FF9F9F; } + +table.fslist { + margin: 10px 20px 10px 20px; + width: 96%; +} +table.fslist, table.fslist td, table.fslist th { + border: 1px solid #3F3F3F; + border-collapse: collapse; + padding: 0px 0px 0px 0px; +} +table.fslist tr td { color: #33CC33; } +table.fslist tr:hover td { color: white; } +table.fslist .fname { text-align: left; width: auto; } +table.fslist .fshp { text-align: center; width: 8%; } +table.fslist .fpwr { text-align: center; width: 15%; } diff --git a/site/static/beta5/css/pg_forums.css b/site/static/beta5/css/pg_forums.css new file mode 100644 index 0000000..d6717ff --- /dev/null +++ b/site/static/beta5/css/pg_forums.css @@ -0,0 +1,177 @@ +/** MENU **/ + +td.mmenu { + width: 20%; + padding: 0px 0px 0px 30px; +} + +td.mmenu a, td.mmenu a:visited { + font-style: normal; + text-decoration: none; + color: #C0C0C0; +} + +td.mmenu a:hover { + text-decoration: underline; +} + +td.mmenu i { color: white; } + +td.mmenu table th { + text-align: left; +} + +td.mmenu table td.mmspc { + width: 20px; +} + +td.mmenu table th a, td.mmenu table th a:visited { + color: inherit; + font-weight: bold; + font-style: italic; +} + + +table.fcat { + padding: 3px 20px 3px 20px; + margin: 10px 0px 10px 0px; +} + +table.fcat th.hdr { + text-align: left; + width: 85%; +} + +table.fcat tr.hdr td, table.fcat tr.hdr th { + border-style: solid; + border-width: 0px 0px 1px 0px; + border-color: white; +} + +table.fcat td.ctype { + text-align: right; + width: 15%; + padding: 0px 10px 0px 0px; +} + +table.fcat th.hdr a { + color: inherit; + font-weight: bold; + font-style: normal; + text-decoration: none; +} + +table.fcat th.hdr a:hover { + text-decoration: underline; +} + +table.fcf th { padding: 0px 0px 5px 0px; } + +table.fcf td.fst { + width: 32px; +} + +table.fcf .fnm { + width: auto; + text-align: left; +} + +table.fcf .fnt, table.fcf .fnp { + width: 60px; + text-align: center; +} + +table.fcf .flp { + width: 200px; + text-align: center; +} + +table.fcf .flpl { + width: 250px; + text-align: center; +} + +/* FORUM DISPLAY */ + +td.maar { text-align: right; vertical-align: middle; padding: 0px 8px 0px 0px; } + +table.fcmd { padding: 15px 0px 15px 0px; } +table.fcmd td { width: 33%; text-align: center; } + +table.ftop { padding: 0px 20px 0px 20px; } +table.ftop th { padding: 0px 0px 5px 0px; border-color: white; border-style: solid; border-width: 0px 0px 1px 0px; } +table.ftop td { padding: 5px 0px 5px 0px; border-color: white; border-style: solid; border-width: 0px 0px 1px 0px; vertical-align: top; } +table.ftop tr:hover td { color: white; } +table.ftop tr:hover td.fem { color: inherit; } +table.ftop td.fem { padding: 15px 0px 0px 0px; text-align: center; font-weight: bold; border-style: none; } +table.ftop .tpic { width: 32px; text-align: center; vertical-align: middle; } +table.ftop .tnm { width: auto; text-align: left; } +table.ftop .trp { width: 60px; text-align: center; } +table.ftop .tps { width: 200px; text-align: center; } + +/* POST FORM */ + +table.fpost { width: auto; } +table.fpost th { text-align: right; padding: 0px 10px 0px 0px; width: 20%; vertical-align: top; } +table.fpost th.hdprev { text-align: center; padding: 0px 0px 15px 0px; width: auto; } +table.fpost th.err { text-align: left; padding: 0px 0px 10px 0px; width: auto; } +table.fpost td#jszone { width: 120px; } +table.fpost td.prev { border-style: solid; border-width: 1px; border-color: white; padding: 4px 4px 4px 4px; } + +/* TOPIC VIEW */ + +table.ftp { + margin: 10px 20px 10px 0px; + border-collapse: collapse; + border-width: 1px; + border-style: solid; + border-color: #3F3F3F; +} +table.ftp th { + text-align: left; + border-width: 1px; + border-style: solid; + border-color: #3F3F3F; + font-size: larger; + font-style: normal; +} +table.ftp th.pfor { font-size: smaller; } +table.ftp td { + border-width: 1px; + border-style: solid; + border-color: #3F3F3F; +} +table.ftp td.cmd { width: 150px; text-align: center } +table.ftp td.edit { text-align: right; font-style: italic; } +div.fsig { padding: 12px 0px 0px 0px; } +div.fsig, div.fsig * { color: #5F5F5F; } +div.fsig hr { height: 1px; color: #3F3F3F; background-color: #3F3F3F; border: 0; } + +td.ftd1 { width: 2%; } +td.ftd2 { width: 4%; } +td.ftd3 { width: 6%; } +td.ftd4 { width: 8%; } +td.ftd5 { width: 10%; } +td.ftd6 { width: 12%; } +td.ftd7 { width: 14%; } +td.ftd8 { width: 16%; } +td.ftd9 { width: 18%; } +td.ftd10 { width: 20%; } +td.ftd11 { width: 22%; } +td.ftd12 { width: 24%; } +td.ftd13 { width: 26%; } +td.ftd14 { width: 28%; } +td.ftd15 { width: 30%; } +td.ftd16 { width: 32%; } +td.ftd17 { width: 34%; } +td.ftd18 { width: 36%; } +td.ftd19 { width: 38%; } +td.ftd20 { width: 40%; } + + +blockquote.quote { + margin: 5px; + padding: 10px; + border: 1px solid #7f7f7f; + background-color: #3f3f3f; +} diff --git a/site/static/beta5/css/pg_manual.css b/site/static/beta5/css/pg_manual.css new file mode 100644 index 0000000..93b23dc --- /dev/null +++ b/site/static/beta5/css/pg_manual.css @@ -0,0 +1,40 @@ +div.mansection { + padding: 0px 0px 0px 30px; +} + +div#manual { + padding: 10px 0px 0px 20px; +} + +div.mansection, div.mansection * { + text-align: justify; +} + +div.mansection table { + width: 100%; + margin: 10px 0px 10px -30px; +} + +div.mansection td { + text-align: left; + vertical-align: top; + padding: 2px 4px 2px 4px; +} + +div.mansection th { + text-align: left; + vertical-align: bottom; + padding: 2px 4px 2px 4px; +} + +div.mansection table, div.mansection th, div.mansection td { + border: 1px solid white; + border-collapse: collapse; +} + +div.mansection a { + font-style: normal; + color: #CCCCCC; + font-weight: bold; + text-decoration: none; +} diff --git a/site/static/beta5/css/pg_map.css b/site/static/beta5/css/pg_map.css new file mode 100644 index 0000000..757f433 --- /dev/null +++ b/site/static/beta5/css/pg_map.css @@ -0,0 +1,130 @@ +/* Grid map */ + +table.map { + border-style: solid; + border-width: 1px; + empty-cells: show; + border-collapse: collapse; + margin: 0 auto 0 auto; + width: auto; +} + +table.map tr td { + border-style: solid; + border-width: 1px; +} + +table.map td.idx { + width: 32px; + height: 32px; +} + +table.map td.mgrcol { + width: 192px; +} + +table.map tr.hd td, table.map tr td.hd { + font-size: 11pt; + vertical-align: middle; + text-align: center; +} + +table.map tr td.system { + height: 192px; +} + + +table.map tr td.arrow { + text-align: center; + vertical-align: middle; +} + +table.map tr td.arrow img { + margin: 0; + padding: 0; + border: 0; + width: 12px; + height: 12px; +} + + +table.sys, table.sysneb1,table.sysneb2,table.sysneb3,table.sysneb4,table.sysprot,table.sysunch { + margin: 0px 0px 0px 0px; + padding: 0px 0px 0px 0px; + border-width: 0px; + border-style: none; + width: 100%; + height: 192px; +} + +table.sysneb1 { background-image: url("__STATICURL__/beta5/pics/nebula1.png"); } +table.sysneb2 { background-image: url("__STATICURL__/beta5/pics/nebula2.png"); } +table.sysneb3 { background-image: url("__STATICURL__/beta5/pics/nebula3.png"); } +table.sysneb4 { background-image: url("__STATICURL__/beta5/pics/nebula4.png"); } + +table.sysprot { font-style: italic; } +table.sysunch td { font-size: smaller; text-align: center; } +table.sysneb1 td, table.sysneb2 td, table.sysneb3 td, table.sysneb4 td { text-align: center; } + +table.map tr.srow { + height: 32px; + border-width: 0px; + border-style: none; +} + +table.map tr.srow td { + border-width: 0px; + border-style: none; + vertical-align: middle; +} + +table.map tr.srow td.pimg { + width: 32px; + text-align: center; +} + +tr.srow a, tr.srow a:visited { text-decoration: none; font-style: normal; } +tr.srow a:hover { text-decoration: underline; font-style: normal; } + +tr.srow td.planetn, td.planetn a, td.planetn a:hover, td.planetn a:visited { color: #CFCFCF; } +tr.srow td.planeto, td.planeto a, td.planeto a:hover, td.planeto a:visited { color: #7FFF7F; font-weight: bold; } +tr.srow td.planeta, td.planeta a, td.planeta a:hover, td.planeta a:visited { color: #7F7FFF; font-weight: bold; } +tr.srow td.prem, td.prem a, td.prem a:hover, td.prem a:visited { color: #AFAF1F; } +tr.srow td.nebzone1, tr.srow td.nebzone1 a, tr.srow td.nebzone1 a:hover, tr.srow td.nebzone1 a:visited { color: #AF1F1F; } +tr.srow td.nebzone2, tr.srow td.nebzone2 a, tr.srow td.nebzone2 a:hover, tr.srow td.nebzone2 a:visited { color: #DF3F3F; } +tr.srow td.nebzone3, tr.srow td.nebzone3 a, tr.srow td.nebzone3 a:hover, tr.srow td.nebzone3 a:visited { color: #EF7F7F; } +tr.srow td.nebzone4, tr.srow td.nebzone4 a, tr.srow td.nebzone4 a:hover, tr.srow td.nebzone4 a:visited { color: #FF9F9F; } + +/* Listing */ + +table.list#plist { margin: 0px 20px 0px 20px; } +table#plist th { width: 110px; } +table#plist th.pname { width: auto; text-align: left; } +table.list#plist td { text-align: center; vertical-align: middle; } +table.list#plist td.pimg { width: 32px; } +table.list#plist td.pname { text-align: left; width: auto; height: 32px; } +table.list#plist a, table.list#plist a:visited { text-decoration: none; font-style: normal; } +table.list#plist a:hover { text-decoration: underline; font-style: normal; } +tr.planetn td, tr.planetn b, tr.planetn a, tr.planetn a:hover, tr.planetn a:visited { color: #CFCFCF; } +tr.planeto td, tr.planeto b, tr.planeto a, tr.planeto a:hover, tr.planeto a:visited { color: #7FFF7F; font-weight: bold; } +tr.planeta td, tr.planeta b, tr.planeta a, tr.planeta a:hover, tr.planeta a:visited { color: #7F7FFF; font-weight: bold; } +tr.prem td, tr.prem b, tr.prem a, tr.prem a:hover, tr.prem a:visited { color: #AFAF1F; } +tr.nebzone1 td, tr.nebzone1 b, tr.nebzone1 a, tr.nebzone1 a:hover, tr.nebzone1 a:visited { color: #AF1F1F; } +tr.nebzone2 td, tr.nebzone2 b, tr.nebzone2 a, tr.nebzone2 a:hover, tr.nebzone2 a:visited { color: #DF3F3F; } +tr.nebzone3 td, tr.nebzone3 b, tr.nebzone3 a, tr.nebzone3 a:hover, tr.nebzone3 a:visited { color: #EF7F7F; } +tr.nebzone4 td, tr.nebzone4 b, tr.nebzone4 a, tr.nebzone4 a:hover, tr.nebzone4 a:visited { color: #FF9F9F; } + +/* Map controls */ + +table.mapctrl { width: 89%; margin: 0% 5% 0% 5%; } +table.mapctrl th { text-align: right; padding: 0px 5px 0px 0px; } +table.mapctrl th#mcttl { text-align: center; padding: 0px 0px 0px 0px; } +table.mapctrl td.mcolors { text-align: center; width: 8%; vertical-align:middle } +#mcown { color: #7FFF7F; } +#mcally { color: #7F7FFF; } +#mcother { color: #CFCFCF; } +#mcprot { font-style: italic; } +#neb1 { color: #AF1F1F; } +#neb2 { color: #DF3F3F; } +#neb3 { color: #EF7F7F; } +#neb4 { color: #FF9F9F; } diff --git a/site/static/beta5/css/pg_market.css b/site/static/beta5/css/pg_market.css new file mode 100644 index 0000000..7ed0e2a --- /dev/null +++ b/site/static/beta5/css/pg_market.css @@ -0,0 +1,120 @@ +td#mkppsel { + text-align: center; + padding: 5px 0px 5px 0px; +} + +table#pubmain td.pubmap { + width: 270px; + padding: 10px 5px 0px 5px; + border-style: solid; + border-color: white; + border-width: 0px 0px 0px 1px; +} + +table#sysmap { + width: 260px; + border-collapse: collapse; + border-style: solid; + border-color: white; + border-width: 1px; +} + +table#sysmap td { + text-align: center; + vertical-align: middle; + padding: 0px; + border-width: 1px; + border-color: white; + border-style: solid; +} +table#sysmap td.vert { width: 32px; height: 192px; } +table#sysmap td.horz { width: 192px; height: 32px; } + +table#mapctr { margin: 0px auto 0px auto; width:auto; } + +/* MAP TABLES */ + +td#mapcnt table { width: 192px; height: 192px; border: 0; padding: 0; margin: 0; border-collapse: collapse; } +td#mapcnt table tr { border: 0; padding: 0; margin: 0; } +td#mapcnt table td { border: 0; padding: 0; margin: 0; } +td.pimg img { border: 0; } +table#sysstdprt { + background-color: #00274f +} +table#sysstd td, table#sysstdprt td { text-align: left; } +table#sysstd td a, table#sysstdprt td a { text-decoration: none; font-style: normal; width: 100%; height: 100%; } +table#sysstd td a:hover, table#sysstdprt td a:hover { text-decoration: underline; } +td.pimg { width: 32px; } +table#sysneb1 { background-image: url("__STATICURL__/beta5/pics/nebula1.png"); } +table#sysneb2 { background-image: url("__STATICURL__/beta5/pics/nebula2.png"); } +table#sysneb3 { background-image: url("__STATICURL__/beta5/pics/nebula3.png"); } +table#sysneb4 { background-image: url("__STATICURL__/beta5/pics/nebula4.png"); } +td.planetn, td.planetn a, td.planetn a:hover, td.planetn a:visited { color: #CFCFCF; } +td.planeto, td.planeto a, td.planeto a:hover, td.planeto a:visited { color: #7FFF7F; font-weight: bold; } +td.planeta, td.planeta a, td.planeta a:hover, td.planeta a:visited { color: #7F7FFF; font-weight: bold; } +td.prem, td.prem a, td.prem a:hover, td.prem a:visited { color: #AFAF1F; font-weight: bold; } +td.nebzone1 { color: #AF1F1F; } +td.nebzone2 { color: #DF3F3F; } +td.nebzone3 { color: #EF7F7F; } +td.nebzone4 { color: #FF9F9F; } + +/* LISTINGS */ + +.lstctrl { text-align:center; width: 50%; } + +table.flsaleitem { border-width: 0px 0px 1px 0px; border-color: white; border-style: solid; padding: 0px 0px 3px 0px; } +table.flsaleitem .lspc { width: 32px; } +table.flsaleitem .pname { text-align: left; } +table.flsaleitem .coords { text-align: center; width: 7%; } +table.flsaleitem .ships { text-align: center; width: 12%; } +table.flsaleitem .seller { text-align: left; } +table.flsaleitem .sexp { text-align: center; width: 24%; } +table.flsaleitem .price { text-align: right; } +table.flsaleitem .action { text-align: right; } + +table.plsaleitem { border-width: 0px 0px 1px 0px; border-color: white; border-style: solid; padding: 0px 0px 3px 0px; } +table.plsaleitem .lspc { width: 32px; } +table.plsaleitem .pname { text-align: left; } +table.plsaleitem .coords { text-align: center; width: 7%; } +table.plsaleitem .pdat { text-align: center; width: 11%; } +table.plsaleitem .pflt { text-align: left; width: 19%; } +table.plsaleitem .seller { text-align: left; } +table.plsaleitem .sexp { text-align: center; } +table.plsaleitem .price { text-align: right; } +table.plsaleitem .action { text-align: right; } + +table.prpendingitem { border-width: 0px 0px 1px 0px; border-color: white; border-style: solid; padding: 5px 0px 5px 0px; } +table.prpendingitem .lspc { width: 32px; } +table.prpendingitem .rdate { width: 25%; text-align: center; } +table.prpendingitem .seller { text-align: left; } +table.prpendingitem .price { width: 20%; text-align: right; } +table.prpendingitem .sexp { width: 25%; text-align: center; } +table.prpendingitem .details { width: auto; text-align: left; } + +table.prhistoryitem { border-width: 0px 0px 1px 0px; border-color: white; border-style: solid; padding: 5px 0px 5px 0px; } +table.prhistoryitem .lspc { width: 32px; } +table.prhistoryitem .rdate { width: 20%; text-align: center; } +table.prhistoryitem .hstat { width: 20%; text-align: center; } +table.prhistoryitem .seller { text-align: left; } +table.prhistoryitem .price { width: 20%; text-align: right; } +table.prhistoryitem .sexp { width: 25%; text-align: center; } +table.prhistoryitem .details { width: auto; text-align: left; } + +table.sopendingitem { border-width: 0px 0px 1px 0px; border-color: white; border-style: solid; padding: 5px 0px 5px 0px; } +table.sopendingitem .lspc { width: 32px; } +table.sopendingitem .rdate { width: 20%; text-align: center; } +table.sopendingitem .otype { width: 15%; text-align: center; } +table.sopendingitem .buyer { text-align: left; } +table.sopendingitem .price { width: 15%; text-align: right; } +table.sopendingitem .sexp { width: 20%; text-align: center; } +table.sopendingitem .details { width: auto; text-align: left; } + +table.sohistoryitem { border-width: 0px 0px 1px 0px; border-color: white; border-style: solid; padding: 5px 0px 5px 0px; } +table.sohistoryitem .lspc { width: 32px; } +table.sohistoryitem .rdate { width: 20%; text-align: center; } +table.sohistoryitem .otype { width: 10%; text-align: center; } +table.sohistoryitem .hstat { width: 10%; text-align: center; } +table.sohistoryitem .buyer { text-align: left; } +table.sohistoryitem .price { width: 10%; text-align: right; padding: 0px 10px 0px 0px; } +table.sohistoryitem .sexp { width: 20%; text-align: center; } +table.sohistoryitem .details { width: auto; text-align: left; } diff --git a/site/static/beta5/css/pg_message.css b/site/static/beta5/css/pg_message.css new file mode 100644 index 0000000..8ac1838 --- /dev/null +++ b/site/static/beta5/css/pg_message.css @@ -0,0 +1,226 @@ +/** MENU **/ + +td.mmenu { + width: 20%; + padding: 0px 0px 0px 30px; +} + +td.mmenu a, td.mmenu a:visited { + font-style: normal; + text-decoration: none; + color: #C0C0C0; +} + +td.mmenu a:hover { + text-decoration: underline; +} + +td.mmenu i { color: white; } + +td.mmenu table th { + text-align: left; +} + +td.mmenu table td.mmspc { + width: 32px; +} + +td.mmenu table th a, td.mmenu table th a:visited { + color: inherit; + font-weight: bold; + font-style: italic; +} + + +/** MESSAGE EDITOR **/ + +table.mcomp { width: auto; } + +table.mcomp th { + text-align: right; + vertical-align: top; + width: 120px; +} + +table.mcomp td.mbut { + text-align: center; +} + +table.mcomp b.err { color: #FF4F3F; } + + +/** FOLDER BROWSING **/ + +table.fctrl { + margin: 15px 16px 0px 16px; +} + +table.fctrl tr.topctrl td { + border-width: 0px 0px 1px 0px; + border-color: white; + border-style: solid; + padding: 5px 0px 5px 0px; + width: 30%; + vertical-align: middle; +} + +td#mppcell { text-align: right; } +td#fcpage { width: 40%; text-align: right; } + +table.fctrl td#msglist { + border-width: 0px 0px 1px 0px; + border-color: white; + border-style: solid; +} + +b.msgmsg { + display: block; + margin: 10px 0px 10px 0px; + text-align: center; +} + +table.msghdr { + padding: 0px 0px 0px 0px; + margin: 5px 0px 5px 0px; +} + +table.msghdr th { + vertical-align: bottom; +} + +.msgview { + text-align: center; +} +.mctst { + width: 5%; + text-align: center; +} +.mctsub { + width: 40%; + text-align: left; +} +.mctdate { + width: 20%; + text-align: left; +} +.mctfrom { + width: 16%; + text-align: left; +} +.mctto { + width: 16%; + text-align: left; +} + +table.list { + padding: 0px 0px 0px 0px; + margin: 0px 0px 0px 0px; + width: 100%; +} + +table.list a { + font-weight: normal; + font-style: normal; + text-decoration: none; +} + +td.mbody { + padding: 0px 0px 5px 0px; + border-width: 0px 0px 1px 0px; + border-style: solid; + border-color: white; +} + +td.mbody a { + font-style: italic; + text-decoration: underline; + color: white; +} + +td.mbody a.rlink { + font-weight: normal; + font-style: normal; + text-decoration: none; +} + +a.rlink:hover { + text-decoration: underline; +} + + +table.list table td { + vertical-align: top; +} +td.mctsub, td.mctdate { + cursor: pointer; +} + +table.msgctrl { + padding: 5px 0px 0px 0px; +} + + +/** FOLDER MANAGEMENT **/ + +tr.fldhdr td, tr.fldhdr th { + padding: 5px 0px 5px 0px; + border-width: 0px 0px 1px 0px; + border-style: solid; + border-color: white; + vertical-align: middle; +} + +tr.fldlst td { + padding: 5px 0px 5px 0px; + border-width: 0px 0px 1px 0px; + border-style: solid; + border-color: white; +} + +table.list td { + padding: 0px 0px 0px 0px; + border-width: 0px; + border-style: none; +} + +table.list td:hover p { color: white } + +.fldspc { + width: 16px; + text-align: right; +} + +.fldname { + width: auto; + text-align: left; +} + +.msgcnt { + width: 80px; + text-align: center; +} + +#addfld, #chgfld { + padding: 5px 0px 0px 0px; + width: 50%; + text-align: center; +} + +td.fldname a { + font-weight: normal; + font-style: normal; +} + +/* BATTLE REPORTS */ + +table.breport { + width: auto; + margin: 0px auto 10px auto; +} + +table.breport th, table.breport td { padding: 0px 10px 0px 10px; } +table.breport th.fown { color: #33CC33; } +table.breport th.fally { color: #6666FF; } +table.breport th.fenemy { color: #FF3300; } +table.breport th.sname { text-align: right; } +table.breport td { text-align: center; } diff --git a/site/static/beta5/css/pg_money.css b/site/static/beta5/css/pg_money.css new file mode 100644 index 0000000..40d308b --- /dev/null +++ b/site/static/beta5/css/pg_money.css @@ -0,0 +1,56 @@ +table#lstpl .picon { + width: 30px; + height: 30px; +} + +table#lstpl .pname { + width: auto; + text-align: left; + padding: 0px 0px 0px 2px; +} + +table#lstpl th { + width: 10%; + padding: 1px; + text-align: right; + border-color: white; + border-style: solid; + border-width: 0px 0px 1px 0px; +} + +table#lstpl th.tdi { border-width: 0px; } + +table#lstpl td { + text-align: right; +} + + +table#lstfl th { + padding: 1px; + text-align: left; + width: 36%; + border-color: white; + border-style: solid; + border-width: 0px 0px 1px 0px; +} + +table#lstfl th.tfu { + border-width: 0px; + width: auto; + text-align: right; +} + +table#lstfl td { + width: 36%; + text-align: left; +} + +table#lstfl .dist { + text-align: center; + width: 9%; +} + +table#lstfl .upkp { + text-align: right; + width: 10%; +} diff --git a/site/static/beta5/css/pg_overview.css b/site/static/beta5/css/pg_overview.css new file mode 100644 index 0000000..26927c7 --- /dev/null +++ b/site/static/beta5/css/pg_overview.css @@ -0,0 +1,5 @@ +b.phapok { color: #33CC33; } +b.phapmed { color: #337FFF; } +b.phapdgr { color: #CCCC33; } +b.phapbad { color: #CC3333; } + diff --git a/site/static/beta5/css/pg_planet.css b/site/static/beta5/css/pg_planet.css new file mode 100644 index 0000000..69c676e --- /dev/null +++ b/site/static/beta5/css/pg_planet.css @@ -0,0 +1,102 @@ +table.phdr, table.phdr tr, table.phdr tr td { + border: 0px; + margin: 0px; + padding: 0px; +} + +table.phdr { + width: 98%; + margin-left: auto; + margin-right: auto; + left: auto; + right: auto; +} + +table.phdr tr td.pimg { + height: 80px; + width: 80px; +} + +td.pimg img { + height: 80px; + width: 80px; + border: 0px; + margin: 0px; + padding: 0px; +} + +table.phdr tr td.pttl { + vertical-align: middle; + text-align: left; +} + +table.phdr tr td.psel { + width: 40%; + vertical-align: middle; + text-align: right; +} + +td.pbwlst { + vertical-align: top; + width: 35%; +} + +td.bqt { + vertical-align: top; +} + +td.bqct { + vertical-align: top; + text-align: center; +} + +td.plnk { + text-align: right; + vertical-align: middle; +} + +table.bqueue { + border-width: 0px 0px 1px 0px; + border-style: solid; + border-collapse: collapse;; + width: 100%; + margin: 0px 0px 15px 0px; +} + +table.bqueue tr td, table.bqueue tr th { + border-width: 0px 0px 1px 0px; + border-style: solid; + border-color: white; +} + +table.bqueue tr:hover td { + color: white; +} + + +table.bqueue tr.bqlf td, table.bqueue tr.bqlf th { + border-style: none; + border: 0px; +} + +table.bqueue td.bqsel { width: 32px; } +table.bqueue th.bqqt { width: 8%; } +table.bqueue td.bqqt { text-align: center; } +table.bqueue th.bqt { width: 42%; text-align: left; } + +.phapok { color: #33CC33; } +.phapmed { color: #337FFF; } +.phapdgr { color: #CCCC33; } +.phapbad { color: #CC3333; } + + +table.fbundle { + margin: 0px auto 0px auto; + width: 95%; +} +table.fbundle th { vertical-align: bottom; } +table.fbundle tr:hover td { color: white; } +table.fbundle .fbsel { width: 32px; text-align: center; } +table.fbundle .fbnam { width: auto; text-align: left; } +table.fbundle .fshp { width: 8%; text-align: center; } +table.fbundle .fpwr { width: 12%; text-align: center; } diff --git a/site/static/beta5/css/pg_planets.css b/site/static/beta5/css/pg_planets.css new file mode 100644 index 0000000..29b3cb7 --- /dev/null +++ b/site/static/beta5/css/pg_planets.css @@ -0,0 +1,67 @@ +table#planets table { + width: 100%; + empty-cells: show; + border-width: 0px 0px 1px 0px; + border-style: solid; +} + +table#planets td { + padding: 0px 0px 3px 0px; +} + +table#planets td { + vertical-align: top; + text-align: right; + width: 10%; +} + +table#planets th { + text-align: right; + width: 10%; +} + +table#planets table .pimg { + max-width: 30px; + width: 30px; + padding: 0px; + vertical-align: top; +} + +table#planets table td.pimg { + height: 30px; + width: 30px; + max-width: 30px; + padding: 0px; +} + +table#planets table .pname { + width: auto; + text-align: left; +} +table#planets table th.pname { + vertical-align: bottom; +} +table#planets table td.pname { + vertical-align: top; +} + +table .bq { + text-align: left; + font-style: italic; + padding: 0px 0px 4px 0px; +} + +table#qbuild td { + vertical-align: middle; + padding: 1px 0px 1px 0px; +} + +table#qbuild .dv { + width: 4%; + text-align: center; +} + +.phapok { color: #33CC33; } +.phapmed { color: #337FFF; } +.phapdgr { color: #CCCC33; } +.phapbad { color: #CC3333; } diff --git a/site/static/beta5/css/pg_preferences.css b/site/static/beta5/css/pg_preferences.css new file mode 100644 index 0000000..350c419 --- /dev/null +++ b/site/static/beta5/css/pg_preferences.css @@ -0,0 +1,16 @@ +table.prefs th { + text-align: left; + padding: 0px 0px 0px 20px; + vertical-align: top; +} +table.prefs td.psec { + text-align: left; + padding: 0px 0px 5px 0px; +} +td.buttons { text-align: center; padding: 0px 0px 50px 0px; } + +table.prefs textarea { width: 75%; } +table.prefs select { width: 75%; } +table.prefs .txt { width: 75%; } +table.prefs .err { font-weight: bold; padding: 0px 0px 0px 30px; } +table.prefs .note { padding: 0px 0px 0px 30px; } diff --git a/site/static/beta5/css/pg_probes.css b/site/static/beta5/css/pg_probes.css new file mode 100644 index 0000000..2ba98b8 --- /dev/null +++ b/site/static/beta5/css/pg_probes.css @@ -0,0 +1,21 @@ +td#prmenu { + text-align: center; + padding: 5px 0px 10px 0px; +} + +table.polpl, table.polpl th, table.polpl td { + border: 1px solid #3F3F3F; + border-collapse: collapse; +} +table.polpl { margin: 0px 20px 10px 20px; } +table.polpl th { text-align: left; width: 33%; } +table.polpl th b { color: inherit; } +table.polpl p { margin: 10px 0px 0px 0px; } + +table.plbcn, table.plbcn th, table.plbcn td { + border: 1px solid #3F3F3F; + border-collapse: collapse; +} +table.plbcn { margin: 0px 20px 20px 20px; } +table.plbcn th.bcnpl { text-align: left; width: 33%; } +table.plbcn th.bcnpl b { color: inherit; } diff --git a/site/static/beta5/css/pg_rank.css b/site/static/beta5/css/pg_rank.css new file mode 100644 index 0000000..f36187b --- /dev/null +++ b/site/static/beta5/css/pg_rank.css @@ -0,0 +1,16 @@ +td#rkpsel { text-align: center; padding: 0px 0px 20px 0px; } + +table.list { padding: 0% 5% 0% 5%; width: 90%; } +table.hdr { + border-color: white; + border-style: solid; + border-width: 0px 0px 1px 0px; + padding: 0px 0px 10px 0px; + margin: 0px 0px 10px 0px; +} +table.row { padding: 0px; margin: 0px; } +table.row th { cursor:pointer; } +.gname { width: 50%; text-align: left; } +.grank { width: 50%; text-align: center; } +.dname { width: 40%; text-align: left; } +.drank { width: 20%; text-align: center; padding: 0px 0px 0px 0px; } diff --git a/site/static/beta5/css/pg_research.css b/site/static/beta5/css/pg_research.css new file mode 100644 index 0000000..9e7a600 --- /dev/null +++ b/site/static/beta5/css/pg_research.css @@ -0,0 +1,17 @@ +table.list td { vertical-align: top; } +td#respsel { text-align: center; } +td.sdesc { width: 48px; text-align: right; } +td.sdesc a { font-style: normal; } +th.tname { width: auto; text-align: left; } +.icost, .itype { width: 10%; text-align: center; } +td.impl { width: 15%; vertical-align: top; } +.compl { width: 15%; text-align: center; } + + +table.rbudget { width: auto; } +table.rbudget td { vertical-align: middle; } +td.rbico { width: 32px; } +td.rbttl { text-align: right; font-weight: bold; } +td.rbval { width: 64px; text-align: center; } +td.rbcl { text-align: right; } +td.rbcr { text-align: left; } diff --git a/site/static/beta5/css/purple.css b/site/static/beta5/css/purple.css new file mode 100644 index 0000000..006351f --- /dev/null +++ b/site/static/beta5/css/purple.css @@ -0,0 +1,54 @@ +/** GENERAL ELEMENTS **/ + +body, p, td, a { color: #C0C0C0; } +h1, h2, h3 { color: #CC33FF; } +h4, h5, h6, th { color: #CC66FF; } +a, a:visited { color: white; } +b { color: white; } +table.list a { color: #C0C0C0; } +table.list tr:hover td, table.list tr:hover td > * { color: white; } + + +/** STATUS AND MENU BARS **/ + +table.topframe { + background-color: #660099; + border: 1px solid #9900CC; +} +td.topmenu { + background-image: url('__STATICURL__/beta5/pics/ttl/def/purple/menubg.png'); + background-repeat: repeat-x; +} +ul ul.tmenu li.tmenu { background-color: #660099; } +ul.tmenu li:hover { background-color: #9966CC; } +a.tmenu:hover { background-color: black; } +ul.tmenu li.tmenu:hover ul.tmenu { border: 1px solid #9900CC; } +td.player, td.funds { color: #DDAAFF; } +td.stime { color: #E0E0E0; } +td.player b, td.funds b { color: white; } + + +/** FORMS **/ +input[type=text],input[type=password],input[type=submit],input[type=reset],input[type=button],select,textarea { + border: 1px solid #660099; + color: #CC66FF; + background-color: black; +} + +input[disabled] { color: #3F3F3F; border: 1px solid #3F3F3F; } + + +/** MAP COLORS **/ + +table.map li:hover, table.map li:hover a { color: white; } + +.mcown { color: #33CC33; } +a.mcown:visited { color: #33CC33; } +.mcally { color: #6666FF; } +a.mcally:visited { color: #6666FF; } +.mcprot { color: #AFAFAF; background-color: black; } +a.mcprot:visited { background-color: black; } +.mcpally { color: #6666FF; background-color: black; } +a.mcpally:visited { color: #6666FF; } +.mcpown { color: #33CC33; background-color: black; } +a.mcpown:visited { color: #33CC33; } diff --git a/site/static/beta5/css/red.css b/site/static/beta5/css/red.css new file mode 100644 index 0000000..5345b49 --- /dev/null +++ b/site/static/beta5/css/red.css @@ -0,0 +1,54 @@ +/** GENERAL ELEMENTS **/ + +body, p, td, th, h1, h2, h3, h4, h5, h6, a { color: #C0C0C0; } +h1, h2, h3 { color: #FF3F3F; } +h4, h5, h6, th { color: #FF5F9F; } +a, a:visited { color: white; } +b { color: white; } +table.list a { color: #C0C0C0; } +table.list tr:hover td, table.list tr:hover td > * { color: white; } + + +/** STATUS AND MENU BARS **/ + +table.topframe { + background-color: #7F1F1F; + border: 1px solid #7F3F3F; +} +td.topmenu { + background-image: url('__STATICURL__/beta5/pics/ttl/def/red/menubg.png'); + background-repeat: repeat-x; +} +ul ul.tmenu li.tmenu { background-color: #7F1F1F; } +ul.tmenu li:hover { background-color: #9F3F3F; } +a.tmenu:hover { background-color: black; } +ul.tmenu li.tmenu:hover ul.tmenu { border: 1px solid #CF3F3F; } +td.player, td.funds, td.stime { color: #FFCCCC; } +td.player b, td.funds b { color: white; } + + + +/** FORMS **/ +input[type=text],input[type=password],input[type=submit],input[type=reset],input[type=button],select,textarea { + border: 1px solid #7F1F1F; + color: #FF5F9F; + background: black; +} + +input[disabled] { color: #3F3F3F; border: 1px solid #3F3F3F; } + + +/** MAP COLORS **/ + +table.map li:hover, table.map li:hover a { color: white; } + +.mcown { color: #33CC33; } +a.mcown:visited { color: #33CC33; } +.mcally { color: #6666FF; } +a.mcally:visited { color: #6666FF; } +.mcprot { color: #AFAFAF; background-color: black; } +a.mcprot:visited { background-color: black; } +.mcpally { color: #6666FF; background-color: black; } +a.mcpally:visited { color: #6666FF; } +.mcpown { color: #33CC33; background-color: black; } +a.mcpown:visited { color: #33CC33; } diff --git a/site/static/beta5/css/thm_classic.css b/site/static/beta5/css/thm_classic.css new file mode 100644 index 0000000..baddff0 --- /dev/null +++ b/site/static/beta5/css/thm_classic.css @@ -0,0 +1,52 @@ +/** MENU BAR **/ + +table.topmenu { + empty-cells: show; + width: 95%; + margin: 0 auto; + border: 4px outset gray; +} + +table.topmenu tr td { + vertical-align: middle; + text-align: center; + border: 1px solid gray; +} + +table.topmenu h1 { + padding: 0; margin: 0; + font-style: normal; + text-decoration: underline; +} + +table.topmenu tr td.bottom { + width: 12%; +} + + +/** PLAYER INFO **/ + +table.player { + width: 99%; + margin: 5px auto 5px auto; +} + +table.player td { vertical-align: middle; } +table.player td.player { + width: 40%; + font-style: italic; + font-weight: bold; +} +table.player td.player b { font-style: normal; } +td.funds { + width: 33%; + text-align: left; + font-style: italic; + font-weight: bold; + padding: 5px 0px 5px 5px; +} +td.funds b { font-style: normal; } +table.player td.stime { + width: 50%; + text-align: right; +} diff --git a/site/static/beta5/css/thm_cripes.css b/site/static/beta5/css/thm_cripes.css new file mode 100644 index 0000000..2423535 --- /dev/null +++ b/site/static/beta5/css/thm_cripes.css @@ -0,0 +1,90 @@ +body { + background-image: none; + background-color: black; +} + +/** MENU BAR **/ + +table.crpmenu { + empty-cells: show; + width: auto; + padding: 0px; + margin: 0px; +} + +table.crpmenu a { + color: inherit; + text-decoration: inherit; + font-weight: inherit; + font-style: inherit; +} + +table.crpmenu tr td { + vertical-align: middle; + text-align: center; + padding: 1px 0px 0px 1px; margin: 0px; + border-width: 0px; + border-style: none; +} + +table.crpmenu h1 { + padding: 0px; + margin: 0px; + font-style: normal; + font-weight: normal; + text-decoration: none; + background-color: #3f3f4f; +} + +table.crpmenu h1:hover { + background-color: #4f4f5f; +} + +table.crpmenu h1.crpsel { + text-decoration: underline; + font-weight: bold; +} + +table.crpmenu td.stime { + text-align: right; +} + +table.crpmenu td.crpmsg { + text-align: left; +} + +td.crpmsg h1, td.crpcash h1, td.crpbt h1 { + background-color: #47473f; +} + +table.crpmenu td.crpcash { + text-align: left; +} + + +/** PLAYER INFO **/ + +table.player { + width: 99%; + margin: 5px auto 5px auto; +} + +table.player td { vertical-align: middle; } +table.player td.player { + width: 40%; + font-style: italic; + font-weight: bold; +} +table.player td.player b { font-style: normal; } +td.funds { + width: 33%; + text-align: left; + font-style: italic; + font-weight: bold; + padding: 5px 0px 5px 5px; +} +td.funds b { font-style: normal; } +table.player td.stime { + width: 50%; + text-align: right; +} diff --git a/site/static/beta5/css/thm_default-ie.css b/site/static/beta5/css/thm_default-ie.css new file mode 100644 index 0000000..7036b54 --- /dev/null +++ b/site/static/beta5/css/thm_default-ie.css @@ -0,0 +1,93 @@ +/** MENU BAR **/ + +table.topmenu { + empty-cells: show; + width: auto; + height: 32px; +} + +table.topmenu tr td { + vertical-align: middle; + text-align: left; + width: auto; +} + +td.icons { + text-align: right; + width: 162px; + height: 32px; +} +td.icons img { + width: 32px; + height: 32px; + border: 0px; +} +td#msgicon { height: 32px; width: 32px; } + + +/** MENU ELEMENTS **/ + +ul.tmenu, ul.tmenu li.tmenu +{ + display: block; + margin: 0px; + padding: 0px; + border: 0px; + width: 100%; + height: 100%; +} +ul.tmenu { margin: 0px 12px 0px 0px; } +li.tmenu { padding: 0px 0px 0px 12px; } + +ul.tmenu li.tmenu a.tmenu { + padding: 3px 12px 4px 12px; + font-weight: bold; + text-decoration: none; + font-style: normal; +} + +table.topmenu ul.tmenu li.tmenu { + width: 100%; +} + +table.topmenu ul.tmenu ul.tmenu { + top: 100%; + left: 0; + width: 110%; +} + +ul.tmenu li.tmenu { + position: relative; + z-index: 9; +} + +ul.tmenu { list-style: none; } +li.tmenu ul.tmenu { position: absolute; } +ul.tmenu ul.tmenu { display: none; } + + +/** PLAYER INFO **/ + +table.player { + width: 99%; + margin: 5px auto 5px auto; +} + +table.player td { vertical-align: middle; } +table.player td.player { + width: 33%; + font-style: italic; + font-weight: bold; +} +table.player td.player b { font-style: normal; } +table.player td.funds { + width: 34%; + text-align: center; + font-style: italic; + font-weight: bold; +} +table.player td.funds b { font-style: normal; } +table.player td.stime { + width: 33%; + text-align: right; +} diff --git a/site/static/beta5/css/thm_default.css b/site/static/beta5/css/thm_default.css new file mode 100644 index 0000000..498826c --- /dev/null +++ b/site/static/beta5/css/thm_default.css @@ -0,0 +1,100 @@ +/** MENU BAR **/ + +table.topmenu { + empty-cells: show; + width: auto; + height: 32px; +} + +table.topmenu tr td { + vertical-align: middle; + text-align: left; + width: auto; +} + +td.icons { + text-align: right; + width: 160px; + height: 32px; +} +td.icons img { + width: 32px; + height: 32px; + border: 0px; +} +td#msgicon { height: 32px; width: 32px; } + + +/** MENU ELEMENTS **/ + +ul.tmenu, ul.tmenu li.tmenu, ul.tmenu li.tmenu a.tmenu +{ + display: block; + margin: 0px; + padding: 0px; + border: 0px; + width: 100%; +} +ul.tmenu { margin: 0px 12px 0px 0px; } +li.tmenu { padding: 0px 0px 0px 12px; } + +ul.tmenu li.tmenu a.tmenu { + padding: 3px 12px 4px 12px; + font-weight: bold; + text-decoration: none; + font-style: normal; +} + +table.topmenu ul.tmenu li.tmenu { + width: 100%; +} + +table.topmenu ul.tmenu ul.tmenu { + top: 100%; + left: 0; + width: auto; +} + +table.topmenu ul.tmenu ul.tmenu ul.tmenu { + top: 15%; + left: 66%; +} + +ul.tmenu li.tmenu { + position: relative; + z-index: 9; +} + +ul.tmenu { list-style: none; } +li.tmenu ul.tmenu { position: absolute; } +ul.tmenu li.tmenu > a.tmenu { width: auto; } +ul.tmenu li:hover { z-index: 10; } +ul.tmenu ul.tmenu, li:hover ul.tmenu ul.tmenu { display: none; } +li:hover ul.tmenu, li:hover li:hover ul.tmenu { display: block; } + + +/** PLAYER INFO **/ + +table.player { + width: 99%; + margin: 5px auto 5px auto; +} + +table.player td { vertical-align: middle; } +table.player td.player { + width: 33%; + font-style: italic; + font-weight: bold; +} +table.player td.player b { font-style: normal; } +table.player td.funds { + width: 34%; + text-align: center; + font-style: italic; + font-weight: bold; +} +table.player td.funds b { font-style: normal; } +table.player td.stime { + width: 33%; + text-align: right; +} diff --git a/site/static/beta5/css/thm_invert-ie.css b/site/static/beta5/css/thm_invert-ie.css new file mode 100644 index 0000000..a116f09 --- /dev/null +++ b/site/static/beta5/css/thm_invert-ie.css @@ -0,0 +1,93 @@ +/** MENU BAR **/ + +table.topmenu { + empty-cells: show; + width: auto; + height: 32px; +} + +table.topmenu tr td { + vertical-align: middle; + text-align: left; + width: auto; +} + +td.icons { + text-align: right; + width: 162px; + height: 32px; +} +td.icons img { + width: 32px; + height: 32px; + border: 0px; +} +td#msgicon { width: 32px; height: 32px; } + + +/** MENU ELEMENTS **/ + +ul.tmenu, ul.tmenu li.tmenu +{ + display: block; + margin: 0px; + padding: 0px; + border: 0px; + width: 100%; + height: 100%; +} +ul.tmenu { margin: 0px 12px 0px 0px; } +li.tmenu { padding: 0px 0px 0px 12px; } + +ul.tmenu li.tmenu a.tmenu { + padding: 3px 12px 4px 12px; + font-weight: bold; + text-decoration: none; + font-style: normal; +} + +table.topmenu ul.tmenu li.tmenu { + width: 100%; +} + +table.topmenu ul.tmenu ul.tmenu { + top: 100%; + left: 0; + width: 110%; +} + +ul.tmenu li.tmenu { + position: relative; + z-index: 9; +} + +ul.tmenu { list-style: none; } +li.tmenu ul.tmenu { position: absolute; } +ul.tmenu ul.tmenu { display: none; } + + +/** PLAYER INFO **/ + +table.player { + width: 99%; + margin: 5px auto 5px auto; +} + +table.player td { vertical-align: middle; } +table.player td.player { + width: 33%; + font-style: italic; + font-weight: bold; +} +table.player td.player b { font-style: normal; } +table.player td.funds { + width: 34%; + text-align: center; + font-style: italic; + font-weight: bold; +} +table.player td.funds b { font-style: normal; } +table.player td.stime { + width: 33%; + text-align: right; +} diff --git a/site/static/beta5/css/thm_invert.css b/site/static/beta5/css/thm_invert.css new file mode 100644 index 0000000..f8d3974 --- /dev/null +++ b/site/static/beta5/css/thm_invert.css @@ -0,0 +1,100 @@ +/** MENU BAR **/ + +table.topmenu { + empty-cells: show; + width: auto; + height: 32px; +} + +table.topmenu tr td { + vertical-align: middle; + text-align: left; + width: auto; +} + +td.icons { + text-align: right; + width: 160px; + height: 32px; +} +td.icons img { + width: 32px; + height: 32px; + border: 0px; +} +td#msgicon { width: 32px; height: 32px; } + + +/** MENU ELEMENTS **/ + +ul.tmenu, ul.tmenu li.tmenu, ul.tmenu li.tmenu a.tmenu +{ + display: block; + margin: 0px; + padding: 0px; + border: 0px; + width: 100%; +} +ul.tmenu { margin: 0px 12px 0px 0px; } +li.tmenu { padding: 0px 0px 0px 12px; } + +ul.tmenu li.tmenu a.tmenu { + padding: 3px 12px 4px 12px; + font-weight: bold; + text-decoration: none; + font-style: normal; +} + +table.topmenu ul.tmenu li.tmenu { + width: 100%; +} + +table.topmenu ul.tmenu ul.tmenu { + top: 100%; + left: 0; + width: auto; +} + +table.topmenu ul.tmenu ul.tmenu ul.tmenu { + top: 15%; + left: 66%; +} + +ul.tmenu li.tmenu { + position: relative; + z-index: 9; +} + +ul.tmenu { list-style: none; } +li.tmenu ul.tmenu { position: absolute; } +ul.tmenu li.tmenu > a.tmenu { width: auto; } +ul.tmenu li:hover { z-index: 10; } +ul.tmenu ul.tmenu, li:hover ul.tmenu ul.tmenu { display: none; } +li:hover ul.tmenu, li:hover li:hover ul.tmenu { display: block; } + + +/** PLAYER INFO **/ + +table.player { + width: 99%; + margin: 5px auto 5px auto; +} + +table.player td { vertical-align: middle; } +table.player td.player { + width: 33%; + font-style: italic; + font-weight: bold; +} +table.player td.player b { font-style: normal; } +table.player td.funds { + width: 34%; + text-align: center; + font-style: italic; + font-weight: bold; +} +table.player td.funds b { font-style: normal; } +table.player td.stime { + width: 33%; + text-align: right; +} diff --git a/site/static/beta5/css/yellow.css b/site/static/beta5/css/yellow.css new file mode 100644 index 0000000..c172241 --- /dev/null +++ b/site/static/beta5/css/yellow.css @@ -0,0 +1,54 @@ +/** GENERAL ELEMENTS **/ + +body, p, td, a { color: #C0C0C0; } +h1, h2, h3 { color: #FFCC33; } +h4, h5, h6, th { color: #FFCC66; } +a, a:visited { color: white; } +b { color: white; } +table.list a { color: #C0C0C0; } +table.list tr:hover td, table.list tr:hover td > * { color: white; } + + +/** STATUS AND MENU BARS **/ + +table.topframe { + background-color: #996600; + border: 1px solid #FFCC00; +} +td.topmenu { + background-image: url('__STATICURL__/beta5/pics/ttl/def/yellow/menubg.png'); + background-repeat: repeat-x; +} +ul ul.tmenu li.tmenu { background-color: #CC9900; } +ul.tmenu li:hover { background-color: #FFCC66; } +a.tmenu:hover { background-color: black; } +ul.tmenu li.tmenu:hover ul.tmenu { border: 1px solid #FFCC00; } +td.player, td.funds { color: #FFDDAA; } +td.stime { color: #E0E0E0; } +td.player b, td.funds b { color: white; } + + +/** FORMS **/ +input[type=text],input[type=password],input[type=submit],input[type=reset],input[type=button],select,textarea { + border: 1px solid #CC9900; + color: #FFCC66; + background-color: black; +} + +input[disabled] { color: #3F3F3F; border: 1px solid #3F3F3F; } + + +/** MAP COLORS **/ + +table.map li:hover, table.map li:hover a { color: white; } + +.mcown { color: #33CC33; } +a.mcown:visited { color: #33CC33; } +.mcally { color: #6666FF; } +a.mcally:visited { color: #6666FF; } +.mcprot { color: #AFAFAF; background-color: black; } +a.mcprot:visited { background-color: black; } +.mcpally { color: #6666FF; background-color: black; } +a.mcpally:visited { color: #6666FF; } +.mcpown { color: #33CC33; background-color: black; } +a.mcpown:visited { color: #33CC33; } diff --git a/site/static/beta5/js/main-en.js b/site/static/beta5/js/main-en.js new file mode 100644 index 0000000..1c8a5f5 --- /dev/null +++ b/site/static/beta5/js/main-en.js @@ -0,0 +1,37 @@ +function formatDate(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; +} + + +Component.Listing.notFoundText = 'No elements found'; +Component.Listing.loadingText = 'Loading ...'; +Component.Listing.errorText = 'Error: the list couldn\'t be loaded'; diff --git a/site/static/beta5/js/main.js b/site/static/beta5/js/main.js new file mode 100644 index 0000000..f7d9601 --- /dev/null +++ b/site/static/beta5/js/main.js @@ -0,0 +1,1399 @@ +function formatNumber(n) +{ + var l = n.length, m = l % 3, c = (l - m) / 3, v = ((m > 0) ? n.substr(0, m) : ''), z; + for (z=0;z '; +} +Component.prototype.newSlot = function(name) { + if (this.slots.containsKey(name)) + return; + var s = new Component.Slot(name,this); + this.slots.put(name, s); +} +Component.prototype.newEvent = function(name, propagate) { + if (this.events.containsKey(name)) + return; + + var e = new Component.Event(name, propagate); + this.events.put(name, e); + + var f = function() { this.triggerEvent(name,arguments); }; + eval("this.on"+name+'=f'); +} +Component.prototype.bindEvent = function(eName,sName,cmp) { + var e = this.events.get(eName), c = (cmp ? cmp : this), s = c.slots.get(sName); + if (!(e && s)) + return; + e.bind(s); +} +Component.prototype.detachEvent = function(eName,sName,cmp) { + var e = this.events.get(eName), c = (cmp ? cmp : this), s = c.slots.get(sName); + if (!(e && s)) + return; + e.detach(s); +} +Component.prototype.triggerEvent = function(eName,args) { + var e = this.events.get(eName); + if (!e) + return; + + var i, n = new Array(); + for (i=0;i' : '/>'); +} +Component.Form.Element.prototype.draw = function () { + var e = this.getElement(); + if (!e) + return; + + var s = this.getTag(); + if (this.hasContents) + s += ''; + + e.innerHTML = s; + this.htmlElement = document.getElementById(this.attributes.get('id')); + this.updateValue(); +} +Component.Form.Element.prototype.updateValue = function (nv) { + if (typeof nv != "undefined") + this.value = nv; + if (this.htmlElement) + this.htmlElement.value = this.value; +} + + + +//---------------------------------------------------------------------------------------------- +// LABEL COMPONENT +//---------------------------------------------------------------------------------------------- + +Component.Form.Label = function(target, text, key) { + if (typeof target == "undefined") + return; + + Component.apply(this, [false]); + this.target = target; + this.text = text; + this.key = key; +} +Component.Form.Label.prototype = new Component; +Component.Form.Label.prototype.addChild = function () { } +Component.Form.Label.prototype.draw = function () { + var e = this.getElement(); + if (!e) + return; + e.innerHTML = ''; +} + + + +//---------------------------------------------------------------------------------------------- +// TEXT INPUT COMPONENT +//---------------------------------------------------------------------------------------------- + +Component.Form.Text = function (defValue) { + if (typeof defValue == 'undefined') + return; + + Component.Form.Element.apply(this, ['input type="text"', false]); + + var ev = ['Click', 'KeyUp', 'KeyDown', 'KeyPress', 'Paste']; + this.newSlot('textEvent'); + for (var i in ev) + { + this.addHTMLEvent(ev[i]); + this.bindEvent(ev[i], 'textEvent'); + } + + this.timer = new Component.Timer(500, false); + this.newSlot('timeout'); + this.timer.bindEvent('Tick', 'timeout', this); + + this.oldValue = this.value = defValue; + this.newEvent('TextChanged'); +} +Component.Form.Text.prototype = new Component.Form.Element; +Component.Form.Text.prototype.textEvent = function() { + if (this.htmlElement) + this.value = this.htmlElement.value; + this.timer.restart(); +} +Component.Form.Text.prototype.timeout = function() { + if (this.htmlElement) + this.value = this.htmlElement.value; + if (this.value != this.oldValue) + { + this.oldValue = this.value; + this.onTextChanged(this.value); + } +} +Component.Form.Text.prototype.setSize = function (sz) { + this.setAttribute('size', (typeof sz == 'undefined') ? '' : sz); +} +Component.Form.Text.prototype.setMaxLength = function (ml) { + if (typeof ml != "undefined" && this.value.length > ml) + { + this.timer.stop(); + this.value = this.value.substr(0, ml); + } + this.setAttribute('maxlength', (typeof ml == 'undefined') ? '' : ml); +} + + + +//---------------------------------------------------------------------------------------------- +// CHECKBOX COMPONENT +//---------------------------------------------------------------------------------------------- + +Component.Form.Checkbox = function (value,name) { + if (typeof value == 'undefined') + return; + + Component.Form.Element.apply(this, ['input type="checkbox"', false]); + + this.newSlot('check'); + this.addHTMLEvent('Click'); + this.bindEvent('Click', 'check'); + + this.newEvent('Checked'); + this.newEvent('Unchecked'); + + this.value = value; + this.checked = false; + + this.setAttribute('name', this.group = name); +} +Component.Form.Checkbox.prototype = new Component.Form.Element; +Component.Form.Checkbox.prototype.check = function(evt) { + this.checked = !this.checked; + + if (typeof evt == "undefined") + this.drawAll(); + + if (this.checked) { + this.attributes.put('checked', 'checked'); + this.onChecked(this.value, this.group); + } else { + this.attributes.remove('checked'); + this.onUnchecked(this.value, this.group); + } +} + + + +//---------------------------------------------------------------------------------------------- +// RADIO BUTTON COMPONENT +//---------------------------------------------------------------------------------------------- + +Component.Form.Radio = function (value,name) { + if (typeof value == 'undefined') + return; + + Component.Form.Element.apply(this, ['input type="radio"', false]); + + this.newSlot('check'); + this.addHTMLEvent('Click'); + this.bindEvent('Click', 'check'); + + this.newEvent('Checked'); + this.newEvent('Unchecked'); + + this.value = value; + this.checked = false; + + this.setAttribute('name', this.group = name); + with (Component.Form.Radio.groups) + { + if (!get(name)) + put(name, new Array()); + get(name).push(this); + } +} +Component.Form.Radio.prototype = new Component.Form.Element; +Component.Form.Radio.groups = new Hashtable(); +Component.Form.Radio.prototype.check = function(evt) { + var k = Component.Form.Radio.groups.keys(); + for (var i in k) + { + var c = Component.Form.Radio.groups.get(k[i]); + if (c.id != this.id && c.checked) + { + c.checked = false; + c.attributes.put('checked', ''); + if (typeof evt == "undefined") + c.drawAll(); + c.onUnchecked(c.group, c.value); + break; + } + } + this.checked = true; + this.attributes.put('checked', 'checked'); + if (typeof evt == "undefined") + this.drawAll(); + this.onChecked(this.value, this.group); +} + + + +//---------------------------------------------------------------------------------------------- +// DROP-DOWN COMPONENT +//---------------------------------------------------------------------------------------------- + +Component.Form.DropDown = function (emptyValue,emptyLabel) { + if (typeof emptyValue == 'undefined') + return; + + Component.Form.Element.apply(this, ['select', true]); + + this.newSlot('htmlSelect'); + this.addHTMLEvent('Change'); + this.bindEvent('Change', 'htmlSelect'); + + this.newEvent('Selected'); + this.options = new Array(); + this.byValue = new Hashtable(); + + if (typeof emptyValue != 'string') + emptyValue = emptyValue.toString(); + this.emptyValue = emptyValue; + this.emptyLabel = emptyLabel; + this.selected = emptyValue; +} +Component.Form.DropDown.prototype = new Component.Form.Element; +Component.Form.DropDown.prototype.clearOptions = function(redraw) { + this.byValue.clear(); + this.options = new Array(); + this.selected = this.emptyValue; +} +Component.Form.DropDown.prototype.appendOption = function(value,text) { + if (typeof value != 'string') + value = value.toString(); + + if (this.byValue.containsKey(value)) + return; + + this.options.push([value, text]); + this.byValue.put(value, this.options.length - 1); + + if (this.selected == this.emptyValue) + this.selected = value; +} +Component.Form.DropDown.prototype.finalize = function() { + this.drawAll(); + this.onSelected(this.selected); +} +Component.Form.DropDown.prototype.htmlSelect = function() { + if (this.htmlElement) + { + var se = this.htmlElement.selectedIndex; + this.selected = this.htmlElement.options[se].value; + } + this.onSelected(this.selected); +} +Component.Form.DropDown.prototype.select = function(v) { + if (typeof v != 'string') + v = v.toString(); + + if (!this.options.length || ! this.byValue.containsKey(v) || this.selected == v) + return; + + this.selected = v; + this.drawAll(); + this.onSelected(this.selected); +} +Component.Form.DropDown.prototype.updateValue = function() { } +Component.Form.DropDown.prototype.draw = function() { + var e = this.getElement(); + if (!e) + return; + + var s = ''; + if (this.options.length) + for (var i in this.options) + s += ''; + else + s = ''; + + e.innerHTML = this.getTag() + s + ''; + this.htmlElement = document.getElementById(this.attributes.get('id')); +} + + + +//---------------------------------------------------------------------------------------------- +// LISTING COMPONENT +//---------------------------------------------------------------------------------------------- +Component.Listing = function(rpcName, updateFreq, dataType, cl, rClass, hClass) +{ + if (typeof rpcName == 'undefined') + return; + + Component.apply(this, [false]); + + this.pageCtrl = null; + this.searchCtrl = null; + this.pageSzCtrl = null; + + this.dataCache = new Component.Listing.DataCache(this, rpcName, updateFreq, dataType); + this.addChild(this.dataCache); + + this.fields = new Hashtable(); + this.fLayout = { }; + this.rows = 0; + this.cols = 0; + this.mClass = cl; + this.rClass = (rClass ? rClass : ''); + this.hClass = (hClass ? hClass : ''); + this.finalized = false; + this.sortField = -1; + this.sortDir = false; + + this.notFoundText = Component.Listing.notFoundText; + this.loadingText = Component.Listing.loadingText; + this.errorText = Component.Listing.errorText; +} +Component.Listing.prototype = new Component; +Component.Listing.prototype.setPageController = function(c,itg) { + if (this.pageCtrl) + // FIXME, should detach the old controller + return; + this.pageCtrl = c; + this.pageCtrl.bindEvent('PageChanged', 'pageChanged', this.dataCache); + if (itg) + this.addChild(this.pageCtrl); + this.dataCache.pageChanged(this.pageCtrl.currentPage); +} +Component.Listing.prototype.setPageSizeController = function(c,itg) { + if (this.pageSzCtrl) + // FIXME, should detach the old controller + return; + this.pageSzCtrl = c; + this.pageSzCtrl.bindEvent('PageSizeChanged', 'pageSizeChanged', this.dataCache); + if (itg) + this.addChild(this.pageSzCtrl); + this.dataCache.pageSizeChanged(this.pageSzCtrl.currentSize); +} +Component.Listing.prototype.setSearchController = function(c,itg) { + if (this.searchCtrl) + // FIXME, should detach the old controller + return; + this.searchCtrl = c; + this.searchCtrl.bindEvent('SearchChanged', 'searchChanged', this.dataCache); + if (itg) + this.addChild(this.searchCtrl); + this.dataCache.searchChanged(this.searchCtrl.getParameter()); +} +Component.Listing.prototype.addField = function(fld) { + var x,y,xe=fld.x+fld.width,ye=fld.y+fld.height; + for (y=fld.y;ymx) + mx = x; + if (y>my) + my = y; + } + + this.rows = my+1; this.cols = mx+1; + for (y=0;y'; + if (hp||hps) + { + s += ' '; + s += ''; + } + s += '
    '; + + e.innerHTML = s; +} +Component.Listing.prototype.getHeaders = function () { + var s = ''; + var lId = -1, x, y, rc = (this.rClass != '' ? (" class='" + this.rClass + "'") : ""); + for (y=0;y'; + s += ''; + for (x=0;x' : ''); + } + return s; +} + + + +//---------------------------------------------------------------------------------------------- +// LISTING COMPONENT - PAGE CONTROLLER +//---------------------------------------------------------------------------------------------- + +Component.Listing.PageController = function(text) { + if (typeof text == 'undefined') + return; + + Component.apply(this, [false]); + + this.text = text; + this.maxPage = 0; + + this.addChild(this.select = new Component.Form.DropDown('', '-')); + this.newSlot('selected'); + this.select.bindEvent('Selected','selected',this); + + this.newSlot('changePage'); + this.newSlot('changeMaxPage'); + + this.newEvent('PageChanged'); +} +Component.Listing.PageController.prototype = new Component; +Component.Listing.PageController.prototype.selected = function(v) { + this.changePage(parseInt(v,10)); +} +Component.Listing.PageController.prototype.changePage = function(np) { + var op = this.currentPage; + this.currentPage = (np > this.maxPage) ? this.maxPage : np; + if (op != this.currentPage) { + this.select.select(this.currentPage); + this.onPageChanged(this.currentPage); + } + this.drawAll(); +} +Component.Listing.PageController.prototype.changeMaxPage = function(nmp) { + var omp = this.maxPage, ocp = this.currentPage; + this.maxPage = nmp; + if (omp != nmp) + { + var i; + this.select.clearOptions(); + for (i=0;i 1) + { + s2 = new Array(); + for (var i in this.modes) + s2.push(this.modes[i][0].html() + this.modes[i][1].html() + ' '); + s = s.replace(/__MS__/, s2.join(' ')); + } + + e.innerHTML = s; +} +Component.Listing.SearchController.prototype.textChanged = function (text) { this.onSearchChanged(this.mode + '!' + text); } +Component.Listing.SearchController.prototype.modeChanged = function (mode) { this.onSearchChanged((this.mode=mode) + '!' + this.textField.value); } +Component.Listing.SearchController.prototype.getParameter = function () { return this.mode + '!' + this.textField.value; } + + + +//---------------------------------------------------------------------------------------------- +// LISTING COMPONENT - SPACERS +//---------------------------------------------------------------------------------------------- + +Component.Listing.Spacer = function (x,y,fClass) { + if (typeof x == 'undefined') + return; + Component.apply(this, [true]); + this.fClass = fClass ? fClass : ''; + this.attrCode = null; + this.x = x; this.y = y; + this.width = this.height = 1; + this.sortable = false; + this.component = null; + this.newEvent('Click'); +} +Component.Listing.Spacer.prototype = new Component; +Component.Listing.Spacer.prototype.getAttributes = function() { + if (typeof this.attrCode == 'string') + return this.attrCode; + var s = ''; + if (this.fClass != '') + s += ' class="' + this.fClass + '"'; + return (this.attrCode = s); +} +Component.Listing.Spacer.prototype.getData = function() { + return ' '; +} +Component.Listing.Spacer.prototype.getTitle = function() { + return ' '; +} + + + +//---------------------------------------------------------------------------------------------- +// LISTING COMPONENT - FIELDS +//---------------------------------------------------------------------------------------------- + +Component.Listing.Field = function (name,sortable,dataField,x,y,w,h,fClass) { + if (typeof name == 'undefined') + return; + Component.Listing.Spacer.apply(this, [x,y,fClass]); + this.name = name; + this.sortable = sortable; + this.dataField = dataField; + this.width = w; this.height = h; +} +Component.Listing.Field.prototype = new Component.Listing.Spacer; +Component.Listing.Field.prototype.getAttributes = function() { + if (typeof this.attrCode == 'string') + return this.attrCode; + var s = ''; + if (this.fClass != '') + s += ' class="' + this.fClass + '"'; + if (this.width != 1) + s += ' colspan="' + this.width + '"'; + if (this.height != 1) + s += ' rowspan="' + this.height + '"'; + return (this.attrCode = s); +} +Component.Listing.Field.prototype.getContents = function(d) { + var v; + eval('v=d.' + this.dataField); + if (typeof v != 'string') + v = v.toString(); + return v; +} +Component.Listing.Field.prototype.getData = function(d) { + return '' + this.getContents(d) + ''; +} +Component.Listing.Field.prototype.getTitle = function() { + var s = ''; + return s + ''; +} + + +//---------------------------------------------------------------------------------------------- +// LISTING COMPONENT - DATA CACHE +//---------------------------------------------------------------------------------------------- + +Component.Listing.DataCache = function (comp, rpcName, updateFreq, dataType) +{ + Component.apply(this, [false]); + + this.newSlot('pageChanged'); + this.newSlot('pageSizeChanged'); + this.newSlot('searchChanged'); + this.newSlot('sortChanged'); + this.newSlot('pageLoaded'); + this.newSlot('checkUpdate'); + this.newSlot('initDisplay'); + this.newSlot('stopDisplay'); + this.newEvent('SettingsChanged'); + this.newEvent('Loaded'); + + this.rpcName = rpcName; + this.dataType = dataType; + this.component = comp; + + this.sortField = null; + this.sortDir = false; + this.searchParam = ''; + this.perPage = 10; + this.page = 0; + this.current = ""; + + this.cache = new Hashtable(); + this.timer = new Component.Timer(updateFreq * 1000, true); + + this.bindEvent('Hide', 'stop', this.timer); + this.bindEvent('Loaded', 'drawAll'); + this.bindEvent('SettingsChanged', 'checkUpdate'); + this.bindEvent('SettingsChanged', 'drawAll'); + this.bindEvent('Show', 'initDisplay'); + this.timer.bindEvent('Tick', 'checkUpdate', this); +} +Component.Listing.DataCache.prototype = new Component; +Component.Listing.DataCache.prototype.pageChanged = function (n) { + this.page = n; + this.onSettingsChanged(); +} +Component.Listing.DataCache.prototype.pageSizeChanged = function (n) { + this.perPage = n; + this.onSettingsChanged(); +} +Component.Listing.DataCache.prototype.searchChanged = function (p) { + this.searchParam = p; + this.onSettingsChanged(); +} +Component.Listing.DataCache.prototype.sortChanged = function (col) { + if (this.sortField && col.id == this.sortField.id) + this.sortDir = ! this.sortDir; + else + this.sortField = col; + this.onSettingsChanged(); +} +Component.Listing.DataCache.prototype.pageLoaded = function (id) { + if (this.current != id) + return; + this.drawAll(); + this.onLoaded(); +} +Component.Listing.DataCache.prototype.checkUpdate = function () { + if (!this.visible) + return; + + var nid = this.page+'#'+this.perPage+'#'+(this.sortField?this.sortField.dataField:'')+'#'+(this.sortDir?1:0)+'#'+this.searchParam; + var ne; + if (this.cache.containsKey(nid)) + ne = this.cache.get(nid); + else { + ne = new Component.Listing.DataCache.Page(this, nid); + ne.bindEvent('Loaded', 'pageLoaded', this); + this.cache.put(nid, ne); + } + this.current = nid; + ne.checkUpdate(); +} +Component.Listing.DataCache.prototype.initDisplay = function () { + if (this.timer.running) + return; + this.timer.start(); + this.onSettingsChanged(); +} +Component.Listing.DataCache.prototype.draw = function () { + var e = document.getElementById(this.component.id + 'data'); + if (!(e && this.cache.containsKey(this.current))) + return; + + var s = this.component.getHeaders(); + var p = this.cache.get(this.current); + + if (p.loading) + s += ''; + else if (p.error) + s += ''; + else if (p.data.length == 0) + s += ''; + else { + var rc, i; + rc = (this.component.rClass != '' ? (" class='" + this.component.rClass + "'") : ""); + + for (i=0;i'; + s += ''; + for (x=0;x' : ''); + } + } + } + + e.innerHTML = s + '
    ' + + this.component.loadingText + '
    ' + + this.component.errorText + '
    ' + + this.component.notFoundText + '
    '; +} + + +//---------------------------------------------------------------------------------------------- +// LISTING COMPONENT - DATA CACHE PAGE +//---------------------------------------------------------------------------------------------- +Component.Listing.DataCache.Page = function (cache, param) +{ + Component.apply(this, [false]); + + this.newSlot('dataReceived'); + this.newEvent('Loaded'); + + this.cache = cache; + this.param = param; + this.loading = true; + this.md5 = ""; + this.error = false; + this.data = new Array(); + this.call = new Component.Ajax(this.cache.rpcName); + + this.call.bindEvent('Returned', 'dataReceived', this); +} +Component.Listing.DataCache.Page.prototype = new Component; +Component.Listing.DataCache.Page.prototype.checkUpdate = function () { + this.loading = true; + this.call.call(this.param, this.md5); +} +Component.Listing.DataCache.Page.prototype.dataReceived = function (data) { + if (data == "") { + this.error = true; + this.data = new Array(); + } + else if (data.indexOf("KEEP#") == 0) + this.cache.component.setMaxPage(parseInt(data.substr(5), 10) - 1); + else { + var dataArr = data.split('\n'); + + var a = dataArr.shift().split('#'); + this.md5 = a.shift(); + this.cache.component.setMaxPage(parseInt(a.shift(), 10) - 1); + + this.data = new Array(); + this.error = false; + + while (dataArr.length > 0) { + var e = new this.cache.dataType (this.cache, this); + e.parse(dataArr); + this.data.push(e); + } + } + + this.loading = false; + this.onLoaded(this.param); +} + + +//---------------------------------------------------------------------------------------------- +// LISTING COMPONENT - GENERIC DATA +//---------------------------------------------------------------------------------------------- +Component.Listing.Data = function (cache, page) { + if (typeof cache == "undefined") + return; + Component.apply(this, [false]); + this.cache = cache; + this.page = page; +} +Component.Listing.Data.prototype = new Component; +Component.Listing.Data.prototype.parse = function (data) { + alert("Listing using elemnts without a parse() method\n" + + "Skipping line: " + data.shift()); +} +Component.Listing.Data.prototype.getRowStyle = function () { + return ''; +} diff --git a/site/static/beta5/js/pg_alliance-en.js b/site/static/beta5/js/pg_alliance-en.js new file mode 100644 index 0000000..76fc2d6 --- /dev/null +++ b/site/static/beta5/js/pg_alliance-en.js @@ -0,0 +1,1682 @@ +var alPgTitles = new Array( + 'Main page', 'Leader election', 'Members', 'Planets', + 'Military status', 'Pending requests', 'Forums admin.', + 'Ranks admin.' + ); +var mbListHeaders = ['Name', 'Rank', 'Last online', 'On vacation']; +var onlineText = "Currently online"; +var lastOnlinePrefix = ""; +var lastOnlineUnits = ['minute', 'minutes', 'hour', 'hours']; +var lastOnlineSuffix = " ago"; +var vacationMode = ['No', 'Yes']; + +function makeAllianceTooltips() +{ + alltt = new Array(); + if (ttDelay == 0) + { + var i; + for (i=0;i<300;i++) + alltt[i] = ""; + return; + } + alltt[0] = tt_Dynamic("Use this text field to type in the tag of the alliance you wish to join"); + alltt[1] = tt_Dynamic("Click here to send your joining request to the alliance's leadership"); + alltt[2] = tt_Dynamic("Use this text field to type in the tag of the alliance you wish to create"); + alltt[3] = tt_Dynamic("Use this text field to type in the full name of the alliance you wish to create"); + alltt[4] = tt_Dynamic("Click here to create the given alliance"); + alltt[5] = tt_Dynamic("Click here to cancel your joining request"); + alltt[10] = tt_Dynamic("Use this text field to type in the tag of the alliance you wish to get information about"); + alltt[11] = tt_Dynamic("Click here to display the information about the alliance you've provided the tag for"); + alltt[20] = tt_Dynamic("Click here to step down as leader of this alliance"); + alltt[21] = tt_Dynamic("Click here to leave this alliance"); + alltt[22] = tt_Dynamic("Use this radio button to switch the alliance in dictatorial mode"); + alltt[23] = tt_Dynamic("Use this radio button to switch the alliance in democratric mode"); + alltt[24] = tt_Dynamic("Use this text field to type in the name of your successor as leader of the alliance"); + alltt[30] = tt_Dynamic("Click here to update the alliance general settings according to your changes"); + alltt[31] = tt_Dynamic("Click here to reset the alliance general settings to their previous values"); + alltt[40] = tt_Dynamic("Click here to take the presidency of the alliance"); + alltt[41] = tt_Dynamic("Click here to apply to presidency of the alliance"); + alltt[42] = tt_Dynamic("Click here to cancel your candidacy for the presidency of the alliance"); + alltt[43] = tt_Dynamic("Click here to change your vote for the alliance presidency according to what you have selected in the candidates list"); + alltt[50] = tt_Dynamic("Click here to sort the candidates list according to this field and switch between ascending and descending ordering"); + alltt[51] = tt_Dynamic("Use this radio button to abstain in the presidency election"); + alltt[52] = tt_Dynamic("Use this radio button to select which candidate you wish to vote for in the presidency election"); + alltt[60] = tt_Dynamic("Use this drop down list to select the alliance listing you wish to display"); + alltt[61] = tt_Dynamic("Use this drop down list to select the number of planets to display on each page of the listing"); + alltt[62] = tt_Dynamic("Use this radio button if you wish to search for a planet in the listing"); + alltt[63] = tt_Dynamic("Use this radio button if you wish to search for a player in the listing"); + alltt[64] = tt_Dynamic("Use this text field to type in the name of the planet or player you wish to search for in the listing"); + alltt[70] = tt_Dynamic("Click here to order the list according to this field and switch between ascending and descending ordering"); + alltt[71] = tt_Dynamic("Click here to go to this individual planet page"); + alltt[72] = tt_Dynamic("Click here to send a private message to the owner of this planet"); + alltt[73] = tt_Dynamic("Use this drop down list to go to another page of the listing"); + alltt[80] = tt_Dynamic("Use this drop down list to select the number of alliance members to display oneach page of the listing"); + alltt[81] = tt_Dynamic("Use this text field to type in the name of the player you wish to search for in the listing"); + alltt[90] = tt_Dynamic("Click here to kick this member out of the alliance"); + alltt[91] = tt_Dynamic("Click here to change the rank of the selected members to the selected rank"); + alltt[92] = tt_Dynamic("Use this drop down list to choose the rank you want to give to the selected members"); + alltt[100] = tt_Dynamic("Check this checkbox to select this alliance member "); + alltt[101] = tt_Dynamic("Click here to send a private message to this alliance member"); + alltt[110] = tt_Dynamic("Check this checkbox to select this player requesting to join the alliance"); + alltt[111] = tt_Dynamic("Click here to accept this player into the alliance"); + alltt[112] = tt_Dynamic("Click here to reject this player's joining request"); + alltt[120] = tt_Dynamic("Click here to go to the forum creation form and create a new forum"); + alltt[130] = tt_Dynamic("Click here to go to the forum modification form and edit this forum's settings"); + alltt[131] = tt_Dynamic("Click here to move this forum up in the forums list"); + alltt[132] = tt_Dynamic("Click here to move this forum down in the forums list"); + alltt[133] = tt_Dynamic("Click here to delete this forum and all its contents"); + alltt[140] = tt_Dynamic("Use this text field to type in the name of the forum"); + alltt[141] = tt_Dynamic("Use this radio button if you want everyone to be able to post new threads in this forum"); + alltt[142] = tt_Dynamic("Use this radio button if you want the moderators of the forum to be the only ones allowed to post new threads in it"); + alltt[143] = tt_Dynamic("Use this drop down list to select where to place this forum in the forums list"); + alltt[144] = tt_Dynamic("Use this text area to type in a description for this forum"); + alltt[145] = tt_Dynamic("Click here to validate your changes to this forum"); + alltt[146] = tt_Dynamic("Click here to cancel your changes to this forum"); + alltt[150] = tt_Dynamic("Check this checkbox to select this rank and modify its access level to this forum"); + alltt[160] = tt_Dynamic("Click here to give this access level to the selected rank(s) for this forum"); + alltt[170] = tt_Dynamic("Click here to go to the rank creation form and create a new rank"); + alltt[171] = tt_Dynamic("Click here to display / hide the dtails about this rank"); + alltt[172] = tt_Dynamic("Click here to go to the modification form and edit this rank"); + alltt[173] = tt_Dynamic("Click to delete this rank"); + alltt[180] = tt_Dynamic("Use this text field to type in the rank designation"); + alltt[181] = tt_Dynamic("Use this drop down list to select the level of listing access the members with this rank will have"); + alltt[182] = tt_Dynamic("Check this checkbox if you want members of this rank to see the list of planets under attack"); + alltt[183] = tt_Dynamic("Use this radio button if you want members of this rank to be diplomatic contact for the alliance and to receive all messages sent to the alliance"); + alltt[184] = tt_Dynamic("Use this radio button if you don't want members of this rank to be diplomatic contact for the alliance"); + alltt[185] = tt_Dynamic("Use this radio button if you want members of this rank to be able to vote for leader elections if the alliance is democratic"); + alltt[186] = tt_Dynamic("Use this radio button if you don't want members of this rank to be able to vote for leader elections if the alliance is democratic"); + alltt[187] = tt_Dynamic("Use this radio button if you want members of this rank to be able to apply for presidency"); + alltt[188] = tt_Dynamic("Use this radio button if you don't want members of this rank to be able to apply for presidency"); + alltt[189] = tt_Dynamic("Use this radio button if you want members of this rank to be able to accept or reject new joining requests"); + alltt[190] = tt_Dynamic("Use this radio button if you don't want members of this rank to be able to accept or reject new joining requests"); + alltt[191] = tt_Dynamic("Use this radio button if you want members of this rank to be forum administrators"); + alltt[192] = tt_Dynamic("Use this radio button if you don't want members of this rank to be forum administrators"); + alltt[193] = tt_Dynamic("Click here to accept the changes you've made to that rank"); + alltt[194] = tt_Dynamic("Click here to cancel the changes you've made to that rank and go back to the previous page"); + alltt[195] = tt_Dynamic("Use this radio button if you don't want members of this rank to be able to kick any member out of the alliance"); + alltt[196] = tt_Dynamic("Use this radio button if you want members of this rank to be able to kick any member out of the alliance"); + alltt[197] = tt_Dynamic("Use this radio button if you want members of this rank to be able to kick only members of a specific rank out of the alliance"); + alltt[198] = tt_Dynamic("Use this radio button if you don't want members of this rank to be able to change the rank of any other member of the alliance"); + alltt[199] = tt_Dynamic("Use this radio button if you want members of this rank to be able to change the rank of any other member of the alliance"); + alltt[200] = tt_Dynamic("Use this radio button if you want members of this rank to be able tochange the rank of other members of the alliance only if they have some specific rank"); + alltt[201] = tt_Dynamic("Use this radio button if you don't want members of this rank to have any access to this forum"); + alltt[202] = tt_Dynamic("Use this radio button if you want members of this rank to have access to this forum as normal users"); + alltt[203] = tt_Dynamic("Use this radio button if you want members of this rank to have access to this forum as moderators"); + alltt[204] = tt_Dynamic("Use this drop down list to select to which rank players of the rank you are about to delete are to be demoted to"); + alltt[205] = tt_Dynamic("Click here to confirm the deletion of this rank and the demotion of the players of this rank to the selected rank"); + alltt[206] = tt_Dynamic("Click here to cancel the deletion of this rank and go back to the previous page"); + alltt[210] = tt_Dynamic("Click here to go to this section of the alliance page"); + alltt[220] = tt_Dynamic("Check this checkbox to select this rang and provide the corresponding rights over the members of this rank to the members of rank you are editing"); +} + + + +//-------------------------------------------------- +// PAGE WHEN PLAYER IS NOT A MEMBER OF AN ALLIANCE +//-------------------------------------------------- + +function drawPageNoAlliance() +{ + var jd, cd1, cd2, ai; + var str = '
    '; + + // Build join/create pannel + str += '

    Join an alliance

    '; + if (plAlliance == "") + { + str += ''; + str += ''; + str += '
     Alliance tag:'; + str += '
    '; + str += ''; + str += '
    '; + + str += '

     

    '; + + str += '

    Create a new alliance

    '; + str += ''; + str += ''; + str += ''; + str += '
     '; + str += 'Alliance tag:'; + str += ' 
    Alliance name:'; + str += ''; + str += '
    '; + str += ' 
    '; + } + else + { + str += '

    Request sent to ' + alName + ' [' + alTag + '] '; + str += '

    '; + } + + // Build alliance info pannel + str += '
    '; + str += drawAllianceInfoPannel(); + str += 'Help
    '; + + var i, e, v = new Array(); + var a = new Array('aljoin', 'actag', 'acname', 'alinfo'); + for (i=0;i'; + str += ''; + str += ''; + str += ' '; + var i, a = new Array('tag', 'name', 'ldname', 'rank', 'points', 'nplanets', 'coords'); + for (i=0;i  '; + str += '  '; + str += ' '; + str += ''; + return str; +} + +function drawPubAllianceInfo() +{ + if (alPubInfo && typeof alPubInfo == 'object') + { + document.getElementById('aihdtag').innerHTML = 'Alliance tag:'; + document.getElementById('aihdname').innerHTML = 'Alliance name:'; + document.getElementById('aihdldname').innerHTML = 'Leader:'; + document.getElementById('aihdrank').innerHTML = 'Rank:'; + document.getElementById('aihdpoints').innerHTML = 'Points:'; + document.getElementById('aihdnplanets').innerHTML = 'Planets:'; + document.getElementById('aihdcoords').innerHTML = 'Avg. coordinates:'; + document.getElementById('aidttag').innerHTML = '[' + alPubInfo.tag + ']'; + document.getElementById('aidtname').innerHTML = alPubInfo.name; + document.getElementById('aidtldname').innerHTML = (alPubInfo.leader == "" ? 'NONE!' : alPubInfo.leader); + document.getElementById('aidtrank').innerHTML = '#' + formatNumber(alPubInfo.rank); + document.getElementById('aidtpoints').innerHTML = formatNumber(alPubInfo.points); + document.getElementById('aidtnplanets').innerHTML = formatNumber(alPubInfo.nplanets); + if (alPubInfo.nplanets > 0) + document.getElementById('aidtcoords').innerHTML = '(' + alPubInfo.cx + ',' + alPubInfo.cy + ')'; + else + document.getElementById('aidtcoords').innerHTML = 'N/A'; + if (alPubInfo.victory != '') { + document.getElementById('aihdvict').innerHTML = 'Victory:'; + document.getElementById('aidtvict').innerHTML = alPubInfo.victory + '%'; + } + } + else + { + var i, a = new Array('tag', 'name', 'ldname', 'rank', 'points', 'nplanets', 'coords'); + for (i=0;i '; + str += 'You are '; + if (alPrivileges[8] == "1") { + str += 'the leader'; + } else { + str += 'a member'; + } + str += ' of [' + myPubInfo.tag + '].'; + if (alPrivileges[8] == 1 && successor != "") { + str += ''; + } + str += ' '; + str += 'Alliance tag:[' + myPubInfo.tag + ']'; + str += 'Alliance name:' + myPubInfo.name + ''; + // FIXME: message link if not leader + str += 'Leader:NONE!' : ('>' + myPubInfo.leader)) + + ''; + if (successor != "") { + str += 'Successor:' + successor + ''; + } + str += 'Government:' + (isDemocracy ? 'democratic' : 'dictatorial') + + 'Tech. trading:'; + if (techTrade == 'N') { + str += 'disabled'; + } else { + str += 'enabled' + + (techTrade == 'R' ? '' : ' (no requests)'); + } + str += ''; + str += 'Rank:#'+formatNumber(myPubInfo.rank)+''; + str += 'Points:'+formatNumber(myPubInfo.points)+''; + str += 'Planets:' + formatNumber(myPubInfo.nplanets) + ''; + str += 'Avg. coordinates:'; + if (myPubInfo.nplanets > 0) + str += '(' + myPubInfo.cx + ',' + myPubInfo.cy + ')'; + else + str += 'N/A'; + str += ''; + + if (myPubInfo.victory != '') { + str += 'Victory:' + myPubInfo.victory + '%'; + } + + if (!lockedAlliances) { + str += ''; + } + str += ''; + + // Alliance settings + if (alPrivileges[8] == "1") { + if (typeof asDemocracy != "boolean") { + asDemocracy = isDemocracy; + asSuccessor = successor; + asTechTrade = techTrade; + } + str += '

    General settings

    '; + str += ''; + if (!lockedAlliances) { + str += ''; + } + str += '' + + '' + + '' + + '
     Government:' + + '
     
    Successor:
     
     
    '; + } + + // Build alliance info pannel + str += drawAllianceInfoPannel(); + str += ''; + + var e = document.getElementById('alinfo'), es; + if (e) + es = e.value; + else + es = ""; + + document.getElementById('alpmain').innerHTML = str; + drawPubAllianceInfo(); + + if (alPrivileges[8] == "1") + { + document.getElementById('lassucc').value = asSuccessor; + updateASControls(); + } + document.getElementById('alinfo').value = es; + + asFocus(); +} + +function drawASButtons() +{ + var str = ' '; + str += ''; + document.getElementById('lasbut').innerHTML = str; +} + +function alertASettings(ei) { + var str = 'Error\n'; + switch (ei) { + case 0: str += "You are no longer the leader of this alliance."; break; + case 1: str += 'The player you selected to be your successor could\nnot be found.'; break; + case 2: str += 'You can\'t name yourself as your own successor.'; break; + case 3: str += 'The player you selected to be your successor is not\na member of the alliance.'; break; + case 4: str += "You can't leave the alliance in this game."; break; + case 200: str += 'You are not allowed to do that while in vacation mode.'; break; + default: str += 'An unknown error has occured.'; break; + } + alert(str); +} + +function confirmLSD() +{ + return confirm( + 'You are about to step down from the head of the\n[' + alTag + + '] alliance.\nThis will make ' + successor + ', your successor, the new leader.\n' + + 'Please confirm you really want to do this.' + ); +} + +function confirmLeave(lt) +{ + var str = 'You are about to leave the [' + alTag + '] alliance.\n'; + switch (lt) + { + case 1: str += 'The successor you designated, ' + successor + ', will take your place\nas the head of the alliance.\n'; break; + case 2: str += 'The alliance will be become leader-less until someone is\nelected to replace you at the head of the alliance.\n'; break; + case 3: str += 'The alliance will be become a leader-less democracy until\nsomeone is elected to replace you at the head of the alliance.\n'; break; + } + str += 'Please confirm.'; + return confirm(str); +} + + +//-------------------------------------------------- +// LEADER ELECTION +//-------------------------------------------------- + +function drawElectionLayout() +{ + var str = '

    Alliance Leader Election

    '; + str += ''; + str += ''; + str += ''; + str += '

    Candidates

     

    Actions

     
    '; + document.getElementById('alpmain').innerHTML = str; +} + +function drawElectionButtons() +{ + var str = '

    '; + if (leCanSel||leCanTake||leCanApply||leCanCancel) + { + if (leCanTake) + str += '
    '; + if (leCanApply) + str += '
    '; + else if (leCanCancel) + str += '
    '; + if (leCanSel && (leNewSel != leSelected)) + str += ''; + } + else + { + str += 'There is nothing you can do here, since '; + if (alPrivileges[8] == 1) + str += 'you are the alliance\'s leader.'; + else + str += 'you don\'t have the right to vote or to apply for presidency.'; + } + str += '

    '; + document.getElementById('leaclist').innerHTML = str; +} + + +function drawCandidateList() +{ + var str; + + if (leCandidates.length > 0) + { + str = ''; + + // Headers + if (leCanSel) + str += ''; + str += ''; + + if (leCanSel) + { + str += ''; + str += ''; + str += ''; + } + + str += '
     ' : '') + 'Name'; + if (leSort == 0) + { + str += '' + (leSortDir ? '; + } + str += '' : '') + 'Votes'; + if (leSort == 1) + { + str += '' + (leSortDir ? '; + } + str += '
    ' + leCandidates[i].nVotes + '
    '; + } + else + str = '

    Noone has applied for presidency yet.

    '; + document.getElementById('lecdlist').innerHTML = str; +} + +function alertElection(ei) { + var str = 'Error\n'; + switch (ei) { + case 0: str += 'You are no longer a member of the alliance.'; break; + case 1: str += 'You no longer have the authorization to do that.'; break; + case 2: str += "The player you voted for is no longer a candidate."; break + case 3: str += "You are already a candidate for presidency."; break + case 4: str += "You aren\'t a candidate for presidency anymore."; break + case 5: str += "You missed the occasion to seize control of the alliance."; break; + case 200: str += 'You are not allowed to use this page while in vacation mode.'; break; + default: str += 'An unknown error has occured.'; break; + } + alert(str); +} + + +//-------------------------------------------------- +// LISTINGS PAGE +//-------------------------------------------------- + + +function drawPlanetList() +{ + var i, str, n, m; + + // Header + str = '

    Alliance planets

    '; + + // List header with page selector and search bar + str += ''; + str += ''; + + // Contents + for (i = firstMember; i < firstMember + mCount; i ++) { + var idx = lsMIndices[i]; + str += ''; + if (canSelect) { + str += ''; + } + str += ''; + } + + str += '
    Display planets / page'; + str += '
    '; + str += 'Search for
    '; + if ( ( kickRanks.indexOf('#' + lsMembers[idx].rank + '#') != -1 + || changeRanks.indexOf('#' + lsMembers[idx].rank + '#') != -1 + ) && !lsMembers[idx].current ) { + str += ''; + } + str += ' '; + str += ''; + if (!lsMembers[idx].current) { + str += ''; + } + str += lsMembers[idx].name + (lsMembers[idx].current ? '' : '') + '' + + lastOnline(lsMembers[idx]) + '' + + vacationMode[lsMembers[idx].onVacation ? 1 : 0] + '
    '; + return str; +} + +function lastOnline(member) { + if (member.lastOnline) { + var str = lastOnlinePrefix; + var lo, loUnit; + if (member.lastOnline > 60) { + lo = (member.lastOnline - (member.lastOnline % 60)) / 60; + loUnit = 1; + } else { + lo = member.lastOnline; + loUnit = 0; + } + str += formatNumber(lo.toString()) + ' ' + lastOnlineUnits[loUnit * 2 + (lo > 1 ? 1 : 0)] + + lastOnlineSuffix; + return str; + } + return onlineText; +} + + +function updateMListPage() { + if (!alCheckPriv[2]()) { + switchToMainPage(); + return; + } + hasFocus = 0; + getPlayerList(); +} + + + +// Planet list + +function Planet(id, owner, msOwner, name, cx, cy, orbit, fact, tur, ua) { + this.id = id; + this.owner = owner; + this.msOwner = msOwner; + this.name = name; + this.coords = '(' + cx + ',' + cy + ',' + orbit + ')'; + this.factories = fact; + this.turrets = tur; + this.underAttack = ua; +} + + +function switchToPListPage() { + clearUpdate(); + if (!alCheckPriv[3]()) { + switchToMainPage(); + return; + } + + amPage = 'PList'; + drawAllianceMenu(); + drawLoadingText(); + getPlanetList(); +} + + +function getPlanetList() { + if (amPage != 'PList') { + return; + } + + var ids = new Array(), i; + for (i = 0; i < lsPlanets.length; i ++) { + ids.push(lsPlanets[i].id); + } + x_getPlanetList(ids.join('#'), planetListReceived); +} + +function planetListReceived(data) { + if (amPage != 'PList') { + return; + } + + if (data == "") { + lsPlanets = new Array(); + } else { + parsePlanetList(data); + } + puTimer = setTimeout('updatePListPage()', 180000); + preparePlanetList(); + drawPlanetList(); +} + +function updatePListPage() { + if (!alCheckPriv[3]()) { + switchToMainPage(); + return; + } + hasFocus = 0; + getPlanetList(); +} + +function removePlanet(id) { + var k; + for (k=0;k 0) + { + var s = l.shift(); + if (st == 0) + { + ca = s.split('#'); + ca[4] = parseInt(ca[4], 10); + ca[5] = parseInt(ca[5], 10); + on = ''; + att = new Array(); + def = new Array(); + st = 1; + } + else if (st == 1) + { + pn = s; + if (ca[1] != "") + st = 2; + else if (ca[4] > 0) + st = 3; + else if (ca[5] > 0) + st = 4; + else + { + lsAttacks.push(new MilitarySituation(ca[0],pn,ca[6],'','',ca[3],ca[2],att,def)); + st = 0; + } + } + else if (st == 2) + { + on = s; + if (ca[4] > 0) + st = 3; + else if (ca[5] > 0) + st = 4; + else + { + lsAttacks.push(new MilitarySituation(ca[0],pn,ca[6],ca[1],on,ca[3],ca[2],att,def)); + st = 0; + } + } + else if (st == 3) + { + def.push(s); + ca[4] --; + if (ca[4] == 0) + { + if (ca[5] > 0) + st = 4; + else + { + lsAttacks.push(new MilitarySituation(ca[0],pn,ca[6],ca[1],on,ca[3],ca[2],att,def)); + st = 0; + } + } + } + else if (st == 4) + { + att.push(s); + ca[5] --; + if (ca[5] == 0) + { + lsAttacks.push(new MilitarySituation(ca[0],pn,ca[6],ca[1],on,ca[3],ca[2],att,def)); + st = 0; + } + } + } +} + +function prepareMilitaryList() { + var i; + lsAIndices = new Array(); + if (lsASearchText == "") { + for (i=0;i 1)) + return; + if (puTimer && amPage == "Pending") + { + clearUpdate(); + var ids = new Array(), i; + for (i=0;i 1)) + return; + if (puTimer && amPage == "Pending") + { + clearUpdate(); + var ids = new Array(), i; + for (i=0;i= 30) { + alertMaximumFCount(); + forumEditCancel(); + } else if (!faEditing.id) { + if (faNewPos != -1 && !forumById(faNewPos)) + faNewPos = -1; + updateFPosSelector(); + } else if (faEditing.id) { + var f = forumById(faEditing.id); + if (!f) { + alertForumDeleted(); + forumEditCancel(); + } else if (faOriginal.name != f.name || faOriginal.userPost != f.userPost || faOriginal.description != f.description) { + alertForumChanged(); + updateFEditor(); + } else { + updateFEditor(); + } + } + 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)')); +} + +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')); + } + if (faEditing.id) + faOriACL = makeForumACLString(); + drawFAccessManager(); + updateFEditor(); +} + +function setFAccessLevel(level) +{ + var i,cc=0; + for (i=0;i 0) + { + drawFAccessManager(); + updateFEditor(); + } +} + +function updateFEditor() +{ + var ok, i; + ok = (faEditing.name.length >= 4); + if (faEditing.id) + ok = ok && ( + faEditing.name != faOriginal.name || faEditing.description != faOriginal.description + || faEditing.userPost != faOriginal.userPost || faOriACL != makeForumACLString() + ); + document.getElementById('feok').disabled = !ok; +} + +function makeForumACLString() +{ + var a = new Array(), i; + for (i=0;ib.name.toLowerCase()?1:-1')); + + var rr = new Array(); + for (i = 0; i < nr; i ++) { + var r, n, i; + a = l.shift().split('#'); + r = new Rank(a[0], l.shift(), a[1], a[2], a[3] == "1", a[4] == "1", a[5] == "1", + a[6] == "1", a[7] == "1", a[8] == "1", a[9] == 1, a[10] == "1", a[11]); + if (r.canKick) { + if (l[0] != "") { + r.ranksKick = l.shift().split('#'); + } else { + l.shift(); + } + } + if (r.canSet) { + if (l[0] != "") { + r.ranksChange = l.shift().split('#'); + } else { + l.shift(); + } + } + if (l[0] == "") { + l.shift(); + } else { + r.fRead = l.shift().split('#'); + } + if (l[0] == "") { + l.shift(); + } else { + r.fMod = l.shift().split('#'); + } + n = rankById(r.id); + if (n) { + r.open = n.open; + } + rr.push(r); + } + rr.sort(new Function('a','b','return a.name.toLowerCase()>b.name.toLowerCase()?1:-1')); + raRanks = rr; + + if (!(raEditing || raDeleting)) { + drawRanksList(); + } + + puTimer = setTimeout('x_getRanks(ranksReceived)', 180000); +} + +function rankById(id) +{ + var i; + for (i=0;i '; + str += (raRanks[i].name == '-' ? (''+sm+'') : raRanks[i].name.replace(/&/g, '&').replace(//g, '>')) + ''; + if (n%3 == 2) + str += ''; + n++; + } + if (n%3 != 0) + { + while (n%3 != 0) + { + str += ' '; + n++; + } + str += ''; + } + str += ''; + document.getElementById('re'+lstid+'list').innerHTML = str; +} + +function updateREditor() { + var ok; + if (!raEditing.id || (raEditing.id && raOriginal.name != "-")) { + ok = (raEditing.name.length > 3); + } else { + ok = true; + } + if (raEditing.id) { + raEditing.ranksChange.sort(new Function('a','b','return a-b')); + raEditing.ranksKick.sort(new Function('a','b','return a-b')); + raEditing.fRead.sort(new Function('a','b','return a-b')); + raEditing.fMod.sort(new Function('a','b','return a-b')); + ok = ok && ( + (raEditing.name != raOriginal.name) || (raEditing.list != raOriginal.list) + || (raEditing.attacks != raOriginal.attacks) || (raEditing.canSet != raOriginal.canSet) + || (raEditing.canKick != raOriginal.canKick) || (raEditing.accept != raOriginal.accept) + || (raEditing.forumAdmin != raOriginal.forumAdmin) || (raEditing.canVote != raOriginal.canVote) + || (raEditing.diplContact != raOriginal.diplContact) + || (raEditing.candidate != raOriginal.candidate) + || (raEditing.techTrade != raOriginal.techTrade) + || (raEditing.ranksChange.join('#') != raOriginal.ranksChange.join('#')) + || (raEditing.ranksKick.join('#') != raOriginal.ranksKick.join('#')) + || (raEditing.fRead.join('#') != raOriginal.fRead.join('#')) + || (raEditing.fMod.join('#') != raOriginal.fMod.join('#')) + ); + } + document.getElementById('reok').disabled = !ok; +} + +function rankEditCancel() { + raEditing = false; + drawRanksList(); +} + +function rankEditOk() { + var i, prs, rkick, rchange, fread, fmod; + + if (raEditing.canSet == 1 && raEditing.ranksChange.length == 0) + raEditing.canSet = 0; + if (raEditing.canKick == 1 && raEditing.ranksKick.length == 0) + raEditing.canKick = 0; + + prs = raEditing.list + '#' + (raEditing.attacks ? '1' : '0') + '#'; + prs += (raEditing.canSet != 0 ? '1' : '0') + '#' + (raEditing.canKick != 0 ? '1' : '0') + '#'; + prs += (raEditing.accept ? '1' : '0') + '#' + (raEditing.forumAdmin ? '1' : '0') + '#'; + prs += (raEditing.diplContact ? '1' : '0') + '#' + (raEditing.canVote ? '1' : '0') + '#'; + prs += (raEditing.candidate ? '1' : '0') + '#' + raEditing.techTrade; + + if (raEditing.canSet == 1) + rchange = raEditing.ranksChange.join('#'); + else + rchange = ""; + + if (raEditing.canKick == 1) + rkick = raEditing.ranksKick.join('#'); + else + rkick = ""; + + if (raEditing.forumAdmin) + fread = fmod = ""; + else + { + fread = raEditing.fRead.join('#'); + fmod = raEditing.fMod.join('#'); + } + + if (raEditing.id) { + x_changeRank(raEditing.id, raEditing.name, prs, rkick, rchange, fread, fmod, rankEdited); + } else { + x_newRank(raEditing.name, prs, rkick, rchange, fread, fmod, rankEdited); + } +} + +function rankEdited(data) { + if (data.indexOf('ERR#') == 0) { + alertRank(parseInt(data.replace(/ERR#/, ''), 10)); + } else { + raEditing = false; + clearUpdate(); + ranksReceived(data); + } +} + +function rankSwitchKick(idx) +{ + var id = raRanks[idx].id; + var ks = '!' + raEditing.ranksKick.join('!') + '!'; + if (ks.indexOf('!' + id + '!') == -1) + raEditing.ranksKick.push(id); + else + { + var i; + for (i=0;i 0) + { + raDeleting = raRanks[idx]; + raDemote = raRanks[i].id; + showRankDeletePage(); + } +} + +function rankDeleteOk() { + x_delRank(raDeleting.id, raDemote, rankDeleted); +} + +function rankDeleteCancel() +{ + raDeleting = false; + drawRanksList(); +} + +function rankDeleted(data) { + raDeleting = false; + clearUpdate(); + if (data.indexOf('ERR#') == 0) { + alertRank(parseInt((data.split('#'))[1], 10)); + } else { + ranksReceived(data); + } +} + + +//-------------------------------------------------- +// LIST SORTING +//-------------------------------------------------- + +function pls_Coord_asc(a,b) +{ + var ra = lsPlanets[a].coords.toLowerCase(); var rb = lsPlanets[b].coords.toLowerCase(); + return (ra < rb) ? -1 : 1; +} + +function pls_Coord_desc(a,b) +{ + var ra = lsPlanets[a].coords.toLowerCase(); var rb = lsPlanets[b].coords.toLowerCase(); + return (ra < rb) ? 1 : -1; +} + +function pls_Planet_asc(a,b) +{ + var ra = lsPlanets[a].name.toLowerCase(); var rb = lsPlanets[b].name.toLowerCase(); + return (ra < rb) ? -1 : 1; +} + +function pls_Planet_desc(a,b) +{ + var ra = lsPlanets[a].name.toLowerCase(); var rb = lsPlanets[b].name.toLowerCase(); + return (ra < rb) ? 1 : -1; +} + +function pls_Owner_asc(a,b) +{ + var ra = lsPlanets[a].owner.toLowerCase(); var rb = lsPlanets[b].owner.toLowerCase(); + return (ra < rb) ? -1 : ((ra > rb) ? 1 : pls_Planet_asc(a,b)); +} + +function pls_Owner_desc(a,b) +{ + var ra = lsPlanets[a].owner.toLowerCase(); var rb = lsPlanets[b].owner.toLowerCase(); + return (ra < rb) ? 1 : ((ra > rb) ? -1 : pls_Planet_desc(a,b)); +} + +function pls_Fact_asc(a,b) +{ + var ra = parseInt(lsPlanets[a].factories, 10); var rb = parseInt(lsPlanets[b].factories, 10); + return (ra < rb) ? -1 : ((ra > rb) ? 1 : pls_Planet_asc(a,b)); +} + +function pls_Fact_desc(a,b) +{ + var ra = parseInt(lsPlanets[a].factories, 10); var rb = parseInt(lsPlanets[b].factories, 10); + return (ra < rb) ? 1 : ((ra > rb) ? -1 : pls_Planet_desc(a,b)); +} + +function pls_Trt_asc(a,b) +{ + var ra = parseInt(lsPlanets[a].turrets, 10); var rb = parseInt(lsPlanets[b].turrets, 10); + return (ra < rb) ? -1 : ((ra > rb) ? 1 : pls_Planet_asc(a,b)); +} + +function pls_Trt_desc(a,b) +{ + var ra = parseInt(lsPlanets[a].turrets, 10); var rb = parseInt(lsPlanets[b].turrets, 10); + return (ra < rb) ? 1 : ((ra > rb) ? -1 : pls_Planet_desc(a,b)); +} + +function mls_Name_asc(a,b) { + var ra = lsMembers[a].name.toLowerCase(); var rb = lsMembers[b].name.toLowerCase(); + return (ra < rb) ? -1 : 1; +} + +function mls_Name_desc(a,b) { + var ra = lsMembers[a].name.toLowerCase(); var rb = lsMembers[b].name.toLowerCase(); + return (ra > rb) ? -1 : 1; +} + +function mls_LastOnline_asc(a,b) { + var ra = lsMembers[a].lastOnline; var rb = lsMembers[b].lastOnline; + return (ra < rb) ? 1 : ((ra > rb) ? -1 : mls_Name_desc(a,b)); +} + +function mls_LastOnline_desc(a,b) { + var ra = lsMembers[a].lastOnline; var rb = lsMembers[b].lastOnline; + return (ra < rb) ? -1 : ((ra > rb) ? 1 : mls_Name_asc(a,b)); +} + +function mls_Vacation_asc(a,b) { + var ra = lsMembers[a].onVacation ? 1 : 0; var rb = lsMembers[b].onVacation ? 1 : 0; + return (ra == rb ? mls_Name_asc(a,b) : (ra - rb)); +} + +function mls_Vacation_desc(a,b) { + var ra = lsMembers[a].onVacation ? 1 : 0; var rb = lsMembers[b].onVacation ? 1 : 0; + return (ra == rb ? mls_Name_desc(a,b) : (rb - ra)); +} + +function mls_Rank_asc(a,b) { + var ra = lsMembers[a].getRawRank(), rb = lsMembers[b].getRawRank(); + return (ra == rb) ? mls_Name_asc(a, b) + : ((ra == '+' || rb == '-') ? -1 + : ((ra == '-' || rb == '+') ? 1 + : ((getRank(lsMembers[a]).toLowerCase() < getRank(lsMembers[b]).toLowerCase()) ? -1 : 1))); +} + +function mls_Rank_desc(a,b) { + var ra = lsMembers[a].getRawRank(), rb = lsMembers[b].getRawRank(); + return (ra == rb) ? mls_Name_asc(a, b) + : ((ra == '+' || rb == '-') ? 1 + : ((ra == '-' || rb == '+') ? -1 + : ((getRank(lsMembers[a]).toLowerCase() < getRank(lsMembers[b]).toLowerCase()) ? 1 : -1))); +} + +function als_Coords_asc(a,b) +{ + var ra = lsAttacks[a].coords.toLowerCase(); var rb = lsAttacks[b].coords.toLowerCase(); + return (ra < rb) ? -1 : 1; +} + +function als_Coords_desc(a,b) +{ + var ra = lsAttacks[a].coords.toLowerCase(); var rb = lsAttacks[b].coords.toLowerCase(); + return (ra < rb) ? 1 : -1; +} + +function als_Name_asc(a,b) +{ + var ra = lsAttacks[a].planetName.toLowerCase(); var rb = lsAttacks[b].planetName.toLowerCase(); + return (ra < rb) ? -1 : 1; +} + +function als_Name_desc(a,b) +{ + var ra = lsAttacks[a].planetName.toLowerCase(); var rb = lsAttacks[b].planetName.toLowerCase(); + return (ra < rb) ? 1 : -1; +} + +function als_Owner_asc(a,b) +{ + var ra = lsAttacks[a].ownerName.toLowerCase(); var rb = lsAttacks[b].ownerName.toLowerCase(); + return (ra < rb) ? -1 : ((ra > rb) ? 1 : als_Planet_asc(a,b)); +} + +function als_Owner_desc(a,b) +{ + var ra = lsAttacks[a].ownerName.toLowerCase(); var rb = lsAttacks[b].ownerName.toLowerCase(); + return (ra < rb) ? 1 : ((ra > rb) ? -1 : als_Planet_desc(a,b)); +} + +function als_Def_asc(a,b) +{ + var ra = parseInt(lsAttacks[a].defPower, 10); var rb = parseInt(lsAttacks[b].defPower, 10); + return (ra < rb) ? -1 : ((ra > rb) ? 1 : als_Name_asc(a,b)); +} + +function als_Def_desc(a,b) +{ + var ra = parseInt(lsAttacks[a].defPower, 10); var rb = parseInt(lsAttacks[b].defPower, 10); + return (ra < rb) ? 1 : ((ra > rb) ? -1 : als_Name_desc(a,b)); +} + +function als_Att_asc(a,b) +{ + var ra = parseInt(lsAttacks[a].attPower, 10); var rb = parseInt(lsAttacks[b].attPower, 10); + return (ra < rb) ? -1 : ((ra > rb) ? 1 : als_Name_asc(a,b)); +} + +function als_Att_desc(a,b) +{ + var ra = parseInt(lsAttacks[a].attPower, 10); var rb = parseInt(lsAttacks[b].attPower, 10); + return (ra < rb) ? 1 : ((ra > rb) ? -1 : als_Name_desc(a,b)); +} + +function lec_Name_asc(a,b) +{ + var ra = a.name.toLowerCase(), rb = b.name.toLowerCase(); + return (ra > rb) ? 1 : -1; +} + +function lec_Name_desc(a,b) +{ + var ra = a.name.toLowerCase(), rb = b.name.toLowerCase(); + return (ra < rb) ? 1 : -1; +} + +function lec_Votes_asc(a,b) +{ + var ra = parseInt(a.nVotes,10), rb = parseInt(b.nVotes,10); + return (ra > rb) ? 1 : ((ra < rb) ? -1 : lec_Name_asc(a,b)); +} + +function lec_Votes_desc(a,b) +{ + var ra = parseInt(b.nVotes,10), rb = parseInt(a.nVotes,10); + return (ra > rb) ? 1 : ((ra < rb) ? -1 : lec_Name_desc(a,b)); +} diff --git a/site/static/beta5/js/pg_allies-en.js b/site/static/beta5/js/pg_allies-en.js new file mode 100644 index 0000000..a163cd6 --- /dev/null +++ b/site/static/beta5/js/pg_allies-en.js @@ -0,0 +1,129 @@ +var listTitle = 'Allies List'; +var emptyList = 'You are not a trusting person, it would seem.'; +var notTrusted = 'Apparently, no one trusts you.'; +var addBanText = 'Blacklist a player'; +var addBanLabel = 'Name of the player to ban:'; +var addBanButton = 'Ban player'; +var blackListTitle = 'Blacklist contents'; +var emptyBlackList = 'Everyone can add you to their Trusted Allies lists.'; +var raHeaders = ['Player', 'Trust Level']; + +function makeAlliesTooltips() +{ + tratt = new Array(); + if (ttDelay == 0) + { + var i; + for (i=0;i<30;i++) + tratt[i] = ""; + return; + } + tratt[0] = tt_Dynamic("Use this text field to type in the name of the new allie to add to your trusted allies list"); + tratt[1] = tt_Dynamic("Click here to add this player to your trusted allies list"); + tratt[10] = tt_Dynamic("Click here to remove the selected player(s) from your trusted allies list"); + tratt[11] = tt_Dynamic("Click here to move the selected player up in the list"); + tratt[12] = tt_Dynamic("Click here to move the selected player down in the list"); + tratt[15] = tt_Dynamic("Click here to remove yourself to this player's trusted allies list"); + tratt[16] = tt_Dynamic("Click here to remove yourself to this player's trusted allies list and prevent him from adding you again"); + tratt[20] = tt_Dynamic("Use this checkbox to select the corresponding trusted ally"); + tratt[21] = tt_Dynamic("Use this checkbox to select this player"); +} + + +function drawAddAlly(value) +{ + var str = '

    Add Ally

    '; + str += '

    Name of the new ally:

    '; + return str; +} + +function drawButtons(cr,cmu,cmd) +{ + var str = ''; + if (cr) + str += ''; + if (cmu) + str += ''; + if (cmd) + str += ''; + if (str == "") + str = " "; + return str; +} + +function drawRButtons() +{ + var str; + str = '' + + ''; + return str; +} + +function drawBLButtons() +{ + var str; + str = ''; + return str; +} + +function addAllyConfirm(name) +{ + var str = 'You are about to add the player named ' + name + ' to your trusted allies list.\n'; + str += 'It will give this player partial control over your fleets when you are not online.\n'; + str += 'Please confirm.'; + return confirm(str); +} + +function confirmRemoveRAllies(ids) +{ + var i, n = new Array(); + var str = 'You are about to remove yourself from the folowing player' + (ids.length > 1 ? "s' " : "'s") + + " Trusted Allies list" + (ids.length > 1 ? "s" : "") + ':\n'; + for (i=0;i 1 ? "these players'" : "this player's") + + ' fleets.\nPlease confirm.'; + return confirm(str); +} + +function confirmBanRAllies(ids) +{ + var i, n = new Array(); + var str = 'You are about to remove yourself from the folowing player' + (ids.length > 1 ? "s' " : "'s") + + " Trusted Allies list" + (ids.length > 1 ? "s" : "") + ' and add ' + (ids.length > 1 ? 'them' : 'him') + + ' to your T.A. blacklist:\n'; + for (i=0;i 1 ? "these players'" : "this player's") + + ' fleets and ' + (ids.length > 1 ? 'they' : 'he') + ' won\'t be able to add you to ' + (ids.length > 1 ? 'their' : 'his') + + ' Trusted Allies list again.\nPlease confirm.'; + return confirm(str); +} + +function commandAlert(ei) +{ + var str = 'Error\n'; + switch (ei) + { + case 0: str += 'Player not found.'; break; + case 1: str += 'Please specify the name of the player to add to the list.'; break; + case 2: str += 'Player not found.'; break; + case 3: str += 'You can\'t add yourself to your allies list.'; break; + case 4: str += 'This player is already in your allies list.'; break; + case 5: str += 'This player is already on 5 other players\' allies list.'; break; + case 6: str += 'This player is on your enemy list.'; break; + case 7: str += 'One of the selected players no longer trusts you anyway.'; break; + case 8: str += 'One of the selected players is already in your T.A. blacklist (this is a bug).'; break; + case 9: str += 'This player has blacklisted you and can\'t be set as a trusted ally anymore.'; break; + case 10: str += 'This player has already been removed from the blacklist.'; break; + case 11: str += 'Please type the name of the player to add to the blacklist.'; break; + case 12: str += 'This player is already in your blacklist.'; break; + case 13: str += 'You can\'t add yourself to your blacklist.'; break; + case 14: str += 'You already have 5 trusted allies.'; break; + case 200: str += 'You are not allowed to edit your trusted allies list while in vacation mode.'; break; + default: str += 'An unknown error has occured.'; break; + } + alert(str); +} diff --git a/site/static/beta5/js/pg_allies.js b/site/static/beta5/js/pg_allies.js new file mode 100644 index 0000000..822f316 --- /dev/null +++ b/site/static/beta5/js/pg_allies.js @@ -0,0 +1,408 @@ +var lsAllies = new Array(), idAllies = new Array(); +var lsRAllies = new Array(), idRAllies = new Array(); +var lsBans = new Array(), idBans = new Array(); + +var tratt; + +function TrustedAlly(id,name) +{ + this.id = id; + this.name = name; + this.selected = false; +} + +function ReverseAlly(id,level,name) +{ + this.id = id; + this.name = name; + this.level = parseInt(level,10); + this.selected = false; +} + +function BannedPlayer(id,name) +{ + this.id = id; + this.name = name; + this.selected = false; +} + +function parseData(data) +{ + var list = new Array(); + var byId = new Array(); + var l = data.split('\n'); + var na, nr, nb; + var i, a, t, id; + + a = l.shift().split('#'); + na = parseInt(a[0], 10); + nr = parseInt(a[1], 10); + nb = parseInt(a[2], 10); + + for (i=0;i 3) + x_getTrusted(listReceived); + else + listReceived(data); +} + +function listReceived(data) +{ + parseData(data); + displayPage(); + update = setTimeout('x_getTrusted(listReceived)', 600000); +} + + +function displayPage() +{ + var str = ""; + + var e = document.getElementById('newally'); + if (lsAllies.length < 5) + str += drawAddAlly(e ? e.value : ""); + str += drawAllyList(); + document.getElementById('allies').innerHTML = str; + document.getElementById('rallies').innerHTML = drawReverseList(); + document.getElementById('bans').innerHTML = drawBlackList(); + updateButtons(); updateRButtons(); updateBLButtons(); +} + + +function handleCommand(data) { + if (data.indexOf('ERR#') == 0) { + var ei = parseInt(data.replace(/ERR#/, ''), 10); + commandAlert(ei); + update = setTimeout('x_getTrusted(listReceived)', 600000); + } else { + var e = document.getElementById('newally'); + if (e) { + e.value = ''; + } + listReceived(data); + } +} + + + +function drawAllyList() +{ + var i, str = '

    ' + listTitle + '

    '; + if (lsAllies.length == 0) + return str + '

    ' + emptyList + '

    '; + + str += ''; + for (i=0;i '; + str += ''; + } + str += '
    ' + lsAllies[i].name + '
     
     
    '; + return str; +} + +function updateButtons() +{ + if (!document.getElementById('buttons')) + return; + + var i, cr = false, cmu = true, cmd = true; + for (i=0;i" + notTrusted + "

    "; + + var str = ''; + str += ''; + str += ''; + + var i; + lsRAllies.sort(new Function('a','b','return a.name.toLowerCase()>b.name.toLowerCase()?1:-1')); + for (i=0;i' + lsRAllies[i].name + '' + + ''; + } + str += '
      '+raHeaders[0]+'' + raHeaders[1] + '
    ' + (lsRAllies[i].level + 1) + '
     
     
    '; + + return str; +} + +function updateRButtons() +{ + if (!document.getElementById('rbuttons')) + return; + + var i; + for (i=0;i

    ' + addBanLabel + '

    ' + blackListTitle + '

    '; + if (lsBans.length == 0) + return str + '

    ' + emptyBlackList + '

    '; + + str += ''; + for (i=0;i '; + str += ''; + } + str += '
    ' + lsBans[i].name + '
     
      
    '; + return str; +} + +function updateBLButtons() +{ + if (!document.getElementById('blbuttons')) + return; + + var i; + for (i=0;i0) ? pname.substr(0, i) : ""; + var s2 = (i message' + (tot > 1 ? 's' : ''); + if (n == 0) + return str; + str += ' (' + formatNumber(n) + ' unread message'+(n>1?'s':'')+')'; + return str; +} + +function makeTopicsText(tot, n) +{ + if (tot == 0) + return "empty forum"; + var str = '' + formatNumber(tot) + ' topic' + (tot > 1 ? 's' : ''); + if (n == 0) + return str; + str += ' (' + formatNumber(n) + ' unread)'; + return str; +} diff --git a/site/static/beta5/js/pg_comms.js b/site/static/beta5/js/pg_comms.js new file mode 100644 index 0000000..b169131 --- /dev/null +++ b/site/static/beta5/js/pg_comms.js @@ -0,0 +1,144 @@ +var dFolders, cFolders; +var genForums, aForums; + +var comtt; + + +function Folder(id, tMsg, nMsg, name) +{ + this.id = id; + this.tMsg = tMsg; + this.nMsg = nMsg; + 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 initPage() { + commsDataReceived(document.getElementById('init-data').value); +} + + +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); + + // Default folders + dFolders = new Array(); + for (i=0;i<3;i++) + { + a = l.shift().split('#'); + dFolders.push(new Folder('', a[0], a[1], '')); + } + + // Custom folders + cFolders = new Array(); + 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('
    ') + '

    '; + } +} diff --git a/site/static/beta5/js/pg_diplomacy-en.js b/site/static/beta5/js/pg_diplomacy-en.js new file mode 100644 index 0000000..3c30a05 --- /dev/null +++ b/site/static/beta5/js/pg_diplomacy-en.js @@ -0,0 +1,157 @@ +function makeDiplomacyTooltips() +{ + diptt = new Array(); + if (ttDelay == 0) + { + var i; + for (i=0;i<5;i++) + diptt[i] = ""; + return; + } + diptt[0] = tt_Dynamic("Click here to go to the alliance page and create or join one"); + diptt[1] = tt_Dynamic("Click here to go to the alliance page and manage your joining request"); + diptt[2] = tt_Dynamic("Click here to go to alliance page and access your alliance specific information"); + diptt[3] = tt_Dynamic("Click here to go to the main page of this specific alliance forum"); + diptt[4] = tt_Dynamic("Click here to go to the scientific assistance page and manage scientific diplomatic exchange"); +} + +function drawNoRelations() +{ + document.getElementById('allies').innerHTML = 'You do not have enemies nor allies.'; +} + +function drawRelations(nenp, nena, nall, nrall) +{ + var str = ""; + + if (nall == 0) + str += "You have no allies."; + else + str += "You have " + nall + " trusted all" + (nall > 1 ? "ies" : "y") + "."; + str += '
    '; + + if (nrall == 0) + str += "Noone trusts you."; + else + str += "" + nrall + " player"+(nrall>1?"s trust":" trusts")+" you."; + str += '
    '; + + if (nenp + nena == 0) + str += "You have no enemies."; + else if (nenp == 0) + str += "Enemies: " + nena + " alliance" + (nena>1?"s":""); + else if (nena == 0) + str += "Enemies: " + nenp + " player" + (nenp>1?"s":""); + else + str += "Enemies: " + nenp + " player" + (nenp>1?"s":"") + " and " + nena + " alliance" + (nena>1?"s":"") + str += "
    "; + + document.getElementById('allies').innerHTML = str; +} + +function drawNoAlliance() +{ + document.getElementById('alliance').innerHTML = '

    You are not a member of any alliance.
    Alliance page

    '; +} + +function drawPending(npl,ax,ay,rk,pts,tag,name,ln) +{ + var str = '

    You are requesting to join this alliance:
    '; + str += 'Alliance tag: ['+tag+']
    Alliance name: ' + name + '
    '; + str += 'Leader: ' + ln + '
    '; + + if (npl == 0) + str += 'No controlled planets.'; + else if (npl == 1) + str += '1 planet at coordinates ('+ax+','+ay+')'; + else + str += ''+npl+' planets at average coordinates ('+ax+','+ay+')'; + str += '
    '; + + if (rk != "") + str += 'The alliance is ranked #'+rk+' with ' + formatNumber(pts) + ' points.
    '; + + str += '
    Alliance page

    '; + + document.getElementById('alliance').innerHTML = str; +} + +function drawAlliance(npl,ax,ay,rk,pts,il,tag,name,ln,prk,fl) +{ + var str = '

    Overview

    '; + + str += '

    Alliance tag: ['+tag+']
    Alliance name: ' + name + '
    '; + if (il != 1) + str += 'Leader: ' + ln + '
    '; + + if (npl == 0) + str += 'No controlled planets.'; + else if (npl == 1) + str += '1 planet at coordinates ('+ax+','+ay+')'; + else + str += ''+npl+' planets at average coordinates ('+ax+','+ay+')'; + str += '
    '; + + if (rk != "") + str += 'The alliance is ranked #'+rk+' with ' + formatNumber(pts) + ' points.
    '; + + str += 'You are '; + if (il == 1) + str += 'the leader'; + else + str += 'a member'; + str += ' of this alliance'; + if (il == 0 && prk != "-") + str += ' with the rank of ' + prk + ''; + + str += '.
    Alliance page

    '; + + if (fl.length > 0) + { + str += '

    Forums

    '; + var i; + for (i=0;i' + a.join('#') + ': '; + if (ntot>0) + { + str += '' + ntot + ' topic' + (ntot>1?"s":""); + if (nunr > 0) + str += ', ' + nunr + ' unread'; + } + else + str += 'empty forum'; + str += '
    '; + } + str += '

    '; + } + + document.getElementById('alliance').innerHTML = str; +} + + +function drawNoAssistance() +{ + document.getElementById('rsass').innerHTML = 'You cannot yet give or receive scientific assistance.'; +} + +function drawAssistance(pend,sto) +{ + var str = 'You have '; + if (pend == 0) + str += 'no pending assistance offers.'; + else if (pend == 1) + str += '1 pending offer.'; + else + str += ''+pend+' pending offers.'; + str += '
    '; + + if (sto == "") + str += 'You haven\'t sent any offer in the last 24 hours.'; + else + str += 'You have sent an offer to '+sto+' in the last 24 hours.'; + str += '
    Scientific Assistance page'; + document.getElementById('rsass').innerHTML = str; +} diff --git a/site/static/beta5/js/pg_diplomacy.js b/site/static/beta5/js/pg_diplomacy.js new file mode 100644 index 0000000..4b1de72 --- /dev/null +++ b/site/static/beta5/js/pg_diplomacy.js @@ -0,0 +1,62 @@ +var diptt; + +function initPage() +{ + var data = document.getElementById('dinit').innerHTML; + if (data.indexOf('\n') != -1) + drawDiplomacyPage(data); + else // IE sucks. + x_getInformation(drawDiplomacyPage); +} + +function drawDiplomacyPage(data) +{ + var l = data.split('\n'); + + // Alliance status + var a = l.shift().split('#'); + if (a[0] == 0) + drawNoAlliance(); + else if (a[0] == 1) + drawPending(a[1],a[2],a[3],a[4],a[5],l.shift(),l.shift(),l.shift()); + else + { + var i, fl = new Array(); + var tag = l.shift(), name = l.shift(); + var leader = (a[6] == 1) ? "" : l.shift(); + var rank = (a[6] == 1) ? "-" : l.shift(); + for (i=0;i per planet'; + else + str = 'N/A'; + document.getElementById('plapop').innerHTML = str; + + if (d[5] != 'N/A') + str = '' + formatNumber(d[5]) + ' per planet'; + else + str = 'N/A'; + document.getElementById('plafct').innerHTML = str; + + if (d[7] != 'N/A') + str = '' + formatNumber(d[7]) + ' per planet'; + else + str = 'N/A'; + document.getElementById('platrt').innerHTML = str; + + if (d[8] != '0') + { + str = '' + d[8] + ' planet'; + if (d[8] > 1) + str += 's'; + str += ' under attack!
    '; + } + else + str = ''; + document.getElementById('platt').innerHTML = str; +} + + +function displayResearchStatus(na, nb) +{ + var str; + if (na == 0 && nb == 0) + return; + str = '

    Status

    '; + if (na > 0) + str += 'Available technologies: ' + na + ''; + if (nb > 0) + str += (na > 0 ? '
    ' : '') + 'Foreseen breakthroughs: ' + nb + ''; + document.getElementById('rsst').innerHTML = str; +} diff --git a/site/static/beta5/js/pg_empire.js b/site/static/beta5/js/pg_empire.js new file mode 100644 index 0000000..4ac335f --- /dev/null +++ b/site/static/beta5/js/pg_empire.js @@ -0,0 +1,118 @@ +var emptt; + +function empire_write(data) +{ + var a, plo, flo, plst, i, mi, str, pid, pname; + a = data.split("\n"); + + // Planets overview + plo = a[0].split('#'); + document.getElementById('plcnt').innerHTML = plo[0]; + if (plo[1] != 'N/A') + { + str = '' + plo[1] + '%'; + } + else + str = plo[1]; + document.getElementById('plahap').innerHTML = str; + if (plo[9] != 'N/A') + { + str = '' + plo[9] + '%'; + } + else + str = plo[9]; + document.getElementById('placor').innerHTML = str; + document.getElementById('plpop').innerHTML = formatNumber(plo[2]); + document.getElementById('plfct').innerHTML = formatNumber(plo[4]); + document.getElementById('pltrt').innerHTML = formatNumber(plo[6]); + empire_planets(plo); + + // Planets list + plst = a[1].split('#'); + mi = plst.length / 2; + str = ''; + for (i=0;i' + pname + ''; + if (i < mi - 1) + { + if ((i-2)%3) + str += ' - '; + else + str += '
    '; + } + } + if (str == '') + str = 'Get a new planet'; + document.getElementById('pllst').innerHTML = str; + + // Fleets + flo = a[2].split('#'); + document.getElementById('fltot').innerHTML = formatNumber(flo[2]); + document.getElementById('flupk').innerHTML = formatNumber(flo[3]); + document.getElementById('flcnt').innerHTML = formatNumber(flo[0]); + document.getElementById('flbat').innerHTML = formatNumber(flo[1]); + document.getElementById('flhcnt').innerHTML = formatNumber(flo[4]); + document.getElementById('flhbat').innerHTML = formatNumber(flo[5]); + document.getElementById('flocnt').innerHTML = formatNumber(flo[6]); + document.getElementById('flobat').innerHTML = formatNumber(flo[7]); + document.getElementById('flomv').innerHTML = formatNumber(flo[8]); + document.getElementById('flowt').innerHTML = formatNumber(flo[9]); + document.getElementById('flgas').innerHTML = formatNumber(flo[10]); + document.getElementById('flfgt').innerHTML = formatNumber(flo[11]); + document.getElementById('flcru').innerHTML = formatNumber(flo[12]); + document.getElementById('flbcr').innerHTML = formatNumber(flo[13]); + tot = parseInt(flo[10], 10) + parseInt(flo[11], 10) + parseInt(flo[12], 10) + parseInt(flo[13], 10); + document.getElementById('flsht').innerHTML = formatNumber(tot.toString()); + + // Research + var rd = a[3].split('#'); + var rbPoints = rd[0], rbPercentage = new Array(); + for (i=0;i<3;i++) + rbPercentage[i] = parseInt(rd[i+1], 10); + var rbCatPoints = new Array(), s = 0; + for (i=0;i<3;i++) + { + rbCatPoints[i] = Math.floor(rbPercentage[i] * rbPoints / 100); + s += rbCatPoints[i]; + } + for (i=0;s 0) { + var a = l.shift().split('#'); + var tp = a.shift(), id = a.shift(); + if (tp == 0) + { + if (pl[id]) + t = pl[id]; + else + t = new Enemy(id, a.join('#')); + enPlayers[id] = t; + lsPlayers.push(t); + } + else + { + if (al[id]) + t = al[id]; + else + t = new Enemy(id, a.join('#')); + enAlliances[id] = t; + lsAlliances.push(t); + } + } +} + + +function initList() +{ + var data = document.getElementById('elinit').innerHTML; + if (data != "") + { + if (data.split('#').length > 3 && data.indexOf('\n') == -1) + { + // IE really sucks. + x_getEnemies(updatePage); + return; + } + parseData(data); + } + displayPage(); +} + + +function updatePage(data) { + if (data == "") { + enPlayers = new Array(); lsPlayers = new Array(); + enAlliances = new Array(); lsAlliances = new Array(); + } else { + parseData(data); + } + displayPage(); +} + + +function displayPage() +{ + drawList('eal', lsAlliances, "lsAlliances"); + drawList('epl', lsPlayers, "lsPlayers"); + update = setTimeout('x_getEnemies(updatePage)', 60000); +} + + +function drawList(lid, lst, lsn) +{ + if (lst.length == 0) + { + document.getElementById(lid + 'div').innerHTML = '

    ' + emptyListText + '

    '; + return; + } + lst.sort(new Function('a','b','return (a.name.toLowerCase()>b.name.toLowerCase())?1:-1')); + + var i, str = ''; + var nr = (lst.length % 2) + (lst.length - (lst.length % 2)) / 2; + for (i=0;i '; + } + str += ''; + if (i%2 == 1) + str += ''; + } + if (i%2 == 1) + str += ''; + str += '
    '+lst[i].name+'
     
     
    '; + document.getElementById(lid + 'div').innerHTML = str; + updateButton(lid, lst, lsn); +} + +function updateButton(lid, lst, lsn) +{ + var i; + for (i=0;i'); +} + + +function listRemove(lst, lsn) +{ + var i, lids = new Array(); + for (i=0;i'; + var locs = ['All planets', 'Own planets', 'Allied planets', 'Other planets']; + for (i=0;i'+locs[i]+''; + str += ''; + + // Modes + str += 'Fleet mode:'; + + // Current status + str += 'Fleet status:'; + + // Allies mode + str += 'Fleet owner:'; + + // Search engine + str += '
    '; + str += 'Search for '; + str += '
    '; + + // Main display + str += '
     
    '; + + // Commands + str += '
    '; + str += '
    Actions: Help
    '; + + str += ''; + document.getElementById('fpage').innerHTML = str; + document.getElementById('sText').value = sText; +} + + +function drawMovingHeader(hasSel) { + var str; + str = ''; + if (hasSel) { + str += ''; + } + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += '
     OwnerNameHaulGA ShipsFightersCruisersBattle CruisersPowerStatus
    Current LocationDestinationE.T.A.H.S. Standby
    '; + return str; +} + + +function drawWaitingHeader(hasSel) { + var str; + str = ''; + if (hasSel) { + str += ''; + } + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += '
     OwnerNameHaulGA ShipsFightersCruisersBattle CruisersPowerStatus
    LocationTime SpentTime LeftLoss Prob.
    '; + return str; +} + + +function drawPlanetBox(planet) { + var str = ''; + var so = (planet.fleetLocation.details && planet.fleetLocation.details.owner != '' && planet.fleetLocation.details.owner != myself.id); + var apow = 0, dpow = 0; + + str += "'; + if (so) { + str += ''; + } + str += "
    " + planet.fleetLocation.name + ''; + str += " (" + planet.fleetLocation.x + ',' + planet.fleetLocation.y + ',' + planet.fleetLocation.orbit + ')'; + if (planet.fleetLocation.details && planet.fleetLocation.details.tag != '') { + str += ' [' + planet.fleetLocation.details.tag + ']'; + } + str += ''; + str += players.get(planet.fleetLocation.details.owner) + ''; + if (turrets != 0) { + str += '' + formatNumber(turrets) + ' turret' + (turrets > 1 ? 's' : ''); + str += '; power: ' + formatNumber(tPower) + ''; + dpow += parseInt(tPower, 10); + } else { + str += 'No turrets'; + } + } + } else { + str += ' colspan="2">' + (planet.fleetLocation.opacity == 1 ? 'Planetary remains' : ('Class ' + (planet.fleetLocation.opacity - 1) + ' nebula')); + } + str += '
    '; + str += ''; + str += ''; + str += ''; + planet.fleets.sort(sortPlanetFleets); + for (j=0;j= planet.fleetLocation.details.pop) { + act.push(a); + } else if (og > 0) { + var ep = parseInt(planet.fleetLocation.details.pop, 10) - og * a.gaPop; + ep = Math.ceil(ep / a.gaPop); + ang.push([a,ep.toString()]); + } + } + } + if (act.length || ang.length || apow != 0 || planet.fleetLocation.details.vacation + || planet.fleetLocation.details.protection) { + str += '
     OwnerNameHaulGA ShipsFightersCruisersBattle CruisersPowerStatus
    '; + if (act.length) { + for (j=0;j' + formatNumber(ang[j][1]) + ' more GA Ship' + + (ang[j][1] > 1 ? 's' : '') + ' to take this planet.'; + if (j != ang.length - 1) { + str += '
    '; + } + } + if (apow != 0) { + str += '
    '; + } + } + if (apow != 0) { + str += 'Battle status: ' + formatNumber((om == 0 ? dpow : apow).toString()) + + ' vs. ' + formatNumber((om == 1 ? dpow : apow).toString()) + ''; + if (planet.fleetLocation.details.vacation || planet.fleetLocation.details.protection) { + str += '
    '; + } + } + if (planet.fleetLocation.details.protection) { + str += 'Planet is under protection!'; + } else if (planet.fleetLocation.details.vacation) { + str += 'Planet is on vacation!'; + } + } + } + str += '
    '; + } else { + str += 'noflt">No fleets found'; + } + + str += '
    '; + return str; +} + + +function getNewFleetName(p) +{ + return prompt("Please type the new name for th"+(p?"ese fleets":"is fleet")+".", ""); +} + +function alertFleetName(e) { + var str = 'Error: '; + switch (e) + { + case 0: + str += 'please specify a name'; + break; + case 1: + str += 'this name is too long'; + break; + case 2: + str += 'this name is too short'; + break; + case 3: + str += 'a fleet was not found'; + break; + case 200: + str += 'you can\'t rename a fleet while in vacation mode'; + break; + default: + str += 'an unknown error has occured'; + break; + } + + alert(str + '.'); +} + + +function getMergedFleetName() +{ + return prompt("Please type the name of the merged fleet. Leave blank to keep\nits former name.\n", ''); +} + +function alertFleetMerge(e) { + var str = 'Error: '; + switch (e) + { + case 0: + str += 'this name is too long'; + break; + case 1: + str += 'this name is too short'; + break; + case 2: + str += 'a fleet was not found'; + break; + case 200: + str += 'you can\'t merge fleets while in vacation mode'; + break; + default: + str += 'an unknown error has occured'; + break; + } + + alert(str + '.'); +} + +function alertFleetSwitch(e) { + var str = 'Error: '; + switch (e) { + case 0: + str += 'you no longer have control over this fleet'; + break; + case 1: + str += 'you can\'t switch to attack mode on a planet you own'; + break; + case 2: + str += 'you (or your alliance) are in the planet owner\'s enemy list'; + break; + case 3: + str += 'the Peacekeepers will not let you get away with that so easily'; + break; + case 200: + str += 'you can\'t switch a fleet\'s status while in vacation mode'; + break; + default: + str += 'an unknown error has occured'; + break; + } + + alert(str + '.'); +} + +function alertFleetDisband(e) { + var str = 'Error: '; + switch (e) { + case 0: + str += 'you don\'t have control over this fleet'; + break; + case 1: + str += 'the selected fleet(s) no longer exist'; + break; + case 2: + str += 'not enough cash to cancel the fleet\'s sale'; + break; + case 200: + str += 'you can\'t disband fleets while in vacation mode'; + break; + default: + str += 'an unknown error has occured'; + break; + } + + alert(str + '.'); +} + +function alertFleetSplit(e) { + var str = 'Error: '; + switch (e) { + case 0: + str += 'you no longer have control over this fleet'; + break; + case 1: + str += 'you are trying to cheat, aren\'t you? If not, contact us'; + break; + case 2: case 5: + str += 'the fleet you tried to split is no longer available'; + break; + case 3: case 4: case 7: + str += 'the fleet\'s size has changed in the meantime'; + break; + case 10: + str += 'the new fleets\' name is too short'; + break; + case 11: + str += 'the new fleets\' name is too long'; + break; + case 200: + str += 'you can\'t split a fleet while in vacation mode'; + break; + default: + str += 'an unknown error has occured'; + break; + } + + alert(str + '.'); +} + +function alertFleetOrders(e) { + var str = 'Error: '; + switch (e) { + case 0: + str += 'you no longer have control over these fleets'; + break; + case 1: + str += 'the destination planet wasn\'t found'; + break; + case 2: + str += 'an internal error occured.\nPlease report this to the staff as "setOrders/2"'; + break; + case 3: + str += 'the fleets you selected are for sale'; + break; + case 4: + str += 'the fleets you selected are no longer available'; + break; + case 200: + str += 'you can\'t change fleet orders while in vacation mode'; + break; + default: + str += 'an unknown error has occured'; + break; + } + + alert(str + '.'); +} + +function alertServerError() { + alert('Error: an error has occured on the server.\nPlease contact the staff.'); +} + +function alertViewSale() { + alert('Error: this sale is no longer available'); +} + +function drawSplitPage() +{ + var i, str = '

    '; + var f = splitParam[0]; + + switch (f.orders.oType) + { + case 0: + str += 'Split fleet orbitting ' + f.orders.loc.name + '


    '; + str += '
    '; + str += ''; + str += ''; + str += ''; + str += drawFleetLine(f, (f.owner == myself.id ? 'own' : 'ally'), false); + str += '
    OwnerNameHaulGA ShipsFightersCruisersBattle CruisersPowerStatus
    '; + break; + case 1: + str += 'Split moving fleet
    '; + str += drawMovingFleetLine(f, false); + str += '
    ' + drawMovingHeader(false) + '
    '; + break; + case 2: + str += 'Split waiting fleet
    '; + str += drawWaitingFleetLine(f, false); + str += '
    ' + drawWaitingHeader() + '
    '; + break; + } + + str += '

    New fleet(s)

    '; + str += ''; + str += ''; + str += ''; + if (f.ships[0] > 0) + { + var c = 'setSplitShips(0,parseInt(this.value, 10))'; + c = "onKeyUp='"+c+"' onClick='"+c+"'"; + str += ''; + } + if (f.ships[1] > 0) + { + var c = 'setSplitShips(1,parseInt(this.value, 10))'; + c = " onKeyUp='"+c+"' onClick='"+c+"'"; + str += ''; + } + if (f.ships[2] > 0) + { + var c = 'setSplitShips(2,parseInt(this.value, 10))'; + c = " onKeyUp='"+c+"' onClick='"+c+"'"; + str += ''; + } + if (f.ships[3] > 0) + { + var c = 'setSplitShips(3,parseInt(this.value, 10))'; + c = " onKeyUp='"+c+"' onClick='"+c+"'"; + str += ''; + } + if (f.ships[2] > 0 || f.ships[3] > 0) + str += ''; + + str += ''; + str += '
    Split type:
     
    Amount of fleets:
    New name:
     
    GA Ships: / ' + formatNumber(f.ships[0]) + '
    Fighters: / ' + formatNumber(f.ships[1]) + '
    Cruisers: / ' + formatNumber(f.ships[2]) + '
    Battle Cruisers: / ' + formatNumber(f.ships[3]) + '
    Haul used:0
    Haul available:0
     
    '; + str += ''; + str += '
    '; + + str += '
    '; + document.getElementById('fpage').innerHTML = str; +} + + +function confirmDisband(p) +{ + return confirm('Are you sure you want to disband ' + (p ? 'these fleets' : 'this fleet') + '?'); +} + + +function drawOrdersPage() +{ + var str = '
    ' + + '

    Change Orders

     
    ' + + '

    New orders

     
     
    ' + + ' 

    Selected fleets

     
    ' + + '

    Available fleets

     
    '; + document.getElementById('fpage').innerHTML = str; +} + +function getMoveToLine() +{ + if (moveTo == -1) + return "No destination selected - Set destination"; + + return "Move to " + moveToLoc.name + " (" + moveToLoc.x + ',' + moveToLoc.y + ',' + moveToLoc.orbit + + ") - Change destination - " + + "Remove destination"; +} + +function getStandByLine() +{ + if (!selCanHS) + { + waitTime = -1; + return "Selection is not capable of Hyperspace travel"; + } + if (waitTime == -1) + return "No Hyperspace stand-by orders - Set delay"; + return "Selection is scheduled to stand by in Hyperspace for " + waitTime + "h " + + " - Change delay" + + " - Remove delay"; +} + +function getModeLine() +{ + var fd = false; + if (orderFleets.length) + { + var i; + for (i=0;i" + + ""; +} + +function getOrderLinks() +{ + var str = ""; + if (orderFleets.length > 0) + str += 'Confirm - '; + str += 'Cancel'; + return str; +} + +function getFleetDelay(current) +{ + return prompt("Please enter the duration of the fleet's Hyperspace stand by,\nin hours.", current); +} + +function fleetDelayError(e) +{ + var str = "Error: "; + switch (e) + { + case 0: + str += "please type a valid number"; + break; + case 1: + str += "the delay must be at least 1 hour"; + break; + case 2: + str += "the delay must be at most 48 hours"; + break; + } + alert(str); +} + + +function drawDestinationSelection() +{ + var str = '
    '; + str += '

    Set Destination

    '; + str += '

    Current selection:  

    '; + str += 'Please use the map on the right to select a new destination.

    '; + str += '

    '; + str += '

    '; + str += '
     
    '; + document.getElementById('fpage').innerHTML = str; +} + + +function drawMapControls() { + var str; + + // Centre map on coords... + str = '

     

    '; + str += ''; + str += ''; + str += ''; + str += ''; + + // Centre map on own planet... + str += ''; + str += ''; + str += ''; + + // Centre map on planet... + str += ''; + str += ''; + str += ''; + + str += '
    '; + str += '
     '; + str += '(,)
    '; + str += '
     '; + str += '
    '; + str += '
     '; + str += '

    '; + return str; +} + +function alertMap() +{ + alert('Error: this planet was not found on the map'); +} + +function getTrajectoryText(id, t) +{ + if (!t) + return "(loading)"; + + var f = fleets.get(id); + if (t.length == 0 && f.orders.oType != 1) + return "N/A"; + + var r, i, str = ''; + + return str; +} + + +function drawFleetTrajectory() +{ + var str, f = fleetTrajectory.fleet, o = allies.get(f.owner); + var used = f.ships[0] * o.gSpace + f.ships[1] * o.fSpace; + var haul = f.ships[2] * o.cHaul + f.ships[3] * o.bHaul; + var m = parseInt(f.mode, 10); + + str = '

    View Fleet Trajectory ...

    ' + + '' + + 'Close

    Selected Fleet

    ' + foAvaHeader + + '
    ' + + players.get(f.owner) + '' + f.name + ''; + if (haul == 0) + str += 'N/A'; + else + { + var hp = Math.round(used * 100 / haul); + str += (hp > 200) ? '>200%' : (hp + '%'); + } + var op = locations.get(fleetTrajectory.from); + str += '' + formatNumber(f.ships[0]) + ' / ' + formatNumber(f.ships[1]) + ' / ' + + formatNumber(f.ships[2]) + ' / ' + formatNumber(f.ships[3]) + '' + + formatNumber(f.power) + '' + curOrdersTxt[m+4] + f.orders.to.name + + curOrdersTxt[m+6] + '

    Trajectory

    Fleet moving in ' + + (fleetTrajectory.hyperspace ? 'Hyperspace' : 'normal space') + '; original point of origin: ' + + '' + op.name + ' (' + op.x + ',' + op.y + ',' + + op.orbit + ')

    ' + + ''; + + var hc = false; + if (fleetTrajectory.changed > 0) + { + str += ''; + hc = true; + } + + var i; + for (i=0;i' + wp.name + ' (' + wp.system + + ',' + wp.orbit + ')'; + } + + if (fleetTrajectory.wait != 0) + { + var wp = fleetTrajectory.waypoints[fleetTrajectory.waypoints.length - 1]; + str += ''; + } + + str += '
    LocationType' + + 'Status
    ' + fleetTrajectory.waypoints[0].name + ' (' + fleetTrajectory.waypoints[0].system + + ',' + fleetTrajectory.waypoints[0].orbit + ')'+ orbitTypes[fleetTrajectory.waypoints[0].opacity] + + 'Rerouting (' + fleetTrajectory.changed + ' minute' + + (fleetTrajectory.changed > 1 ? 's' : '') + ')
    ' + + orbitTypes[wp.opacity] + ''; + if (!hc) + { + var n = wp.eta - fleetTrajectory.left; + if (n > 0) + str += 'Left orbit ' + n + ' minute' + (n>1?'s':'') + ' ago'; + else + str += 'Just left orbit'; + } + else if (i < fleetTrajectory.waypoints.length - 1) + { + var n = fleetTrajectory.left + fleetTrajectory.changed - wp.eta; + str += 'Leaving orbit in ' + n + ' minute' + (n>1?'s':''); + } + else + { + var n = fleetTrajectory.left + fleetTrajectory.changed - wp.eta; + str += 'Reaching destination in ' + n + ' minute' + (n>1?'s':''); + } + str += '
    ' + wp.name + ' (' + wp.system + + ',' + wp.orbit + ')' + + orbitTypes[wp.opacity] + 'Standing by in Hyperspace for ' + + fleetTrajectory.wait + ' hour' + (fleetTrajectory.wait>1?'s':'') + '
    '; + document.getElementById('fpage').innerHTML = str; +} + + +function drawSellLayout() +{ + var str; + + str = '
    ' + + '

    Sell Fleets ...

     
    ' + + '
     
    '; + + document.getElementById('fpage').innerHTML = str; +} + +function FleetSale_drawForm() +{with(this){ + var str; + + str = '

     

    Orbitting ' + fleetLocation.name + ' (' + fleetLocation.x + ',' + fleetLocation.y + ',' + fleetLocation.orbit + + ')

    Selected Fleets

    ' + + ''; + + var tot = [0,0,0,0,0]; + for (var i=0;i'; + for (var j=0;j<4;j++) + { + str += ''; + tot[j] += parseInt(fleets[i].ships[j], 10); + } + str += ''; + tot[4] += parseInt(fleets[i].power, 10); + } + str += ''; + for (i=0;i<5;i++) + str += ''; + + var smc = ['','','','']; + smc[mode] = ' checked="checked"'; + + str += '
    Fleet NameG.A. ShipsFightersCruisers' + + 'Battle CruisersPower
    ' + formatNumber(fleets[i].ships[j]) + '' + formatNumber(fleets[i].power) + '
    Total' + formatNumber(tot[i].toString()) + '

    Sale details

    ' + + '' + + '
       
      
      
     
    '; + + return str; +}} + + +function drawSellLinks(valid) +{ + var str; + if (valid) + str = 'Confirm - '; + else + str = ''; + str += 'Cancel'; + document.getElementById('chordc').innerHTML = str; +} + + +function saleAlert(ec, id) { + var str; + if (ec == -1) + str = "Go away, kiddie."; + else if (ec == 4) + str = 'Error: some fleets were no longer available for sale; they have been\nremoved from the ' + + 'list. Please verify the updated fleet sale form.'; + else if (ec == 5) { + str = 'Error: none of the previously selected fleets are available for sale.'; + } else if (ec == 200) { + str = 'Error: you are not allowed to sell fleets while in vacation mode.'; + } else if (ec == 201) { + str = 'Error: you are not allowed to sell fleets while under Peacekeeper protection.'; + } else { + str = 'An error occured while handling the sale at ' + locations.get(id).name + ':\n'; + switch (ec) + { + case 0: + str += 'The target player could not be found.'; + break; + case 1: + str += 'The target player is in a different protection level.'; + break; + case 2: + str += 'The price is invalid.'; + break; + case 3: + str += 'Auction sales require an expiration date.'; + break; + case 6: + str += 'You are trying to sell or give a fleet to yourself.'; + break; + case 7: + str += 'The target player has not been in the game long enough to\naccept the offer.'; + break; + } + } + alert(str); +} + + +function confirmCancelSale(pl) +{ + return confirm('Are you sure you want to cancel the sale of th' + (pl?'ese fleets':'is fleet') + '?'); +} + + +function drawSaleDetails(l, name) +{ + var str, f = fleets.get(l[1]), o = allies.get(f.owner); + var used = f.ships[0] * o.gSpace + f.ships[1] * o.fSpace; + var haul = f.ships[2] * o.cHaul + f.ships[3] * o.bHaul; + var m = parseInt(f.mode, 10); + + str = '

    Fleet Sale Details

    ' + + '' + + 'Close

    Fleet

    ' + foAvaHeader + + '
    ' + o.getName() + + '' + f.name + '' + if (haul == 0) + str += 'N/A'; + else + { + var hp = Math.round(used * 100 / haul); + str += (hp > 200) ? '>200%' : (hp + '%'); + } + str += '' + formatNumber(f.ships[0]) + ' / ' + formatNumber(f.ships[1]) + ' / ' + + formatNumber(f.ships[2]) + ' / ' + formatNumber(f.ships[3]) + '' + + formatNumber(f.power) + '' + + (f.orders.oType == 3 ? 'On sale at ' : 'Waiting for transfer at ') + f.orders.loc.name + + '

    Sale details

    '; + if (st == 1) + str += ''; + str += ''; + break; + case '1': + str += 'Public offer' + + ''; + st = 2; + break; + case '2': + str += 'Auction sale' + + ''; + if (l[8] != '') + { + str += ''; + if (l[3] == "") + str += ''; + } + st = 3; + break; + } + if (l[3] != "") + { + var tx = parseInt(l[5],10) + 1; + str += ''; + if (st > 1) + str += ''; + str += ''; + } + str += '
    Sale type:'; + + var st; + switch (l[6]) + { + case '0': + if (l[7] == '0') + { + str += 'Gift'; + st = 0; + } + else + { + str += 'Direct offer'; + st = 1; + } + str += '
    To player:' + + name + '
    Price:€' + formatNumber(l[7]) + '
    Expires:' + (l[2] == "" ? 'Never' : formatDate(l[2])) + '
    Price:' + formatNumber(l[7]) + '
    Expires:' + (l[2] == "" ? 'Never' : formatDate(l[2])) + '
    Minimum bid:' + formatNumber(l[7]) + '
    Expires:' + formatDate(l[2]) + '
    Latest bid at:' + formatDate(l[10]) + '
    ' + + 'Bid:€' + formatNumber(l[8]) + '
    Bid by:' + + name + '
    Finalized:' + formatDate(l[3]) + '
    Sold to:' + + name + '
    Transfer time:' + tx + ' hour' + (tx > 1 ? 's' : '') + '
    '; + + document.getElementById('fpage').innerHTML = str; +} diff --git a/site/static/beta5/js/pg_fleets.js b/site/static/beta5/js/pg_fleets.js new file mode 100644 index 0000000..52b7374 --- /dev/null +++ b/site/static/beta5/js/pg_fleets.js @@ -0,0 +1,2704 @@ +var autoUpdate, autoLock, onMainPage = false; +var allies, fleets, locations, players, myself; +var fAutoId = 0; +var listLocations, listMode, fDispMode, alliesMode; +var sType, sText; +var ppList, apList, opList, mfList, wfList; +var fSel, fDisp, splitParam; +var orderFleets, moveTo, moveToLoc, orderMode, waitTime, selCanHS, guessOrders, gDest, gWait, sMovex, sMovey; +var dMapX, dMapY, newDest, map, loadingMap = false, readingMoveLocation = false; +var mapCType, mapParm; +var trajectories, lTraj, trajLock; +var fleetTrajectory, sales; +var mapRemember = false; +var mFleetSelectable, mFleetSelected; +var wFleetSelectable, wFleetSelected; + + +function Ally(id, isMe, fSpace, gSpace, cHaul, bHaul, gaPop, canSell) +{ + this.id = id; + this.isMe = isMe; + this.gSpace = gSpace; + this.fSpace = fSpace; + this.cHaul = cHaul; + this.bHaul = bHaul; + this.gaPop = gaPop; + this.canSell = canSell; + + this.getName = Ally_getName; + + this.planets = new Array(); + this.fleets = new Array(); + this.gasAt = new Hashtable(); + + if (isMe) + myself = this; +} + +function Ally_getName() +{ + return players.get(this.id); +} + + +function Fleet(id, owner, gaships, fighters, cruisers, bcruisers, power, mode, upkeep, name) +{ + this.id = (id == "" ? ("a" + (++fAutoId)) : id); + this.owner = owner; + this.ships = [gaships,fighters,cruisers,bcruisers]; + this.power = power; + this.upkeep = upkeep; + this.mode = mode; + this.name = name; + this.selected = false; + + this.orders = null; +} + +function FLocation(id, canMove) +{ + this.oType = 0; + this.locId = id; + this.loc = null; + this.canMove = canMove; +} + + +function FMove(to, from, now, eta, hs, reroute) +{ + this.oType = 1; + this.fromId = from; this.from = null; + this.toId = to; this.to = null; + this.curId = now; this.cur = null; + this.eta = eta; this.wait = null; + this.hyperspace = (hs == '1'); + this.reroute = parseInt(reroute,10); +} + + +function FWait(loc, from, left, spent, minLoss, maxLoss) +{ + this.oType = 2; + this.fromId = from; this.from = null; + this.locId = loc; this.loc = null; + this.spent = spent; this.left = left; + this.minLoss = minLoss; this.maxLoss = maxLoss; +} + + +function FOnSale(loc, sale, ib) +{ + this.oType = 3; + this.locId = loc; this.loc = null; + this.saleId = sale; + this.inBundle = (ib == 1); +} + + +function FSold(loc, sale, soldTo, timeLeft, ib) +{ + this.oType = 4; + this.locId = loc; this.loc = null; + this.soldTo = soldTo; this.timeLeft = timeLeft; + this.saleId = sale; + this.inBundle = (ib == 1); +} + + +function FleetLocation(id, opacity, x, y, orbit, name) +{ + this.id = id; + this.opacity = opacity; + this.x = x; + this.y = y; + this.orbit = orbit; + this.name = name; + + this.fleets = new Array(); + this.details = null; + + this.updateSelection = function () { + var e = document.getElementById('fsel-p-' + this.id); + if (!e) { + return; + } + if (this.selectableFleets == 0) { + e.innerHTML = ' '; + return; + } + e.innerHTML = ''; + }; + + this.selectAll = function () { + if (autoLock) { + setTimeout('locations.get(' + this.id + ').selectAll()', 500); + return; + } + autoLock = true; + + if (this.selectableFleets == 0) { + autoLock = false; + return; + } + for (var i in this.fleets) { + var e = document.getElementById('fsel' + this.fleets[i].id); + if (!e) { + continue; + } + e.checked = this.fleets[i].selected = !(this.selectableFleets == this.selectedFleets); + } + if (this.selectableFleets == this.selectedFleets) { + this.selectedFleets = 0; + } else { + this.selectedFleets = this.selectableFleets; + } + updateActions(); + + autoLock = false; + }; +} + +function PlanetDetails(owner, turrets, tPower, pop, vacation, protection, tag) { + this.owner = owner; + this.turrets = turrets; + this.tPower = tPower; + this.pop = pop; + this.vacation = (vacation == 1); + this.protection = (protection == 1); + this.tag = tag; +} + +function FilteredLocation(id) +{ + this.id = id; + this.fleetLocation = locations.get(id); + this.fleets = new Array(); +} + + +function MapSystem(x, y, opacity, prot) { + this.x = x; + this.y = y; + this.opacity = opacity; + this.protection = prot; + this.locations = new Array(); +} + +function MapLocation(id, x, y, orbit, opacity, ally, name) +{ + this.id = id; + this.x = x; + this.y = y; + this.orbit = orbit; + this.color = parseInt(opacity, 10); + if (this.color != 0) + this.color += 2; + else + this.color = parseInt(ally); + this.name = name; +} + +function WayPoint(x, y, orbit, eta, name) +{ + this.name = name; + this.coords = "(" + x + "," + y + "," + orbit + ")"; + this.eta = eta; +} + +function FleetTrajectory(fid, from, to, changed, left, hs, wait) +{ + this.fleetId = fid; + this.fleet = fleets.get(fid); + this.from = from; + this.to = to; + this.changed = parseInt(changed,10); + this.left = parseInt(left,10); + this.hyperspace = (hs == '1'); + this.wait = parseInt(wait,10); + this.waypoints = new Array(); +} + +function FleetWayPoint(id, eta, sys, orb, op, name) +{ + this.id = id; + this.eta = parseInt(eta,10); + this.system = sys; + this.orbit = orb; + this.opacity = op; + this.name = name; +} + + +function FleetSale(loc) +{ + this.id = loc; + this.fleetLocation = locations.get(loc); + this.mode = 0; + this.player = ''; + this.price = 0; + this.priceText = ''; + this.expires = 0; + this.isValid = false; + this.error = -1; + this.fleets = new Array(); + + this.drawForm = FleetSale_drawForm; + this.update = FleetSale_update; + this.validate = FleetSale_validate; + this.setMode = FleetSale_setMode; + this.setExpire = FleetSale_setExpire; +} + +function FleetSale_setMode(m) +{ + this.mode = m; + this.update(); + this.validate(); +} + +function FleetSale_setExpire(x) +{ + this.expires = x; + this.validate(); +} + +function FleetSale_update() +{ + var id = 's' + this.id, c0 = 'sales.get('+this.id+').validate();return true', code = ' onChange="'+c0+'" onKeyUp="'+c0+'" onClick="'+c0+'" ';; + switch (this.mode) + { + case 0: + if (document.getElementById(id+'price')) + document.getElementById(id+'pril').innerHTML = document.getElementById(id+'pri').innerHTML = ' '; + if (!document.getElementById(id+'target')) + { + document.getElementById(id+'tar').innerHTML = ''; + document.getElementById(id+'tarl').innerHTML = ''; + } + document.getElementById(id+'target').value = this.player; + break; + + case 1: + if (!document.getElementById(id+'price')) + document.getElementById(id+'pri').innerHTML = ''; + if (!document.getElementById(id+'target')) + { + document.getElementById(id+'tar').innerHTML = ''; + document.getElementById(id+'tarl').innerHTML = ''; + } + document.getElementById(id+'pril').innerHTML = ''; + document.getElementById(id+'target').value = this.player; + document.getElementById(id+'price').value = this.priceText; + break; + + case 2: + case 3: + if (!document.getElementById(id+'price')) + document.getElementById(id+'pri').innerHTML = ''; + if (document.getElementById(id+'target')) + document.getElementById(id+'tar').innerHTML = document.getElementById(id+'tarl').innerHTML = ' '; + document.getElementById(id+'pril').innerHTML = ''; + document.getElementById(id+'price').value = this.priceText; + break; + } +} + +function FleetSale_validate() +{ + this.isValid = false; + this.error = -1; + + if (document.getElementById('s'+this.id+'target')) + this.player = document.getElementById('s'+this.id+'target').value; + if (document.getElementById('s'+this.id+'price')) + { + this.priceText = document.getElementById('s'+this.id+'price').value; + this.price = parseInt(this.priceText, 10); + } + + if (this.mode < 2 && this.player == '') + { + this.error = 1; + drawSellLinks(false); + return; + } + if (this.mode != 0 && (isNaN(this.price) || this.price <= 0)) + { + this.error = 2; + drawSellLinks(false); + return; + } + if (this.mode == 3 && this.expires == 0) + { + this.error = 3; + drawSellLinks(false); + return; + } + + this.isValid = true; + checkAllSales(); +} + + +function getTrajectory(fleet, to) +{ + var fLoc = (fleet.orders.oType != 1 ? fleet.orders.loc : fleet.orders.cur); + var key = fLoc.id + ';' + ((fleet.ships[2] != 0 || (fleet.ships[2] == 0 && fleet.ships[3] == 0)) ? 1 : 0) + ';' + fleet.owner + ';' + to.id; + if (trajectories.containsKey(key)) + return trajectories.get(key); + if (('#' + lTraj.join('#') + '#').indexOf('#' + key + '#') == -1) + { + lTraj.push(key); + x_getTrajectory(key, gotTrajectory); + } + return null; +} + +function gotTrajectory(data) +{ + if (data == "") + return; + + while (trajLock); + trajLock = true; + + var l = data.split('\n'); + var t = new Array(), key = l.shift(), a, w, eta = 0; + + while (l.length) + { + a = l.shift().split('#'); + w = new WayPoint(a.shift(), a.shift(), a.shift(), (eta += parseInt(a.shift(), 10)), a.join('#')); + t.push(w); + } + + trajectories.put(key, t); + + t = new Array(); + for (a=0;a 2 && fDispMode - 2 == f.orders.oType + ) && ( alliesMode == 0 + || alliesMode == 1 && f.owner == myself.id + || alliesMode == 2 && f.owner != myself.id && allies.get(f.owner) + || alliesMode == 3 && !allies.get(f.owner) + ) && ( sType == 1 + || sType == 0 && (sText == '' || f.name.toLowerCase().indexOf(sText.toLowerCase()) != -1) + || sType == 2 && (sText == '' || players.get(f.owner).toLowerCase().indexOf(sText.toLowerCase()) != -1) + ) + ) + flt.push(keys[i]); + } + + if (fDispMode < 3) + { + if (listLocations < 2) + for (i=0;i'; +} + +function selectAllWaitingFleets () { + if (autoLock) { + setTimeout('selectAllWaitingFleets()', 500); + return; + } + autoLock = true; + + if (wFleetSelectable == 0) { + autoLock = false; + return; + } + for (var i in wfList) { + var e = document.getElementById('fsel' + wfList[i].id); + if (!e) { + continue; + } + e.checked = wfList[i].selected = !(wFleetSelectable == wFleetSelected); + } + if (wFleetSelectable == wFleetSelected) { + wFleetSelected = 0; + } else { + wFleetSelected = wFleetSelectable; + } + updateActions(); + + autoLock = false; +} + +function updateMovingSelection () { + var e = document.getElementById('f-move-sel'); + if (!e) { + return; + } + if (mFleetSelectable == 0) { + e.innerHTML = ' '; + return; + } + e.innerHTML = ''; +} + +function selectAllMovingFleets () { + if (autoLock) { + setTimeout('selectAllMovingFleets()', 500); + return; + } + autoLock = true; + + if (mFleetSelectable == 0) { + autoLock = false; + return; + } + for (var i in mfList) { + var e = document.getElementById('fsel' + mfList[i].id); + if (!e) { + continue; + } + e.checked = mfList[i].selected = !(mFleetSelectable == mFleetSelected); + } + if (mFleetSelectable == mFleetSelected) { + mFleetSelected = 0; + } else { + mFleetSelected = mFleetSelectable; + } + updateActions(); + + autoLock = false; +} + + + +function updateActions() +{ + var cRen = false, cTraj = true, cMove = true, cSwitch = true, + cMerge = true, cDisband = true, cSell = myself.canSell, cCancelSale = myself.canSell; + var i, n, ns; + + for (i=n=ns=0;i' + + ' - ' + actionText[1] + ''; + if (cTraj && n == 1) + i += ' - ' + actionText[8] + ''; + if (cMove) + i += ' - ' + actionText[2] + ''; + if (cSwitch) + i += ' - ' + actionText[3] + ''; + if (cMerge && n > 1) + i += ' - ' + actionText[5] + ''; + if (cMerge && n == 1 && ns > 1) + i += ' - ' + actionText[4] + ''; + if (cSell) + i += ' - ' + actionText[6] + ''; + if (cCancelSale) + { + if (n == 1) + i += ' - ' + actionText[11] + ''; + i += ' - ' + actionText[10] + ''; + } + if (cDisband) + i += ' - ' + actionText[7] + ''; + document.getElementById('factions').innerHTML = i; +} + + +function drawFleetLine(fleet,type,hasSel) { + var o = allies.get(fleet.owner); + var i, str = ''; + var sc = ''; + if (hasSel) + { + str += ''; + if (o && (fleet.orders.oType != 3 && fleet.orders.oType != 4 || !fleet.orders.inBundle)) + { + str += ''; + fDisp.push(fleet); + sc = ' onclick="with (document.getElementById(\'fsel'+fleet.id+'\')) { checked = !checked; };' + + 'selectFleet('+fleet.id+');return true" '; + + var loc = fleet.orders.loc; + loc.selectableFleets ++; + loc.selectedFleets += (fleet.selected ? 1 : 0); + } else { + str += ' '; + } + str += ''; + } + str += ''; + } else { + str += sc + '>'; + } + str += players.get(fleet.owner); + str += ''; + + if (o) + { + var used = fleet.ships[0] * o.gSpace + fleet.ships[1] * o.fSpace; + var haul = fleet.ships[2] * o.cHaul + fleet.ships[3] * o.bHaul; + if (haul == 0) + str += 'N/A'; + else + { + var hp = Math.round(used * 100 / haul); + str += (hp > 200) ? '>200%' : (hp + '%'); + } + } + else + str += ' '; + str += ''; + + for (i=0;i<4;i++) + str += ''+formatNumber(fleet.ships[i])+''; + + str += ''+formatNumber(fleet.power)+''; + if (fleet.orders.oType == 0) + str += (fleet.mode == 1 ? statusText[0] : statusText[1]) + ', ' + (fleet.orders.canMove == 'Y' ? statusText[2] : statusText[3]); + else if (fleet.orders.oType == 3) + str += statusText[4] + (fleet.orders.inBundle ? statusText[6] : ''); + else if (fleet.orders.oType == 4) + str += statusText[5]; + str += ''; + + return str; +} + + +function drawMovingFleetLine(fleet, hasSel) +{ + var o = allies.get(fleet.owner); + var i, str = ''; + var sc = ''; + str += ''; + + if (hasSel) { + str += ''; + mFleetSelectable ++; + mFleetSelected += (fleet.selected ? 1 : 0); + fDisp.push(fleet); + sc = ' onclick="with (document.getElementById(\'fsel'+fleet.id+'\')) { checked = !checked; };' + + 'selectFleet('+fleet.id+');return true" '; + } + + str += ''; + + for (i=0;i<4;i++) + str += ''; + str += ''; + str += ''; + + str += ''; + str += '
    '; + } else { + str += sc + '>'; + } + str += players.get(fleet.owner); + if (myself.id != fleet.owner) + str += ''; + str += ''; + + var used = fleet.ships[0] * o.gSpace + fleet.ships[1] * o.fSpace; + var haul = fleet.ships[2] * o.cHaul + fleet.ships[3] * o.bHaul; + if (haul == 0) + str += 'N/A'; + else + { + var hp = Math.round(used * 100 / haul); + str += (hp > 200) ? '>200%' : (hp + '%'); + } + str += ''+formatNumber(fleet.ships[i])+''+formatNumber(fleet.power)+'' + (fleet.mode == 1 ? statusText[0] : statusText[1]) + '
    ' + fleet.orders.cur.name + ''; + if (fleet.orders.cur.details && fleet.orders.cur.details.tag != '') { + str += ' [' + fleet.orders.cur.details.tag + ']'; + } + str += '' + fleet.orders.to.name + ''; + if (fleet.orders.to.details && fleet.orders.to.details.tag != '') { + str += ' [' + fleet.orders.to.details.tag + ']'; + } + str += '' + fleet.orders.eta + ' min'; + if (fleet.orders.wait) + { + str += fleet.orders.wait.left + 'h ('; + if (fleet.orders.wait.maxLoss == 0) + str += '0%'; + else + str += fleet.orders.wait.minLoss + '% - ' + fleet.orders.wait.maxLoss + '%'; + str += ')'; + } + else + str += 'N/A'; + str += '
    '; + + return str; +} + + +function drawWaitingFleetLine(fleet,hasSel) +{ + var o = allies.get(fleet.owner); + var i, str = ''; + var sc = ''; + str += ''; + + if (hasSel) { + str += ''; + wFleetSelectable ++; + wFleetSelected += (fleet.selected ? 1 : 0); + fDisp.push(fleet); + sc = ' onclick="with (document.getElementById(\'fsel'+fleet.id+'\')) { checked = !checked; };' + + 'selectFleet('+fleet.id+');return true" '; + } + + str += ''; + + for (i=0;i<4;i++) + str += ''+formatNumber(fleet.ships[i])+''; + str += ''+formatNumber(fleet.power)+''; + str += '' + (fleet.mode == 1 ? statusText[0] : statusText[1]) + ''; + + str += '' + + '' + fleet.orders.spent + 'h' + + fleet.orders.left + 'h'; + if (fleet.orders.maxLoss == 0) + str += '0%'; + else + str += fleet.orders.minLoss + '% - ' + fleet.orders.maxLoss + '%'; + str += '
    '; + } else { + str += sc + '>'; + } + str += players.get(fleet.owner); + if (myself.id != fleet.owner) + str += ''; + str += ''; + + var used = fleet.ships[0] * o.gSpace + fleet.ships[1] * o.fSpace; + var haul = fleet.ships[2] * o.cHaul + fleet.ships[3] * o.bHaul; + if (haul == 0) + str += 'N/A'; + else + { + var hp = Math.round(used * 100 / haul); + str += (hp > 200) ? '>200%' : (hp + '%'); + } + str += '
    ' + fleet.orders.loc.name + ''; + if (fleet.orders.loc.details && fleet.orders.loc.details.tag != '') { + str += ' [' + fleet.orders.loc.details.tag + ']'; + } + str += '
    '; + + return str; +} + + +function drawMainContents() +{ + fDisp = new Array(); + + if (ppList.size() == 0 && apList.size() == 0 && opList.size() == 0 && mfList.length == 0 && wfList.length == 0) + { + document.getElementById('fmain').innerHTML = '
    '+noFleetsFound+'
    '; + return; + } + + var i,sz,keys,j,p,f,str = ''; + + if ((sz = ppList.size()) != 0) + { + str += '

    '+sectionTitles[0]+'

    '; + keys = ppList.keys(); + keys.sort(sortPPList); + for (i=0;i'; + keys = apList.keys(); + keys.sort(sortAPList); + for (i=0;i'; + keys = opList.keys(); + keys.sort(sortOPList); + for (i=0;i'; + str += ''; + mfList.sort(sortMovingFleets); + for (j=0;j'; + str += '
    ' + drawMovingHeader(true) + '
    '; + wfList.sort(sortWaitingFleets); + for (j=0;j 64) + { + alertFleetName(1); + str = ""; + } + else if (str.length < 3) + { + alertFleetName(2); + str = ""; + } + } + + x_renameFleets(id, str, renameCallback); +} + + +function renameSelected() +{ + if (autoLock) + { + setTimeout('renameSelected()', 500); + return; + } + lockUpdate(); + + var str = ""; + while (str == "") + { + str = getNewFleetName(false); + if (typeof str == 'object' && !str) + { + unlockUpdate(); + return; + } + if (str == "") + alertFleetName(0); + else if (str.length > 64) + { + alertFleetName(1); + str = ""; + } + else if (str.length < 3) + { + alertFleetName(2); + str = ""; + } + } + + var s = new Array(); + for (i=0;i 64) + { + alertFleetMerge(0); + str = " "; + } + else if (str.length < 3 && str != '') + { + alertFleetMerge(1); + str = " "; + } + } + + var i, s = new Array(); + for (i=0;i1)) + x_disbandFleets(s.join('#'), disbandCallback); + else + unlockUpdate(); +} + +function disbandCallback(str) +{ + if (str.indexOf('ERR#') == 0) + { + var l = str.split('#'); + alertFleetDisband(parseInt(l[1], 10)); + unlockUpdate(); + } + else + { + prepareUpdate(); + parseMainData(str); + } +} + + +function splitSelected() +{ + if (autoLock) + { + setTimeout('splitSelected()', 500); + return; + } + lockUpdate(); + + splitParam = new Array(); + for (var i=0;i 0 || splitParam[0].ships[3] > 0) + { + splitParam[7] = ships[0] * o.gSpace + ships[1] * o.fSpace; + splitParam[8] = ships[2] * o.cHaul + ships[3] * o.bHaul; + document.getElementById('hused').innerHTML = splitParam[7]; + document.getElementById('havail').innerHTML = splitParam[8]; + } + + var s=0; + for (i=0;i<4;i++) + s += ships[i]; + + var canSplit = (s > 0); + if (splitParam[0].orders.oType == 2 || splitParam[0].orders.oType == 1 && splitParam[0].orders.hyperspace) + canSplit = canSplit && (splitParam[7] <= splitParam[8]); + document.getElementById('bsplit').disabled = !canSplit; +} + +function setSplitCount(amount) +{ + splitParam[2] = amount; + if (splitParam[1] == 0) + { + updateSplitHaul(); + updateSplitOk(); + } + else + updateAutoSplit(); +} + +function setSplitShips(type,amount) +{ + var flds = ['sgas','sfgt','scru','sbcr']; + if (amount < 0 || isNaN(amount)) + return; + splitParam[type+3] = amount; + updateSplitHaul(); + updateSplitOk(); +} + +function updateSplitHaul() +{ + if (splitParam[0].ships[2] == 0 && splitParam[0].ships[3] == 0) + { + splitParam[7] = splitParam[8] = 0; + return; + } + var o = allies.get(splitParam[0].owner); + splitParam[7] = splitParam[3] * o.gSpace + splitParam[4] * o.fSpace; + splitParam[8] = splitParam[5] * o.cHaul + splitParam[6] * o.bHaul; + document.getElementById('hused').innerHTML = splitParam[7]; + document.getElementById('havail').innerHTML = splitParam[8]; +} + +function updateSplitOk() +{ + var ships = [0,0,0,0]; + var diffs = [0,0,0,0]; + var i,s1=0,s2=0,neg=false; + for (i=0;i<4;i++) + { + ships[i] = splitParam[i+3] * splitParam[2]; + s1 += ships[i]; + diffs[i] = splitParam[0].ships[i] - ships[i]; + neg = neg || (diffs[i] < 0); + s2 += diffs[i]; + } + + var canSplit = (s1 > 0 && !neg && s2 > 0); + if (splitParam[0].orders.oType == 2 || splitParam[0].orders.oType == 1 && splitParam[0].orders.hyperspace) + { + var ou, os, o = allies.get(splitParam[0].owner); + ou = diffs[0] * o.gSpace + diffs[1] * o.fSpace; + os = diffs[2] * o.cHaul + diffs[3] * o.bHaul; + canSplit = canSplit && (splitParam[7] <= splitParam[8]) && (ou <= os); + } + document.getElementById('bsplit').disabled = !canSplit; +} + +function cancelSplit() +{ + prepareUpdate(); + x_getFleetsList(parseMainData); +} + +function doFleetSplit() +{ + document.getElementById('bsplit').disabled = true; + if (splitParam[1] == 0) + x_manualSplit(splitParam[0].id, splitParam[9], splitParam[2], splitParam[3], splitParam[4], splitParam[5], splitParam[6], splitCallback); + else + x_autoSplit(splitParam[0].id, splitParam[9], splitParam[2], splitCallback); +} + +function splitCallback(str) +{ + if (str.indexOf('ERR#') == 0) { + var l = str.split('#'); + alertFleetSplit(parseInt(l[1], 10)); + document.getElementById('bsplit').disabled = false; + } else { + clearSelection(); + prepareUpdate(); + parseMainData(str); + } +} + + +function moveSelected() +{ + if (autoLock) + { + setTimeout('moveSelected()', 500); + return; + } + lockUpdate(); + + orderFleets = new Array(); + for (var i=0;i' + actionText[0] + '

    '; + } + + var i,str = '
    ' + drawWaitingHeader(true) + '
    ' + foSelHeader + ''; + selCanHS = true; gWait = gDest = -2; + var n = new Array(); + sMovex = null; sMovey = null; + orderFleets.sort(sortOrderFleets); + for (i=0;i 0 && !f.orders.wait || f.orders.wait && gWait != f.orders.wait.left) + gWait = gDest = -1; + } + else if (f.orders.oType == 2) + { + if (gWait == -2 && gDest == -2) + { + gDest = -1; + gWait = f.orders.left; + } + else if (gDest > 0 || f.orders.left != gWait) + gWait = gDest = -1; + } + else + gWait = gDest = -1; + orderMode = parseInt(f.mode, 10); + } + + var oc = ' onClick="removeOrdersSelection('+f.id+')"'; + str += '' + f.name + ''; + if (haul == 0) + str += 'N/A'; + else + { + var hp = Math.round(used * 100 / haul); + str += (hp > 200) ? '>200%' : (hp + '%'); + } + str += '' + formatNumber(f.ships[0]) + ' / ' + formatNumber(f.ships[1]) + ' / '; + str += formatNumber(f.ships[2]) + ' / ' + formatNumber(f.ships[3]) + ''; + str += formatNumber(f.power) + ''; + } + orderFleets = n; + str += '
    ' + + players.get(f.owner) + ' 
    '; + guessOrders = false; + if (gWait > 0) + waitTime = gWait; + if (gDest > 0) + { + moveTo = gDest; + moveToLoc = locations.get(moveTo); + } + + return str; +} + +function getOAvailFleets() +{ + // Fleets are available if: + // they are not selected + // AND ( they are idling AND can move + // OR they are moving + // OR they are waiting in HS ) + // AND ( there is a destination + // AND ( the fleet is HS capable + // OR the fleet is not HS capable AND the target is in the same system ) + // OR there is no destination ) + // AND ( there are HS stand-by orders AND the fleet is HS capable + // OR there are no HS stand-by orders ) + var i, sst = '#' + orderFleets.join('#') + '#'; + var afl = new Array(), fk = fleets.keys(); + + for (i=0;i= 3 + ) + continue; + afl.push(f); + } + + if (!afl.length) + return '

    ' + noFleetsFound + '

    '; + + var i,str = '' + foAvaHeader + ''; + afl.sort(sortAvailFleets); + for (i=0;i'; + } + str += '
    '; + str += players.get(f.owner) + '' + f.name + ''; + if (haul == 0) + str += 'N/A'; + else + { + var hp = Math.round(used * 100 / haul); + str += (hp > 200) ? '>200%' : (hp + '%'); + } + str += '' + formatNumber(f.ships[0]) + ' / ' + formatNumber(f.ships[1]) + ' / '; + str += formatNumber(f.ships[2]) + ' / ' + formatNumber(f.ships[3]) + ''; + str += formatNumber(f.power) + ''; + var m = parseInt(f.mode, 10); + if (f.orders.oType == 0) + str += curOrdersTxt[m] + f.orders.loc.name + curOrdersTxt[2 + m]; + else if (f.orders.oType == 1) + str += curOrdersTxt[m+4] + f.orders.to.name + curOrdersTxt[m+6]; + else + str += curOrdersTxt[m+8] + f.orders.loc.name + curOrdersTxt[m+10]; + str += '
    '; + + return str; +} + +function removeOrdersSelection(id) +{ + var n = new Array(), i; + for (i=0;i 48) + { + fleetDelayError(2); + str = ""; + } + } + waitTime = nt; + updateOrdersPage(); +} + +function cancelOrdersDelay() +{ + waitTime = -1; + updateOrdersPage(); +} + +function removeOrdersDestination() +{ + moveTo = -1; + updateOrdersPage(); +} + +function gotLocation(data) { + var l = data.split("\n"); + var a, o; + + a = l.shift().split('#'); + moveToLoc = new FleetLocation(a.shift(),a.shift(),a.shift(),a.shift(),a.shift(),a.join('#')); + if (moveToLoc.opacity == 0) { + a = l.shift().split('#'); + moveToLoc.details = new PlanetDetails(a.shift(),a.shift(),a.shift(),a.shift(),a.shift(),a.join('#')); + } + readingMoveLocation = false; + updateOrdersPage(); +} + +function setOrdersDestination() { + if (mapRemember && typeof sMovex == 'object') { + } else if (moveTo == -1) { + var ok = false; + if (typeof sMovex != 'object') { + dMapX = parseInt(sMovex,10); + dMapY = parseInt(sMovey,10); + ok = true; + } else { + if (orderFleets.length) { + var f = fleets.get(orderFleets[0]); + if (f) { + var fLoc = (f.orders.oType != 1 ? f.orders.loc : f.orders.cur); + dMapX = parseInt(fLoc.x, 10); + dMapY = parseInt(fLoc.y, 10); + ok = true; + } + } + } + if (! ok) { + if (myself.planets.length) { + dMapX = parseInt(myself.planets[0].x,10); + dMapY = parseInt(myself.planets[0].y,10); + } else { + var i = 0, j, k = allies.keys(); + while (i < k.length) + { + var a = allies.get(k[i]); + if (!a.isMe && a.planets.length) + { + dMapX = parseInt(a.planets[0].x,10); + dMapY = parseInt(a.planets[0].y,10); + break; + } + i++; + } + if (i == k.length) + dMapX = dMapY = 0; + } + } + } else { + dMapX = parseInt(moveToLoc.x,10); + dMapY = parseInt(moveToLoc.y,10); + } + newDest = moveTo; + mapCType = 0; + + drawDestinationSelection(); + if (!map.containsKey(dMapX + ';' + dMapY)) + { + loadingMap = true; + x_getMapData(dMapX, dMapY, gotMapData); + } + else + { + loadingMap = false; + updateDestinationSelection(); + } +} + +function gotMapData(data) +{ + if (data == "ERR") + alertMap(); + else + { + var l = data.split('\n'); + var a = l.shift().split("#"), i; + dMapX = parseInt(a[0], 10); dMapY = parseInt(a[1], 10); + var system = new MapSystem(dMapX, dMapY, a[2], a[3] == '1'); + + for (i=0;i<6;i++) + { + if (system.opacity != -1) + { + a = l.shift().split('#'); + var loc = new MapLocation(a.shift(), dMapX, dMapY, i+1, a.shift(), a.shift(), a.join('#')); + system.locations.push(loc); + map.put("p" + loc.id, loc); + map.put("pn-" + loc.name.toLowerCase(), loc); + } + else + system.locations.push(null); + } + map.put(dMapX + ";" + dMapY, system); + } + updateDestinationSelection(); + loadingMap = false; +} + +function updateDestinationSelection() { + updateDestLine(); + if (typeof sMovex == 'object') { + drawGalacticMap(); + } else { + drawSystemMap(); + } +} + +function updateDestLine() +{ + if (newDest == -1) + document.getElementById('cursel').innerHTML = noSelection; + else + { + var d = map.get("p" + newDest); + document.getElementById('cursel').innerHTML = d.name + " (" + d.x + "," + d.y + "," + d.orbit + ")"; + } +} + +function drawSystemMap() { + var sys = map.get(dMapX + ";" + dMapY); + var str = "'; + } else { + str += ""; + } + } + + str += '
    "; + var i; + + for (i = 0; i < 6; i ++) { + var loc = sys.locations[i]; + if (sys.opacity == 0) { + var img = (loc.color == 4) ? "prem_s" : ("pl/s/"+loc.id); + str += "
    [P]" + + loc.name + '
    " + loc.name + "

    ' + smapCoordTxt + '' + dMapX + ' , ' + dMapY + '

    '; + + if (sys.protection) { + str = '' + str + ''; + } + document.getElementById('sdmap').innerHTML = str; +} + +function drawGalacticMap() +{ + var sys = map.get(dMapX + ";" + dMapY); + var op = sys.opacity == -1 ? 0 : sys.opacity; + var str = "" + + "" + + "" + +"
     " + + ""+dirTxt[0]+" 
    " + + ""+dirTxt[1]+"" + (sys.protection ? '' : '') + + ""; + var i; + + for (i=0;i<6;i++) + { + var loc = sys.locations[i]; + if (loc && sys.opacity == 0) + { + var img = (loc.color == 4) ? "prem_s" : ("pl/s/"+loc.id); + str += "'; + } + else if (loc) + str += ""; + else + str += ''; + } + + str += "
    [P]" + + loc.name + '
    "+loc.name+"
    '+unchartedTxt+'
    " + (sys.protection ? '
    ' : '') + + "
    "+dirTxt[2]+"
     "+dirTxt[3]+" 
    "; + str += drawMapControls(); + document.getElementById('sdmap').innerHTML = str; +} + +function updateGalacticMap() +{ + if (loadingMap) + return; + + var e; + + if (document.getElementById('mct0').checked) + { + var x = parseInt(document.getElementById('cx').value, 10); + var y = parseInt(document.getElementById('cy').value, 10); + mapCType = 0; + if (isNaN(x) || isNaN(y)) + return + moveMapTo(x,y); + } + else if (document.getElementById('mct1').checked) + { + e = document.getElementById('mcpo'); + var p = e.options[e.selectedIndex].value; + mapCType = 1; + if (p == 0) + return; + mapParm = p; + if (map.containsKey('p'+p)) + { + var po = map.get('p'+p); + moveMapTo(parseInt(po.x,10), parseInt(po.y,10)); + } + else + { + loadingMap = true; + x_moveMapToId(p, gotMapData); + } + } + else + { + e = document.getElementById('mcpn').value; + mapCType = 2; + if (e == "") + return; + mapParm = e; + if (map.containsKey('pn-'+e.toLowerCase())) + { + var po = map.get('pn-'+e.toLowerCase()); + moveMapTo(parseInt(po.x,10), parseInt(po.y,10)); + } + else + { + loadingMap = true; + x_moveMapToName(e, gotMapData); + } + } +} + +function moveMap(dx,dy) +{ + if (loadingMap) + return; + + mapCType = 0; + moveMapTo(dMapX + dx, dMapY + dy); +} + +function moveMapTo(nx, ny) +{ + if (map.containsKey(nx + ';' + ny)) + { + dMapX = nx; dMapY = ny; + drawGalacticMap(); + } + else + { + loadingMap = true; + x_getMapData(nx, ny, gotMapData); + } +} + +function setDestination(id) +{ + if (id == newDest) + return; + newDest = id; + document.getElementById('sdok').disabled = (newDest == moveTo); + updateDestLine(); +} + +function confirmSetDestination() +{ + moveTo = newDest; + if (locations.containsKey(moveTo)) + moveToLoc = locations.get(moveTo); + else + moveToLoc = null; + cancelSetDestination(); +} + +function cancelSetDestination() +{ + drawOrdersPage(); + updateOrdersPage(); +} + + +function viewTrajectory() +{ + if (autoLock) + { + setTimeout('viewTrajectory()', 500); + return; + } + lockUpdate(); + + var i, f = null; + for (i=0;i1)) + x_cancelSales(s.join('#'), salesCanceled); + else + unlockUpdate(); +} + +function salesCanceled(str) +{ + if (str.indexOf('ERR#') == 0) + { + var l = str.split('#'); + alertCancelSales(parseInt(l[1], 10)); + unlockUpdate(); + } + else + { + prepareUpdate(); + parseMainData(str); + } +} + + +function viewSaleSelected() +{ + if (autoLock) + { + setTimeout('viewSaleSelected()', 500); + return; + } + lockUpdate(); + + var id = null; + for (i=0;i pb.toLowerCase() ? 1 : -1)))); +} + +function sortPPList(ia,ib) { + var a=ppList.get(ia).fleetLocation,b=ppList.get(ib).fleetLocation; + return (a.x!=b.x + ?(parseInt(a.x,10)-parseInt(b.x,10)) + :(a.y!=b.y + ?(parseInt(a.y,10)-parseInt(b.y,10)) + :(parseInt(a.orbit,10)-parseInt(b.orbit,10)) + ) + ); +} + +function sortAPList(ia,ib) { + var a=apList.get(ia).fleetLocation,b=apList.get(ib).fleetLocation; + return (a.details.owner==b.details.owner + ?(a.x!=b.x + ?(parseInt(a.x,10)-parseInt(b.x,10)) + :(a.y!=b.y + ?(parseInt(a.y,10)-parseInt(b.y,10)) + :(parseInt(a.orbit,10)-parseInt(b.orbit,10)) + ) + ):(players.get(a.details.owner).toLowerCase()>players.get(b.details.owner).toLowerCase()?1:-1)); +} + +function sortOPList(ia,ib) +{ + var a=opList.get(ia).fleetLocation,b=opList.get(ib).fleetLocation; + return (a.x!=b.x + ?(parseInt(a.x,10)-parseInt(b.x,10)) + :(a.y!=b.y + ?(parseInt(a.y,10)-parseInt(b.y,10)) + :(parseInt(a.orbit,10)-parseInt(b.orbit,10)) + ) + ); +} + +function sortPlanetFleets(ia,ib) +{ + var a=fleets.get(ia),b=fleets.get(ib); + return (a.owner == b.owner + ? (parseInt(b.power,10)-parseInt(a.power,10)) + : (a.owner == myself.id ? -1 : (b.owner == myself.id ? 1 : + (a.mode == 0 && b.mode == 1 ? -1 : (a.mode == 1 && b.mode == 0 ? 1 : + (players.get(a.owner).toLowerCase()>players.get(b.owner).toLowerCase() ? 1 : -1) + )) + )) + ); +} + +function sortMovingFleets(a,b) +{ + return (a.owner == b.owner + ? (a.orders.eta == b.orders.eta + ? (parseInt(b.power,10)-parseInt(a.power,10)) + : (parseInt(a.orders.eta,10)-parseInt(b.orders.eta,10)) + ) : (a.owner == myself.id ? -1 : (b.owner == myself.id ? 1 : + (players.get(a.owner).toLowerCase()>players.get(b.owner).toLowerCase() ? 1 : -1) + )) + ); +} + +function sortWaitingFleets(a,b) +{ + return (a.owner == b.owner + ? (a.orders.left == b.orders.left + ? (parseInt(b.power,10)-parseInt(a.power,10)) + : (parseInt(a.orders.left,10)-parseInt(b.orders.left,10)) + ) : (a.owner == myself.id ? -1 : (b.owner == myself.id ? 1 : + (players.get(a.owner).toLowerCase()>players.get(b.owner).toLowerCase() ? 1 : -1) + )) + ); +} + +function sortOrderFleets(ia,ib) +{ + var a=fleets.get(ia),b=fleets.get(ib); + return (a.owner == b.owner + ? (parseInt(b.power,10)-parseInt(a.power,10)) + : (a.owner == myself.id ? -1 : (b.owner == myself.id ? 1 : + (players.get(a.owner).toLowerCase()>players.get(b.owner).toLowerCase() ? 1 : -1) + )) + ); +} + +function sortAvailFleets(a,b) +{ + return (a.owner == b.owner + ? (a.orders.oType == b.orders.oType && a.mode == b.mode + ? (parseInt(b.power,10)-parseInt(a.power,10)) + : (a.orders.oType * 2 + parseInt(a.mode,10) - b.orders.oType * 2 - parseInt(b.mode,10)) + ) : (a.owner == myself.id ? -1 : (b.owner == myself.id ? 1 : + (players.get(a.owner).toLowerCase()>players.get(b.owner).toLowerCase() ? 1 : -1) + )) + ); +} + + +function sortSalesKeys(ia,ib) +{ + var a=locations.get(ia),b=locations.get(ib); + return (a.x!=b.x + ?(parseInt(a.x,10)-parseInt(b.x,10)) + :(a.y!=b.y + ?(parseInt(a.y,10)-parseInt(b.y,10)) + :(parseInt(a.orbit,10)-parseInt(b.orbit,10)) + )); +} diff --git a/site/static/beta5/js/pg_forums-en.js b/site/static/beta5/js/pg_forums-en.js new file mode 100644 index 0000000..fc25632 --- /dev/null +++ b/site/static/beta5/js/pg_forums-en.js @@ -0,0 +1,53 @@ +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.'); +} diff --git a/site/static/beta5/js/pg_forums.js b/site/static/beta5/js/pg_forums.js new file mode 100644 index 0000000..86d224a --- /dev/null +++ b/site/static/beta5/js/pg_forums.js @@ -0,0 +1,10 @@ +function countSelected() +{ + var n = 0, i = 0, e; + while (e = document.getElementById('msel' + i)) + { + n += e.checked ? 1 : 0; + i++; + } + return n; +} diff --git a/site/static/beta5/js/pg_map-en.js b/site/static/beta5/js/pg_map-en.js new file mode 100644 index 0000000..70f506c --- /dev/null +++ b/site/static/beta5/js/pg_map-en.js @@ -0,0 +1,79 @@ +var uncharted = '(uncharted)'; +var dirs = ['Up', 'Down', 'Left', 'Right']; +var hdNames = ['Coordinates', 'Planet', 'Tag', 'Opacity']; +var lcHeader = ['Protected', null, 'Target']; +var yesNo = ['Yes', 'No']; + +function makeMapTooltips() +{ + maptt = new Array(); + if (ttDelay == 0) + { + for (i=0;i<31;i++) + maptt[i] = ""; + return; + } + maptt[0] = tt_Dynamic("Use this drop down list to change the information displayed on the map"); + maptt[1] = tt_Dynamic("Use this radio button to center the map on the typed in coordinates"); + maptt[2] = tt_Dynamic("Use this text field to enter the horizontal coordinate you want the map to be centered on."); + maptt[3] = tt_Dynamic("use this text field to enter the vertical coordinate you want the map to be centered on."); + maptt[4] = tt_Dynamic("Use this drop down list to select the size of the grid in which the map is displayed."); + maptt[5] = tt_Dynamic("Use this radio button to center the map on chosen own planet"); + maptt[6] = tt_Dynamic("Use this drop down list to select the planet you own to use to center the map"); + maptt[7] = tt_Dynamic("Click here to update the display according to the provided parameters"); + maptt[8] = tt_Dynamic("Use this radio button to center the map on chosen planet"); + maptt[9] = tt_Dynamic("Use this text field to enter the name of a planet to search for; if it exists the map will be centered on the system in which it is located."); + maptt[10] = tt_Dynamic("Click this button to move the display towards positive vertical coordinates."); + maptt[11] = tt_Dynamic("Click this button to move the display towards negative horizontal coordinates."); + maptt[12] = tt_Dynamic("Click this button to move the display towards positive horizontal coordinates."); + maptt[13] = tt_Dynamic("Click this button to move the display towards negative vertical coordinates."); + maptt[20] = tt_Dynamic("Click here to order the list according to this field and switch between descending and ascending order"); + maptt[30] = tt_Dynamic("Click here to go to the individual page of this planet"); +} + + +function drawMapControls() +{ + var str = '
    '; + var i; + + str += ''; + + str += ''; + str += ''; + + str += ''; + str += ''; + str += ''; + str += '' + switch (gameType) { + case '0': str += ''; break; + case '1': str += ''; break; + case '2': str += ''; break; + } + str += ''; + + str += '
    Display:Centre on'; + str += ' '; + str += 'coordinates (,)Map colors
    Grid size:or'; + str += ' '; + str += 'own planet Nebula 1Nebula 2Nebula 3Nebula 4
     or'; + str += ' '; + str += 'planet OwnAlliedOtherProtected Target
    '; + document.getElementById('jsctrls').innerHTML = str; +} diff --git a/site/static/beta5/js/pg_map.js b/site/static/beta5/js/pg_map.js new file mode 100644 index 0000000..be77761 --- /dev/null +++ b/site/static/beta5/js/pg_map.js @@ -0,0 +1,824 @@ +var gameType; +var map; +var maptt; + +var centreMode = 1; +var centreOnTxt; +var centreOnOwn; + +var plPlanets; +var inUpdate = true; + + +//-------------------------------------------------------------------------------------- +// PLANETS AND NEBULAS +//-------------------------------------------------------------------------------------- + +function Planet(system,id,name,status,relation,tag) +{ + this.id = id; + this.name = name.replace(/&/g,'&').replace(//g,'>'); + this.status = status; + this.opacity = (status == "1") ? 1 : 0; + this.relation = relation; + this.tag = tag; + this.system = system; + + this.drawName = Planet_drawName; + this.drawTag = Planet_drawTag; +} + +function Planet_drawName() +{ + var str = ''; + if (this.opacity) { + str += '[P]'; + } else { + str += '[P]'; + } + str += '' + this.name + ''; + return str; +} + +function Planet_drawTag() +{ + var str = ''; + if (this.opacity) { + str += '[P]N/A'; + } else { + str += '[P]' + + '[' + this.tag + ']'; + } + return str; +} + + +function Nebula(id,opacity,name) +{ + this.id = id; + this.name = name; + this.opacity = opacity; + + this.drawName = Nebula_drawName; + this.drawTag = Nebula_drawTag; +} + +function Nebula_drawName() +{ + var str = '' + this.name + ''; + return str; +} + +function Nebula_drawTag() +{ + var str = 'N/A'; + return str; +} + + +//-------------------------------------------------------------------------------------- +// SYSTEMS AND UNCHARTED SPACE +//-------------------------------------------------------------------------------------- + +function System(id,x,y,md5,nebula,prot) +{ + this.id = id; + this.x = parseInt(x, 10); + this.y = parseInt(y, 10); + this.md5 = md5; + this.nebula = nebula; + this.protection = (prot != '0'); + this.planets = new Array(); + this.update = Math.round((new Date().getTime()) / 1000); + + this.drawPlanets = System_drawPlanets; + this.drawAlliances = System_drawAlliances; +} + +function System_drawPlanets() +{ + var str = ''; + + var i; + for (i=0;i'; +} + +function System_drawAlliances() +{ + var str = '
    '; + + var i; + for (i=0;i'; +} + + +function Uncharted() +{ + this.md5 = '-'; + this.update = Math.round((new Date().getTime()) / 1000); + this.drawPlanets = Uncharted_getCode; + this.drawAlliances = Uncharted_getCode; +} + +function Uncharted_getCode() +{ + var i, str = '
    '; + for (i=0;i<6;i++) + str += ''; + return str + '
    '+uncharted+'
    '; +} + + +//-------------------------------------------------------------------------------------- +// THE MAP +//-------------------------------------------------------------------------------------- + +function MapListItem(s,p,o) +{ + this.system = s; + this.planet = p; + this.orbit = o; +} + +function Map(mode) +{ + this.systems = new Hashtable(); + this.cx = false; + this.cy = false; + this.size = false; + this.refresh = ''; + this.mode = mode; + + this.list = new Array(); + this.sortType = 1; + this.sortDir = false; + + this.setParm = Map_setParm; + this.checkUpdates = Map_checkUpdates; + this.parse = Map_parse; + this.clearCache = Map_clearCache; + this.draw = Map_draw; + + this.mapGridBegin = Map_gridBegin; + this.mapGridEnd = Map_gridEnd; + this.mapListBegin = Map_listBegin; + this.mapListEnd = Map_listEnd; + + this.mapList = Map_list; + this.mapPlanets = Map_planets; + this.mapAlliances = Map_alliances; +} + +function Map_setParm(cx,cy,size) +{ + if (cx == this.cx && cy == this.cy && size == this.size) + return; + + this.cx = cx; + this.cy = cy; + this.size = size; + + var mod; + mod = this.size % 2; + this.minX = this.cx + (mod - this.size) / 2; + this.maxX = this.cx + (this.size + mod - 2) / 2; + this.minY = this.cy + (mod - this.size) / 2; + this.maxY = this.cy + (this.size + mod - 2) / 2; + + this.checkUpdates(); +} + +function Map_checkUpdates() +{ + + var i,j,as = new Array(), str; + var now = Math.round((new Date().getTime()) / 1000); + for (i=this.minX;i<=this.maxX;i++) for (j=this.minY;j<=this.maxY;j++) + { + var str = i + ',' + j, s = this.systems.get(str); + + if (!s) + as.push(str + ','); + else if (now - s.update > 60) + { + as.push(str + ',' + s.md5); + s.update = now; + } + } + this.refresh = as.join('#'); +} + +function Map_clearCache() +{ + var i, s, lk = this.systems.keys(), nk = 0; + var now = Math.round((new Date().getTime()) / 1000); + var Mx=this.maxX+4,mx=this.minX-4, My=this.maxY+4,my=this.minY-4; + + for (i=0;i=mx&&s.y<=My&&s.y>=my) || nk > 50 || now-s.update<300) + { + nk ++; + continue; + } + this.systems.remove(lk[i]); + } +} + +function Map_parse(data) +{ + var l = data.split('\n'); + var st = 0, s, pdat, i; + var mod = '#'; + + while (l.length) + { + i = l.shift(); + if (st == 0) + { + var a = i.split('#'); + if (a[0] == '') { + s = new Uncharted(); + } else { + s = new System(a[0], a[1], a[2], a[3], a[4], a[5]); + st = (a[4] > 0) ? 3 : 1; + } + this.systems.put(a[1] + "," + a[2], s); + mod += a[1] + "," + a[2] + '#'; + } else if (st == 1) { + pdat = i.split('#'); + st ++; + } + else if (st == 2) + { + s.planets.push(new Planet(s, pdat.shift(), i, pdat.shift(), pdat.shift(), pdat.join('#'))); + st = (s.planets.length == 6) ? 0 : 1; + } + else if (st == 3) + { + pdat = i.split('#'); + s.planets.push(new Nebula(pdat.shift(), pdat.shift(), pdat.join('#'))); + if (s.planets.length == 6) + st = 0; + } + } + + this.refresh = ""; +} + +function Map_draw() +{ + var str; + switch (this.mode) + { + case 0: str = this.mapGridBegin() + this.mapPlanets() + this.mapGridEnd(); break; + case 1: str = this.mapGridBegin() + this.mapAlliances() + this.mapGridEnd(); break; + case 2: str = this.mapListBegin() + this.mapList() + this.mapListEnd(); break; + } + document.getElementById('jsmapint').innerHTML = str; +} + +function Map_gridBegin() +{ + var i, tmp = ""; + tmp += ""; + tmp += ""; + tmp += ""; + + tmp += ""; + for (i=this.minX;i<=this.maxX;i++) + tmp += ""; + tmp += ""; + return tmp; +} + +function Map_gridEnd() +{ + var i, tmp = ""; + for (i=this.minX;i<=this.maxX;i++) + tmp += ""; + tmp += ""; + tmp += ""; + tmp += ""; + tmp += ""; + tmp += "
     -X"; + tmp += ""+dirs[0]+"X 
    Y"+i+"Y
    -Y "+i+" -Y
     -X"; + tmp += ""+dirs[1]+"X 
    "; + return tmp; +} + +function Map_planets() +{ + var j, i, str = ''; + for (j=this.maxY;j>=this.minY;j--) + { + str += ''; + if (j == this.maxY) + { + str += ""; + str += ""+dirs[2]+""; + } + str += ""+j+""; + for (i=this.minX;i<=this.maxX;i++) + str += '' + this.systems.get(i+','+j).drawPlanets() + ''; + str += ""+j+""; + if (j == this.maxY) + { + str += ""; + str += ""+dirs[3]+""; + } + str += ''; + } + return str; +} + +function Map_alliances() +{ + var j, i, str = ''; + for (j=this.maxY;j>=this.minY;j--) + { + str += ''; + if (j == this.maxY) + { + str += ""; + str += ""+dirs[2]+""; + } + str += ""+j+""; + for (i=this.minX;i<=this.maxX;i++) + str += '' + this.systems.get(i+','+j).drawAlliances() + ''; + str += ""+j+""; + if (j == this.maxY) + { + str += ""; + str += ""+dirs[3]+""; + } + str += ''; + } + return str; +} + +function Map_listBegin() +{ + var hdTypes = ['Coord','Name','Alliance','Opacity','Protected']; + var hdCols = ['', ' class="pname" colspan="2"', '', '', '']; + var i,j,o; + + this.list = new Array(); + for (i=this.minX;i<=this.maxX;i++) for (j=this.minY;j<=this.maxY;j++) + { + var s = this.systems.get(i+','+j); + if (!s || !s.planets) + continue; + for (k=0;k<6;k++) + this.list.push(new MapListItem(s,s.planets[k],k+1)); + } + var smet = 'this.list.sort(spl_' + hdTypes[this.sortType] + '_' + (this.sortDir ? "desc" : "asc") + ')'; + eval(smet); + + var str = ''; + if (hdNames.length == 4 && gameType != 1) { + hdNames.push(lcHeader[gameType]); + } + for (i = 0; i < hdNames.length; i ++) { + str += ''; + if (this.sortType == i) { + str += ''; + } + str += hdNames[i]; + if (this.sortType == i) { + str += '' + (this.sortDir ? dirs[0] : dirs[1]) + ''; + } + str += ''; + } + str += ''; + + return str; +} + +function Map_listEnd() +{ + return '
    '; +} + +function Map_list() { + var i, str = ''; + for (i = 0; i < this.list.length; i ++) { + str += ','; + str += this.list[i].orbit + ')'; + str += ''; + } else { + str += ' class="pimg">[P]'; + } + } else { + str += ' class="pname" colspan="2">'; + } + str += '' + + this.list[i].planet.name + ''; + if (this.list[i].system.nebula == 0) { + str += '[' + this.list[i].planet.tag + ']'; + } else { + str += '-'; + } + str += '' + this.list[i].planet.opacity; + if (gameType != 1) { + str += '' + (this.list[i].system.protection == 0 ? yesNo[1] : yesNo[0]); + } + str += ''; + } + return str; +} + + +//-------------------------------------------------------------------------------------- +// INITIALISATION AND GENERIC FUNCTIONS +//-------------------------------------------------------------------------------------- + +function PlayerPlanet(id, name) +{ + this.id = id; + this.name = name; +} + +function mapInit(data) +{ + gameType = document.getElementById('gametype').innerHTML; + + // Receiving: mode followed by x#y#is_id#id_or_name followed by (id#name)* + var l = data.split('\n'); + var m = parseInt(l.shift(), 10); + var a = l.shift().split('#'); + + plPlanets = new Array(); + while (l.length > 0) + { + var p = l.shift().split('#'); + plPlanets.push(new PlayerPlanet(p.shift(), p.join('#'))); + } + + centreOn = (a[2] == 1) ? a[3] : ""; + centreOnOwn = (a[2] == 0) ? a[3] : 0; + + map = new Map(m); + map.setParm(parseInt(a[0],10),parseInt(a[1],10),5); + drawMapLayout(); + + x_updateData(map.refresh, mapDataReceived); + setTimeout('maintainMap()', 11000); +} + +function drawMapLayout() +{ + var str = " Help" + + "
     "; + document.getElementById('jsmap').innerHTML = "" + str + "
    "; + drawMapControls(); + setTimeout('x_getPlayerPlanets(playerPlanetsReceived)', 300000); +} + +function playerPlanetsReceived(data) +{ + plPlanets = new Array(); + if (data != "") + { + var l = data.split('\n'); + while (l.length > 0) + { + var p = l.shift().split('#'); + plPlanets.push(new PlayerPlanet(p.shift(), p.join('#'))); + } + } + drawPlayerList(); + setTimeout('x_getPlayerPlanets(playerPlanetsReceived)', 300000); +} + +function mapDataReceived(data) +{ + if (data != "") + map.parse(data); + map.draw(); + inUpdate = false; +} + +function maintainMap() +{ + if (inUpdate) + { + setTimeout('maintainMap()', 500); + return; + } + inUpdate = true; + map.clearCache(); + map.checkUpdates(); + refreshMap(); + setTimeout('maintainMap()', 11000); +} + +function refreshMap() +{ + if (map.refresh == '') + { + map.draw(); + inUpdate = false; + } + else + x_updateData(map.refresh, mapDataReceived); +} + +function resultReceived(data) +{ + if (data == "ERR") + { + inUpdate = false; + return; + } + + var a = data.split('#'); + if (a[2] == 1) + { + centreOn = a[3]; + centreMode = 2; + document.getElementById('cm2').checked = true; + document.getElementById('cp').value = centreOn; + } + else + { + centreOnOwn = a[3]; + centreMode = 1; + document.getElementById('cm1').checked = true; + drawPlayerList(); + } + document.getElementById('cx').value = a[0]; + document.getElementById('cy').value = a[1]; + + map.setParm(parseInt(a[0],10),parseInt(a[1],10),map.size); + refreshMap(); +} + +function drawPlayerList() +{ + var i, str = ''; + document.getElementById('plpl').innerHTML = str; +} + + +//-------------------------------------------------------------------------------------- +// CONTROLS +//-------------------------------------------------------------------------------------- + +function setSize(s) +{ + if (inUpdate) + { + setTimeout('setSize('+s+')', 500); + return; + } + inUpdate = true; + map.setParm(map.cx,map.cy,parseInt(s,10)); + refreshMap(); +} + +function setMode(m) +{ + if (inUpdate) + { + setTimeout('setMode("'+m+'")', 500); + return; + } + + inUpdate = true; + map.mode = parseInt(m,10); + map.draw(); + inUpdate = false; +} + +function moveUp() +{ + if (inUpdate) + { + setTimeout('moveUp()', 50); + return; + } + inUpdate = true; + document.getElementById('cy').value = map.cy + 1; + document.getElementById('cm0').checked = true; + centreMode = 0; + map.setParm(map.cx, map.cy+1, map.size); + refreshMap(); +} + +function moveDown() +{ + if (inUpdate) + { + setTimeout('moveDown()', 50); + return; + } + inUpdate = true; + document.getElementById('cy').value = map.cy - 1; + document.getElementById('cm0').checked = true; + centreMode = 0; + map.setParm(map.cx, map.cy-1, map.size); + refreshMap(); +} + +function moveLeft() +{ + if (inUpdate) + { + setTimeout('moveLeft()', 50); + return; + } + inUpdate = true; + document.getElementById('cx').value = map.cx - 1; + document.getElementById('cm0').checked = true; + centreMode = 0; + map.setParm(map.cx-1, map.cy, map.size); + refreshMap(); +} + +function moveRight() +{ + if (inUpdate) + { + setTimeout('moveRight()', 50); + return; + } + inUpdate = true; + document.getElementById('cx').value = map.cx + 1; + document.getElementById('cm0').checked = true; + centreMode = 0; + map.setParm(map.cx+1, map.cy, map.size); + refreshMap(); +} + +function manualUpdate() +{ + if (inUpdate) + { + setTimeout('manualUpdate()', 500); + return; + } + inUpdate = true; + + if (centreMode == 0) + { + var tx, nx, ty, ny; + tx = document.getElementById('cx').value; ty = document.getElementById('cy').value; + if (tx == '-0') tx = '0'; + if (ty == '-0') ty = '0'; + nx = parseInt(tx,10); ny = parseInt(ty,10); + if (tx != nx.toString() || ty != ny.toString()) + { + inUpdate = false; + return; + } + map.setParm(nx, ny, map.size); + refreshMap(); + } + else if (centreMode == 1) + { + var i; + for (i=0;ib.system.x); + else if (a.system.y!=b.system.y) + sup = (a.system.y>b.system.y); + else + sup = (a.orbit>b.orbit); + return sup?1:-1; +} + +function spl_Coord_desc(a,b) +{ + var sup; + if (a.system.x!=b.system.x) + sup = (a.system.x>b.system.x); + else if (a.system.y!=b.system.y) + sup = (a.system.y>b.system.y); + else + sup = (a.orbit>b.orbit); + return sup?-1:1; +} + +function spl_Name_asc(a,b) +{ + var ra = a.planet.name.toLowerCase(); var rb = b.planet.name.toLowerCase(); + return (ra < rb) ? -1 : 1; +} + +function spl_Name_desc(a,b) +{ + var ra = a.planet.name.toLowerCase(); var rb = b.planet.name.toLowerCase(); + return (ra > rb) ? -1 : 1; +} + +function spl_Alliance_asc(a,b) +{ + var ra, rb; + ra = (a.system.nebula == 0) ? a.planet.tag.toLowerCase() : ""; + rb = (b.system.nebula == 0) ? b.planet.tag.toLowerCase() : ""; + return (ra < rb) ? -1 : (ra > rb ? 1 : spl_Name_asc(a,b)); +} + +function spl_Alliance_desc(a,b) +{ + var ra, rb; + ra = (a.system.nebula == 0) ? a.planet.tag.toLowerCase() : ""; + rb = (b.system.nebula == 0) ? b.planet.tag.toLowerCase() : ""; + return (ra < rb) ? 1 : (ra > rb ? -1 : spl_Name_desc(a,b)); +} + +function spl_Opacity_asc(a,b) +{ + var ra = parseInt(a.planet.opacity, 10); var rb = parseInt(b.planet.opacity, 10); + return (ra < rb) ? -1 : ((ra > rb) ? 1 : spl_Coord_asc(a,b)); +} + +function spl_Opacity_desc(a,b) +{ + var ra = parseInt(a.planet.opacity, 10); var rb = parseInt(b.planet.opacity, 10); + return (ra < rb) ? 1 : ((ra > rb) ? -1 : spl_Coord_desc(a,b)); +} + +function spl_Protected_asc(a,b) { + var ra = a.system.protection, rb = b.system.protection; + return (ra && !rb) ? -1 : ((rb && ! ra) ? 1 : spl_Coord_asc(a, b)); +} + +function spl_Protected_desc(a,b) { + var ra = a.system.protection, rb = b.system.protection; + return (ra && !rb) ? 1 : ((rb && ! ra) ? -1 : spl_Coord_desc(a, b)); +} diff --git a/site/static/beta5/js/pg_market-en.js b/site/static/beta5/js/pg_market-en.js new file mode 100644 index 0000000..68a3289 --- /dev/null +++ b/site/static/beta5/js/pg_market-en.js @@ -0,0 +1,411 @@ +var menuText = ['Public Offers','Direct Offers','Sent Offers']; +var searchText = ['Search for planet: ', 'Search for location: ']; +var prhStatus = ['Cancelled', 'Transfer Cancelled', 'Accepted', 'Expired', 'Rejected']; +var soModes = ['Gift', 'Private Sale', 'Public Sale', 'Auction Sale']; + +// Listing texts, defaults +var notFound = 'No items found.'; +var listingText = ['Display %1% items / page', 'Page %1% / %2%']; +// Planet sales +var noPSales = '

    There are no planets for sale in this area.

    '; +var noPSalesFound = 'No matching planets were found.'; +var psListingText = ['Display %1% planets / page', 'Page %1% / %2%']; +var psHeaders = ['Planet', 'Coords.', 'Pop.', 'Turrets', 'Fact.', 'Fleets', 'Owner', 'Expiration', 'Price', 'Action']; +// Fleet sales +var noFSales = '

    There are no fleets for sale in this area.

    '; +var noFSalesFound = 'No matching locations were found.'; +var fsListingText = ['Display %1% planets / page', 'Page %1% / %2%']; +var fsHeaders = ['Location', 'Coords.', 'G.A.S.', 'Fgt.', 'Cru.', 'B.Cru.', 'Owner', 'Expiration', 'Price', 'Action'] +// Pending private offers +var noPOffers = '

    There are no offers pending our approval.

    '; +var noPOffersFound = 'No offers matching this location.

    '; +var proHeaders = ['Received', 'Sender', 'Price', 'Expiration', 'Details']; +// Private offers history +var noPHist = '

    The history is empty.

    '; +var noPHistFound = 'No archived offers matching this location.

    '; +var prhHeaders = ['Received', 'Sender', 'Price', 'Last Change', 'Status', 'Details']; +// Pending sent offers +var noSOffers = '

    We have not offered to sell or give anything recently.

    '; +var noSOffersFound = 'No offers matching this location.

    '; +var sopHeaders = ['Date', 'Offer Type', 'Price', 'Expiration', 'To Player', 'Details']; +// Private offers history +var noSHist = '

    The history is empty.

    '; +var noSHistFound = 'No archived offers matching this location.

    '; +var sohHeaders = ['Last Change', 'Status', 'Sent', 'Offer Type', 'Price', 'To Player', 'Details']; + + +function drawPublicLayout() +{ + var str = ''; + + str += ''; + + // Map table + str += '

    Planets For Sale

     
    '; + str += '

     

    Fleets For Sale

     
    '; + str += ''; + str += ''; + str += '
     Up 
    Left '; + str += 'Right
     Down 
    '; + + // Centre map on coords... + str += '

     

    '; + str += ''; + str += ''; + str += ''; + + // Centre map on own planet... + str += ''; + str += ''; + str += ''; + + // Centre map on planet... + str += ''; + str += ''; + str += ''; + + // Distance + str += '
    '; + str += '
     '; + str += '(,)
    '; + str += '
     '; + str += '
    '; + str += '
     '; + str += '

    Distance:

    '; + + str += '
    '; + document.getElementById('mkppage').innerHTML = str; + + // Draw listings + pfsListing.draw(); + ffsListing.draw(); +} + + +function drawPSFleets(v) +{ + if (!v.fleet) + return "No fleet"; + return "G: " + v.fleet[1] + "; F: " + v.fleet[2] + "; C: " + v.fleet[3] + "; B: " + v.fleet[4] + ""; +} + +function drawPSAction(v) +{ + var s = sellers.get(v.seller); + if (s.isMe) + return 'Cancel Sale'; + else if (v.auction) + return 'Place Bid'; + return 'Buy'; +} + +function drawFSAction(v) +{ + var s = sellers.get(v.seller); + if (s.isMe) + return 'Cancel Sale'; + else if (v.auction) + return 'Place Bid'; + return 'Buy'; +} + +function drawExpire(v) +{ + if (v.expires == '') + return 'Never'; + return formatDate(v.expires); +} + + +// Messages for planet sales + +function confirmPCancel(p) +{ + return confirm('You are about to cancel the public sale of planet ' + p.name + '.\nPlease confirm.'); +} + +function confirmBuyPlanet(p) +{ + return confirm('Are you sure you want to buy planet ' + p.name + ' for\n' + formatNumber(p.price) + ' euros?'); +} + +function planetBought() +{ + alert('Planet has been bought. Control will be transfered to you in 4h, unless\nthe transfer is canceled by the current owner or he loses the planet.'); +} + +function postPBuyError(err) { + if (err == '0') { + alert('Unable to buy the planet: the offer has expired.'); + } else if (err == '1') { + alert('Unable to buy the planet: not enough cash.'); + } else if (err == '200') { + alert('You can\'t use the marketplace while on vacation.'); + } else if (err == '201') { + alert('You can\'t use the marketplace while under Peacekeeper protection.'); + } else{ + alert('Unable to buy the planet: unknown error.'); + } +} + +function confirmBidPlanet(p) { + return prompt('Please place your bid for planet ' + p.name + '.\nMinimum bid: ' + formatNumber(p.price) + ' euros.', (parseInt(p.price)+1).toString()); +} + +function pBidError(p) { + alert('Minimum bid for planet ' + p.name + ' is ' + formatNumber(p.price) + ' euros.\nYour bid must be higher than this.'); +} + +function postPBidError(d) { + if (d=='0') { + alert('Unable to place the bid: the offer has expired.'); + startUpdate(); + } else if (d=='1') { + alert('Unable to place the bid: not enough cash.'); + startUpdate(); + } else if (d == '200') { + alert('You can\'t use the marketplace while on vacation.'); + startUpdate(); + } else if (err == '201') { + alert('You can\'t use the marketplace while under Peacekeeper protection.'); + startUpdate(); + } else { + var a = d.split('#'); + var p = getPlanetOffer(a[1]); + if (!p) { + alert('An unknown error has occured.'); + startUpdate(); + return; + } + alert('Unable to place the bid on planet ' + p.name + '\nSomeone has placed a new bid in the meantime.\nThe minimum price is now ' + formatNumber(a[2])); + p.price = a[2]; + pfsListing.updateDisplay(); + doPlacePBid(p.offer); + } +} + + +// Messages for fleet sales + +function getFleetComposition(f) +{ + var a = new Array(); + if (f.fleet[0] > 0) a.push(f.fleet[0] + ' GA Ship' + (f.fleet[0] > 1 ? 's' : '')); + if (f.fleet[1] > 0) a.push(f.fleet[1] + ' Fighter' + (f.fleet[1] > 1 ? 's' : '')); + if (f.fleet[2] > 0) a.push(f.fleet[2] + ' Cruiser' + (f.fleet[2] > 1 ? 's' : '')); + if (f.fleet[3] > 0) a.push(f.fleet[3] + ' Battle Cruiser' + (f.fleet[3] > 1 ? 's' : '')); + return a.join(', '); +} + +function confirmFCancel(f) +{ + return confirm( + 'You are about to cancel the public sale of the fleet located at ' + f.plName + '.\n' + + 'Fleet composition: ' + getFleetComposition(f) + '.\nPlease confirm.' + ); +} + +function confirmBuyFleet(f) +{ + return confirm( + 'Are you sure you want to buy the fleet located at ' + f.plName + ' for\n' + formatNumber(f.price) + ' euros?\n' + + 'Fleet composition: ' + getFleetComposition(f) + '.\n'); +} + +function fleetBought() +{ + alert('The fleet has been bought. Control will be transfered to you in 4h, unless\nthe transfer is canceled by the current owner or the fleet is destroyed.'); +} + +function postFBuyError(err) { + if (err == '0') { + alert('Unable to buy the fleet: the offer has expired.'); + } else if (err == '1') { + alert('Unable to buy the fleet: not enough cash.'); + } else if (err == '200') { + alert('You can\'t use the marketplace while on vacation.'); + } else if (err == '201') { + alert('You can\'t use the marketplace while under Peacekeeper protection.'); + } else { + alert('Unable to buy the fleet: unknown error.'); + } +} + +function confirmBidFleet(f) { + return prompt( + 'Please place your bid for the fleet located at ' + f.plName + '.\n' + + 'Fleet composition: ' + getFleetComposition(f) + '.\n' + + 'Minimum bid: ' + formatNumber(f.price) + ' euros.', + (parseInt(f.price,10) + 1).toString()); +} + +function fBidError(f) { + alert('Minimum bid for fleet at ' + f.plName + ' is ' + formatNumber(f.price) + ' euros.\nYour bid must be higher than this.'); +} + +function postFBidError(d) { + if (d=='0') { + alert('Unable to place the bid: the offer has expired.'); + startUpdate(); + } else if (d=='1') { + alert('Unable to place the bid: not enough cash.'); + startUpdate(); + } else if (d == '200') { + alert('You can\'t use the marketplace while on vacation.'); + startUpdate(); + } else if (err == '201') { + alert('You can\'t use the marketplace while under Peacekeeper protection.'); + startUpdate(); + } else { + var a = d.split('#'); + var p = getFleetOffer(a[1]); + if (!p) { + alert('An unknown error has occured.'); + startUpdate(); + return; + } + alert('Unable to place the bid on fleet at ' + f.plName + '\nSomeone has placed a new bid in the meantime.\nThe minimum price is now ' + formatNumber(a[2])); + p.price = a[2]; + ffsListing.updateDisplay(); + doPlaceFBid(p.offer); + } +} + + +// Private offers + +function drawPrivateLayout() +{ + var str = '

    Pending Offers

     

     

    '; + str += '

    History

     
    '; + document.getElementById('mkppage').innerHTML = str; + + // Draw listings + proListing.draw(); + prhListing.draw(); +} + +function drawPRPrice(i) + { return i.price != 0 ? ('€' + formatNumber(i.price)) : 'Free'; } + +function drawPRDetails(i) +{ + var str = ''; + var s, j, ts = ['G.A. Ship', 'Fighter', 'Cruiser', 'Battle Cruiser'], r = new Array(); + if (i.planet[0] != '') + { + str = 'The planet '+i.pName+' at coordinates (' + i.x + ',' + i.y + ',' + i.orbit + '): '; + str += '' + formatNumber(i.planet[1]) + 'M inhabitants, ' + formatNumber(i.planet[2]) + ' turrets and '; + str += formatNumber(i.planet[3]) + ' factories'; + if (i.fleet[0] != '') + { + str += ' along with a fleet of '; + for (j=0;j<4;j++) + { + if (i.fleet[1+j] == 0) + continue; + s = '' + formatNumber(i.fleet[1+j]) + ' ' + ts[j]; + if (i.fleet[1+j] > 1) + s += 's'; + r.push(s); + } + s = r.pop(); + str += r.join(', ') + (r.length > 0 ? ' and ' : '') + s; + } + str += '.'; + } + else + { + str += 'A fleet of '; + for (j=0;j<4;j++) + { + if (i.fleet[1+j] == 0) + continue; + s = '' + formatNumber(i.fleet[1+j]) + ' ' + ts[j]; + if (i.fleet[1+j] > 1) + s += 's'; + r.push(s); + } + s = r.pop(); + str += r.join(', ') + (r.length > 0 ? ' and ' : '') + s; + str += ' located in orbit around ' + i.pName + ' (' + i.x + ',' + i.y + ',' + i.orbit + ').'; + } + if (!i.aDate && i.buyer == null) + { + str += '
    [ Accept offer - '; + str += 'Decline offer ]'; + } + else if (!i.aDate && i.buyer != null) + str += '
    [ Cancel ]'; + + return str; +} + +function confirmBuyPrivate(p) +{ + var str = 'Are you sure you want to accept this private offer?'; + if (p.price > 0) + str += '\nIt will cost you ' + formatNumber(p.price) + ' euros.'; + return confirm(str); +} + +function confirmDeclinePrivate() +{ + return confirm('Please confirm that you want to decline this offer.'); +} + +function privateError(err) { + if (err == '0') { + alert('Unable to accept the offer: it has expired.'); + } else if (err == '1') { + alert('Unable to accept the offer: not enough cash.'); + } else if (err == '200') { + alert('You can\'t use the marketplace while on vacation.'); + } else if (err == '201') { + alert('You can\'t use the marketplace while under Peacekeeper protection.'); + } else { + alert('Unable to accept the offer: unknown error.'); + } +} + + +// Sent offers + +function drawSentLayout() +{ + var str = '

    Pending Offers

     

     

    '; + str += '

    History

     
    '; + document.getElementById('mkppage').innerHTML = str; + + // Draw listings + sopListing.draw(); + sohListing.draw(); +} + +function drawSentBuyer(v) +{ + if (v.buyer == '') + return "None"; + + var s = sellers.get(v.buyer); + return (s.isMe||s.quit ? '' : ('')) + s.name + (s.isMe||s.quit ? '' : ''); +} + +function confirmCancelSale() +{ + return confirm('You are about to cancel an offer. Please confirm.'); +} diff --git a/site/static/beta5/js/pg_market.js b/site/static/beta5/js/pg_market.js new file mode 100644 index 0000000..16035b1 --- /dev/null +++ b/site/static/beta5/js/pg_market.js @@ -0,0 +1,1731 @@ +var currentPage, pageUpdate, layout = -1; +var pageHandlers = ['Public','Private','Sent']; + + +//---------------------------------------------------------------------------------------------- +// LISTINGS +//---------------------------------------------------------------------------------------------- + +function ListingColumn(title,hdSpan,tdcl,custom,dc,sa,sd) +{ + this.title = title; + this.hdSpan = hdSpan; + this.colClass = tdcl; + this.drawContents = (custom ? null : dc); + this.drawColumn = (custom ? dc : null); + this.sortAsc = sa; + this.sortDesc = sd; +} + +function Listing(element,list,varname,slabel) +{ + this.element = element; + this.list = list; + this.varName = varname; + this.searchLabel = slabel; + this.columns = new Array(); + this.lines = new Array(); + this.sortBy = 0; + this.sortDir = false; + this.searchText = ''; + this.perPage = 20; + this.indices = new Array(); + this.nPages = 0; + this.page = 0; + this.emptyText = notFound; + this.notFound = notFound; + + this.addColumn = Listing_addColumn; + + this.draw = Listing_draw; + this.searchFor = Listing_searchFor; + this.updateDisplay = Listing_updateDisplay; + this.setPage = Listing_setPage; + this.setSort = Listing_setSort; + + this.updateIndices = Listing_updateIndices; + this.makeAll = Listing_makeAll; + this.makeList = Listing_makeList; + this.makePage = Listing_makePage; +} + +function Listing_addColumn(column, lIdx) +{ + if (!this.lines[lIdx]) + this.lines[lIdx] = new Array(); + this.columns.push(column); + this.lines[lIdx].push(this.columns.length - 1); +} + +function Listing_draw() +{ + this.updateIndices(); + document.getElementById(this.element).innerHTML = this.makeAll(); +} + +function Listing_searchFor(text) +{ + if (this.searchText == text) + return; + this.searchText = text; + this.updateDisplay(); +} + +function Listing_updateDisplay() +{ + var vals; + eval('vals='+this.list); + if (vals.length && document.getElementById('selpage_'+this.element)) + { + this.updateIndices(); + document.getElementById('selpage_'+this.element).innerHTML = this.makePage(); + document.getElementById('lstcontents_'+this.element).innerHTML = this.makeList(); + } + else + this.draw(); +} + +function Listing_setPage(np) +{ + if (this.page == np) + return; + this.page = np; + document.getElementById('lstcontents_'+this.element).innerHTML = this.makeList(); +} + +function Listing_setSort(ns) +{ + this.sortBy = ns; + this.sortDir = !this.sortDir; + this.updateDisplay(); +} + +function Listing_updateIndices() +{ + var i,vals; + eval('vals='+this.list); + + this.indices = new Array(); + if (this.searchText == "") + for (i=0;i0?1:0); + if (this.nPages > 0 && this.page >= this.nPages) + this.page = this.nPages - 1; +} + +function Listing_makeAll() +{ + var vals,str; + eval('vals='+this.list); + if (vals.length) + { + var i; + str = ''; + str += ''; + + str += ''; + str += ''; + + str += '
    '; + + var s2 = ''; + + str += listingText[0].replace(/%1%/,s2) + ''; + str += this.makePage() + '
    '; + str += '

    ' + this.makeList() + '
    '; + } + else + str = this.emptyText; + return str; +} + +function Listing_makePage() +{ + var str; + if (this.nPages == 0) + str = ' '; + else if (this.nPages == 1) + str = listingText[1].replace(/%.%/g, '1'); + else + { + var i,s2 = ''; + str = listingText[1].replace(/%1%/,s2).replace(/%2%/,this.nPages); + } + return str; +} + +function Listing_makeList() +{ + if (this.indices.length == 0) + return '

    ' + this.notFound + '
    '; + + var i,j,str = ''; + + if (this.lines.length > 1) + str += ''; + + var vals,k,f = this.page * this.perPage; + var l = Math.min(f + this.perPage, this.indices.length); + eval('vals='+this.list); + for (i=f;i 1) + str += ''; + } + str += '
    '; + for (j=0;j' : '') + title; + if (this.sortBy == idx) + { + str += '';
+				str += (this.sortDir ? '; + } + str += ''; + }} + str += ''; + } + if (this.lines.length > 1) + str += '
    '; + for (k=0;k 1) + str += '
    '; + + return str; +} + + +//------------------------------------------------------------------------ +// PUBLIC OFFERS OBJECTS +//------------------------------------------------------------------------ + +var mapCType, mapX, mapY, mapDist, mapParm; +var myPlanets, sysType, sysProt, sysPlanets; +var pOffers, fOffers, sellers; + + +function SysPlanet(id,status,own,ally,name) +{ + this.id = id; + this.status = status; + this.ownership = own ? 2 : (ally ? 1 : 0); + this.name = name; +} + +function Seller(id,isMe,quit,name) +{ + this.id = id; + this.isMe = isMe; + this.quit = quit; + this.name = name; +} + +function PublicPlanetOffer(offer,expires,id,seller,x,y,orbit,price,auction,pop,tur,fact,name) +{ + this.offer = offer; + this.expires = expires; + this.id = id; + this.seller = seller; + this.x = x; + this.y = y; + this.orbit = orbit; + this.price = price; + this.auction = auction; + this.population = pop; + this.turrets = tur; + this.fact = fact; + this.name = name; + this.fleet = null; +} + +function PublicFleetOffer(offer,expires,id,seller,x,y,orbit,plId,price,auction,sg,sf,sc,sb,name) +{ + this.offer = offer; + this.expires = expires; + this.id = id; + this.seller = seller; + this.x = x; + this.y = y; + this.orbit = orbit; + this.plId = plId; + this.price = price; + this.auction = auction; + this.plName = name; + this.fleet = [sg, sf, sc, sb]; + + this.match = PublicFleetOffer_match; +} + +function PublicFleetOffer_match(t) + { return this.plName.toLowerCase().indexOf(t.toLowerCase()) != -1; } + + +//------------------------------------------------------------------------ +// PAGE INIT. & AUTO-UPDATE +//------------------------------------------------------------------------ + +function lockUpdate(p) +{ + if (!pageUpdate || p!=currentPage) + return false; + var p = pageUpdate; + pageUpdate = null; + clearTimeout(p); + return true; +} + +function startUpdate() +{ + if (pageUpdate) + return; + pageUpdate = setTimeout('updateData()', 60000); +} + +function drawMenu() +{ + var i,str = ''; + for (i=0;i' : (''); + str += menuText[i] + (i == currentPage ? '
    ' : ''); + } + document.getElementById('mkppsel').innerHTML = str; +} + +function dataReceived(data) +{ + var s = pageHandlers[currentPage]; + eval('parse' + s + 'Offers(data)'); + if (layout != currentPage) + { + drawMenu(); + eval('draw' + s + 'Layout()'); + layout = currentPage; + } + eval('draw' + s + 'Page()'); + startUpdate(); +} + +function updateData() +{ + if (!lockUpdate(currentPage)) + { + setTimeout('updateData()', 500); + return; + } + var s = 'x_get' + pageHandlers[currentPage] + 'Offers(dataReceived)'; + eval(s); +} + +function initMarket() +{ + var e = document.getElementById('mkpfpage'); + if (!e) + return; + currentPage = parseInt(e.innerHTML, 10); + + // Public offers, Planets + pfsListing = new Listing('plsale', 'pOffers','pfsListing',searchText[0]); + pfsListing.addColumn(new ListingColumn(psHeaders[0], 2, 'pname', true, drawPSName, sortPSNameAsc, sortPSNameDesc), 0); + pfsListing.addColumn(new ListingColumn(psHeaders[1], 1, 'coords', false, drawCoords, sortPSCrdAsc, sortPSCrdDesc), 0); + pfsListing.addColumn(new ListingColumn(psHeaders[2], 1, 'pdat', false, drawPSPop, sortPSPopAsc, sortPSPopDesc), 0); + pfsListing.addColumn(new ListingColumn(psHeaders[3], 1, 'pdat', false, drawPSTurrets, sortPSTurAsc, sortPSTurDesc), 0); + pfsListing.addColumn(new ListingColumn(psHeaders[4], 1, 'pdat', false, drawPSFactories, sortPSFacAsc, sortPSFacDesc), 0); + pfsListing.addColumn(new ListingColumn(psHeaders[5], 1, 'pflt', false, drawPSFleets, sortPSFltAsc, sortPSFltDesc), 0); + pfsListing.addColumn(new ListingColumn(' ', 1, 'lspc', false, function () { return ' ' }, null, null), 1); + pfsListing.addColumn(new ListingColumn(psHeaders[6], 1, 'seller', false, drawSeller, sortPSSelAsc, sortPSSelDesc), 1); + pfsListing.addColumn(new ListingColumn(psHeaders[7], 2, 'sexp', false, drawExpire, sortPSExpAsc, sortPSExpDesc), 1); + pfsListing.addColumn(new ListingColumn(psHeaders[8], 2, 'price', false, drawPrice, sortPSPriAsc, sortPSPriDesc), 1); + pfsListing.addColumn(new ListingColumn(psHeaders[9], 1, 'action', false, drawPSAction, sortPSActAsc, sortPSActDesc), 1); + pfsListing.emptyText = noPSales; pfsListing.notFound = noPSalesFound; + + // Public offers, Fleets + ffsListing = new Listing('flsale','fOffers','ffsListing',searchText[1]); + ffsListing.addColumn(new ListingColumn(fsHeaders[0], 3, 'pname', true, drawFSLocation, sortFSLocAsc, sortFSLocDesc), 0); + ffsListing.addColumn(new ListingColumn(fsHeaders[1], 1, 'coords', false, drawCoords, sortFSCrdAsc, sortFSCrdDesc), 0); + ffsListing.addColumn(new ListingColumn(fsHeaders[2], 1, 'ships', false, drawFSGAShips, sortFSGASAsc, sortFSGASDesc), 0); + ffsListing.addColumn(new ListingColumn(fsHeaders[3], 1, 'ships', false, drawFSFighters, sortFSFgtAsc, sortFSFgtDesc), 0); + ffsListing.addColumn(new ListingColumn(fsHeaders[4], 1, 'ships', false, drawFSCruisers, sortFSCruAsc, sortFSCruDesc), 0); + ffsListing.addColumn(new ListingColumn(fsHeaders[5], 1, 'ships', false, drawFSBCruisers, sortFSBCrAsc, sortFSBCrDesc), 0); + ffsListing.addColumn(new ListingColumn(' ', 1, 'lspc', false, function () { return ' ' }, null, null), 1); + ffsListing.addColumn(new ListingColumn(fsHeaders[6], 1, 'seller', false, drawSeller, sortFSSelAsc, sortFSSelDesc), 1); + ffsListing.addColumn(new ListingColumn(fsHeaders[7], 2, 'sexp', false, drawExpire, sortFSExpAsc, sortFSExpDesc), 1); + ffsListing.addColumn(new ListingColumn(fsHeaders[8], 2, 'price', false, drawPrice, sortFSPriAsc, sortFSPriDesc), 1); + ffsListing.addColumn(new ListingColumn(fsHeaders[9], 2, 'action', false, drawFSAction, sortFSActAsc, sortFSActDesc), 1); + ffsListing.emptyText = noFSales; ffsListing.notFound = noFSalesFound; + + // Pending private offers + proListing = new Listing('prpending', 'prOffers', 'proListing', searchText[0]); + proListing.addColumn(new ListingColumn(proHeaders[0], 1, 'rdate', false, drawPRReceived, sortPRRecAsc, sortPRRecDesc), 0); + proListing.addColumn(new ListingColumn(proHeaders[1], 1, 'seller', false, drawSeller, sortPRSelAsc, sortPRSelDesc), 0); + proListing.addColumn(new ListingColumn(proHeaders[2], 1, 'price', false, drawPRPrice, sortPRPriAsc, sortPRPriDesc), 0); + proListing.addColumn(new ListingColumn(proHeaders[3], 1, 'sexp', false, drawExpire, sortPRExpAsc, sortPRExpDesc), 0); + proListing.addColumn(new ListingColumn(' ', 1, 'lspc', false, function () { return ' ' }, null, null), 1); + proListing.addColumn(new ListingColumn(proHeaders[4], 3, 'details', false, drawPRDetails, null, null), 1); + proListing.emptyText = noPOffers; proListing.notFound = noPOffersFound; proListing.perPage = 5; proListing.sortDir = true; + + // Private offers history + prhListing = new Listing('prhistory', 'prHistory', 'prhListing', searchText[0]); + prhListing.addColumn(new ListingColumn(prhHeaders[3], 1, 'rdate', false, drawPHActionDate, sortPHADtAsc, sortPHADtDesc), 0); + prhListing.addColumn(new ListingColumn(prhHeaders[0], 1, 'rdate', false, drawPRReceived, sortPHRecAsc, sortPHRecDesc), 0); + prhListing.addColumn(new ListingColumn(prhHeaders[4], 1, 'hstat', false, drawPHStatus, sortPHStaAsc, sortPHStaDesc), 0); + prhListing.addColumn(new ListingColumn(prhHeaders[1], 1, 'seller', false, drawSeller, sortPHSelAsc, sortPHSelDesc), 0); + prhListing.addColumn(new ListingColumn(prhHeaders[2], 1, 'price', false, drawPRPrice, sortPHPriAsc, sortPHPriDesc), 0); + prhListing.addColumn(new ListingColumn(' ', 1, 'lspc', false, function () { return ' ' }, null, null), 1); + prhListing.addColumn(new ListingColumn(prhHeaders[5], 4, 'details', false, drawPRDetails, null, null), 1); + prhListing.emptyText = noPHist; prhListing.notFound = noPHistFound; prhListing.perPage = 5; prhListing.sortDir = true; + + // Pending sent offers + sopListing = new Listing('sopending', 'soPending', 'sopListing', searchText[0]); + sopListing.addColumn(new ListingColumn(sopHeaders[0], 1, 'rdate', false, drawPRReceived, sortSOSntAsc, sortSOSntDesc), 0); + sopListing.addColumn(new ListingColumn(sopHeaders[1], 1, 'otype', false, drawSentMode, sortSOModAsc, sortSOModDesc), 0); + sopListing.addColumn(new ListingColumn(sopHeaders[2], 1, 'price', false, drawSentPrice, sortSOPriAsc, sortSOPriDesc), 0); + sopListing.addColumn(new ListingColumn(sopHeaders[3], 1, 'sexp', false, drawExpire, sortSOExpAsc, sortSOExpDesc), 0); + sopListing.addColumn(new ListingColumn(sopHeaders[4], 1, 'buyer', false, drawSentBuyer, sortSOBuyAsc, sortSOBuyDesc), 0); + sopListing.addColumn(new ListingColumn(' ', 1, 'lspc', false, function () { return ' ' }, null, null), 1); + sopListing.addColumn(new ListingColumn(sopHeaders[5], 4, 'details', false, drawPRDetails, null, null), 1); + sopListing.emptyText = noSOffers; sopListing.notFound = noSOffersFound; sopListing.perPage = 5; sopListing.sortDir = true; + + // Sent offers, history + sohListing = new Listing('sohistory', 'soHistory', 'sohListing', searchText[0]); + sohListing.addColumn(new ListingColumn(sohHeaders[0], 1, 'rdate', false, drawPHActionDate, sortSHADtAsc, sortSHADtDesc), 0); + sohListing.addColumn(new ListingColumn(sohHeaders[1], 1, 'hstat', false, drawPHStatus, sortSHStaAsc, sortSHStaDesc), 0); + sohListing.addColumn(new ListingColumn(sohHeaders[2], 1, 'rdate', false, drawPRReceived, sortSHSntAsc, sortSHSntDesc), 0); + sohListing.addColumn(new ListingColumn(sohHeaders[3], 1, 'otype', false, drawSentMode, sortSHModAsc, sortSHModDesc), 0); + sohListing.addColumn(new ListingColumn(sohHeaders[4], 1, 'price', false, drawSentPrice, sortSHPriAsc, sortSHPriDesc), 0); + sohListing.addColumn(new ListingColumn(sohHeaders[5], 1, 'buyer', false, drawSentBuyer, sortSHBuyAsc, sortSHBuyDesc), 0); + sohListing.addColumn(new ListingColumn(' ', 1, 'lspc', false, function () { return ' ' }, null, null), 1); + sohListing.addColumn(new ListingColumn(sohHeaders[6], 5, 'details', false, drawPRDetails, null, null), 1); + sohListing.emptyText = noSHist; sohListing.notFound = noSHistFound; sohListing.perPage = 5; sohListing.sortDir = true; + + e = document.getElementById('mkpinit').innerHTML; + if (e.indexOf('\n') == -1 && e.split('#').length > 2) + { + var s = 'x_get' + pageHandlers[currentPage] + 'Offers(dataReceived)'; + eval(s); + } + else + dataReceived(e); +} + +function selectPage(nb) +{ + if (!lockUpdate(currentPage)) + return; + currentPage = nb; + var s = 'x_get' + pageHandlers[currentPage] + 'Offers(dataReceived)'; + eval(s); +} + + +//------------------------------------------------------------------------ +// PUBLIC OFFERS DATA & ACTIONS +//------------------------------------------------------------------------ + +function parsePublicOffers(data) +{ + var l = data.split('\n'); + var a = l.shift().split('#'); + + // Get map parameters + mapCType = parseInt(a.shift(), 10); mapX = parseInt(a.shift(), 10); + mapY = parseInt(a.shift(), 10); mapDist = parseInt(a.shift(), 10); + mapParm = (mapCType > 0) ? a.join('#') : ''; + + // Get own planet list + var i, np = parseInt(l.shift(),10); + myPlanets = new Hashtable(); + for (i=0;i -1) + { + sysPlanets = new Array(); + for (i=0;i<6;i++) + { + a = l.shift().split("#"); + sysPlanets.push(new SysPlanet(a.shift(),a.shift(),(a.shift()=='1'),(a.shift()=='1'),a.join('#'))); + } + } + + // Read offers + pOffers = new Array(); fOffers = new Array(); sellers = new Hashtable(); + a = l.shift().split('#'); + var s='',nop=parseInt(a[0],10),nof=parseInt(a[1],10); + if (nop+nof == 0) + return; + np = 0; + + // Planet offers + for (i=0;i'; + else + str += 'prem_s.png" alt="[P]" />'; + str += '' + name + ''; + }} + } + else + { + for (i=0;i<6;i++) + {with(sysPlanets[i]){ + str += '' + name + ''; + }} + } + + str += ''; + document.getElementById('mapcnt').innerHTML = str; +} + +function drawPublicPage() +{ + drawSystemMap(); + pfsListing.updateDisplay(); + ffsListing.updateDisplay(); +} + +function updateMapControls() +{ + var i; + for (i=0;i<3;i++) + document.getElementById('mct'+i).checked = (i==mapCType); + document.getElementById('cx').value = mapX; + document.getElementById('cy').value = mapY; + + var e = document.getElementById('mcpo'); + for (i=0;i' + v.x + ',' + v.y + '
    ,'+v.orbit+')'; } +function drawSeller(v) +{ + var s = sellers.get(v.seller); + return (s.isMe||s.quit ? '' : ('')) + s.name + (s.isMe||s.quit ? '' : ''); +} +function drawPrice(v) + { return '€' + formatNumber(v.price); } + +// Planet sales listing functions +function drawPSName(v) +{ + var str = '[P]'; + str += ''+v.name+''; + return str; +} +function sortPSNameAsc(a,b) + { var ra = pOffers[a].name.toLowerCase(), rb = pOffers[b].name.toLowerCase(); return (ra > rb) ? 1 : -1; } +function sortPSNameDesc(a,b) + { var ra = pOffers[a].name.toLowerCase(), rb = pOffers[b].name.toLowerCase(); return (ra > rb) ? -1 : 1; } + +function sortPSCrdAsc(a,b) +{ + var va = pOffers[a]; var vb = pOffers[b]; + return (va.x>vb.x)?1:(va.xvb.y?1:(va.yvb.orbit?1:-1)))); +} +function sortPSCrdDesc(a,b) +{ + var va = pOffers[a]; var vb = pOffers[b]; + return (va.x>vb.x)?-1:(va.xvb.y?-1:(va.yvb.orbit?-1:1)))); +} + +function drawPSPop(v) + { return formatNumber(v.population); } +function sortPSPopAsc(a,b) + { var ra=parseInt(pOffers[a].population,10),rb=parseInt(pOffers[b].population,10); return (ra>rb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(ra'; + str += ''+v.plName+''; + return str; +} +function sortFSLocAsc(a,b) + { var ra = fOffers[a].plName.toLowerCase(), rb = fOffers[b].plName.toLowerCase(); return (ra>rb)?1:(rarb)?-1:(ravb.x)?1:(va.xvb.y?1:(va.yvb.orbit?1:(va.orbitvb.x)?-1:(va.xvb.y?-1:(va.yvb.orbit?-1:(va.orbitrb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:-1; } +function sortFSIdDesc(a,b) + { var ra=parseInt(fOffers[a].id,10),rb=parseInt(fOffers[b].id,10); return (ra>rb)?-1:1; } + + +//------------------------------------------------------------------------ +// PRIVATE OFFERS DATA & ACTIONS +//------------------------------------------------------------------------ + +var prOffers, prHistory; +var proListing, prhListing; + +function PrivateOffer(id, seller, price, x, y, orbit, planet, fleet, started, expires, pName) +{ + this.id = id; + this.seller = seller; + this.price = price; + this.x = x; + this.y = y; + this.orbit = orbit; + this.started = started; + this.expires = expires; + this.pName = pName; + this.planet = [planet, 0, 0, 0]; + this.fleet = [fleet, 0, 0, 0, 0]; + this.match = PrivateOffer_match; +} + +function PrivateHistory(seller, price, x, y, orbit, isPlanet, hasFleet, started, action, aDate, pName) +{ + this.seller = seller; + this.price = price; + this.x = x; this.y = y; this.orbit = orbit; + this.planet = [isPlanet, 0, 0, 0]; + this.fleet = [hasFleet, 0, 0, 0, 0]; + this.started = started; this.action = action; this.aDate = aDate; + this.pName = pName; + + this.match = PrivateOffer_match; +} + +function PrivateOffer_match(t) + { return this.pName.toLowerCase().indexOf(t.toLowerCase()) != -1; } + +function parsePrivateOffers(data) +{ + var l = data.split('\n'); + var i, a = l.shift().split('#'); + var nOffers = a[0], nHistory = a[1]; + var s='',ns=0; + + // Read offers data + prOffers = new Array(); + for (i=0;irb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(rarb)?1:(rarb)?-1:(raCompose a message'; + str += ''; + + str += ''; + if (page == "Folders") + str += 'Folders'; + else + str += 'Folders'; + str += ''; + + var i, ic; + for (i=0;i '; + str += ''; + str += ic ? '' : (''); + if (folders[i].nCount > 0) + str += "" + if (folders[i].type == 'CUS') + str += folders[i].name; + else if (folders[i].type == 'IN') + str += 'Inbox'; + else if (folders[i].type == 'INT') + str += 'Internal transmissions'; + else + str += 'Sent messages'; + if (folders[i].nCount > 0) + str += " (" + folders[i].nCount + ")"; + str += ic ? '' : ''; + str += ''; + } + str += ' '; + str += 'Forums'; + str += ''; + + document.getElementById('mmenu').innerHTML = str; +} + + + +function drawComposePage() +{ + var i, a = new Array('player', 'planet', 'alliance'); + var str = '

    Compose a message

    '; + str += ''; + + if (pCompose.sent) + str += ''; + else if (pCompose.errors[0] != 0) + { + str += ''; + } + str += ''; + + if (pCompose.errors[1] != 0) + { + str += ''; + } + str += ''; + + if (pCompose.errors[2] != 0) + { + str += ''; + } + str += ''; + str += ''; + str += '

    Your message has been sent

     '; + switch (pCompose.errors[0]) + { + case 1: str += 'Please specify message recipient.'; break; + case 2: str += 'Recipient not found.'; break; + } + str += '
    Message to:
     '; + switch (pCompose.errors[1]) + { + case 1: str += 'Please specify message subject.'; break; + case 2: str += 'Subject is too long (max. 64 characters).'; break; + } + str += '
    Subject:
     '; + switch (pCompose.errors[2]) + { + case 1: str += 'You are not allowed to send empty messages.'; break; + } + str += '
    Message:
     
    '; + + if (pCompose.rId != -1) + str += '

    Replying to...

    ' + pCompose.rText + '

    '; + + str += '
    '; + document.getElementById('mbody').innerHTML = str; +} + + + +function drawPageNotFound() +{ + document.getElementById('mbody').innerHTML = '

    Folder not found

    The folder you are trying to browse cannot be found.

    '; +} + + + +function drawFolderLayout() +{ + var str = '

    "'; + switch (folders[vFolder].type) + { + case "IN": str += "Inbox"; break; + case "INT": str += "Internal Transmissions"; break; + case "OUT": str += "Sent Messages"; break; + case "CUS": str += folders[vFolder].name; break; + } + str += '" Folder

    Loading browser...

    '; + document.getElementById('mbody').innerHTML = str; +} + + +function drawFolderControls(s) +{ + var i, str = '
    '; + str += ''; + + str += ''; + } + + str += '
    '; + + str += 'Messages per page:
    '; + document.getElementById('msglist2').innerHTML = str; + + if (crc) + { + drawMainMenu(); + updateHeader(); + } +}} + +function switchSort(t) +{with(folders[vFolder]){ + if (settings[1] != t) + settings[1] = t; + settings[2] = !settings[2]; + x_setSortParameters(type, id, settings[1], settings[2] ? "1" : "0", emptyCB); + buildMessageDisplay(); +}} + +function setMsgPerPage() +{with(folders[vFolder]){ + var sel = document.getElementById('fcmpp'), si = sel.selectedIndex; + settings[0] = parseInt(sel.options[si].value, 10) * 10; + x_setMessagesPerPage(type, id, sel.options[si].value, emptyCB); + buildMessageDisplay(); +}} + +function switchThreaded() +{with(folders[vFolder]){ + settings[3] = !settings[3]; + x_switchThreaded(type, id, emptyCB); + buildMessageDisplay(); +}} + +function changePage() +{with(folders[vFolder]){ + var sel = document.getElementById('pgs'), si = sel.selectedIndex; + settings[4] = parseInt(sel.options[si].value, 10); + buildMessageDisplay(); +}} + +function switchOpen(i) +{with(folders[vFolder]){ + messages[i].isopen = !messages[i].isopen; + if (messages[i].isopen && messages[i].message == "") + x_getMessageText(messages[i].id, gotMessageText); + else + drawList(); +}} + +function gotMessageText(data) +{ + var i, a = data.split('\n'); + var mId = a[0].split('#'); + if (mId.length == 1) + return; + idx = findFolder(mId[0], mId[1]); + if (idx == -1) + return; + with (folders[idx]) + { + for (i=0;i 2) + { + a.shift(); + a.shift(); + messages[i].message = a.join('\n'); + } + else + messages[i].message = msgNotFound; + } + drawList(); +} + +function switchSelection(idx) +{with(folders[vFolder]){ + messages[idx].selected = !messages[idx].selected; + if (!messages[idx].selected && selAll) + { + selAll = false; + document.getElementById('mlsa').checked = false; + } + updateControls(); +}} + +function switchSelAll() +{with(folders[vFolder]){ + var i,j=settings[0]*settings[4]; + selAll = !selAll; + for (i=0;i 0) + drawSFControls(fs, cd); + else + document.getElementById('chgfld').innerHTML = ' '; +} + +function renameFolder(fid) +{ + var i = findFolder("CUS", fid); + if (i == -1) + return; + var nn = ""; + while (nn == "") + { + nn = promptRename(folders[i].name); + if (typeof nn == 'object' && !nn) + return; + if (nn == "") + alertFolderName(0); + else if (nn.length > 32) + { + alertFolderName(1); + nn = ""; + } + } + if (udFList) + clearTimeout(udFList); + x_renameFolder(fid, nn, parseFolderList); +} + +function addFolder() +{ + var n = document.getElementById('adfldn').value; + if (n == "") + { + alertFolderName(0); + return; + } + else if (n.length > 32) + { + alertFolderName(1); + return; + } + if (udFList) + clearTimeout(udFList); + x_addFolder(n, parseFolderList); +} + +function deleteFolders() +{ + var i, a = new Array(), tm = 0; + for (i=0;i 0 && !confirmFDelete(a.length, tm)) + return; + x_deleteFolders(a.join('#'), parseFolderList); +} + +function flushFolders() +{ + var i, a = new Array(), tm = 0; + for (i=0;iIt is impossible to transfer funds while on vacation mode.

    '; +} + + +function drawTransferWait(days) +{ + return '

    You need to wait another ' + days + ' day' + (days > 1 ? 's' : '') + + ' before being able to transfer funds.

    '; +} + + +function drawTransferForm() +{ + var str = '

    '; + str += 'Transfer € '; + str += 'to player '; + str += ' '; + str += '

    '; + return str; +} + + +function drawPlanetTableHdr() +{ + var str = ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + return str; +} + + +function drawPlanetTableFtr(n) +{ + var str = ''; + str += ''; + str += ''; + str += '
    PlanetBase incomeIndustrial FactoriesFactory IncomeFactory UpkeepTurret UpkeepExpenseCorruption CostProfit
    Total Daily Income:€' + formatNumber(n) + '
    '; + return str; +} + + +function drawFleetTableHdr() +{ + var str = ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + return str; +} + + +function drawFleetTableFtr(n) +{ + var str = ''; + str += ''; + str += ''; + str += '
    NameLocationDistanceDelayUpkeep
    Total Fleet Upkeep:€' + formatNumber(n) + '
    '; + return str; +} + + +function drawNoPlanets() +{ + return "

    You do not own any planet.

    "; +} + + +function drawNoFleets() +{ + return "

    You do not own any fleet.

    "; +} + + +function tfError(en) +{ + var str; + switch (en) + { + case 1: + str = 'You must indicate the amount to transfer.'; + break; + case 2: + str = 'You must indicate a player to transfer funds to.'; + break; + case 3: + str = 'The target player wasn\'t found. Please check the name.'; + break; + case 4: + str = 'You cannot transfer funds to yourself.'; + break; + case 5: + str = 'The target player cannot receive money yet.'; + break; + case 6: + str = 'You cheat! We already told you you can\'t transfer funds!'; + break; + case 7: + str = 'You can\'t transfer funds while under Peacekeeper protection.'; + break; + case 8: + str = 'This player is under Peacekeeper protection.'; + break; + case 9: + str = 'You don\'t have that much money.'; + break; + case 10: + str = 'This player is on vacation.'; + break; + default: + str = 'An unkown error has occured: ' + en; + break; + } + alert('Funds Transfer: error\n\n' + str); + +} + + +function confirmTransfer(am, pl) +{ + return confirm('Funds Transfer: please confirm\n\nYou are about to transfer '+formatNumber(am.toString())+' euro'+(am>1?'s':'')+'\nto player ' + pl); +} + + +function showTransferOk() +{ + alert('Funds have been transfered.'); +} diff --git a/site/static/beta5/js/pg_money.js b/site/static/beta5/js/pg_money.js new file mode 100644 index 0000000..6e6217f --- /dev/null +++ b/site/static/beta5/js/pg_money.js @@ -0,0 +1,133 @@ +var cfUpdate; + +var montt; + +function displayCash(data) +{ + document.getElementById("cfunds").innerHTML = '€' + formatNumber(data); + cfUpdate = setTimeout('x_getCash(displayCash);', 15000); +} + + +function displayPage(data) { + var a = data.split("\n"); + var b = a[0].split('#'); + var str, i, c = '', d = ''; + + if (b[0] == "0" && b[1] == "0") { + if (document.getElementById('tgo')) { + c = document.getElementById('tamt').value; + d = document.getElementById('tdst').value; + } + str = drawTransferForm(); + } else if (b[0] != "0") { + str = drawTransferWait(b[0]); + } else if (b[1] != "0") { + str = drawTransferVacation(); + } + document.getElementById('transfer').innerHTML = str; + if (b[0] == "0" && b[1] == "0") { + document.getElementById('tamt').value = c; + document.getElementById('tdst').value = d; + } + + var pcnt = parseInt(b[2],10), fcnt = parseInt(b[3], 10); + document.getElementById('pinc').innerHTML = '€' + formatNumber(b[4]); + document.getElementById('fupk').innerHTML = '€' + formatNumber(b[5]); + document.getElementById('dprof').innerHTML = formatNumber(b[6]); + + if (pcnt > 0) + { + str = drawPlanetTableHdr(); + for (i=0;i' + + '' + name + + '€' + formatNumber(pdat[2]) + '' + formatNumber(pdat[7]) + + '€' + formatNumber(pdat[3]) + '€' + formatNumber(pdat[4]) + + '€' + formatNumber(pdat[5]) + '€' + + formatNumber(tmp.toString()) + '€' + formatNumber(pdat[6]) + + '€' + formatNumber(pdat[1]) + ''; + } + str += drawPlanetTableFtr(b[4]); + } + else + str = drawNoPlanets(); + document.getElementById('planets').innerHTML = str; + + if (fcnt > 0) + { + str = drawFleetTableHdr(); + var nb = 1 + pcnt*2; + for (i=0;i' + fname; + str += '' + lname + '' + fdat[1]; + str += '' + fdat[2] + ''; + str += '€' + formatNumber(fdat[3]) + ''; + } + str += drawFleetTableFtr(b[5]); + } + else + str = drawNoFleets(); + document.getElementById('fleets').innerHTML = str; + + setTimeout('x_getCashDetails(displayPage)', 300000); +} + + +function transferOk(data) +{ + if (data == '0') + { + if (cfUpdate) + clearTimeout(cfUpdate); + x_getCash(displayCash); + updateHeader(); + document.getElementById('tamt').value = ''; + document.getElementById('tdst').value = ''; + showTransferOk(); + } + else + tfError(parseInt(data, 10)); + document.getElementById('tgo').disabled = false; +} + + +function transferFunds() +{ + var a, b; + document.getElementById('tgo').disabled = true; + + a = parseInt(document.getElementById('tamt').value, 10); + if (isNaN(a) || a <= 0) + { + tfError(1); + document.getElementById('tgo').disabled = false; + return; + } + + b = document.getElementById('tdst').value; + if (b == '') + { + tfError(2); + document.getElementById('tgo').disabled = false; + return; + } + + if (!confirmTransfer(a, b)) + { + document.getElementById('tgo').disabled = false; + return; + } + + x_transferFunds(b, a, transferOk); +} diff --git a/site/static/beta5/js/pg_overview-en.js b/site/static/beta5/js/pg_overview-en.js new file mode 100644 index 0000000..3e8419e --- /dev/null +++ b/site/static/beta5/js/pg_overview-en.js @@ -0,0 +1,247 @@ +function makeOverviewTooltips() +{ + ovett = new Array(); + if (ttDelay == 0) + { + var i; + for (i=0;i<23;i++) + ovett[i] = ""; + return; + } + ovett[0] = tt_Dynamic("Click here to switch to Complete Overview mode"); + ovett[1] = tt_Dynamic("Click here to go to your inbox and read your new external message(s)"); + ovett[2] = tt_Dynamic("Click here to go to your internal transmissin folder and read your new internal message(s)"); + ovett[3] = tt_Dynamic("Click here to go to the compose page and prepare a new message to be sent"); + ovett[4] = tt_Dynamic("Click here to go to this general forum main page"); + ovett[5] = tt_Dynamic("Click here to go to your alliance forum main page"); + ovett[10] = tt_Dynamic("Click here to switch to Short Overview mode"); + ovett[11] = tt_Dynamic("Click here to go directly to this folder"); + ovett[12] = tt_Dynamic("Click here to go directly to the planets overview page"); + ovett[13] = tt_Dynamic("Click here to go directly to the fleets overview page"); + ovett[14] = tt_Dynamic("Click here to go directly to the research management page"); + ovett[15] = tt_Dynamic("Click here to go directly to the money page"); + ovett[16] = tt_Dynamic("Click here to go to the main page for this forum category"); + ovett[17] = tt_Dynamic("Click here to go to this forum main page"); + ovett[18] = tt_Dynamic("Click here to go to you alliance forums main page"); + ovett[19] = tt_Dynamic("Click here to go to the maps page"); + ovett[20] = tt_Dynamic("Click here to go to the universe overview page"); + ovett[21] = tt_Dynamic("Click here to go to the ticks page"); + ovett[22] = tt_Dynamic("Click here to go to the rankings page"); +} + + +function makeNextText(name) +{ + return "Next " + name + ": "; +} + +function makeTopicsText(tot, n) +{ + if (tot == 0) + return "empty forum"; + var str = '' + formatNumber(tot) + ' topic' + (tot > 1 ? 's' : ''); + if (n == 0) + return str; + str += ' (' + formatNumber(n) + ' unread)'; + return str; +} + +function drawShortOverview() { + var str = ''; + + str += ''; + + str += ''; + str += '

    Empire

    Universe

    Messages

    You have '; + if (dFolders[0].nMsg > 0 && dFolders[1].nMsg > 0) + { + str += ''+formatNumber(dFolders[0].nMsg)+' external'; + str += ' and '+formatNumber(dFolders[1].nMsg)+' internal messages'; + } + else if (dFolders[0].nMsg > 0) + { + str += ''+formatNumber(dFolders[0].nMsg)+' external message'; + if (dFolders[0].nMsg > 1) + str += 's'; + } + else if (dFolders[1].nMsg > 0) + { + str += ''+formatNumber(dFolders[1].nMsg)+' internal message'; + if (dFolders[1].nMsg > 1) + str += 's'; + } + else + str += 'no new messages'; + str += '.
    Compose a message.

    '; + + str += '

    Planets

    ' + + (protection == 0 ? '' : ( + 'Under protection - ' + protection + ' day' + + (protection > 1 ? 's' : '') + ' left (' + + 'Break protection)
    ')) + + 'Planets owned: ' + plOverview[0] + '
    ' + + 'Total population: ' + formatNumber(plOverview[2]) + '
    ' + + 'Total factories: ' + formatNumber(plOverview[4]) + '' + + '

    ' + str += '

    Fleets

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

    '; + str += '

    Money

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

    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 += '

    Planets

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

    '; + str += '

    Next ticks

    '; + str += '

    Rankings

    General ranking: #'+formatNumber(rankings[2])+'
    '; + str += 'Round ranking: ' + (rankings[10] == '' ? 'N/A' : ('#' + formatNumber(rankings[10]))) + '

    '; + document.getElementById('overview').innerHTML = str; +} + +function drawCompleteOverview() { + var str = ''; + + str += ''; + + str += ''; + str += '

    Empire

    Universe

    Messages

    '; + var i,dfld = ['Inbox','Internal Transmissions','Outbox'],dcmd=['I','T','O']; + for (i=0;i<3;i++) + { + str += ''+dfld[i]+': ' + formatNumber(dFolders[i].tMsg); + str += ' message' + (dFolders[i].tMsg > 1 ? 's' : ''); + if (dFolders[i].nMsg > 0) + str += ' (' + formatNumber(dFolders[i].nMsg) + ' unread)'; + str += '
    '; + } + str += 'Compose a message.

    '; + + str += '

    Planets

    ' + + (protection == 0 ? '' : ( + 'Under protection - ' + protection + ' day' + + (protection > 1 ? 's' : '') + ' left (' + + 'Break protection)
    ')) + + 'Planets owned: '+plOverview[0]+''; + if (plOverview[0] > 0) + { + str += '
    Average happiness: ' + plOverview[1] + '%
    '; + str += 'Average corruption: '+formatNumber(plOverview[9])+'%
    '; + str += 'Total population: '+formatNumber(plOverview[2])+' (avg. ' + str += formatNumber(plOverview[3]) + ')
    '; + str += 'Total factories: '+formatNumber(plOverview[4])+' (avg. ' + str += formatNumber(plOverview[5]) + ')
    '; + str += 'Total turrets: '+formatNumber(plOverview[6])+' (avg. ' + str += formatNumber(plOverview[7]) + ')'; + } + str += '
    More details...

    '; + + str += '

    Fleets

    Total fleet power: '+formatNumber(flOverview[0])+'' + if (flOverview[1] > 0) + { + str += '
    ' + formatNumber(flOverview[1]) + ' fleet' + (flOverview[1]>1?'s':''); + if (flOverview[2] > 0) + str += ' (' + formatNumber(flOverview[2]) + ' engaged in battle)'; + } + str += '
    More details...

    '; + + str += '

    Research

    '; + if (nResearch == 0) + str += 'Sorry, no new technology has been discovered at this time.'; + else + str += '' + nResearch + ' new technolog' + (nResearch > 1 ? 'ies have' : 'y has') + ' been discovered.'; + str += '
    More details...

    '; + + str += '

    Money

    '; + str += 'Income: €'+formatNumber(moOverview[0])+'
    '; + str += 'Fleet Upkeep: €'+formatNumber(moOverview[1])+'
    '; + str += 'Daily Profit: €'+formatNumber(moOverview[2])+'
    '; + str += 'More details...

    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 += '

    Universe

    '+formatNumber(unOverview[0])+' planets';// ('; + str += /*formatNumber(unOverview[2]) + ' at the same prot. level)*/'
    '; + str += formatNumber(unOverview[1]) + ' systems occupied by nebulas
    '; + str += 'Maps - More details...

    '; + + str += '

    Next ticks

    More details...

    '; + str += '

    Rankings

    General ranking: #'+formatNumber(rankings[2])+' ('+formatNumber(rankings[1])+' points)
    '; + str += 'Civilisation ranking: #'+formatNumber(rankings[4])+' ('+formatNumber(rankings[3])+' points)
    '; + str += 'Military ranking: #'+formatNumber(rankings[8])+' ('+formatNumber(rankings[7])+' points)
    '; + str += 'Financial ranking: #'+formatNumber(rankings[6])+' ('+formatNumber(rankings[5])+' points)
    '; + str += 'Inflicted damage ranking: #'+formatNumber(rankings[12])+' ('+formatNumber(rankings[11])+' points)
    '; + if (rankings[10] == '') + str += 'You are too weak to be in the round rankings.'; + else + str += 'Round ranking: #' + formatNumber(rankings[10]) + ' ('+formatNumber(rankings[9])+' points)' + str += '
    More details...

    '; + document.getElementById('overview').innerHTML = str; +} + + +function getDaysText(p) +{ + return "day" + (p?'s':''); +} + + +function confirmBreakProtection() { + return confirm('You are about to break away from Peacekeeper protection.\n' + + 'Anyone will be able to attack your planets afterwards.\n' + + 'Please confirm.'); +} diff --git a/site/static/beta5/js/pg_overview.js b/site/static/beta5/js/pg_overview.js new file mode 100644 index 0000000..63a3b4c --- /dev/null +++ b/site/static/beta5/js/pg_overview.js @@ -0,0 +1,267 @@ +var dFolders, cFolders; +var genForums, aForums, allianceId; +var plOverview, flOverview, moOverview, nResearch; +var unOverview,stDiff,ticks,tUpdate,rankings; +var complete, protection, updateTimer; + +var ovett; + +function emptyCB(data) { } + + +function Tick(id,first,interval,last,name) +{ + this.id = id; + this.first = parseInt(first, 10); + this.interval = parseInt(interval, 10); + this.stopTime = (last != "") ? parseInt(last, 10) : -1; + this.name = name; + + this.started = true; + this.stDays = 0; + this.previous = 0; + this.next = 0; + this.last = 0; + this.remaining = 0; + + this.compute = Tick_compute; + this.draw = Tick_draw; +} + +function Tick_compute(st) +{ + this.started = (st >= this.first); + if (!this.started) + { + var tl = this.first - st; + this.stDays = Math.floor((tl - (tl % 86400)) / 86400); + this.next = this.first; + this.remaining = this.next - (st + this.stDays * 86400); + this.last = -1; + return; + } + else + this.stDays = 0; + + var f = this.first % 86400; + var n = (st % 86400); + var tm = n + ((n -1) + { + var s = (this.stopTime - this.first); + var m = s % this.interval; + this.last = this.first + s - m + } + else + this.last = -1; + + if (this.last != -1 && nx + st > this.last) + { + this.next = -1; + this.previous = this.last; + } + else + { + this.next = nx + st; + this.previous = this.next - this.interval; + this.remaining = nx; + } +} + +function Tick_draw() +{ + var str = makeNextText(this.name); + if (this.next != -1) + { + if (!this.started) + str += '' + this.stDays + ' ' + getDaysText(this.stDays > 1) + ', '; + str += '' + + var rh = (this.remaining - (this.remaining % 3600)) / 3600, + rm = (this.remaining - rh*3600 - (this.remaining % 60)) / 60, + rs = this.remaining - (rh*60+rm)*60; + var s = rh.toString(); + if (s.length == 1) + s = '0' + s; + str += s + ':'; + s = rm.toString(); + if (s.length == 1) + s = '0' + s; + str += s + ':'; + s = rs.toString(); + if (s.length == 1) + s = '0' + s; + str += s; + } + else + str += 'N/A'; + str += ''; + return str; +} + + +function Folder(id, tMsg, nMsg, name) +{ + this.id = id; + this.tMsg = tMsg; + this.nMsg = nMsg; + 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 initPage() { + overviewReceived(document.getElementById('init-data').value); +} + + +function updatePage() +{ + clearTimeout(tUpdate); + x_getOverview(overviewReceived); +} + + +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]; + + // Default folders + dFolders = new Array(); + for (i=0;i<3;i++) + { + a = l.shift().split('#'); + dFolders.push(new Folder('', a[0], a[1], '')); + } + + // Custom folders + cFolders = new Array(); + for (i=0;i 0) + { + a = l.shift().split('#'); + var t = new Tick(a.shift(), a.shift(), a.shift(), a.shift(), a.join('#')); + ticks.push(t); + } +} + + +function overviewReceived(data) { + var l = data.split('\n'); + + complete = (l.shift() == 1); + protection = parseInt(l.shift(), 10); + parseComms(l); + plOverview = l.shift().split('#'); + flOverview = l.shift().split('#'); + moOverview = l.shift().split('#'); + moOverview[2] = (parseInt(moOverview[0],10) - parseInt(moOverview[1],10)).toString(); + nResearch = l.shift(); + unOverview = l.shift().split('#'); + rankings = l.shift().split('#'); + parseTicks(l); + + drawOverviewPage(); + updateTimer = setTimeout('updatePage()', 60000); +} + + +function drawOverviewPage() +{ + if (complete) + drawCompleteOverview(); + else + drawShortOverview(); + drawTicks(); +} + +function drawTicks() +{ + var now = Math.round((new Date().getTime()) / 1000) - stDiff; + var i, str = ''; + for (i=0;i"; + } + document.getElementById('ticks').innerHTML = str; + tUpdate = setTimeout('drawTicks()', 1000); +} + +function switchMode() +{ + complete = !complete; + clearTimeout(tUpdate); + drawOverviewPage(); + x_switchOvMode(emptyCB); +} + +function breakProtection() { + if (!confirmBreakProtection()) { + return; + } + clearTimeout(updateTimer); + clearTimeout(tUpdate); + x_breakProtection(overviewReceived); +} diff --git a/site/static/beta5/js/pg_planet-en.js b/site/static/beta5/js/pg_planet-en.js new file mode 100644 index 0000000..fc5d4a8 --- /dev/null +++ b/site/static/beta5/js/pg_planet-en.js @@ -0,0 +1,658 @@ +// Orbit +function Orbit_drawFleetSummary() +{with(this){ + var f = new Array(document.getElementById('fsum1'), document.getElementById('fsum2'), + document.getElementById('fsum3'), document.getElementById('fsum4')); + var i = 1; + + if ( fleets[1] == '0' && fleets[2] == '0' && fleets[3] == '0' + || fleets[1] == '' && fleets[2] == '' && fleets[3] == '') + { + f[0].innerHTML = f[1].innerHTML = f[2].innerHTML = f[3].innerHTML = ' '; + return; + } + + var ff, ef, ffs, efs; + if (fleets[0] == '0') + { + f[0].innerHTML = 'Local fleets'; + ff = 'Defending'; ef = 'Attacking'; + ffs = fleets[2]; efs = fleets[3]; + } + else + { + ff = 'Friendly'; ef = 'Enemy'; + if (fleets[0] == '1') + { + if (fleets[3] == '0') + f[0].innerHTML = 'Fleets standing by'; + else + f[0].innerHTML = 'Defending the planet'; + ffs = fleets[2]; efs = fleets[3]; + } + else + { + f[0].innerHTML = 'Attacking the planet'; + ffs = fleets[3]; efs = fleets[2]; + } + } + + if (fleets[1] != '0') + { + f[i].innerHTML = 'Own fleet power: ' + formatNumber(fleets[1]) + ''; + i++; + } + if (efs != '0') + { + f[i].innerHTML = '' + ff + ' fleet power: ' + formatNumber(efs) + ''; + i++; + } + if (ffs != '0') + f[i].innerHTML = ''+ef+' fleet power: ' + formatNumber(ffs) + ''; +}} + + +// Nebula + +function Nebula_drawLayout() +{ + var str; + document.getElementById('plactions').innerHTML = 'Send Fleets - Centre Map'; + document.getElementById('pimg').innerHTML = 'Nebula - Opacity '+this.opacity+''; + document.getElementById('plname').innerHTML = 'Nebula ' + this.orbit.name; + + str = '

    Details

    '; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += '
     Coordinates: ('+this.orbit.drawCoords()+')  
     Opacity: '+this.opacity+'  
       
       
    '; + document.getElementById("pldesc").innerHTML = str; +} + + +// Remains +function Remains_drawLayout() +{ + var str; + document.getElementById('plactions').innerHTML = 'Send Fleets - Centre Map'; + document.getElementById('pimg').innerHTML = 'Planetary Remains'; + document.getElementById('plname').innerHTML = 'Planetary Remains ' + this.orbit.name; + + str = '

    Details

    '; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += '
     Coordinates: ('+this.orbit.drawCoords()+')  
     Opacity: 1  
       
       
    '; + document.getElementById("pldesc").innerHTML = str; +} + + +// Planet +function Planet_drawLayout() +{ + document.getElementById('pimg').innerHTML = 'Planet'; + var str = '

    Planet Overview

    '; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += '
     Coordinates: ('+this.orbit.drawCoords()+')   
     Alliance:    
     Population:    
     Turrets:     
     Status:    

    '; + + str += ''; + + str += ''; + + document.getElementById("pldesc").innerHTML = str; +} + + +function Planet_draw() +{ + var e, str; + document.getElementById('plname').innerHTML = 'Planet ' + this.orbit.name; + + str = 'Send Fleets - Centre Map'; + if (this.isOwn) + { + if (this.sellForm) + { + str = 'Confirm Sale - '; + str += 'Cancel'; + } + else if (this.cAction == '0') + { + if (this.canRename) + str += ' - Rename'; + if (this.canAbandon) + str += ' - Abandon'; + if (this.canSell) + str += ' - Sell/Give'; + if (this.canDestroy) + str += ' - Blow it up!'; + } + else if (this.cAction == '1') + { + if (this.time == 0) + { + if (this.sellId == '') + str += ' - Planet is for sale (Cancel)'; + else + { + str += ' - Planet offered to ' + this.sellName + ' '; + str += '(Cancel)'; + } + } + else + { + str += ' - ' + this.sellName + ' taking control in '; + str += this.time + 'h (Cancel)'; + } + } + else if (this.cAction == '2') + { + str += ' - Wormhole Super Nova in ' + this.time + ' hour' + (this.time > 1 ? 's' : '') + ' (Cancel)'; + } + else if (this.cAction == '3') + { + str += ' - Abandoning the planet in ' + this.time + ' hour tick' + (this.time > 1 ? 's' : '') + ' (Cancel)'; + } + } + else + str += ' - Message'; + document.getElementById('plactions').innerHTML = str; + + // Planet overview + document.getElementById('ptag').innerHTML = (this.tag == '') ? '-' : ('['+this.tag+']'); + document.getElementById('ptur').innerHTML = (this.turrets == '') ? '?' : (''+formatNumber(this.turrets)+''); + document.getElementById('pstat').innerHTML = this.protection ? "protected" : ( + this.vacation ? "on vacation" : "normal" + ); + document.getElementById('ppop').innerHTML = (this.population == '') ? '?' : (''+formatNumber(this.population)+''); + if (this.isOwn) + { + str = 'Happiness: ' + this.happiness + '%'; + document.getElementById('phap').innerHTML = str; + str = 'Corruption: ' + this.corruption + '%'; + document.getElementById('pcor').innerHTML = str; + document.getElementById('pprof').innerHTML = 'Planet Income: €' + formatNumber(this.profit) + ''; + } + else + document.getElementById('pcor').innerHTML = document.getElementById('phap').innerHTML = document.getElementById('pprof').innerHTML = ' '; + if (this.tFactories != '') + { + document.getElementById('pfact1').innerHTML = 'Total factories: ' + formatNumber(this.tFactories) + ''; + document.getElementById('pfact2').innerHTML = ' ' + } + else if (this.iFactories != '') + { + document.getElementById('pfact1').innerHTML = 'Industrial factories: ' + formatNumber(this.iFactories) + ''; + document.getElementById('pfact2').innerHTML = 'Military factories: ' + formatNumber(this.mFactories) + ''; + } + + if (!this.isOwn || this.sellForm) + { + e = document.getElementById('pcontrol'); + if (e.style) + e = e.style; + e.display = 'none'; + if (!this.isOwn) + return; + } + + // "Sell" form + e = document.getElementById('psale'); + if (e.style) + e = e.style; + if (this.sellForm) + { + e.display = 'block'; + document.getElementById('sm'+this.sellForm.mode).checked = true; + drawSaleForm(this.sellForm); + return; + } + else + e.display = 'none'; + + e = document.getElementById('pcontrol'); + if (e.style) + e = e.style; + e.display = 'block'; + + document.getElementById('ifc2').innerHTML = formatNumber(this.iFactories); + document.getElementById('ifcc').innerHTML = '€' + formatNumber(this.caps[1]); + document.getElementById('mfc2').innerHTML = formatNumber(this.mFactories); + document.getElementById('mfcc').innerHTML = '€' + formatNumber(this.caps[2]); + + var i, ml = parseInt(this.caps[0], 10); + str = ''; + for (i=0;i' + getBQItemName(i, true) + ' (€'; + str += formatNumber(this.caps[i+4]) + ')
    '; + } + document.getElementById('bqstuff').innerHTML = str; + document.getElementById('bqueue').innerHTML = this.drawBuildQueue(); + document.getElementById('bqbut').innerHTML = drawBQButtons(this); + document.getElementById('dtur').innerHTML = ' (Destroy)'; +} + + +function drawSaleForm() +{ + var i,e = document.getElementById('psexp'); + for (i=0;i 1) + { + orbit.spec.sellForm.player = e.value; + document.getElementById('pstarl').innerHTML = document.getElementById('pstar').innerHTML = ' '; + } + else if (!e && orbit.spec.sellForm.mode <= 1) + { + document.getElementById('pstar').innerHTML = ''; + document.getElementById('pstarl').innerHTML = ''; + document.getElementById('pstarget').value = orbit.spec.sellForm.player; + } + + e = document.getElementById('psprice'); + if (e && orbit.spec.sellForm.mode < 1) + { + orbit.spec.sellForm.player = e.value; + document.getElementById('pspril').innerHTML = document.getElementById('pspri').innerHTML = ' '; + } + else if (!e && orbit.spec.sellForm.mode >= 1) + { + document.getElementById('pspri').innerHTML = ''; + document.getElementById('psprice').value = orbit.spec.sellForm.player; + } + if (orbit.spec.sellForm.mode == 0) + document.getElementById('pspril').innerHTML = ' '; + else if (orbit.spec.sellForm.mode == 3) + document.getElementById('pspril').innerHTML = ''; + else + document.getElementById('pspril').innerHTML = ''; + + if (orbit.spec.sellForm.fleets.length == 0) + document.getElementById('bndflt').innerHTML = '

    No fleets are available.

    '; + else + {with(orbit.spec.sellForm){ + var str; + str = '' + + '' + + ''; + for (i=0;i'; + str += '
     NameG.A. ShipsFightersCruisersBattle CruisersPower
    ' + fleets[i][6] + '' + formatNumber(fleets[i][1]) + + '' + formatNumber(fleets[i][2]) + '' + formatNumber(fleets[i][3]) + + '' + formatNumber(fleets[i][4]) + '' + formatNumber(fleets[i][5]) + + '
    '; + document.getElementById('bndflt').innerHTML = str; + }} +} + + + +function getBQItemName(id, pl) +{ + var names = ['Turret', 'GA Ship', 'Fighter', 'Cruiser', 'Battle Cruiser']; + return names[id] + (pl ? 's' : ''); +} + + +function drawBQHeader() +{ + var str; + str = ''; + str += ' '; + str += ' '; + str += 'Time to build'; + str += ''; + str += ''; + str += ' '; + str += 'Qty'; + str += 'Type'; + str += 'Ind.'; + str += 'Cum.'; + str += ''; + return str; +} + +function getEmptyBQ() +{ + return '

    The Build Queue is empty

    '; +} + + +function drawBQButtons(planet) +{ + var str; + if (!planet.bq.length) + return " "; + str = ''; + str += ''; + str += ''; + str += ''; + return str; +} + + +function alertFlush() +{ + return confirm('Are you sure you want to flush the build queue?'); +} + + +function plError(en) +{ + var str; + switch (en) + { + case -1: + str = 'You are no longer the owner of this planet.'; + break; + case 0: + case 24: + str = 'You don\'t have enough money for this operation.'; + break; + case 1: + str = 'You must select the type of items to build.'; + break; + case 2: + str = 'Please specify the quantity of items to add to the build queues.'; + break; + case 3: + str = 'Please specify the quantity of factories to build or destroy.'; + break; + case 4: + str = 'Please specify the quantity of replacement items.'; + break; + case 5: + str = 'You must select at least one item in the queue.'; + break; + case 6: + str = 'You can\'t move up the first item of the build queue.'; + break; + case 7: + str = 'You can\'t move down the last item of the build queue.'; + break; + case 8: + str = 'The number of factories to destroy exceed the planet\'s quantity of factories.'; + break; + case 9: + str = 'Your planet must keep a minimum of 1 military factory.'; + break; + case 10: + str = 'You can\'t destroy more than 10% of a planet\'s factories in 24h.'; + break; + case 11: + str = 'Control over this planet is being transfered.'; + break; + case 12: + str = 'This planet is under siege.'; + break; + case 13: + str = 'This planet name is too long (maximum 15 characters).'; + break; + case 14: + str = 'This planet name is incorrect (letters, numbers, spaces and _.@-+\'/ only).'; + break; + case 15: + str = 'Multiple spaces are not allowed.'; + break; + case 16: + str = 'This planet name is too short (minimum 2 characters).'; + break; + case 17: + str = 'Planet names must contain at least one letter'; + break; + case 18: + str = 'Impossible to cancel the transfer, you do not have enough cash for a refund.'; + break; + case 19: + str = 'Please specify a valid amount of turrets to destroy.'; + break; + case 20: + str = 'Please specify a number of turrets that\'s actually inferior to the amount\nof turrets on the planet ...'; + break; + case 21: + str = 'You can\'t destroy more than 20% of a planet\'s turrets in 24h.'; + break; + case 22: + str = 'You can\'t destroy turrets while the planet is being transferred\nto another player.'; + break; + case 23: + str = 'You can\'t destroy turrets while the planet is under siege.'; + break; + case 25: + str = 'You can\'t build factories while the planet is being transferred\nto another player.'; + break; + case 26: + str = 'Your population is too low, you can\'t build that many factories.'; + break; + case 37: + str = 'You can\'t destroy factories so soon after building them.\nYou have to wait for two hours after you last built factories.'; + break; + case 38: + str = 'This planet name is unavailable.'; + break; + case 200: + str = 'You can\'t do anything while in vacation mode.'; + break; + default: + str = 'An unkown error has occured: ' + en; + break; + } + alert('Planet Manager: error\n\n' + str); +} + + +function factoryConfirm(act, ft, qt) +{ + var str; + if (act == 0) + { + var c = qt; + str = 'You are about to build '; + str += formatNumber(qt.toString()) + ' ' + (ft == '1' ? 'military' : 'industrial') + ' factor' + (qt > 1 ? 'ies' : 'y'); + str += '.\nThis operation will cost you '; + c *= parseInt(orbit.spec.caps[ft == '1' ? 2 : 1], 10); + str += formatNumber(c.toString()) + ' euros.\nPlease confirm.'; + } + else + { + str = 'You are about to destroy ' + qt + ' '; + str += (ft == '1' ? 'military' : 'industrial') + ' factor' + (qt > 1 ? 'ies' : 'y'); + str += '.\nPlease note that your factories will not be refunded.\nPlease confirm this operation.'; + } + return confirm(str); +} + + +function promptNewName() { + return prompt('Please type in the new name for this planet:', ''); +} + + +function promptDestroyTurrets() { + return prompt('How many turrets do you wish to destroy?', '1'); +} + + +function saleError(e) { + var str; + switch (e) { + case 1: str = 'You must indicate a player to give / sale the planet to.'; break; + case 2: str = 'Please specify a price.'; break; + case 3: str = 'Auction sales must have an expiration date. Please select one.'; break; + case 4: str = 'Target player not found.'; break; + case 5: str = 'Target player is under Peacekeeper protection.'; break; + case 6: str = 'Unable to sell something to yourself.'; break; + case 7: str = 'Fleet not found.'; break; + case 200: str = 'You can\'t do anything while in vacation mode.'; break; + case 201: str = 'You can\'t sell planets while under Peacekeeper protection.'; break; + default: str = 'An unknown error has occured.'; break; + } + alert('Planet Sale - Error\n' + str); +} + +function userConfirmSale() +{ + return confirm("Please confirm that you really want to give / sell this planet."); +} + + +function confirmAbandon() +{ + return confirm('You are about the abandon this planet.\nPlease confirm.'); +} + +function confirmCancelAbandon() +{ + return confirm('You were about the abandon this planet.\nPlease confirm you want to cancel this action.'); +} + +function confirmCancelSale() +{ + return confirm('You are about to cancel the sale of this planet.\nPlease confirm.'); +} + +function confirmDestroy() +{ + return confirm( + 'You are about to make this planet\'s wormhole go supernova.\n' + + 'The planet will be destroyed, and the people under your rule will be scared to\n' + + 'death because of it.\n' + + 'It will take the wormhole 4 hours to reach the point where it actually explodes.\n' + + 'Please confirm.' + ); +} + +function confirmCancelDestruction() +{ + return confirm('You are about to cancel the planet\'s destruction\nPlease confirm.'); +} diff --git a/site/static/beta5/js/pg_planet.js b/site/static/beta5/js/pg_planet.js new file mode 100644 index 0000000..2173a4c --- /dev/null +++ b/site/static/beta5/js/pg_planet.js @@ -0,0 +1,672 @@ +var pdUpdate; +var orbit; +var planet; +var plId; +var pdIsOwn; +var miSel; +var miDir = -1; +var mlSel = -1; + + +//--------------------------------------------------------------------------- +// PLANET LIST +//--------------------------------------------------------------------------- + +var plUpdate; + +function drawPlanetList(data) +{ + var cs = document.getElementById('oid').value; + var str, i, a, n, f; + + a = data.split('\n'); + n = parseInt(a.shift(),10); + if (n > 0) + { + str = ''; + f = false; + for (i=0;i'; + } + str = ''; + } + else + str = '---'; + document.getElementById('psel').innerHTML = str; + plUpdate = setTimeout('x_getPlanetList(drawPlanetList)', 600000); +} + + +//--------------------------------------------------------------------------- +// GENERIC ORBIT OBJECT +//--------------------------------------------------------------------------- + +function Orbit(type,id,x,y,orbit,name) +{ + this.type = type; + this.id = id; + this.x = x; + this.y = y; + this.orbit = orbit; + this.name = name; + this.fleets = null; + this.spec = null; + this.drawAll = Orbit_drawAll; + this.draw = Orbit_draw; + this.drawCoords = Orbit_drawCoords; + this.drawFleets = Orbit_drawFleetSummary; +} + +function Orbit_drawAll() +{ + if (!this.spec) + return + this.spec.drawLayout(); + this.draw(); +} + +function Orbit_draw() +{ + this.drawFleets(); + if (!this.spec.draw) + return + this.spec.draw(); +} + +function Orbit_drawCoords() +{ + return ''+this.x+','+this.y+','+this.orbit; +} + + +//--------------------------------------------------------------------------- +// NEBULA OBJECT +//--------------------------------------------------------------------------- + +function Nebula(orbit,opacity) +{ + this.orbit = orbit; + this.opacity = opacity; + this.drawLayout = Nebula_drawLayout; +} + + +//--------------------------------------------------------------------------- +// REMAINS OBJECT +//--------------------------------------------------------------------------- + +function Remains(orbit) +{ + this.orbit = orbit; + this.drawLayout = Remains_drawLayout; +} + + +//--------------------------------------------------------------------------- +// PLANET OBJECT +//--------------------------------------------------------------------------- + +function Planet(orbit) +{ + this.orbit = orbit; + this.bq = new Array(); + this.sellForm = null; + + this.drawLayout = Planet_drawLayout; + this.draw = Planet_draw; + this.drawBuildQueue = Planet_drawQueue; +} + +function BQItem() +{ + this.selected = false; +} + +function parsePlanetData(planet, l) +{ + var a = l.shift().split('#'); + planet.isOwn = (a.shift() == '1'); + planet.vacation = (a.shift() == '1'); + planet.protection = (a.shift() == '1'); + planet.turrets = a.shift(); + planet.population = a.shift(); + planet.tFactories = a.shift(); + planet.iFactories = a.shift(); + planet.mFactories = a.shift(); + planet.happiness = a.shift(); + planet.corruption = a.shift(); + planet.profit = a.shift(); + planet.tag = a.join('#'); + if (!planet.isOwn) + return; + + // Actions and capabilities + a = l.shift().split('#'); + planet.cAction = a.shift(); + if (planet.cAction == '0') + { + planet.canRename = (a.shift() == '1'); + planet.canSell = (a.shift() == '1'); + planet.canDestroy = (a.shift() == '1'); + planet.canAbandon = (a.shift() == '1'); + } + else if (planet.cAction == '1') + { + planet.time = a.shift(); + planet.sellId = a.shift(); + planet.sellName = a.shift(); + } + else + planet.time = a.shift(); + planet.caps = l.shift().split('#'); + + // Build queue + var i = 0, obq = planet.bq; + planet.bq = new Array(); + while (l.length) + { + var bqi = (i'; + str += '' + getBQItemName(this.bq[i].type, (this.bq[i].qt > 1)) + ''; + str += '' + this.bq[i].ttb + 'h'; + str += '' + this.bq[i].cttb + 'h'; + str += ''; + } + + str += ''; + return str; +} + + +//--------------------------------------------------------------------------- +// PLANET SALE +//--------------------------------------------------------------------------- + +function SellForm() +{ + this.mode = 0; + this.player = ''; + this.price = 0; + this.expires = 0; + this.fleets = new Array(); + this.validate = SellForm_validate; +} + +function SellForm_validate() +{ + if (this.mode < 2) + { + this.player = document.getElementById('pstarget').value; + if (this.player == '') + return 1; + } + if (this.mode != 0) + { + this.price = parseInt(document.getElementById('psprice').value, 10); + if (isNaN(this.price) || this.price <= 0) + return 2; + } + if (this.mode == 3 && this.expires == 0) + return 3; + + return 0; +} + + +function sellPlanet() +{ + x_getSellableFleets(orbit.id, gotFleetsList); +} + + +function gotFleetsList(data) +{ + if (data != "ERR#-1") + { + orbit.spec.sellForm = new SellForm(); + if (data != "") + { + var l = data.split('\n'); + while (l.length) + { + var a = l.shift().split('#'); + var b = new Array(); + b.push(a.shift()); b.push(a.shift()); b.push(a.shift()); + b.push(a.shift()); b.push(a.shift()); b.push(a.shift()); + b.push(a.join('#')); b.push(false); + orbit.spec.sellForm.fleets.push(b); + } + } + } + orbit.draw(); +} + +function closeSaleForm() +{ + orbit.spec.sellForm = null; + orbit.draw(); +} + +function confirmSale() +{with(orbit.spec.sellForm){ + var e = validate(); + if (e) + { + saleError(e); + return; + } + var a = new Array(); + for (e=0;e= 2) + orbit.spec = new Nebula(orbit, t - 1); + else if (t == 1) + orbit.spec = new Remains(orbit); + else + { + orbit.spec = new Planet(orbit); + parsePlanetData(orbit.spec, l); + } + orbit.drawAll(); + } + startUpdate(); +} + + +//--------------------------------------------------------------------------- +// ACTIONS +//--------------------------------------------------------------------------- + +function renamePlanet() +{ + if (!lockUpdate()) + return; + var nn = promptNewName(); + if (typeof nn == 'object' && !nn) + { + startUpdate(); + return; + } + x_rename(plId, nn, plExecuteOk); +} + +function factAction(ta, tf) +{ + var n, str; + if (!lockUpdate()) + return; + + str = (tf == 1) ? 'mfc' : 'ifc'; + n = parseInt(document.getElementById(str).value, 10); + if (isNaN(n) || n <= 0) + { + plError(3); + startUpdate(); + return; + } + if (!factoryConfirm(ta, tf, n)) + { + startUpdate(); + return; + } + x_factoryAction(plId, ta, n, tf, plExecuteOk); +} + +function addToQueue() +{ + if (!lockUpdate()) + return; + + if (mlSel == -1) + { + plError(1); + startUpdate(); + return; + } + + var n = parseInt(document.getElementById('bwfq').value, 10); + if (isNaN(n) || n <= 0) + { + plError(2); + startUpdate(); + return; + } + x_addToQueue(plId, n, mlSel, plExecuteOk); +} + +function getSelectedItems() +{ + var j, s = new Array(); + for (j=0;j

    Controlled planets

    ' + + (vacation ? '' : ('Quick builder mode - ')) + + 'Help' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + if (planets.length > 0) { + for (i = 0; i < planets.length; i ++) { + str += buildPlanetLine(i); + } + } else { + str += ''; + str += ''; + str += ''; + str += ''; + } + str += '
     PlanetCoord.Pop.Happ.Ind.TurretsProfit
    Corr.Mil.
     
    You do not control any planet.
    Get a new planet
     
    '; + return str; +} + +function drawBQSummary(bq) { + var i, str; + var types = new Array('Turret', 'GA Ship', 'Fighter', 'Cruiser', 'Battle Cruiser'); + var csb = ';padding: 4px 0px 0px 0px;border-style:solid;border-color:white;border-width: 1px 0px 0px 0px'; + + if (bq.length == 0) { + return ' '; + } + return 'Build queue loaded for ' + bq[bq.length - 1].cttb + 'h'; +} + + +function buildBuilderLine(ipl) { + var str, j; + var types = new Array('Turret', 'GA Ship', 'Fighter', 'Cruiser', 'Battle Cruiser'); + str = ''; + str += '
    '; + str += ''; + if (planets[ipl].bq.length > 0) { + var as = ' href="#" style="color: white;text-decoration:underline;font-weight:normal" '; + str += ' (select items: ' + + 'all - ' + + 'none - ' + + 'invert)'; + } + str += '' + formatNumber(planets[ipl].pop) + '' + + drawHappiness(planets[ipl].hap) + '' + formatNumber(planets[ipl].ind) + + '' + formatNumber(planets[ipl].tur) + '
    '; + + if (planets[ipl].bq.length == 0) { + str += 'Build Queue is empty'; + } else { + for (j=0;j' + formatNumber(planets[ipl].bq[j].qt) + + ' ' + types[planets[ipl].bq[j].type]; + if (planets[ipl].bq[j].qt > 1) { + str += 's'; + } + var v = (useCTTB ? planets[ipl].bq[j].cttb : planets[ipl].bq[j].ttb); + str += ' (' + v + 'h) '; + } + } + str += '' + drawCorruption(planets[ipl].corruption) + '' + formatNumber(planets[ipl].mil) + + '
    '; + return str; +} + + +function drawQuickBuilder() +{ + var str; + // Header + str = '
    '; + str += ''; + str += ''; + // Planet operations header + str += ''; + // Buttons + str += ''; + // Planet list header + str += ''; + str += ''; + str += ''; + str += ''; + str += '

    Quick builder

    '; + str += ''; + // Factories + str += ''; + // Add to queue(s) + str += ''; + // Flush queue(s) + str += ''; + // Replace items + str += ''; + str += '

    Planet operations

    '; + str += ''; + str += ' factories
    '; + str += ' to the queues
    '; + str += ' in the queue
    '; + str += '
    '; + str += ''; + str += '

    Controlled planets

    '; + // End + str += '
    '; + + return str; +} + + +function drawQBList() { + var str, i; + str = ''; + // Planet list + if (planets.length > 0) { + for (i=0;iGet a new planet'; + str += ''; + } + str += '
    '; + str += '' + + '' + + '' + + '
    PlanetPopulationHappinessIndustrialTurrets
    Build queueCorruptionMilitary
     
    '; + return str; +} + + +function bqItems() +{ + var i,str; + var types = new Array('turret', 'GA ship', 'fighter', 'cruiser', 'battle cruiser'); + str = ''; + for (i=0;i' + types[i] + ''; + return str; +} + + +function qbError(en) +{ + var str; + switch (en) + { + case 0: + str = 'You must select an action.'; + break; + case 1: + str = 'You must select at least one planet.'; + break; + case 2: + str = 'Please specify the quantity of items to add to the build queues.'; + break; + case 3: + str = 'Please specify the quantity of factories to build or destroy.'; + break; + case 14: + case 4: + str = 'You don\'t have enough money for this operation.'; + break; + case 5: + str = 'The number of factories to destroy exceed one of the planet\'s\nquantity of factories.'; + break; + case 6: + str = 'Your planets must keep a minimum of 1 military factory.'; + break; + case 7: + str = 'You can\'t destroy more than 10% of a planet\'s factories in 24h.'; + break; + case 8: + str = 'At least one of the selected planets is under siege.'; + break; + case 9: + str = 'You must select at least one item in the queue.'; + break; + case 10: + str = 'Please specify the quantity of replacement items.'; + break; + case 11: + str = 'You can\'t move up the first item of a build queue.'; + break; + case 12: + str = 'You can\'t move down the last item of a build queue.'; + break; + case 13: + str = 'You can\'t use the quick builder while in vacation mode.'; + break; + case 15: + str = 'You can\'t build factories while the planet is being transferred\nto another player.'; + break; + case 16: + str = 'Your population is too low, you can\'t build that many factories.'; + break; + case 34: + str = 'You can\'t destroy factories so soon after building them.\nYou have to wait for two hours after you last built factories.'; + break; + default: + str = 'An unkown error has occured: ' + en; + break; + } + alert('Quick Builder: error\n\n' + str); +} diff --git a/site/static/beta5/js/pg_planets.js b/site/static/beta5/js/pg_planets.js new file mode 100644 index 0000000..39951a9 --- /dev/null +++ b/site/static/beta5/js/pg_planets.js @@ -0,0 +1,530 @@ +var vacation = true; +var qbMode = false; +var useCTTB = false; +var action = 0; +var milLevel; +var planets; +var updTimer; +var mustHide; +var miSel, miDir; + +var plstt; + +function BQItem(type, qt, ttb, cttb) +{ + this.type = type; + this.qt = qt; + this.ttb = ttb; + this.cttb = cttb; + this.selected = false; +} + + +function Planet(id,name,x,y,orbit,pop,hap,ind,tur,mil,prof,cor,bqstr) +{ + this.id = id; + this.name = name; + this.x = x; + this.y = y; + this.orbit = orbit; + this.pop = pop; + this.hap = hap; + this.corruption = cor; + this.ind = ind; + this.tur = tur; + this.mil = mil; + this.prof = prof; + this.selected = false; + + var i,j; + this.bq = new Array(); + bqd = bqstr.split('#'); + if (bqd.length == 1) + return; + ni = bqd.length / 4; + for (i=0;i[P]' + + '' + + '' + planets[ipl].name + '' + + '(' + planets[ipl].x + + ',' + planets[ipl].y + ',' + planets[ipl].orbit + ')' + + '' + + formatNumber(planets[ipl].pop) + '' + + '' + drawHappiness(planets[ipl].hap) + '' + + '' + formatNumber(planets[ipl].ind) + '' + + '' + formatNumber(planets[ipl].tur) + '' + + '€' + formatNumber(planets[ipl].prof) + + '' + + '' + + ' ' + drawBQSummary(planets[ipl].bq) + + '' + drawCorruption(planets[ipl].corruption) + '' + + '' + formatNumber(planets[ipl].mil) + '' + + '' + return str; +} + + +function getMilitaryLevel(data) +{ + milLevel = parseInt(data, 10) + 2; + document.getElementById('jsplanets').innerHTML = drawQuickBuilder(); +} + + +function buildQuickBuilder() +{ + if (!milLevel) + x_getMilitaryLevel(getMilitaryLevel); + else + document.getElementById('jsplanets').innerHTML = drawQuickBuilder(); +} + + +function buildQBList() +{ + var e = document.getElementById('qb_ftype'), e2 = document.getElementById('qb_planets'); + if (!(e && e2)) + setTimeout('buildQBList()', 500); + else + e2.innerHTML = drawQBList(); +} + + +function buildPlanetList() +{ + document.getElementById('jsplanets').innerHTML = drawMainDisplay(); +} + + +function planetListReceived(data) +{ + var i, j, lines, np, pdat, nbp, sel, sstr, bqsel, bqstr, bqlen, bqlstr; + + // Store selections and build queue lengths as strings + sel = new Array(); + bqlen = new Array(); + bqsel = new Array(); + if (planets) + { + np = 0; + for (i=0;i= 70) { + str += 'ok'; + } else if (value >= 40) { + str += 'med'; + } else if (value >= 20) { + str += 'dgr'; + } else { + str += 'bad'; + } + str += '">' + value + '%'; + return str; +} + +function drawCorruption(value) { + var str = '' + value + '%'; + return str; +} diff --git a/site/static/beta5/js/pg_probes-en.js b/site/static/beta5/js/pg_probes-en.js new file mode 100644 index 0000000..9eb50d3 --- /dev/null +++ b/site/static/beta5/js/pg_probes-en.js @@ -0,0 +1,53 @@ +var pgTitles = ['Policy', 'Beacons']; +var empPolTitle = "General Policy"; +var plaPolTitle = "Planet-specific Policy"; +var polTexts = ['probes from trusted allies', 'probes from alliance members', 'probes from other players', 'probes from enemies']; +var polValue = ['Destroy', 'Jam', 'Allow']; +var noPlanets = 'No planets were found.'; +var planetTxt = 'Planet'; +var polUse = 'Use a planet-specific policy instead of the general policy'; +var polUseEmpire = 'This planet follows the empire\'s general policy'; +var bcnCurrent = ['No beacon', 'Hyperspace Beacon', 'Probing Beacon', 'Advanced Warning Beacon']; +var bcnNoUpgrade = 'There is no available upgrade for this planet\'s beacon.'; +var bcnAlreadyBuilt = 'A beacon or probe has been built on this planet recently; you must wait for the next Day Tick.'; +var bcnUpgrade = "You can upgrade this planet\'s beacon to the level of __B__ (cost: €__C__)"; +var bcnUpgradeBtn = "Upgrade Beacon"; +var bcnInfoLevel = ['Minimal', 'Very rough estimate', 'Rough estimate', 'Fleet size', 'Positive identification']; +var bcnUnknown = 'Unknown'; + + +function PolicyPage_error(a) +{ + var ec = parseInt(a.shift(), 10), str = 'Probes policy manager: error\n\n'; + switch (ec) + { + case 0: str += 'Database error.'; break + case 1: str += 'Invalid parameters.'; break; + case 2: str += 'You no longer have control over this planet.'; break; + default: str += 'Something weird has happened (unknown error).'; break; + } + alert(str); +} + + +function BeaconsPage_error(a) +{ + var ec = parseInt(a.shift(), 10), str = 'Beacons manager: error\n\n'; + switch (ec) + { + case 0: str += 'Invalid parameters.'; break; + case 1: str += 'You no longer have control over this planet.'; break; + case 2: str += 'You don\'t have enough cash to pay for this upgrade.'; break; + case 200: str += 'You can\'t upgrade beacons while in vacation mode'; break; + default: str += 'Something weird has happened (unknown error).'; break; + } + alert(str); +} + + +function BeaconsPage_confirmUpgrade(p) +{ + var str = 'Are you sure you want to upgrade the beacon on planet\n' + p.name + + ' to the level of ' + bcnCurrent[p.cLevel + 1] + '\nfor ' + p.upgrade + ' euros?'; + return confirm(str); +} diff --git a/site/static/beta5/js/pg_probes.js b/site/static/beta5/js/pg_probes.js new file mode 100644 index 0000000..399e129 --- /dev/null +++ b/site/static/beta5/js/pg_probes.js @@ -0,0 +1,520 @@ +//-------------------------------------------------------------------- +// Initialisation code + +var page; + +function initPage() +{ + var e = document.getElementById('pinit'); + if (!e) + return; + page = new Page(e.innerHTML); + page.action(['update']); +} + + +//-------------------------------------------------------------------- +// Page management + +function Page(cPage) +{ + this.action = Page_action; + this.init = Page_init; + this.update = Page_update; + this.updated = Page_updated; + this.drawLayout = Page_drawLayout; + this.startTimer = Page_startTimer; + this.setPage = Page_setPage; + + this.lockLock = false; + this.timerLock = false; + this.timer = null; + + this.menu = new Menu(cPage); + this.page = null; + + this.init(); + this.drawLayout(); +} + +function Page_action(args) +{ + var tl = true; + while (tl) + { + while (this.lockLock) ; + this.lockLock = true; + tl = this.timerLock; + if (!tl) + { + clearTimeout(this.timer); + this.timerLock = true; + } + this.lockLock = false; + } + + var x = 'this.' + args.shift() + '(', i; + for (i=0;i0?',':'') + 'args[' + i + ']'; + eval(x + ')'); +} + +function Page_setPage(nPage) +{ + this.menu.cPage = nPage; + this.init(); +// this.menu.draw(); + this.drawLayout(); + this.update(); +} + +function Page_init() +{ + var classes = [/*'PolicyPage',*/ 'BeaconsPage']; + var i; + + for (i=0;i 0) + str += ' - '; + if (this.cPage == this.ids[i]) + str += ''; + else + str += ''; + str += this.titles[i] + (this.cPage == this.ids[i] ? "" : ""); + } + + document.getElementById('prmenu').innerHTML = str; +} + + +//-------------------------------------------------------------------- +// Policy page + + +function PolicyPage(main) +{ + this.error = PolicyPage_error; + this.drawLayout = PolicyPage_drawLayout; + this.updateEmpire = PolicyPage_updateEmpire; + this.updatePlanetList = PolicyPage_updatePlanetList; + this.updatePlanet = PolicyPage_updatePlanet; + + this.parsePolicy = function(s) { var p=new Array(),i; for(i=0;i<4;i++) p[i]=s.charAt(i); return p; }; + this.parse = PolicyPage_parse; + this.parseMain = PolicyPage_parseMain; + + this.setEmpPolicy = PolicyPage_setEmpPolicy; + this.setPlPolicy = PolicyPage_setPlPolicy; + this.togglePlanet = PolicyPage_togglePlanet; + + this.main = main; + this.policy = null; + this.planets = null; + this.updateHeader = false; + this.pLines = ['ta', 'am', 'ot', 'en']; +} + + +function PolicyPage_parse(data) +{ + var l = data.split('\n'); + var t = l.shift(); + if (t == "0") + this.parseMain(l); + else if (t == "1") + { + this.policy = this.parsePolicy(l[0]); + this.updateEmpire(); + } + else if (t == "2") + { + var i; + t = l[0].split('#'); + for (i=0;i 0) + { + a = l.shift().split('#'); + var p = new Array(); + p[0] = a.shift(); + p[1] = '' + a.shift() + ',' + a.shift() + ',' + a.shift(); + if (a[0] == "") + a.shift(); + else + p[3] = this.parsePolicy(a.shift()); + p[2] = a.join('#'); + this.planets.push(p); + nPlanets --; + } + + this.updateEmpire(); + this.updatePlanetList(); + for (a=0;a

    '; + + for (i=0;i0) + str += '
    '; + + var j, idpfx = "ep" + this.pLines[i]; + for (j=3;j>0;j--) + str += ' '; + str += polTexts[i] + } + + str += '

    ' + plaPolTitle + '

     
    '; + + return str; +} + +function PolicyPage_updateEmpire() +{ + var i; + for (i=0;i'; + else + { + for (i=0;i' + + p[2] + ' (' + p[1] + ')' + + ' '; + } + } + document.getElementById('plapol').innerHTML = str; +} + +function PolicyPage_updatePlanet(index) +{ + var i, j, p = this.planets[index], pfx = "pl" + p[0], up = (typeof p[3] == "object" && p[3]), e, ht; + document.getElementById(pfx + 'up').checked = up; + ht = (document.getElementById(pfx+'t').innerHTML != ' '); + e = document.getElementById(pfx+'ta0'); + + if (up && !(e && ht)) + { + var str = '

    '; + for (i=0;i0) + str += '
    '; + for (j=3;j>0;j--) + str += ' '; + str += polTexts[i]; + } + document.getElementById(pfx+'t').innerHTML = str + '

    '; + } + else if (!up && (e || !ht)) + document.getElementById(pfx+'t').innerHTML = '

    ' + polUseEmpire + '

    '; + + if (!up) + return; + + for (i=0;i,"+orbit; + this.cLevel = parseInt(current,10); + this.upgrade = (upgrade == "" ? "" : formatNumber(upgrade)); + this.built = (built == 1); + this.nHS = parseInt(nHs,10); + this.nMv = parseInt(nMv,10); + + this.hsFleets = new Array(); + this.hsMove = new Array(); +} + + +//-------------------------------------------------------------------- +// Beacons page + + +function BeaconsPage(main) +{ + this.error = BeaconsPage_error; + this.drawLayout = function() { return ""; }; + + this.parse = BeaconsPage_parse; + this.parsePlanet = BeaconsPage_parsePlanet; + + this.drawAll = BeaconsPage_drawAll; + this.drawPlanet = BeaconsPage_drawPlanet; + + this.upgrade = BeaconsPage_upgrade; + this.confirmUpgrade = BeaconsPage_confirmUpgrade; + + this.main = main; + this.planets = new Hashtable(); + this.techLevel = 0; +} + + +function BeaconsPage_parse(data) +{ + var l = data.split("\n"); + var mode = l.shift(); + + if (mode == "0") + { + var a = l.shift().split('#'); + this.techLevel = parseInt(a.shift(), 10); + a = parseInt(a.shift(), 10); + while (a > 0) + { + this.parsePlanet(l); + a --; + } + this.drawAll(); + } + else if (mode == "1") + { + var id = this.parsePlanet(l); + this.drawPlanet(id); + } +} + +function BeaconsPage_parsePlanet(data) +{ + var a = data.shift().split('#'); + var p = new BP_Planet(a.shift(),a.shift(),a.shift(),a.shift(),a.shift(),a.shift(),a.shift(),a.shift(),a.shift(),a.join('#')); + this.planets.put(p.id, p); + + if (p.nHS > 0) { + for (var i = 0; i < p.nHS; i ++) { + var f = {}; + a = data.shift().split('#'); + f.level = parseInt(a.shift(), 10); + f.size = a.shift(); + f.ownerID = a.shift(); + f.ownerName = a.shift(); + p.hsFleets.push(f); + } + } + + return p.id; +} + + +function BeaconsPage_drawAll() +{ + var i, keys = this.planets.keys(); + var str = ''; + for (i=0;i 
    '; + document.getElementById('prpage').innerHTML = str; + for (i=0;i' + + p.name + ' (' + p.coords + ')' + bcnCurrent[p.cLevel] + ''; + + if (p.nHS > 0) { + str += 'Fleets detected!' + + 'Information levelFleet sizeFleet owner'; + for (var i = 0; i < p.nHS; i ++) { + var f = p.hsFleets[i]; + str += '' + bcnInfoLevel[f.level] + + '' + + (f.size == '' ? bcnUnknown : formatNumber(f.size)) + + '' + + (f.ownerID == '' ? bcnUnknown : ( + '' + f.ownerName + '')) + + ''; + } + } + + str += ''; + if (p.upgrade == "") + str += bcnNoUpgrade; + else if (p.built) + str += bcnAlreadyBuilt; + else + { + var u = bcnUpgrade; + u = u.replace(/__B__/, bcnCurrent[p.cLevel+1]); + u = u.replace(/__C__/, p.upgrade); + str += u + ' '; + } + + str += ''; + e.innerHTML = str; +} + + +function BeaconsPage_upgrade(id) +{ + var p = this.planets.get(id); + if (!(p && this.confirmUpgrade(p))) + { + this.main.startTimer(); + return; + } + + this.main.updateHeader = true; + x_upgradeBeacon(id, ajaxCallback); +} diff --git a/site/static/beta5/js/pg_rank-en.js b/site/static/beta5/js/pg_rank-en.js new file mode 100644 index 0000000..596cb71 --- /dev/null +++ b/site/static/beta5/js/pg_rank-en.js @@ -0,0 +1,81 @@ +Rankings.pointsText = 'points'; +Rankings.noPlayersFound = 'No players were found.'; +Rankings.noAlliancesFound = 'No alliances were found.'; +Rankings.pageText = 'Page __CP__ / __MP__'; +Rankings.playersPerPageText = 'Display __PS__ players / page'; +Rankings.alliancesPerPageText = 'Display __PS__ alliances / page'; +Rankings.searchPlayerText = 'Search for player : __TF__'; +Rankings.searchAllianceText = 'Search for alliance : __TF__'; + +Rankings.Summary.loadingText = 'Please wait; loading ...'; + +Rankings.Headers.player = 'Player name'; +Rankings.Headers.alliance = 'Alliance tag'; +Rankings.Headers.ranking = 'Ranking / Points'; +Rankings.Headers.civRanking = 'Civilization
    Ranking / Points'; +Rankings.Headers.milRanking = 'Military
    Ranking / Points'; +Rankings.Headers.finRanking = 'Financial
    Ranking / Points'; + + +var menuText = ['Summary', 'General Rankings', 'Detailed Rankings', 'Alliance Rankings', 'Overall Round Rankings', 'Inflicted Damage Ranking']; + + +function makeRanksTooltips() +{ + rantt = new Array(); + if (ttDelay == 0) + { + var i; + for (i=30;i<31;i++) + rantt[i] = ""; + return; + } +// rantt[0] = tt_Dynamic("Use this drop down list to select the number of items to display on each page"); +// rantt[1] = tt_Dynamic("Use this text field to search a particular string in this ranking listing"); +// rantt[10] = tt_Dynamic("Use this drop down list to go to the selected page of this rankings"); +// rantt[20] = tt_Dynamic("Click here to sort the list according to this field and switch between ascending / descending order"); + rantt[30] = tt_Dynamic("Click here to go to this particular rankings listing"); +} + + +Rankings.Summary.prototype.getText = function() { + var str = ''; + + str += ''; + str += ''; + + str += ''; + + str += ''; + str += ''; + + str += ''; + + str += ''; + str += ''; + + str += '

    General ranking

    '; + str += 'This ranking corresponds to a combination of your civilisation, military and financial rankings. It represents your current overall advancement and strength in the game.'; + str += '

    Your general ranking: #' + formatNumber(this.rkGen) + ' (' + formatNumber(this.ptGen) + ' points)'; + str += '

    Civilization Ranking

    '; + str += 'This ranking represents the advancement level of the society in your empire. It takes into account technology level, population and happiness.'; + str += '

    Your civilization ranking: #' + formatNumber(this.rkCiv) + ' (' + formatNumber(this.ptCiv) + ' points)'; + str += '

    Overall Round Ranking

    '; + str += 'This ranking is calculated based on your previous general rankings. It allows for a long term estimate of your strength and advancement.'; + str += '

    '; + if (this.rkRnd == "") + str += 'You are too weak to have an overall round ranking.'; + else + str += 'Your overall ranking: #' + formatNumber(this.rkRnd) + ' (' + formatNumber(this.ptRnd) + ' points)'; + str += '

    Military Ranking

    '; + str += '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.'; + str += '

    Your military ranking: #' + formatNumber(this.rkMil) + ' (' + formatNumber(this.ptMil) + ' points)'; + str += '

    Inflicted Damage Ranking

    '; + str += 'This ranking represents the amount of damage you have inflicted on other players\' fleets since you started playing.'; + str += '

    Your inflicted damage ranking: #' + formatNumber(this.rkDam) + ' (' + formatNumber(this.ptDam) + ' points)'; + str += '

    Financial Ranking

    '; + str += 'This ranking corresponds to the economic health of your empire. It takes into account your banked cash, your income and the number of industrial factories you own.'; + str += '

    Your financial ranking: #' + formatNumber(this.rkFin) + ' (' + formatNumber(this.ptFin) + ' points)'; + str += '

    '; + return str; +} diff --git a/site/static/beta5/js/pg_rank.js b/site/static/beta5/js/pg_rank.js new file mode 100644 index 0000000..75ac571 --- /dev/null +++ b/site/static/beta5/js/pg_rank.js @@ -0,0 +1,237 @@ +var pages = ['Summary','General','Details','Alliance','Round','Damage']; +var comps = ['summary','genListing','detListing','allListing','rndListing','idrListing']; +var rkPage = null; + +var summary, genListing, detListing, allListing, rndListing, idrListing; +var currentPage = null; + +var rantt; + + + +Rankings = {}; +Rankings.Headers = {}; +Rankings.makeListing = function(rpc, dataType, nfText, ppText, sText, sMode) { + var ls = new Component.Listing(rpc, 3600, dataType, 'list', 'row', 'hdr'); + ls.notFoundText = nfText; + ls.getDocID = function() { return 'rkpage'; } + ls.getElement = function() { return document.getElementById('rkpage'); }; + + ls.setPageController(new Component.Listing.PageController(Rankings.pageText), true); + + var c = new Component.Listing.PageSizeController(ppText, 5, 25, 5); + c.select.select('15'); + ls.setPageSizeController(c, true); + + c = new Component.Listing.SearchController(sText); + c.addMode(0, sMode); + c.textField.setMaxLength(15); c.textField.setSize(16); + ls.setSearchController(c, true); + + return ls; +} + +Rankings.setPage = function (page) { Rankings.setPage.ajax.call(page); } +Rankings.setPage.ajax = new Component.Ajax('setPage'); + + +Rankings.GeneralRankings = function (cache) { Component.Listing.Data.apply(this, [cache]); } +Rankings.GeneralRankings.prototype = new Component.Listing.Data; +Rankings.GeneralRankings.prototype.parse = function (data) { + var a = data.shift().split('#'); + var isMe = (a.shift() == '1'); + var bb = isMe ? "" : ""; + var be = isMe ? "" : ""; + + this.rank = bb + '#' + formatNumber(a.shift()) + ' / ' + + formatNumber(a.shift()) + " " + Rankings.pointsText + be; + this.player = bb + a.join('#') + be; +} + + +Rankings.DetailedRankings = function (cache) { Component.Listing.Data.apply(this, [cache]); } +Rankings.DetailedRankings.prototype = new Component.Listing.Data; +Rankings.DetailedRankings.prototype.parse = function (data) { + var a = data.shift().split('#'); + var isMe = (a.shift() == '1'); + var bb = isMe ? "" : ""; + var be = isMe ? "" : ""; + + this.civRank = bb + '#' + formatNumber(a.shift()) + ' / ' + + formatNumber(a.shift()) + " " + Rankings.pointsText + be; + this.milRank = bb + '#' + formatNumber(a.shift()) + ' / ' + + formatNumber(a.shift()) + " " + Rankings.pointsText + be; + this.finRank = bb + '#' + formatNumber(a.shift()) + ' / ' + + formatNumber(a.shift()) + " " + Rankings.pointsText + be; + this.player = bb + a.join('#') + be; +} + + +Rankings.AllianceRankings = function (cache) { Component.Listing.Data.apply(this, [cache]); } +Rankings.AllianceRankings.prototype = new Component.Listing.Data; +Rankings.AllianceRankings.prototype.parse = function (data) { + var a = data.shift().split('#'); + + this.rank = '#' + formatNumber(a.shift()) + ' / ' + + formatNumber(a.shift()) + " " + Rankings.pointsText; + this.alliance = a.join('#'); +} + + +Rankings.Summary = function () { + Component.apply(this, [false]); + + this.newSlot('dataReceived'); + this.newSlot('startUpdate'); + this.newSlot('stopUpdate'); + this.newSlot('updateData'); + + this.timer = new Component.Timer(3600*1000, true); + this.ajax = new Component.Ajax('getPlayerData'); + this.md5 = ''; + this.loading = true; + + this.bindEvent('Hide', 'stopUpdate'); + this.ajax.bindEvent('Returned', 'dataReceived', this); + this.bindEvent('Show', 'startUpdate'); + this.timer.bindEvent('Tick', 'updateData', this); +} +Rankings.Summary.prototype = new Component; +Rankings.Summary.prototype.getDocID = function() { return 'rkpage'; } +Rankings.Summary.prototype.getElement = function() { return document.getElementById('rkpage'); }; +Rankings.Summary.prototype.dataReceived = function (data) { + this.loading = false; + if (data != 'KEEP') { + var a = data.split('#'); + + this.md5 = a.shift(); + this.ptGen = a.shift(); + this.rkGen = a.shift(); + this.ptCiv = a.shift(); + this.rkCiv = a.shift(); + this.ptMil = a.shift(); + this.rkMil = a.shift(); + this.ptFin = a.shift(); + this.rkFin = a.shift(); + this.ptRnd = a.shift(); + this.rkRnd = a.shift(); + this.ptDam = a.shift(); + this.rkDam = a.shift(); + } + this.drawAll(); +} +Rankings.Summary.prototype.startUpdate = function () { + this.loading = true; + this.timer.start(); + this.drawAll(); + this.ajax.call(this.md5); +} +Rankings.Summary.prototype.stopUpdate = function () { this.timer.stop(); } +Rankings.Summary.prototype.updateData = function () { this.ajax.call(this.md5); } +Rankings.Summary.prototype.draw = function () { + var e = this.getElement(); + if (!e) + return; + + var str; + if (this.loading) + str = '
    ' + + Rankings.Summary.loadingText + ''; + else + str = this.getText(); + e.innerHTML = str; +} + + + +//---------------------------------------------------------------------------------------------- +// GENERAL +//---------------------------------------------------------------------------------------------- + +function initPage() { + var c; + + summary = new Rankings.Summary(); + + // General rankings + genListing = Rankings.makeListing('getGeneralRankings', Rankings.GeneralRankings, + Rankings.noPlayersFound, Rankings.playersPerPageText, Rankings.searchPlayerText, + "player"); + genListing.addField(new Component.Listing.Field(Rankings.Headers.player, true, 'player', 0, 0, 1, 1, 'gname')); + genListing.addField(c = new Component.Listing.Field(Rankings.Headers.ranking, true, 'rank', 1, 0, 1, 1, 'grank')); + genListing.dataCache.sortField = c; + genListing.finalizeLayout(); + + // Detailed rankings + detListing = Rankings.makeListing('getDetailedRankings', Rankings.DetailedRankings, + Rankings.noPlayersFound, Rankings.playersPerPageText, Rankings.searchPlayerText, + "player"); + detListing.addField(new Component.Listing.Field(Rankings.Headers.player, true, 'player', 0, 0, 1, 1, 'dname')); + detListing.addField(new Component.Listing.Field(Rankings.Headers.civRanking, true, 'civRank', 1, 0, 1, 1, 'drank')); + detListing.addField(new Component.Listing.Field(Rankings.Headers.milRanking, true, 'milRank', 2, 0, 1, 1, 'drank')); + detListing.addField(new Component.Listing.Field(Rankings.Headers.finRanking, true, 'finRank', 3, 0, 1, 1, 'drank')); + detListing.finalizeLayout(); + + // Alliance rankings + allListing = Rankings.makeListing('getAllianceRankings', Rankings.AllianceRankings, + Rankings.noAlliancesFound, Rankings.alliancesPerPageText, Rankings.searchAllianceText, + "alliance"); + allListing.addField(new Component.Listing.Field(Rankings.Headers.alliance, true, 'alliance', 0, 0, 1, 1, 'dname')); + allListing.addField(c = new Component.Listing.Field(Rankings.Headers.ranking, true, 'rank', 1, 0, 1, 1, 'drank')); + allListing.dataCache.sortField = c; + allListing.finalizeLayout(); + + // Round rankings + rndListing = Rankings.makeListing('getRoundRankings', Rankings.GeneralRankings, + Rankings.noPlayersFound, Rankings.playersPerPageText, Rankings.searchPlayerText, + "player"); + rndListing.addField(new Component.Listing.Field(Rankings.Headers.player, true, 'player', 0, 0, 1, 1, 'gname')); + rndListing.addField(c = new Component.Listing.Field(Rankings.Headers.ranking, true, 'rank', 1, 0, 1, 1, 'grank')); + rndListing.dataCache.sortField = c; + rndListing.finalizeLayout(); + + // Damage rankings + idrListing = Rankings.makeListing('getDamageRankings', Rankings.GeneralRankings, + Rankings.noPlayersFound, Rankings.playersPerPageText, Rankings.searchPlayerText, + "player"); + idrListing.addField(new Component.Listing.Field(Rankings.Headers.player, true, 'player', 0, 0, 1, 1, 'gname')); + idrListing.addField(c = new Component.Listing.Field(Rankings.Headers.ranking, true, 'rank', 1, 0, 1, 1, 'grank')); + idrListing.dataCache.sortField = c; + idrListing.finalizeLayout(); + + switchToPage(document.getElementById('rkinit').innerHTML); +} + +function switchToPage(page) +{ + if (rkPage == page) + return; + + if (currentPage) + currentPage.hide(); + + rkPage = page; + drawRankingsMenu(); + + var i; + for (i=0;i' : (''); + s += menuText[i] + ((pages[i] == rkPage) ? '' : ''); + rkm.push(s); + } + document.getElementById('rkpsel').innerHTML = rkm.join(' - '); +} diff --git a/site/static/beta5/js/pg_research-en.js b/site/static/beta5/js/pg_research-en.js new file mode 100644 index 0000000..fb8de99 --- /dev/null +++ b/site/static/beta5/js/pg_research-en.js @@ -0,0 +1,654 @@ +var pageTitles = new Array('Research topics', 'Laws', 'Research budget', 'Diplomacy'); +var rTypes = new Array('Fundamental', 'Military', 'Civilian'); + +function makeResearchTooltips() +{ + rett = new Array(); + if (ttDelay == 0) + { + var i; + for (i=0;i<82;i++) + rett[i] = ""; + return; + } + rett[0] = tt_Dynamic("Click here to hide / show this research topic"); + rett[1] = tt_Dynamic("Click here to implement this technology"); + rett[10] = tt_Dynamic("Click here to hide / show this law"); + rett[11] = tt_Dynamic("Click here to revoke this law"); + rett[12] = tt_Dynamic("Click here to enact this law"); + rett[20] = tt_Dynamic("Click here to decrease the percentage of research points attributed to this category by 10 points"); + rett[21] = tt_Dynamic("Click here to decrease the percentage of research points attributed to this category by 1 point"); + rett[22] = tt_Dynamic("Click here to increase the percentage of research points attributed to this category by 1 point"); + rett[23] = tt_Dynamic("Click here to increase the percentage of research points attributed to this category by 10 points"); + rett[30] = tt_Dynamic("Click here to validate the new budget attributions"); + rett[31] = tt_Dynamic("Click here to reset the budget attributions"); + rett[40] = tt_Dynamic("Check this radio button to offer scientific assistance"); + rett[41] = tt_Dynamic("Type here the name of the player to send the offer to"); + rett[42] = tt_Dynamic("Type here the amount of cash you want to receive in exchange for the offer"); + rett[43] = tt_Dynamic("Click here to send the offer"); + rett[44] = tt_Dynamic("Check this radio button to offer the selected technology"); + rett[45] = tt_Dynamic("Choose here the technology you want to offer"); + rett[50] = tt_Dynamic("Select here the page of pending technology exchange offers you want to display"); + rett[51] = tt_Dynamic("Click here to accept this technology exchange"); + rett[52] = tt_Dynamic("Click here to decline this technology exchange"); + rett[60] = tt_Dynamic("Selected here the page of the history of technology exchanges to display"); + rett[70] = tt_Dynamic("Click here to implement new technologies, view foreseen breakthroughs and already implemented technologies"); + rett[71] = tt_Dynamic("Click here to enact and revoke laws"); + rett[72] = tt_Dynamic("Click here to balance your research budget between fundamental, military and civilian research"); + rett[73] = tt_Dynamic("Click here to give or sell technologies and scientific knowledge with other empires, and examine offers made to you"); + rett[80] = tt_Dynamic("Click here to unlock the category"); + rett[81] = tt_Dynamic("Click here to prevent this category from being changed along with the other two"); +} + +function getLoadingText() +{ + return "Loading page data ..."; +} + + + + +function getTopicsPage() +{ + var str = '

    New technologies

    '; + var tl = '!' + rtExpanded.join('!!') + '!'; + var i, d, id; + + if (rtCompleted.length == 0) + str += '

    No new technology has been discovered.

    '; + else + { + str += ''; + str += ''; + for (i=0;i'; + str += '[ ' + (d ? '-' : '+') + ' ] '; + if (d) { + str += ''; + } + else + str += ''; + str += ''; + str += ''; + if (d) + str += ''; + str += ''; + } + str += '
     TechnologyTypeCost 
    ' + rtTitles[id] + '
    '; + str += rtDescriptions[id] + '
    More information ...
    ' + rtTitles[id] + '' + rTypes[rtTypes[id]] + '€' + formatNumber(rtCosts[id]) + 'Implement technology
      

    '; + } + + str += '

    Foreseen breakthroughs

    '; + if (rtForeseen.length == 0) + str += '

    No breakthrough is currently in sight.

    '; + else + { + str += ''; + str += ''; + for (i=0;i'; + str += '[ ' + (d ? '-' : '+') + ' ] '; + if (d) + { + str += ''; + } + else + str += ''; + str += ''; + str += ''; + if (d) + str += ''; + str += ''; + } + str += '
     TechnologyTypeEstimated costCompletion
    ' + rtTitles[id] + '
    '; + str += rtDescriptions[id] + '
    More information ...
    ' + rtTitles[id] + '' + rTypes[rtTypes[id]] + '€' + formatNumber(rtCosts[id]) + '' + rtForeseenP[id] + '%
      

    '; + } + + str += '

    Implemented technologies

    ' + + drawImplementedList(tl, 0) + drawImplementedList(tl, 1) + drawImplementedList(tl, 2) + + '
    '; + + return str; +} + + +function drawImplementedList(tl, type) +{ + var str = '

    ' + rTypes[type] + ' research

    '; + if (rtImplemented.length == 0) + return str + '

    No such technology has been implemented at the moment.

    '; + + var n = 0; + for (i=0;i'; + str += '[ ' + (d ? '-' : '+') + ' ] '; + if (d) + { + str += '' + rtTitles[id] + '
    '; + str += rtDescriptions[id] + '
    More information ... '; + } + else + str += '' + rtTitles[id] + ''; + } + if (n>0) + str += ''; + else + str += '

    No such technology has been implemented at the moment.

    '; + return str; +} + + +function confirmTech(id) +{ + var str; + str = 'Implementing ' + rtTitles[id] + '\nThis will cost ' + formatNumber(rtCosts[id]) + ' euros.\n'; + str += 'Are you sure you want to do this?'; + return confirm(str); +} + + +function implError(code) { + var str = 'Error: '; + switch (code) { + case 0: + str += 'you do not have enough cash to implement this technology'; + break; + case 1: + str += 'trying to cheat by hacking the server _is_ evil'; + break; + case 200: + str += 'you can\'t implement technologies while in vacation mode'; + break; + default: + str += 'an unknown error has occured'; + break; + } + alert(str + '.'); +} + + + + +function getLawsPage() +{ + var str = '

    Enacted laws

    '; + var tl = '!' + rtExpanded.join('!!') + '!'; + var i, d, id; + + if (lEnacted.length == 0) + str += '

    No laws have been enacted yet.

    '; + else + { + str += ''; + str += ''; + for (i=0;i'; + str += '[ ' + (d ? '-' : '+') + ' ] '; + if (d) + str += ''; + else + str += ''; + if (lTimeRevoke[id] == 0) + { + str += ''; + } + else + { + str += ''; + } + if (d) + str += ''; + str += ''; + } + str += '
     LawCost 
    ' + rtTitles[id] + '
    ' + rtDescriptions[id] + '
    More information ...
    ' + rtTitles[id] + '€' + formatNumber(rtCosts[id]) + 'Revoke' + lTimeRevoke[id] + ' day' + (lTimeRevoke[id] > 1 ? 's' : ''); + str += ' left before the law can be revoked.
      

    '; + } + + str += '

    Available laws

    '; + if (lAvailable.length == 0) + str += '

    No laws are available.

    '; + else + { + str += ''; + str += ''; + for (i=0;i'; + str += '[ ' + (d ? '-' : '+') + ' ] '; + if (d) + str += ''; + else + str += ''; + if (lTimeEnact[id] == 0) + { + str += ''; + } + else + { + str += ''; + } + if (d) + str += ''; + str += ''; + } + str += '
     LawCost 
    ' + rtTitles[id] + '
    ' + rtDescriptions[id] + '
    More information ...
    ' + rtTitles[id] + '€' + formatNumber(rtCosts[id]) + 'Enact' + lTimeEnact[id] + ' day' + (lTimeEnact[id] > 1 ? 's' : ''); + str += ' left before the law can be enacted.
      

    '; + } + + return str; +} + + +function confirmEnact(id) +{ + var str; + str = 'Enacting ' + rtTitles[id] + ' will cost ' + formatNumber(rtCosts[id]) + ' euros.\n'; + str += 'You will not be able to revoke the law before 5 days have passed.\n'; + str += 'Are you sure you want to do this?'; + return confirm(str); +} + + +function confirmRevoke(id) +{ + var str; + str = 'Revoking ' + rtTitles[id] + ' will cost ' + formatNumber(rtCosts[id]) + ' euros.\n'; + str += 'You will not be able to enact the law again before 5 days have passed.\n'; + str += 'Are you sure you want to do this?'; + return confirm(str); +} + + +function slawError(code) { + var str = 'Error: '; + switch (code) { + case 0: + str += 'you do not have enough cash to switch this law\'s status'; + break; + case 1: + str += 'trying to cheat by hacking the server _is_ evil'; + break; + case 200: + str += 'you can\'t enact or revoke laws while in vacation mode'; + break; + default: + str += 'an unknown error has occured'; + break; + } + alert(str + '.'); +} + + + +function budgetError(code) { + var str = 'Error: '; + switch (code) { + case 0: + str += 'trying to cheat by hacking the server _is_ evil'; + break; + case 200: + str += 'you can\'t modify your research budget while in vacation mode'; + break; + default: + str += 'an unknown error has occured'; + break; + } + alert(str + '.'); +} + + + +function drawAttributionsDisplay() +{ + var i, str = '

    Research budget

    '; + str += '

    Research points: per day

    '; + str += '
    '; + + for (i=0;i '; + str += ''; + if (i==0) + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + if (i==0) + str += ''; + str += ''; + if (i==0) + str += ''; + str += ''; + } + str += ''; + str += ''; + str += ''; + str += '
    ' + rTypes[i] + ' research: <<< %>>>    point(s)
     
       
    '; + + document.getElementById('respage').innerHTML = str; +} + + +function drawBudgetControls() +{ + document.getElementById('rbcl').innerHTML = ''; + document.getElementById('rbcr').innerHTML = ''; +} + + + +function drawDiplomacyRestrained(d) +{ + var str = '

    Give / receive scientific assistance

    '; + str += '

    Your account is too young to use this feature. You must wait for ' + d + ' more day' + (d > 1 ? 's' : '') + '.

    '; + document.getElementById('respage').innerHTML = str; +} + + +function drawRDSentAlready() +{ + var str = '

    Send scientific assistance

    '; + str += '

    We already sent an assistance offer in the last 22 hours (' + formatDate(rdSentOffer.time) + ').
    '; + str += 'We offered to give ' + rdSentOffer.player + ' '; + if (rdSentOffer.tech == "") + str += 'research assistance'; + else + str += 'the "' + rtTitles[rdSentOffer.tech] + '" technology'; + if (rdSentOffer.price > 0) + str += ' in exchange for €' + formatNumber(rdSentOffer.price) + ''; + str += '; the offer '; + if (rdSentOffer.status == "P") + str += 'is still pending.'; + else if (rdSentOffer.status == "A") + str += 'has been accepted.'; + else + str += 'has been declined.'; + str += '

    '; + document.getElementById('rdgive').innerHTML = str; +} + + +function drawRDSendForm() +{ + var i, str = '

    Send scientific assistance

    '; + + str += ''; + str += ' 0) + { + str += ' onClick="rdfType=0;document.getElementById(\'rdstype0\').checked=true" >'; + str += ' in exchange for €'; + } + str += '
     Offer  0) + { + str += '
     '; + } + str += ' technology 
    '; + + document.getElementById('rdgive').innerHTML = str; + document.getElementById('rdsto').value = rdfTarget; + document.getElementById('rdsprice').value = rdfPrice; +} + + +function drawRDPending() +{ + var str = '

    Received assistance offers

    '; + if (rdRecOffers.length == 0) + str += '

    No assistance offers have been received.

    '; + else + { + var n, m, i; + n = rdRecOffers.length; + m = n % 5; + n = (n-m)/5 + (m>0?1:0); + if (rdRecPage >= n) + rdRecPage = n-1; + + str += '
     Page '; + if (n == 1) + str += "1 / 1"; + else + { + str += ' / ' + n; + } + str += '
     
    '; + var r = new Array( + 'Unfortunately, we do not have enough cash to accept this offer.', + 'Since we have already accepted another offer in the past 22 hours, we cannot accept this offer.', + 'However, we have already discovered or are about to discover this technology.', + 'However, we do not have the scientific knowledge required to accept this offer.' + ); + for (i=5*rdRecPage;i has offered to give us '; + if (rdRecOffers[i].tech == "") + str += 'scientific assistance'; + else + str += 'the "' + rtTitles[rdRecOffers[i].tech] + '" technology'; + if (rdRecOffers[i].price > 0) + str += ' in exchange for €' + formatNumber(rdRecOffers[i].price) + ''; + str += '. The offer was received at ' + formatDate(rdRecOffers[i].time) + '.
    '; + + if (rdRecOffers[i].status2 == 0) + { + str += ''; + str += ' '; + } + else + { + str += r[rdRecOffers[i].status2 - 1] + ' You may either decline the offer or let it expire.
    '; + str += ''; + } + + str += '
    '; + } + str += '
    '; + } + document.getElementById('rdrec').innerHTML = str; +} + + +function drawRDHistory() +{ + var str = '

    History

    '; + if (rdHistory.length == 0) + str += '

    The history is empty.

    '; + else + { + var n, m, i; + n = rdHistory.length; + m = n % 5; + n = (n-m)/5 + (m>0?1:0); + if (rdHistPage >= n) + rdHistPage = n-1; + + str += '
     Page '; + if (n == 1) + str += "1 / 1"; + else + { + str += ' / ' + n; + } + str += '
     
    '; + for (i = 5 * rdHistPage; i < rdHistory.length && i < 5 * (rdHistPage + 1); i ++) { + str += ''; + } + str += '
    '; + if (rdHistory[i].type == "R") + str +='Player ' + rdHistory[i].player + ' offered to give us '; + else + str +='We offered to give player ' + rdHistory[i].player + ' '; + if (rdHistory[i].tech == "") + str += 'scientific assistance'; + else + str += 'the "' + rtTitles[rdHistory[i].tech] + '" technology'; + if (rdHistory[i].price > 0) + str += ' in exchange for €' + formatNumber(rdHistory[i].price) + ''; + str += '. The offer was ' + (rdHistory[i].type == "R" ? 'received' : 'sent') + ' at ' + formatDate(rdHistory[i].time); + str += ' and '; + if (rdHistory[i].status == "E") + str += "expired one day later."; + else if (rdHistory[i].status == "A") + str += "was accepted."; + else + str += "was declined."; + + str += '
    '; + } + document.getElementById('rdhist').innerHTML = str; +} + + +function confirmSendOffer(tp, tc, pl, pr) +{ + var str = "You are about to offer "; + if (tp == 0) + str += "scientific assistance"; + else + str += 'data on the "' + rtTitles[tc] + '" technology'; + str += '\nto ' + pl; + if (pr > 0) + str += ' in exchange for ' + formatNumber(pr.toString()) + ' euros.'; + str += '\nYou will not be able to make another offer for 24 hours.'; + return confirm(str); +} + + +function rdOfferError(ec) +{ + var str = "Error :\n"; + + switch (ec) + { + case 1: str += "You have not selected a technology to offer."; break + case 2: str += "You have not specified a player to send the offer to."; break; + case 3: str += "The price you specified is invalid."; break; + case 4: str += "This player does not exist."; break; + case 5: str += "You do not seem to have this technology."; break; + case 6: str += "You can't send a diplomatic offer to yourself."; break; + case 7: str += "This player is on vacation."; break; + case 8: str += "Invalid data received."; break; + case 9: str += "It seems you have already sent an offer in the past 24h."; break; + case 10: str += "This player is under Peacekeeper protection."; break; + case 200: str += "You can't send diplomatic offers while in vacation mode."; break; + case 201: str += "You can't send diplomatic offers while under Peacekeeper protection."; break; + default: str += "An unknown error has happened ("+ec+")."; break; + } + + alert(str); +} + + +function rdAcceptError(ec) { + var str = "Error :\n"; + + switch (ec) { + case 1: str += "You do not have enough cash to accept this offer."; break + case 2: str += "You have already accepted this offer."; break; + case 3: str += "You have already declined this offer."; break; + case 4: str += "Unknown offer identifier."; break; + case 5: str += "You don't have the dependencies for this technology."; break; + case 200: str += "You can't accept or reject diplomatic offers while in vacation mode."; break; + case 201: str += "You can't accept diplomatic offers while under Peacekeeper protection."; break; + default: str += "An unknown error has happened ("+ec+")."; break; + } + + alert(str); +} + + +function confirmAcceptOffer(i) +{ + var str = "Are you sure you want to accept " + rdRecOffers[i].player + "'s offer to\ngive us "; + if (rdRecOffers[i].type == "R") + str += "scientific assistance"; + else + str += "the \"" + rtTitles[rdRecOffers[i].tech] + "\" technology"; + if (rdRecOffers[i].price > 0) + str += " in exchange for\n" + formatNumber(rdRecOffers[i].price) + " euros"; + str += "?"; + return confirm(str); + +} + + +function confirmRejectOffer(i) +{ + var str = "Are you sure you want to decline " + rdRecOffers[i].player + "'s offer to\ngive us "; + if (rdRecOffers[i].type == "R") + str += "scientific assistance"; + else + str += "the \"" + rtTitles[rdRecOffers[i].tech] + "\" technology"; + if (rdRecOffers[i].price > 0) + str += " in exchange for\n" + formatNumber(rdRecOffers[i].price) + " euros"; + str += "?"; + return confirm(str); + +} diff --git a/site/static/beta5/js/pg_research.js b/site/static/beta5/js/pg_research.js new file mode 100644 index 0000000..6de5193 --- /dev/null +++ b/site/static/beta5/js/pg_research.js @@ -0,0 +1,655 @@ +var pageMode; +var pageModes = new Array('Topics', 'Laws', 'Attributions', 'Diplomacy'); +var puTimer; + +var onGetComplete; +var rtGetData = new Array(); // Research topics for which data must be requested +var rtTitles = new Array(); // Research topics titles +var rtDescriptions = new Array(); // Research topics descriptions +var rtCosts = new Array(); // Research topics implementation cost +var rtTypes = new Array(); // Research topics types +var rtExpanded = new Array(); // Topics for which the description is being displayed + +var rtAll; // All research topics or laws + +var rtCompleted; // Completed researches +var rtImplemented; // Implemted technologies +var rtForeseen; // Foreseen breakthroughs +var rtForeseenP; // Foreseen breakthroughs completion percentages + +var lEnacted; // Enacted laws +var lTimeRevoke; // Time left before a law can be revoked +var lAvailable; // Available laws +var lTimeEnact; // Time left before a law can be enacted again + +var rbPoints; // Research points / day +var rbPercentage; // Percentage going to each research type +var rbOriPercentage; // Percentage going to each research type before player modification +var rbCatPoints; // Points going to each category +var rbLocks; // Locked categories + +var rdDiplomacyDelay; +var rdSentOffer; // Offer sent by the player, false if none +var rdRecOffers; // List of received offers +var rdHistory; // List of past offers +var rdAvailTech; // List of available technologies +var rdRecPage = 0; // Current page for the received offers list +var rdHistPage = 0; // Current page for the history +var rdfType = 0; +var rdfTech = 0; +var rdfTarget = ""; +var rdfPrice = ""; + +var rett; + +function initResearchPage(data) +{ + setPage(data); +} + + +function showDescription(id) +{ + rtExpanded.push(id); + if (pageMode == 'Topics') + topicsDisplayOk(); + else if (pageMode == 'Laws') + lawsDisplayOk(); +} + + +function hideDescription(id) +{ + var t = rtExpanded, i; + rtExpanded = new Array(); + for (i=0;i tb ? 1 : -1); +} + + +function descriptionsReceived(data) +{ + if (data != "") + { + var i, l, a = data.split("\n"); + l = a.length / 4; + for (i=0;i'; + else + str += ''; + str += pageTitles[i]; + if (pageModes[i] != pageMode) + str += ''; + else + str += ''; + } + document.getElementById('respsel').innerHTML = str; +} + + + + +function buildTopicsDisplay(data) +{ + var all = data.split('!'); + var t, i; + + rtCompleted = (all[1] == "" ? new Array() : all[1].split('#')); + rtAll = rtCompleted; + rtImplemented = (all[0] == "" ? new Array() : all[0].split('#')); + rtAll = rtAll.concat(rtImplemented); + + t = all[2].split('#'); + rtForeseen = new Array(); + rtForeseenP = new Array(); + for (i=0;all[2] != "" && i 0) + { + onGetComplete = 'topicsDisplayOk();'; + x_getDescriptions(rtGetData.join('#'), descriptionsReceived); + } + else + topicsDisplayOk(); + + puTimer = setTimeout('x_getTopicsData(buildTopicsDisplay)', 900000); +} + + +function topicsDisplayOk() +{ + rtCompleted.sort(sortResearch); + rtImplemented.sort(sortResearch); + rtForeseen.sort(sortResearch); + document.getElementById('respage').innerHTML = getTopicsPage(); +} + + +function implementResult(data) { + if (data == "OK") { + updateHeader(); + setPage('Topics'); + } else { + var a = data.split('#'); + implError(parseInt(a[1], 10)); + } +} + + +function implementTechnology(id) +{ + if (!confirmTech(id)) + return; + x_implementTechnology(id, implementResult); +} + + + + +function buildLawsDisplay(data) +{ + var t, i; + + t = data.split('#'); + lEnacted = new Array(); + lTimeRevoke = new Array(); + lAvailable = new Array(); + lTimeEnact = new Array(); + for (i=0;data != "" && i 0) + { + onGetComplete = 'lawsDisplayOk();'; + x_getDescriptions(rtGetData.join('#'), descriptionsReceived); + } + else + lawsDisplayOk(); + + puTimer = setTimeout('x_getLawsData(buildLawsDisplay)', 900000); +} + + +function lawsDisplayOk() +{ + lEnacted.sort(sortResearch); + lAvailable.sort(sortResearch); + document.getElementById('respage').innerHTML = getLawsPage(); +} + + +function switchLawResult(data) { + if (data == "OK") { + updateHeader(); + setPage('Laws'); + } else { + var a = data.split('#'); + slawError(parseInt(a[1], 10)); + } +} + + +function revokeLaw(id) +{ + if (!confirmRevoke(id)) + return; + x_switchLaw(id, switchLawResult); +} + + +function enactLaw(id) +{ + if (!confirmEnact(id)) + return; + x_switchLaw(id, switchLawResult); +} + + + + +function buildAttributionsDisplay(data) +{ + var a = data.split('#'), i; + + rbPoints = a[0]; + rbOriPercentage = new Array(); + for (i=0;i'; + else if (l) + t = ' '; + else + t = '[U]'; + document.getElementById('rblock'+i).innerHTML = t; + d = d || (rbPercentage[i] != rbOriPercentage[i]); + } + if (d) + drawBudgetControls(); + else + { + document.getElementById('rbcl').innerHTML = ' '; + document.getElementById('rbcr').innerHTML = ' '; + } +} + + +function switchLock(l) +{ + var i, a, s = '!' + rbLocks.join('!') + '!'; + if (s.indexOf('!' + l + '!') == -1) + rbLocks.push(l); + else + { + a = new Array(); + for (i=0;i 100) + rv = 100 - rbPercentage[a]; + else + rv = v; + if (rv == 0) + return; + + mv = mv.concat(rbPercentage); + mv.sort(new Function('a', 'b', 'return a - b')); + m = ''; + for (i=0;i 100) + continue; + + rbPercentage[a] += rs; + rbPercentage[j] -= rs; + rv -= rs; + orv = 0; + } + + computeCatPoints(); + updateAttributionsValues(); +} + + +function resetBudget() +{ + rbPercentage = (new Array()).concat(rbOriPercentage); + rbLocks = new Array(); + computeCatPoints(); + updateAttributionsValues(); +} + + +function validateBudget() +{ + var s = rbPercentage.join('#'); + x_validateBudget(s, budgetValidated); +} + + +function budgetValidated(data) { + if (data == "OK") { + rbOriPercentage = (new Array()).concat(rbPercentage); + computeCatPoints(); + updateAttributionsValues(); + } else { + var a = data.split('#'); + budgetError(parseInt(a[1], 10)); + } +} + + + +function ResearchOffer(id,type,status,status2,price,tech,time,player) +{ + this.id = id; + this.type = type; + this.status = status; + this.status2 = status2; + this.price = price; + this.tech = tech; + this.time = parseInt(time, 10); + this.player = player; + + var d = new Date(); + var t = d.getTime(); + t = (t - (t % 1000)) / 1000; + this.today = (t - this.time <= rdDiplomacyDelay); +} + + +function buildDiplomacyDisplay(data) +{ + if (data.indexOf('\n') == -1) + drawDiplomacyRestrained(data); + else + { + var i, o, a = data.split('\n'), tl; + + rdDiplomacyDelay = parseInt(a.shift(), 10); + tl = a.shift().split('#'); + rdAvailTech = new Array(); + rtGetData = new Array(); + for (i=0;i 1) + { + for (i=0;i 0) + { + onGetComplete = 'drawDiplomacyPage();'; + x_getDescriptions(rtGetData.join('#'), descriptionsReceived); + } + else + drawDiplomacyPage(); + } + + puTimer = setTimeout('x_getDiplomacyData(buildDiplomacyDisplay)', 900000); +} + +function drawDiplomacyLayout() +{ + var str = '
    '; + str += ''; + str += '
     
     
      
    '; + document.getElementById('respage').innerHTML = str; +} + +function drawDiplomacyPage() +{ + if (typeof rdSentOffer == "object") + drawRDSentAlready(); + else + { + rdAvailTech.sort(sortResearch); + drawRDSendForm(); + } + drawRDPending(); + drawRDHistory(); +} + + +function sendOffer() +{ + if (rdfType == 1 && rdfTech == 0) + { + rdOfferError(1); + return; + } + + if (rdfTarget == "") + { + rdOfferError(2); + return; + } + + var p; + if (rdfPrice != "") + { + p = parseInt(rdfPrice, 10); + if (rdfPrice != p.toString() || p < 0) + { + rdOfferError(3); + return; + } + } + else + p = 0; + + if (!confirmSendOffer(rdfType, rdfTech, rdfTarget, p)) + return; + + x_sendOffer(rdfType, rdfTech, rdfTarget, p, offerSent); +} + + +function offerSent(data) +{ + if (data.indexOf('ERR#') == 0) + { + var a = data.split("#"); + a[1] = parseInt(a[1], 10); + rdOfferError(a[1]); + } + else + buildDiplomacyDisplay(data); +} + + +function acceptOffer(oid) +{ + var i; + for (i=0;i

    ' + + TechTrade.AccessDenied.mainText + '
    ' + + (TechTrade.main.alliance != '' + ? TechTrade.AccessDenied.allianceTxt + : TechTrade.AccessDenied.noAllianceTxt) + + '

    '; +}; + + +/* TechTrade.Page component + * + * Handles the main technology trading page, including the menu and contents. + */ +TechTrade.Page = function () { + Component.apply(this, [false]); + + this.newSlot('showPage'); + + this.menu = new TechTrade.Menu(); + this.addChild(this.menu); + this.menu.bindEvent('Selected', 'showPage', this); + + this.pages = { + Submit: new TechTrade.Page.Main(), + Req: new TechTrade.Page.Request(), + ViewList: new TechTrade.Page.List(), + SOrders: new TechTrade.Page.SetOrders(), + VOrders: new TechTrade.Page.ViewOrders() + }; + this.cPage = null; +}; +TechTrade.Page.prototype = new Component; +TechTrade.Page.prototype.getDocID = function () { return 'page-contents'; }; +TechTrade.Page.prototype.setPage = function (page) { + this.menu.select(page); +}; +TechTrade.Page.prototype.showPage = function (page) { + if (this.cPage) { + this.removeChild(this.cPage); + } + TechTrade.main.page = page; + this.cPage = this.pages[page]; + if (this.cPage) { + this.addChild(this.cPage); + this.cPage.show(); + } +}; +TechTrade.Page.prototype.draw = function () { + var e = this.getElement(); + if (!e) { + return; + } + + if (TechTrade.main.list) { + e.innerHTML = '
    ' + + '
     
     
    '; + } else { + e.innerHTML = '

    ' + TechTrade.Page.loadingText + + '

    '; + } +}; + + +/* TechTrade.Menu component + * + * Displays the menu at the top of the page + */ +TechTrade.Menu = function () { + Component.apply(this, [false]); + + this.selected = null; + this.newEvent('Selected', false); + this.bindEvent('Selected', 'draw'); +}; +TechTrade.Menu.prototype = new Component; +TechTrade.Menu.prototype.getDocID = function () { return 'page-menu'; }; +TechTrade.Menu.prototype.select = function (page) { + if (page != this.selected) { + this.selected = page; + this.onSelected(page); + } +}; +TechTrade.Menu.prototype.draw = function () { + var e = this.getElement(); + if (!e) { + return; + } + + var entries = []; + for (var i = 0; i < TechTrade.Menu.entries.length ; i ++) { + if (TechTrade.main.privileges < TechTrade.Menu.minPriv[i] + || (! TechTrade.main.hasRequests && TechTrade.Menu.needReq[i]) ) { + continue; + } + var str; + if (TechTrade.Menu.entries[i] == this.selected) { + str = ''; + } else { + str = ''; + } + str += TechTrade.Menu.text[i] + (TechTrade.Menu.entries[i] == this.selected ? '' : ''); + entries.push(str); + } + + e.innerHTML = entries.join(' - '); +}; +TechTrade.Menu.entries = [ 'Submit', 'Req', 'ViewList', 'SOrders', 'VOrders' ]; +TechTrade.Menu.minPriv = [ 1, 2, 3, 4, 4 ]; +TechTrade.Menu.needReq = [ false, true, false, false, false ]; + + +/* TechTrade.Page.Main component + * + * Handles the tech list submission / player orders viewing page. + */ +TechTrade.Page.Main = function () { + Component.apply(this, [false]); + + this.newSlot('preparePage'); + this.newSlot('dataReceived'); + + this.detachEvent('Show', 'drawAll'); + this.bindEvent('Show', 'preparePage'); + + this.loadPage = new Component.Ajax('getSubmitPage'); + this.sendList = new Component.Ajax('submitList'); + this.ajaxSendOffer = new Component.Ajax('sendOffer'); + this.loadPage.bindEvent('Returned', 'dataReceived', this); + this.sendList.bindEvent('Returned', 'dataReceived', this); + this.ajaxSendOffer.bindEvent('Returned', 'dataReceived', this); +}; +TechTrade.Page.Main.prototype = new Component; +TechTrade.Page.Main.prototype.dataReceived = function (data) { + if (data == 'FU') { + TechTrade.main.getInfo.call(); + return; + } + + var toStore = {}; + var spData = data.split('\n'); + var line; + + // Submission data + line = spData.shift().split('#'); + toStore.canSubmit = (line.shift() == 1); + if (line[0] != '') { + toStore.prevSubmission = parseInt(line.shift(), 10); + } else { + toStore.prevSubmission = null; + line.shift(); + } + if (line[0] != '') { + toStore.nextSubmission = parseInt(line.shift(), 10); + } else { + toStore.nextSubmission = null; + } + + // Send order + line = spData.shift(); + if (line == '-') { + toStore.sendOrder = null; + } else { + toStore.sendOrder = {}; + line = line.split('#'); + toStore.sendOrder.orderedOn = parseInt(line.shift(), 10); + if (line[0] == '') { + toStore.sendOrder.obeyedOn = null; + line.shift(); + } else { + toStore.sendOrder.obeyedOn = parseInt(line.shift(), 10); + } + toStore.sendOrder.canSend = (line.shift() == '1'); + toStore.sendOrder.what = line.shift(); + toStore.sendOrder.playerID = line.shift(); + toStore.sendOrder.playerName = line.join('#'); + } + + // Receive order + line = spData.shift(); + if (line == '-') { + toStore.recOrder = null; + } else { + toStore.recOrder = {}; + line = line.split('#'); + toStore.recOrder.orderedOn = parseInt(line.shift(), 10); + if (line[0] == '') { + toStore.recOrder.obeyedOn = null; + line.shift(); + } else { + toStore.recOrder.obeyedOn = parseInt(line.shift(), 10); + } + toStore.recOrder.what = line.shift(); + toStore.recOrder.playerID = line.shift(); + toStore.recOrder.playerName = line.join('#'); + } + + // Draw page + this.data = toStore; + this.drawAll(); +}; +TechTrade.Page.Main.prototype.preparePage = function () { + this.data = null; + this.loadPage.call(); + this.drawAll(); +}; +TechTrade.Page.Main.prototype.getDocID = function () { return "page-panel"; }; +TechTrade.Page.Main.prototype.draw = function () { + var e = this.getElement(); + if (!e || TechTrade.main.page != 'Submit') { + return; + } + + if (this.data) { + e.innerHTML = '' + + '' + + ''; + } else { + e.innerHTML = '

    ' + TechTrade.Page.subLoadingText + + '

    '; + } +}; +TechTrade.Page.Main.prototype.drawSubmit = function () { + var str = '

    '; + if (this.data.prevSubmission) { + str += TechTrade.Page.Main.lastSubText.replace(/__LS__/, formatDate(this.data.prevSubmission)); + } else { + str += TechTrade.Page.Main.noLastSubText; + } + if (this.data.canSubmit) { + str += '

    ' + + '

    '; + } else { + str += '
    ' + TechTrade.Page.Main.nextSubText.replace(/__NS__/, formatDate(this.data.nextSubmission)) + + '

    '; + } + + return str; +}; +TechTrade.Page.Main.prototype.drawOrders = function () { + if (!(this.data.sendOrder || this.data.recOrder)) { + return TechTrade.Page.Main.noOrdersText; + } + + var str = '', otext; + if (this.data.sendOrder) { + otext = TechTrade.Page.Main.sendOrder; + otext = otext.replace(/__TN__/, TechTrade.main.list[this.data.sendOrder.what].name); + otext = otext.replace(/__TI__/, this.data.sendOrder.what); + otext = otext.replace(/__PI__/, this.data.sendOrder.playerID); + otext = otext.replace(/__PN__/, this.data.sendOrder.playerName); + otext = otext.replace(/__OR__/, formatDate(this.data.sendOrder.orderedOn)); + str += '

    ' + TechTrade.Page.Main.sendOrderTitle + '

    ' + otext; + if (this.data.sendOrder.obeyedOn) { + otext = TechTrade.Page.Main.sendOrderObeyed; + otext = otext.replace(/__OE__/, formatDate(this.data.sendOrder.obeyedOn)); + str += '
    ' + otext; + } else { + str += '

    ' + + '

    '; + } + str += '

    '; + } + + if (this.data.recOrder) { + otext = TechTrade.Page.Main.recOrder; + otext = otext.replace(/__TN__/, TechTrade.main.list[this.data.recOrder.what].name); + otext = otext.replace(/__TI__/, this.data.recOrder.what); + otext = otext.replace(/__PI__/, this.data.recOrder.playerID); + otext = otext.replace(/__PN__/, this.data.recOrder.playerName); + otext = otext.replace(/__OR__/, formatDate(this.data.recOrder.orderedOn)); + str += '

    ' + TechTrade.Page.Main.recOrderTitle + '

    ' + otext; + if (this.data.recOrder.obeyedOn) { + str += TechTrade.Page.Main.recOrderObeyed.replace(/__OE__/, formatDate( + this.data.recOrder.obeyedOn + )); + } + str += '.

    '; + } + + return str; +}; +TechTrade.Page.Main.prototype.submitList = function () { + var e = document.getElementById('submit-tech-list'); + if (!e) { + return; + } + e.disabled = true; + this.sendList.call(); +}; +TechTrade.Page.Main.prototype.sendOffer = function () { + var e = document.getElementById('send-tech-offer'); + if (!e) { + return; + } + e.disabled = true; + this.ajaxSendOffer.call(); +}; + + +/** TechTrade.Page.Request component + * + * Handles the requests page + */ +TechTrade.Page.Request = function () { + Component.apply(this, [false]); + + this.newSlot('preparePage'); + this.newSlot('dataReceived'); + + this.detachEvent('Show', 'drawAll'); + this.bindEvent('Show', 'preparePage'); + + this.addSelector = new Component.Form.DropDown('', TechTrade.Page.Request.noSelection); + this.addChild(this.addSelector); + this.addSelector.bindEvent('Selected', 'drawAll', this); + + this.loadPage = new Component.Ajax('getRequestsPage'); + this.submitRequests = new Component.Ajax('submitRequests'); + this.loadPage.bindEvent('Returned', 'dataReceived', this); + this.submitRequests.bindEvent('Returned', 'dataReceived', this); +}; +TechTrade.Page.Request.prototype = new Component; +TechTrade.Page.Request.prototype.getDocID = function () { return "page-panel"; }; +TechTrade.Page.Request.prototype.draw = function () { + var e = this.getElement(); + if (!e || TechTrade.main.page != 'Req') { + return; + } + + if (this.data) { + var str = '

    ' + TechTrade.Page.Request.title + '

    '; + if (this.data.requests.length) { + str += '
      '; + for (var i = 0; i < this.data.requests.length; i ++) { + str += this.drawRequestLine(i); + } + str += '
    '; + } else { + str += '

    ' + TechTrade.Page.Request.noReqText + '

    '; + } + str += ''; + if (this.data.originalRequests != this.data.requests.join('#')) { + } + if (this.data.canRequest.length && this.data.requests.length < 5) { + str += '

    ' + TechTrade.Page.Request.addRequest + '   ' + + this.drawAddLinks() + '

    '; + } else if (this.data.canRequest.length) { + str += '

    ' + TechTrade.Page.Request.fiveRequests + '

    '; + } else { + str += '

    ' + TechTrade.Page.Request.noAvailableTechs + '

    '; + } + e.innerHTML = str; + } else { + e.innerHTML = '

    ' + TechTrade.Page.subLoadingText + + '

    '; + } +}; +TechTrade.Page.Request.prototype.preparePage = function () { + this.data = null; + this.loadPage.call(); + this.drawAll(); +}; +TechTrade.Page.Request.prototype.dataReceived = function (data) { + if (data == 'FU') { + TechTrade.main.getInfo.call(); + return; + } + + var spData = data.split('\n'); + var toStore = {}; + + toStore.requests = (spData[0] == '' ? [] : spData[0].split('#')); + toStore.canRequest = (spData[1] == '' ? [] : spData[1].split('#')); + toStore.originalRequests = spData[0]; + + this.data = toStore; + this.updateSelector(); + this.drawAll(); +}; +TechTrade.Page.Request.prototype.updateSelector = function () { + this.addSelector.clearOptions(); + var reqStr = '#' + this.data.requests.join('##') + '#'; + var canRequest = []; + for (var i in this.data.canRequest) { + if (reqStr.indexOf('#' + this.data.canRequest[i] + '#') == -1) { + canRequest.push(this.data.canRequest[i]); + } + } + canRequest.sort(function (a, b) { + var ta = TechTrade.main.list[a].name.toLowerCase(), + tb = TechTrade.main.list[b].name.toLowerCase(); + return (ta == tb) ? 0 : (ta > tb ? 1 : -1); + }); + for (var i in canRequest) { + this.addSelector.appendOption( + canRequest[i], TechTrade.main.list[canRequest[i]].name + ); + } +}; +TechTrade.Page.Request.prototype.drawAddLinks = function () { + var v = this.addSelector.selected; + if (v == '') { + return ''; + } + return '' + TechTrade.Page.Request.addRequestManual + + ' - ' + + TechTrade.Page.Request.addRequestAdd + ''; +}; +TechTrade.Page.Request.prototype.addRequest = function () { + var v = this.addSelector.selected; + if (v == '' || this.data.requests.length == 5) { + this.drawAll(); + return; + } + this.data.requests.push(v); + this.updateSelector(); + this.drawAll(); +}; +TechTrade.Page.Request.prototype.drawRequestLine = function (index) { + var tech = this.data.requests[index]; + var str = '
  • ' + + TechTrade.main.list[tech].name + ' (' + TechTrade.Page.Request.delRequest + + ''; + if (index > 0) { + str += ' - ' + TechTrade.Page.Request.increasePriority + ''; + } + if (index < 4 && this.data.requests.length > index + 1) { + str += ' - ' + TechTrade.Page.Request.decreasePriority + ''; + } + str += ')'; + return str; +}; +TechTrade.Page.Request.prototype.increasePriority = function (index) { + var v1 = this.data.requests[index]; + this.data.requests[index] = this.data.requests[index - 1]; + this.data.requests[index - 1] = v1; + this.drawAll(); +}; +TechTrade.Page.Request.prototype.decreasePriority = function (index) { + var v1 = this.data.requests[index]; + this.data.requests[index] = this.data.requests[index + 1]; + this.data.requests[index + 1] = v1; + this.drawAll(); +}; +TechTrade.Page.Request.prototype.removeRequest = function (index) { + this.data.requests.splice(index, 1); + this.updateSelector(); + this.drawAll(); +}; +TechTrade.Page.Request.prototype.submit = function () { + var e = document.getElementById('submit-requests'); + if (!e) { + return; + } + e.disabled = true; + this.submitRequests.call(this.data.requests.join('#')); +}; + + +/** TechTrade.Page.List component + * + * Displays the alliance-wide technology list + */ +TechTrade.Page.List = function () { + Component.apply(this, [false]); + + this.newSlot('preparePage'); + this.newSlot('dataReceived'); + + this.newSlot('pppChanged'); + this.newSlot('tppChanged'); + + this.detachEvent('Show', 'drawAll'); + this.bindEvent('Show', 'preparePage'); + + this.md5 = null; + this.data = null; + this.xCoord = this.yCoord = 0; + + this.pppSelector = new Component.Form.DropDown('', '???'); + this.addChild(this.pppSelector); + for (var i = 2; i < 11; i ++) { + this.pppSelector.appendOption(i, i); + } + this.pppSelector.bindEvent('Selected', 'pppChanged', this); + + this.tppSelector = new Component.Form.DropDown('', '???'); + this.addChild(this.tppSelector); + for (var i = 1; i < 9; i ++) { + this.tppSelector.appendOption(i * 10, i * 10); + } + this.tppSelector.bindEvent('Selected', 'tppChanged', this); + + this.loadPage = new Component.Ajax('getAllianceList'); + this.loadPage.bindEvent('Returned', 'dataReceived', this); + this.sendOptions = new Component.Ajax('setListOptions'); +}; +TechTrade.Page.List.prototype = new Component; +TechTrade.Page.List.prototype.getDocID = function () { return "page-panel"; }; +TechTrade.Page.List.prototype.draw = function () { + var e = this.getElement(); + if (!e || TechTrade.main.page != 'ViewList') { + return; + } + + if (this.data) { + if (this.forceRedraw || !document.getElementById('alliance-list-grid')) { + e.innerHTML = this.genGrid(); + this.forceRedraw = false; + } + this.drawGridContents(); + } else { + e.innerHTML = '

    ' + TechTrade.Page.subLoadingText + + '

    '; + } +}; +TechTrade.Page.List.prototype.preparePage = function () { + this.loadPage.call(this.md5); + this.drawAll(); +}; +TechTrade.Page.List.prototype.dataReceived = function (data) { + if (data == 'FU') { + TechTrade.main.getInfo.call(); + return; + } else if (data == 'NC') { + return; + } + + var spLine, nTechs; + var spData = data.split('\n'); + var newMD5 = spData.shift(); + + spLine = spData.shift().split('#'); + if (! this.techsPerPage) { + this.techsPerPage = parseInt(spLine[0], 10); + this.playersPerPage = parseInt(spLine[1], 10); + } + + var nPlayers = parseInt(spData.shift(), 10); + var toStore = []; + + for (var i = 0; i < nPlayers; i ++) { + spLine = spData.shift().split('#'); + var pId = spLine.shift(); + var pData = {}; + + pData.onVacation = (spLine.shift() == '1'); + pData.lastSubmission = parseInt(spLine.shift(), 10); + pData.restriction = (spLine.shift() != '0'); + nTechs = parseInt(spLine.shift(), 10); + pData.name = spLine.join('#'); + pData.techs = []; + + for (var j = 0; j < nTechs; j ++) { + spLine = spData.shift().split('#'); + pData.techs[spLine[0]] = spLine[1]; + } + + spLine = spData.shift(); + if (spLine == '') { + pData.requests = []; + } else { + pData.requests = spLine.split('#'); + } + + toStore[pId] = pData; + } + + this.md5 = newMD5; + this.data = toStore; + + this.plCache = []; + for (j in this.data) { + this.plCache.push(j); + } + var me = this; + this.plCache.sort(function (a, b) { + var pa = me.data[a].name.toLowerCase(), pb = me.data[b].name.toLowerCase(); + return (pa > pb ? 1 : (pa < pb ? -1 : 0)); + }); + + if (this.playersPerPage > this.plCache.length) { + this.playersPerPage = this.plCache.length; + } + if (this.playersPerPage + this.xCoord > this.plCache.length) { + this.xCoord = this.plCache.length - (this.playersPerPage - 1); + } + + this.tppSelector.select(this.techsPerPage); + this.pppSelector.select(this.playersPerPage); + + this.drawAll(); +}; +TechTrade.Page.List.prototype.genGrid = function () { + var str0 = '
  •  

    ' + + TechTrade.Page.Main.submitTitle + '

    ' + this.drawSubmit() + '

    ' + TechTrade.Page.Main.ordersTitle + + '

    ' + this.drawOrders() + '

     
    '; + var str1 = ''; + var str2 = ''; + var str3 = ''; + var str4 = ''; + for (var i = 0; i < this.playersPerPage; i ++) { + str0 += ''; + str1 += ''; + str2 += ''; + str3 += ''; + } + for (var i = 0; i < this.techsPerPage; i ++) { + str4 += ''; + for (var j = 0; j < this.playersPerPage; j ++) { + str4 += ''; + } + str4 += ''; + } + str4 += ''; + return str0 + str1 + str2 + str3 + str4 + '
    ' + + TechTrade.Page.List.playersPerPage + '   - ' + TechTrade.Page.List.techsPerPage + '  ' + + '
     ' + + TechTrade.Page.List.playerHdr + ' 
    ' + + TechTrade.Page.List.lastSubHdr + '
    ' + TechTrade.Page.List.vacationHdr + '
    ' + TechTrade.Page.List.restrainedHdr + '
     
        
      
     
    '; +}; +TechTrade.Page.List.prototype.drawGridContents = function () { + if (!document.getElementById('alliance-list-grid') || ! TechTrade.main.list) { + return; + } + + var i, j; + + if (! this.tlCache) { + this.tlCache = []; + for (j in TechTrade.main.list) { + this.tlCache.push(j); + } + } + + if (this.yCoord > 0) { + document.getElementById('list-up').innerHTML = '/\\'; + } else { + document.getElementById('list-up').innerHTML = ' '; + } + if (this.yCoord + this.techsPerPage < this.tlCache.length) { + document.getElementById('list-down').innerHTML = '\\/'; + } else { + document.getElementById('list-down').innerHTML = ' '; + } + + if (this.xCoord > 0) { + document.getElementById('list-left').innerHTML = '<<'; + } else { + document.getElementById('list-left').innerHTML = ' '; + } + if (this.xCoord + this.playersPerPage < this.plCache.length) { + document.getElementById('list-right').innerHTML = '>>'; + } else { + document.getElementById('list-right').innerHTML = ' '; + } + + for (i = this.yCoord, j = 0; i < this.yCoord + this.techsPerPage; i ++, j ++) { + document.getElementById('tech-hdr-' + j).innerHTML = TechTrade.main.list[this.tlCache[i]].name; + } + for (i = this.xCoord, j = 0; i < this.xCoord + this.playersPerPage; i ++, j ++) { + if (! this.plCache[i]) { + document.getElementById('player-hdr-' + j).innerHTML = ' '; + document.getElementById('last-sub-hdr-' + j).innerHTML = ' '; + document.getElementById('vac-hdr-' + j).innerHTML = ' '; + document.getElementById('res-hdr-' + j).innerHTML = ' '; + for (k = 0; k < this.techsPerPage; k ++) { + with (document.getElementById('ptech-' + j + '-' + k)) { + innerHTML = ' '; + backgroundColor = color = '#000000'; + } + } + continue; + } + + document.getElementById('player-hdr-' + j).innerHTML = this.data[this.plCache[i]].name; + var d = this.data[this.plCache[i]].lastSubmission; + document.getElementById('last-sub-hdr-' + j).innerHTML = d ? formatDate(d) : '-'; + document.getElementById('vac-hdr-' + j).innerHTML = TechTrade.Page.List.boolText[ + this.data[this.plCache[i]].onVacation ? 1 : 0 + ]; + document.getElementById('res-hdr-' + j).innerHTML = TechTrade.Page.List.boolText[ + this.data[this.plCache[i]].restriction ? 1 : 0 + ]; + + var k, l; + for (k = this.yCoord, l = 0; k < this.yCoord + this.techsPerPage; k ++, l ++) { + var cell = document.getElementById('ptech-' + j + '-' + l); + var tStat = this.data[this.plCache[i]].techs[this.tlCache[k]]; + + if (!tStat) { + var z; + for (z = 0; z < this.data[this.plCache[i]].requests.length; z ++) { + if (this.data[this.plCache[i]].requests[z] == this.tlCache[k]) { + cell.style.backgroundColor = '#ff0000'; + cell.style.color = '#ffffff'; + cell.innerHTML = TechTrade.Page.List.requested + ' (#' + (z + 1) + ')'; + break; + } + } + if (z == this.data[this.plCache[i]].requests.length) { + cell.style.backgroundColor = '#000000'; + cell.style.color = '#7f7f7f'; + cell.innerHTML = this.data[this.plCache[i]].lastSubmission ? '-' : '?'; + } + continue; + } + + var col, bgCol, contents; + switch (tStat) { + case 'N': + col = '#000000'; bgCol = '#bfbf7f'; contents = TechTrade.Page.List.techStatus[0]; + break; + case 'F': + col = '#ff0000'; bgCol = '#8fbf8f'; contents = TechTrade.Page.List.techStatus[1]; + break; + case 'I': + col = '#0000ff'; bgCol = '#5fbf5f'; contents = TechTrade.Page.List.techStatus[2]; + break; + case 'L': + col = '#0000ff'; bgCol = '#5fbf5f'; contents = TechTrade.Page.List.techStatus[3]; + break; + } + cell.style.backgroundColor = bgCol; + cell.style.color = col; + cell.innerHTML = contents; + } + } + +}; +TechTrade.Page.List.prototype.moveUp = function () { + if (this.yCoord > 0) { + this.yCoord --; + this.drawGridContents(); + } +}; +TechTrade.Page.List.prototype.moveDown = function () { + if (this.tlCache && this.yCoord + this.techsPerPage < this.tlCache.length) { + this.yCoord ++; + this.drawGridContents(); + } +}; +TechTrade.Page.List.prototype.moveLeft = function () { + if (this.xCoord > 0) { + this.xCoord --; + this.drawGridContents(); + } +}; +TechTrade.Page.List.prototype.moveRight = function () { + if (this.plCache && this.xCoord + this.playersPerPage < this.plCache.length) { + this.xCoord ++; + this.drawGridContents(); + } +}; +TechTrade.Page.List.prototype.pppChanged = function (value) { + var v = parseInt(value, 10); + if (this.playersPerPage == v) { + return; + } + if (v > this.plCache.length) { + this.pppSelector.select(this.plCache.length); + return; + } + this.playersPerPage = v; + if (this.playersPerPage + this.xCoord > this.plCache.length) { + this.xCoord = this.plCache.length - this.playersPerPage; + } + this.sendOptions.call(this.playersPerPage, this.techsPerPage); + this.forceRedraw = true; + this.drawAll(); +}; +TechTrade.Page.List.prototype.tppChanged = function (value) { + if (this.techsPerPage == parseInt(value, 10)) { + return; + } + this.techsPerPage = parseInt(value, 10); + if (this.techsPerPage + this.yCoord > this.tlCache.length) { + this.yCoord = this.tlCache.length - this.techsPerPage; + } + this.forceRedraw = true; + this.sendOptions.call(this.playersPerPage, this.techsPerPage); + this.drawAll(); +}; + + +/* TechTrade.Page.ViewOrders component + * + * Handles the list of current orders + */ +TechTrade.Page.ViewOrders = function () { + Component.apply(this, [false]); + + this.newSlot('preparePage'); + this.newSlot('dataReceived'); + + this.detachEvent('Show', 'drawAll'); + this.bindEvent('Show', 'preparePage'); + + this.loadPage = new Component.Ajax('getCurrentOrders'); + this.loadPage.bindEvent('Returned', 'dataReceived', this); +}; +TechTrade.Page.ViewOrders.prototype = new Component; +TechTrade.Page.ViewOrders.prototype.dataReceived = function (data) { + if (data == 'FU') { + TechTrade.main.getInfo.call(); + return; + } else if (data == 'NC') { + return; + } + + var toStore = []; + var spData = data.split('\n'); + this.md5 = spData.shift(); + var line; + + this.plCache = []; + while (line = spData.shift()) { + line = line.split('#'); + var record = {}; + var id = line.shift(); + + record.sendTo = line.shift(); + record.sendTech = line.shift(); + record.sendIssued = parseInt(line.shift(), 10); + record.sendObeyed = parseInt(line.shift(), 10); + + record.recvFrom = line.shift(); + record.recvTech = line.shift(); + record.recvIssued = parseInt(line.shift(), 10); + record.recvObeyed = parseInt(line.shift(), 10); + + record.name = line.join('#'); + + toStore[id] = record; + this.plCache.push(id); + } + + this.plCache.sort(function (a,b) { + var ra = toStore[a].name.toLowerCase(), rb = toStore[b].name.toLowerCase(); + return (ra == rb ? 0 : (ra > rb ? 1 : -1)); + }); + + // Draw page + this.data = toStore; + this.drawAll(); +}; +TechTrade.Page.ViewOrders.prototype.preparePage = function () { + this.loadPage.call(this.md5); + this.drawAll(); +}; +TechTrade.Page.ViewOrders.prototype.getDocID = function () { return "page-panel"; }; +TechTrade.Page.ViewOrders.prototype.draw = function () { + var e = this.getElement(); + if (!e || TechTrade.main.page != 'VOrders') { + return; + } + + if (this.data) { + e.innerHTML = '

    ' + TechTrade.Page.ViewOrders.title + '

    ' + + (this.data.length ? this.drawContents() + : ('

    ' + TechTrade.Page.ViewOrders.noOrders + '

    ')); + } else { + e.innerHTML = '

    ' + TechTrade.Page.subLoadingText + + '

    '; + } +}; +TechTrade.Page.ViewOrders.prototype.drawContents = function () { + var str = ''; + + for (var i in this.plCache) { + var player = this.data[this.plCache[i]]; + var text; + + str += ''; + } + + str += '
    ' + + TechTrade.Page.ViewOrders.playerHdr + '' + + TechTrade.Page.ViewOrders.sendHdr + '' + + TechTrade.Page.ViewOrders.recvHdr + '
    ' + player.name + + ''; + if (player.sendTo == '') { + str += TechTrade.Page.ViewOrders.noOrders; + } else { + text = TechTrade.Page.ViewOrders.sendTech; + str += text.replace(/__TI__/, player.sendTech).replace(/__TN__/, + TechTrade.main.list[player.sendTech].name).replace(/__PN__/, + this.data[player.sendTo].name).replace(/__OI__/, + formatDate(player.sendIssued)); + if (player.sendObeyed) { + str += '
    ' + TechTrade.Page.ViewOrders.techSent.replace(/__OE__/, + formatDate(player.sendObeyed)); + } + } + str += '
    ' + if (player.recvFrom == '') { + str += TechTrade.Page.ViewOrders.noOrders; + } else { + text = TechTrade.Page.ViewOrders.receiveTech; + str += text.replace(/__TI__/, player.recvTech).replace(/__TN__/, + TechTrade.main.list[player.recvTech].name).replace(/__PN__/, + this.data[player.recvFrom].name).replace(/__OI__/, + formatDate(player.recvIssued)); + if (player.recvObeyed) { + str += '
    ' + TechTrade.Page.ViewOrders.techReceived.replace(/__OE__/, + formatDate(player.recvObeyed)); + } + } + str += '
    '; + return str; +}; + + +/* TechTrade.Page.SetOrders component + * + * Handles the list of current orders + */ +TechTrade.Page.SetOrders = function () { + Component.apply(this, [false]); + + this.newEvent('TechListUpdated'); + this.newEvent('SubmitError'); + + this.newSlot('preparePage'); + this.newSlot('dataReceived'); + this.newSlot('sendOrders'); + this.newSlot('ordersSent'); + + this.detachEvent('Show', 'drawAll'); + this.bindEvent('Show', 'preparePage'); + + this.editor = new TechTrade.OrdersEditor(this); + this.addChild(this.editor); + this.bindEvent('TechListUpdated', 'techListUpdated', this.editor); + this.bindEvent('SubmitError', 'submitError', this.editor); + this.editor.bindEvent('Submitted', 'sendOrders', this); + + this.loadPage = new Component.Ajax('getChangeOrdersData'); + this.loadPage.bindEvent('Returned', 'dataReceived', this); + this.submitOrders = new Component.Ajax('submitOrders'); + this.submitOrders.bindEvent('Returned', 'ordersSent', this); +}; +TechTrade.Page.SetOrders.prototype = new Component; +TechTrade.Page.SetOrders.prototype.dataReceived = function (data) { + if (data == 'FU') { + TechTrade.main.getInfo.call(); + return; + } else if (data == 'NC') { + return; + } + + // Fetch the tech lists + var spLine, nTechs; + var spData = data.split('\n'); + var newMD5 = spData.shift(); + + spLine = spData.shift().split('#'); + this.lastSet = parseInt(spLine.shift(), 10); + this.nextSet = parseInt(spLine.shift(), 10); + if (this.nextSet == 0) { + var nPlayers = parseInt(spData.shift(), 10); + var techList = []; + + for (var i = 0; i < nPlayers; i ++) { + spLine = spData.shift().split('#'); + var pId = spLine.shift(); + var pData = {}; + + pData.onVacation = (spLine.shift() == '1'); + pData.lastSubmission = parseInt(spLine.shift(), 10); + pData.restriction = (spLine.shift() != '0'); + nTechs = parseInt(spLine.shift(), 10); + pData.name = spLine.join('#'); + pData.techs = []; + + for (var j = 0; j < nTechs; j ++) { + spLine = spData.shift().split('#'); + pData.techs[spLine[0]] = spLine[1]; + } + + spLine = spData.shift(); + if (spLine == '') { + pData.requests = []; + } else { + pData.requests = spLine.split('#'); + } + + techList[pId] = pData; + } + + this.techList = techList; + + // Generate the player name cache + this.plCache = []; + for (j in this.techList) { + this.plCache.push(j); + } + var me = this; + this.plCache.sort(function (a, b) { + var pa = me.techList[a].name.toLowerCase(), pb = me.techList[b].name.toLowerCase(); + return (pa > pb ? 1 : (pa < pb ? -1 : 0)); + }); + this.onTechListUpdated(this.techList, this.plCache); + } else { + this.plCache = this.techList = null; + } + this.md5 = newMD5; + + // Draw page + this.drawAll(); +}; +TechTrade.Page.SetOrders.prototype.sendOrders = function (orders) { + var toSend = []; + for (var i in orders) { + if (! orders[i].sendTo) { + continue; + } + toSend.push(orders[i].player + '#' + orders[i].sendTech + '#' + orders[i].sendTo); + } + this.submitOrders.call(toSend.join('!')); +}; +TechTrade.Page.SetOrders.prototype.ordersSent = function (data) { + if (data == 'FU') { + TechTrade.main.getInfo.call(); + return; + } + + if (data.indexOf('ERR') == 0) { + this.onSubmitError(); + data = data.replace(/^ERR/, ''); + } + this.dataReceived(data); +}; +TechTrade.Page.SetOrders.prototype.preparePage = function () { + this.data = null; + this.loadPage.call(this.md5); + this.drawAll(); +}; +TechTrade.Page.SetOrders.prototype.getDocID = function () { return "page-panel"; }; +TechTrade.Page.SetOrders.prototype.draw = function () { + var e = this.getElement(); + if (!e || TechTrade.main.page != 'SOrders') { + return; + } + + if (this.md5) { + var str = '

    ' + TechTrade.Page.SetOrders.title + '

    '; + if (this.nextSet != 0) { + str += '

    ' + TechTrade.Page.SetOrders.alreadySubmitted.replace(/__LS__/, + formatDate(this.lastSet)).replace(/__NS__/, formatDate(this.nextSet)) + '

    '; + } else { + if (this.editor.getElement()) { + return; + } + str += '
     
    '; + } + e.innerHTML = str; + } else { + e.innerHTML = '

    ' + TechTrade.Page.subLoadingText + + '

    '; + } +}; + + +/** TechTrade.OrdersEditor component + * + * Handles editing the technology trading orders + */ +TechTrade.OrdersEditor = function (page) { + Component.apply(this, [false]); + + this.newSlot('techListUpdated'); + this.newSlot('playerChanged'); + this.newSlot('techChanged'); + this.newSlot('submitError'); + this.newEvent('Submitted'); + + this.plSelector = new Component.Form.DropDown('-', '----'); + this.addChild(this.plSelector); + this.plSelector.bindEvent('Selected', 'playerChanged', this); + + this.tSelector = new Component.Form.DropDown('-', '----'); + this.addChild(this.tSelector); + this.tSelector.bindEvent('Selected', 'techChanged', this); + + this.page = page; + this.orders = null; +}; +TechTrade.OrdersEditor.prototype = new Component; +TechTrade.OrdersEditor.prototype.techListUpdated = function (techList, plCache) { + this.techList = techList; + this.dependencies = TechTrade.computeDependencies(techList); + if (this.orders) { + alert("FIXME: tech list received, orders should be updated"); + } else { + this.ordersSet = 0; + this.orders = []; + this.rOrders = []; + for (var i in plCache) { + var pid = plCache[i]; + this.orders.push({ + player: pid, + sendTech: null, + sendTo: null, + recvTech: null, + recvFrom: null + }); + this.rOrders[pid] = i; + } + } + this.drawAll(); +}; +TechTrade.OrdersEditor.prototype.draw = function () { + var e = this.getElement(); + if (!e || ! this.orders) { + return; + } + + if (this.submitting) { + e.innerHTML = '

    ' + TechTrade.OrdersEditor.submitting + '

    '; + } else if (this.orderEdit) { + e.innerHTML = this.drawEditor(); + } else { + e.innerHTML = this.drawList(); + } +}; +TechTrade.OrdersEditor.prototype.drawList = function () { + var bs0 = 'text-align:left;border-style:solid;border-color:white;border-width: 0px 0px 1px 0px'; + var bs = 'vertical-align:top;border-style:solid;border-color:white;border-width: 1px 0px;padding:10px 0px'; + var str = '' + + ''; + + for (var i in this.orders) { + var pOrder = this.orders[i]; + + str += ''; + if (!this.dependencies[pOrder.player].ok) { + str += ''; + } else { + str += ''; + } + str += '
    ' + TechTrade.Page.ViewOrders.playerHdr + + '' + TechTrade.Page.ViewOrders.sendHdr + + '' + TechTrade.Page.ViewOrders.recvHdr + + '
    ' + this.techList[pOrder.player].name + '' + + TechTrade.OrdersEditor.cantTrade + ''; + if (pOrder.sendTech) { + str += TechTrade.OrdersEditor.sendTech.replace(/__TI__/, + pOrder.sendTech).replace(/__TN__/, + TechTrade.main.list[pOrder.sendTech].name).replace(/__PN__/, + this.techList[pOrder.sendTo].name) + + '
    (' + TechTrade.OrdersEditor.delOrder + + ' - ' + TechTrade.OrdersEditor.editOrder + + ')'; + } else { + str += TechTrade.Page.ViewOrders.noOrders + ' (' + + TechTrade.OrdersEditor.addOrder + ')'; + } + str += '
    '; + if (pOrder.recvTech) { + str += TechTrade.OrdersEditor.recvTech.replace(/__TI__/, + pOrder.recvTech).replace(/__TN__/, + TechTrade.main.list[pOrder.recvTech].name).replace(/__PN__/, + this.techList[pOrder.recvFrom].name) + + '
    (' + TechTrade.OrdersEditor.delOrder + + ' - ' + TechTrade.OrdersEditor.editOrder + + ')'; + } else { + str += TechTrade.Page.ViewOrders.noOrders + ' (' + + TechTrade.OrdersEditor.addOrder + ')'; + } + } + str += '

    '; + + return str; +}; +TechTrade.OrdersEditor.prototype.confirmSubmit = function () { + var e = document.getElementById('submit-orders'); + if (!e) { + return; + } + + e.disabled = true; + if (confirm(TechTrade.OrdersEditor.confirmSubmit)) { + this.submitting = true; + this.drawAll(); + this.onSubmitted(this.orders); + } else { + e.disabled = false; + } +}; +TechTrade.OrdersEditor.prototype.submitError = function () { + alert(TechTrade.OrdersEditor.submitError); + this.submitting = false; +}; +TechTrade.OrdersEditor.prototype.delOrder = function (index, isRec) { + var order = this.orders[index]; + + if (isRec) { + var oOrder = this.orders[this.rOrders[order.recvFrom]]; + oOrder.sendTo = oOrder.sendTech = order.recvFrom = order.recvTech = null; + } else { + var oOrder = this.orders[this.rOrders[order.sendTo]]; + oOrder.recvFrom = oOrder.recvTech = order.sendTo = order.sendTech = null; + } + this.ordersSet --; + this.drawAll(); +}; +TechTrade.OrdersEditor.prototype.addOrder = function (index, isRec) { + this.initEditor({ + order: index, + isReceive: isRec, + oTech: null, + oPlayer: null + }); +}; +TechTrade.OrdersEditor.prototype.editOrder = function (index, isRec) { + this.initEditor({ + order: index, + isReceive: isRec, + oTech: isRec ? this.orders[index].recvTech : this.orders[index].sendTech, + oPlayer: isRec ? this.orders[index].recvFrom : this.orders[index].sendTo + }); +}; +TechTrade.OrdersEditor.prototype.drawEditor = function () { + var eParams = this.orderEdit; + var order = this.orders[eParams.order]; + var text = eParams.isReceive ? TechTrade.OrdersEditor.willReceive : TechTrade.OrdersEditor.willSend; + var str = '

    ' + text.replace(/__PN__/, + this.techList[order.player].name).replace(/__SP1__/, + ' ').replace(/__SP2__/, + ' ') + + ' ' + + '

    '; + + return str; +}; +TechTrade.OrdersEditor.prototype.cancelEdit = function () { + this.orderEdit = null; + this.drawAll(); +}; +TechTrade.OrdersEditor.prototype.confirmEdit = function () { + if (this.orderEdit) { + var order = this.orders[this.orderEdit.order]; + var rOrder = this.orders[this.rOrders[this.orderEdit.player]]; + if (this.orderEdit.isReceive) { + var oOrder = this.orderEdit.oPlayer ? this.orders[this.rOrders[this.orderEdit.oPlayer]] : null; + if (oOrder) { + oOrder.sendTech = null; + oOrder.sendTo = null; + } + + order.recvTech = this.orderEdit.tech; + order.recvFrom = this.orderEdit.player; + rOrder.sendTech = this.orderEdit.tech; + rOrder.sendTo = order.player; + } else { + var oOrder = this.orderEdit.oPlayer ? this.orders[this.rOrders[this.orderEdit.oPlayer]] : null; + if (oOrder) { + oOrder.recvTech = null; + oOrder.recvFrom = null; + } + + rOrder.recvTech = this.orderEdit.tech; + rOrder.recvFrom = order.player; + order.sendTech = this.orderEdit.tech; + order.sendTo = this.orderEdit.player; + } + if (!this.orderEdit.oPlayer) { + this.ordersSet ++; + } + } + this.cancelEdit(); +}; +TechTrade.OrdersEditor.prototype.initEditor = function (editData) { + this.orderEdit = editData; + this.orderEdit.tech = this.orderEdit.oTech; + this.orderEdit.player = this.orderEdit.oPlayer; + this.updateEditorStatus(); +}; +TechTrade.OrdersEditor.prototype.updateEditorStatus = function () { + this.orderEdit.confirmOk = (this.orderEdit.tech && this.orderEdit.player); + + var order = this.orders[this.orderEdit.order]; + var tPlayers = [], tTechs = []; + var depList = this.orderEdit.isReceive ? this.dependencies[order.player].canReceive + : this.dependencies[order.player].canSend; + + for (var i in depList) { + var oOrder = this.orders[this.rOrders[depList[i].player]]; + if (this.orderEdit.isReceive && oOrder.sendTech && oOrder.sendTo != order.player + || !this.orderEdit.isReceive && oOrder.recvTech && oOrder.recvFrom != order.player) { + + continue; + } + + if (('#' + tPlayers.join('#') + '#').indexOf('#' + depList[i].player + '#') == -1 + && (!this.orderEdit.tech || depList[i].tech == this.orderEdit.tech) ) { + tPlayers.push(depList[i].player); + } + if (('#' + tTechs.join('#') + '#').indexOf('#' + depList[i].tech + '#') == -1 + && (!this.orderEdit.player || depList[i].player == this.orderEdit.player) ) { + tTechs.push(depList[i].tech); + } + } + + this.plSelector.clearOptions(); + this.plSelector.appendOption('', TechTrade.OrdersEditor.selectPlayer); + for (var i in tPlayers) { + this.plSelector.appendOption(tPlayers[i], this.techList[tPlayers[i]].name); + } + this.plSelector.selected = this.orderEdit.player ? this.orderEdit.player : ''; + + this.tSelector.clearOptions(); + this.tSelector.appendOption('', TechTrade.OrdersEditor.selectTech); + for (var i in tTechs) { + this.tSelector.appendOption(tTechs[i], TechTrade.main.list[tTechs[i]].name); + } + this.tSelector.selected = this.orderEdit.tech ? this.orderEdit.tech : ''; + + this.drawAll(); +}; +TechTrade.OrdersEditor.prototype.playerChanged = function (player) { + if (!this.orderEdit) { + return; + } + this.orderEdit.player = (player == '' ? null : player); + this.updateEditorStatus(); +}; +TechTrade.OrdersEditor.prototype.techChanged = function (tech) { + if (!this.orderEdit) { + return; + } + this.orderEdit.tech = (tech == '' ? null : tech); + this.updateEditorStatus(); +}; diff --git a/site/static/beta5/js/pg_ticks-en.js b/site/static/beta5/js/pg_ticks-en.js new file mode 100644 index 0000000..d48a393 --- /dev/null +++ b/site/static/beta5/js/pg_ticks-en.js @@ -0,0 +1,6 @@ +var tText = ['Previous tick', 'Next tick', 'Last tick', 'Time remaining', 'These ticks have stopped.']; + +function getDaysText(p) +{ + return "day" + (p?'s':''); +} diff --git a/site/static/beta5/js/pg_ticks.js b/site/static/beta5/js/pg_ticks.js new file mode 100644 index 0000000..85653c1 --- /dev/null +++ b/site/static/beta5/js/pg_ticks.js @@ -0,0 +1,177 @@ +var ticks; +var tList; +var updateTimer; +var stDifference; +var nUpdates; + + +function Tick(id, first, interval, last) +{ + this.id = id; + this.first = parseInt(first, 10); + this.interval = parseInt(interval, 10); + this.stopTime = (last != "") ? parseInt(last, 10) : -1; + + this.started = true; + this.stDays = 0; + this.previous = 0; + this.next = 0; + this.last = 0; + this.remaining = 0; + + this.compute = Tick_compute; + this.draw = Tick_draw; + this.setEnd = Tick_setEnd; +} + +function Tick_setEnd(last) +{ + this.stopTime = (last != "") ? parseInt(last, 10) : -1; +} + +function Tick_compute(st) +{ + var f = this.first % 86400; + var n = (st % 86400); + var tm = n + ((n= this.first); + if (!this.started) + { + var tl = this.first - st; + this.stDays = Math.floor((tl - (tl % 86400)) / 86400); + this.next = this.first; + this.remaining = this.next - (st + this.stDays * 86400); + this.last = -1; + return; + } + else + this.stDays = 0; + + if (this.stopTime > -1) + { + var s = (this.stopTime - this.first); + var m = s % this.interval; + this.last = this.first + s - m + } + else + this.last = -1; + + if (this.last != -1 && nx + st > this.last) + { + this.next = -1; + this.previous = this.last; + } + else + { + this.next = nx + st; + this.previous = this.next - this.interval; + this.remaining = nx; + } +} + +function Tick_draw() +{ + var str = '

    ' + tText[0]+ ': '; + if (this.started && this.previous >= this.first) + str += '' + formatDate(this.previous) + ''; + else + str += 'N/A'; + str += '
    '; + if (this.next != -1) + { + str += tText[1] + ': ' + formatDate(this.next + this.stDays * 86400) + ' (' + tText[3] + ': '; + if (!this.started) + str += '' + this.stDays + ' ' + getDaysText(this.stDays > 1) + ', '; + + str += ''; + var rh = (this.remaining - (this.remaining % 3600)) / 3600, + rm = (this.remaining - rh*3600 - (this.remaining % 60)) / 60, + rs = this.remaining - (rh*60+rm)*60; + var s = rh.toString(); + if (s.length == 1) + s = '0' + s; + str += s + ':'; + s = rm.toString(); + if (s.length == 1) + s = '0' + s; + str += s + ':'; + s = rs.toString(); + if (s.length == 1) + s = '0' + s; + str += s + ')'; + if (this.last != -1) + { + str += '
    ' + tText[2] + ': ' + formatDate(this.last) + ''; + } + } + else + str += '' + tText[4] + ''; + str += '

    '; + + document.getElementById('tick' + this.id).innerHTML = str; +} + + +function readInitString() +{ + var l = document.getElementById('tickinit').innerHTML.split('#'); + var now = Math.round((new Date().getTime()) / 1000); + var st = parseInt(l.shift(), 10); + stDifference = now - st; + + ticks = new Array(); + tList = new Array(); + while (l.length > 0) + { + var t = new Tick(l.shift(), l.shift(), l.shift(), l.shift()); + ticks[t.id] = t; + tList.push(t); + t.compute(st); + t.draw(); + } + + nUpdates = 0; + setTimeout('updateTicks()', 1000); +} + +function updateTicks() +{ + var i; + var now = Math.round((new Date().getTime()) / 1000) - stDifference; + var as = true; + for (i=0;i 0) + { + var id = l.shift(); + ticks[id].setEnd(l.shift()); + } + + nUpdates = 0; + setTimeout('updateTicks()', 1000); +} diff --git a/site/static/beta5/js/pg_universe-en.js b/site/static/beta5/js/pg_universe-en.js new file mode 100644 index 0000000..a297165 --- /dev/null +++ b/site/static/beta5/js/pg_universe-en.js @@ -0,0 +1,20 @@ +function makeNextText(name) +{ + return "Next " + name + ": "; +} + +function drawRoundRankings(points,rank) +{ + var str; + if (points == "") + str = 'You are too weak to be in the overall round rankings.'; + else + str = 'Your overall round ranking is #' + formatNumber(rank) + ' (' + formatNumber(points) + ' points)'; + document.getElementById('rndrank').innerHTML = str; +} + + +function getDaysText(p) +{ + return "day" + (p?'s':''); +} diff --git a/site/static/beta5/js/pg_universe.js b/site/static/beta5/js/pg_universe.js new file mode 100644 index 0000000..705ab21 --- /dev/null +++ b/site/static/beta5/js/pg_universe.js @@ -0,0 +1,172 @@ +var stDiff; +var ticks; +var nUpdates; + +function Tick(id,first,interval,last,name) +{ + this.id = id; + this.first = parseInt(first, 10); + this.interval = parseInt(interval, 10); + this.stopTime = (last != "") ? parseInt(last, 10) : -1; + this.name = name; + + this.started = true; + this.stDays = 0; + this.previous = 0; + this.next = 0; + this.last = 0; + this.remaining = 0; + + this.compute = Tick_compute; + this.draw = Tick_draw; +} + +function Tick_compute(st) +{ + var f = this.first % 86400; + var n = (st % 86400); + var tm = n + ((n= this.first); + if (!this.started) + { + var tl = this.first - st; + this.stDays = Math.floor((tl - (tl % 86400)) / 86400); + this.next = this.first; + this.remaining = this.next - (st + this.stDays * 86400); + this.last = -1; + return; + } + + if (this.stopTime > -1) + { + var s = (this.stopTime - this.first); + var m = s % this.interval; + this.last = this.first + s - m + } + else + this.last = -1; + + if (this.last != -1 && nx + st > this.last) + { + this.next = -1; + this.previous = this.last; + } + else + { + this.next = nx + st; + this.previous = this.next - this.interval; + this.remaining = nx; + } +} + +function Tick_draw() +{ + var str = makeNextText(this.name); + if (this.next != -1) + { + if (!this.started) + str += '' + this.stDays + ' ' + getDaysText(this.stDays > 1) + ', '; + str += '' + + var rh = (this.remaining - (this.remaining % 3600)) / 3600, + rm = (this.remaining - rh*3600 - (this.remaining % 60)) / 60, + rs = this.remaining - (rh*60+rm)*60; + var s = rh.toString(); + if (s.length == 1) + s = '0' + s; + str += s + ':'; + s = rm.toString(); + if (s.length == 1) + s = '0' + s; + str += s + ':'; + s = rs.toString(); + if (s.length == 1) + s = '0' + s; + str += s; + } + else + str = 'N/A'; + str += ''; + return str; +} + +function initPage() { + drawUniversePage(document.getElementById('init-data').value); +} + +function drawUniversePage(data) +{ + var l = data.split('\n'); + + // Get universe information + var a = l.shift().split('#'); + document.getElementById('npl').innerHTML = formatNumber(a[0]); + document.getElementById('nnpl').innerHTML = formatNumber(a[1]); + document.getElementById('nnsys').innerHTML = formatNumber(a[2]); + document.getElementById('avgf').innerHTML = formatNumber(a[3]); + document.getElementById('avgt').innerHTML = formatNumber(a[4]); + + // Get protection zone information +// a = l.shift().split('#'); +/* document.getElementById('sznpl').innerHTML = formatNumber(a[0]); + document.getElementById('sznnpl').innerHTML = formatNumber(a[1]); + document.getElementById('sznnsys').innerHTML = formatNumber(a[2]); + document.getElementById('szavgf').innerHTML = formatNumber(a[3]); + document.getElementById('szavgt').innerHTML = formatNumber(a[4]); + // Time left in protection should be drawn in the LD file */ + + // Rankings + a = l.shift().split('#'); + document.getElementById('rknplayers').innerHTML = formatNumber(a[0]); + document.getElementById('genpts').innerHTML = formatNumber(a[1]); + document.getElementById('genrank').innerHTML = '#' + formatNumber(a[2]); + document.getElementById('civpts').innerHTML = formatNumber(a[3]); + document.getElementById('civrank').innerHTML = '#' + formatNumber(a[4]); + document.getElementById('finpts').innerHTML = formatNumber(a[5]); + document.getElementById('finrank').innerHTML = '#' + formatNumber(a[6]); + document.getElementById('milpts').innerHTML = formatNumber(a[7]); + document.getElementById('milrank').innerHTML = '#' + formatNumber(a[8]); + drawRoundRankings(a[9],a[10]); + document.getElementById('idpts').innerHTML = formatNumber(a[11]); + document.getElementById('idrank').innerHTML = '#' + formatNumber(a[12]); + + // Time to ticks + a = l.shift().split('#'); + var now = Math.round((new Date().getTime()) / 1000); + var str = ''; + stDiff = now - parseInt(a.shift(),10); + ticks = new Array(); + while (l.length > 0) + { + a = l.shift().split('#'); + var t = new Tick(a.shift(), a.shift(), a.shift(), a.shift(), a.join('#')); + ticks.push(t); + t.compute(now - stDiff); + str += t.draw() + "
    "; + } + document.getElementById('ticks').innerHTML = str; + + nUpdates = 0; + setTimeout('updateDisplay()', 1000); +} + + +function updateDisplay() +{ + var now = Math.round((new Date().getTime()) / 1000) - stDiff; + var i, str = ''; + for (i=0;i"; + } + document.getElementById('ticks').innerHTML = str; + + nUpdates ++; + if (nUpdates == 12) + setTimeout('x_getInformation(drawUniversePage)', 1000); + else + setTimeout('updateDisplay()', 1000); +} diff --git a/site/static/beta5/js/rpc-en.js b/site/static/beta5/js/rpc-en.js new file mode 100644 index 0000000..bd2c2b1 --- /dev/null +++ b/site/static/beta5/js/rpc-en.js @@ -0,0 +1,57 @@ +function rpc_alertSupport() +{ + alert("LegacyWorlds fatal error\nIt seems your browser has no support for asynchronous JavaScript.\nYou must upgrade your browser."); +} + +function rpc_alertFunction(name) +{ + alert("LegacyWorlds bug\nAn undefined RPC function has been called ('" + name + "')\nThis is a bug, please report it to webmaster@legacyworlds.com."); +} + +function rpc_alertCallback(name, aType, nArgs) +{ + var str = 'Dumping args[]:\n' + nArgs.join('\n'); + alert("LegacyWorlds bug\nThe '"+name+"' RPC function was called without a callback function.\nThe last argument's type was: '"+aType + + "'.\nThis is a bug, please report it to webmaster@legacyworlds.com.\n\n" + str); +} + +function rpc_alertCallID(id) +{ + alert("LegacyWorlds bug\nThe server returned data for call ID '"+id+"' which can't be found in the queue.\nThis is a bug, please report it to webmaster@legacyworlds.com."); +} + +function rpc_showErrorPage() { + var str = '

    LegacyWorlds

    A connection error occured when the page tried to contact the server. This means that you were disconnected from the internet ' + + 'or that the server is currently unavailable.

    You may try reloading the ' + + 'page shortly if you wish to do so. Alternatively you can hang around and enjoy this cute error page.

    '; + var t = document.getElementsByTagName("body"); + if (!t || !t[0]) + return; // FIXME + t[0].innerHTML = str; +} + +function rpc_alertFatalError(code) { + var codes = [ + "Could not open configuration file", "Could not connect to database", "Failed to set up tracking data", + "Failed to set up tracking data", "Failed to set up tracking data", "Failed to set up tracking data", + 'Invalid request', 'Invalid request', 'Page not found', 'Page not found', 'Internal error', 'Internal error', + "Failed to set up session data", "Failed to set up session data", "Failed to set up session data", + "Failed to set up session data", 'Internal error', 'Internal error', "Internal error", 'Internal error', + 'Internal error', 'Internal error', 'Internal error', 'Internal error', 'Internal error', 'Internal error', + 'Invalid extension', 'Unhandled extension', 'Internal error', 'Internal error', 'Resource not found', + ]; + alert("A fatal error occured on the server.\nError " + code + ": " + codes[code] + "\nSorry for the inconvenience."); +} + +function rpc_alertKicked(reason) { + alert("You have been kicked from the game!\n" + (reason != "" ? ("Reason: " + reason) : "No reason was given")); +} + +function rpc_alertUnkownError(data) { + alert("LegacyWorlds bug\nThe server sent an unknown error code: " + data + "\nThis is a bug, please report it to webmaster@legacyworlds.com."); +} + +function rpc_alertUnknownStatus(text) { + alert("LegacyWorlds bug\nThe server sent an invalid RPC reply.\nThis is a bug, please report it to webmaster@legacyworlds.com." + + ("\n" + text)); +} diff --git a/site/static/beta5/js/rpc.js b/site/static/beta5/js/rpc.js new file mode 100644 index 0000000..7a55d4d --- /dev/null +++ b/site/static/beta5/js/rpc.js @@ -0,0 +1,369 @@ +var rpc_objCode = ''; +var rpc_defaultMethod = "GET"; +var rpc_queueLock = false; +var rpc_lockLock = false; +var rpc_currentCalls = new Array(); +var rpc_functions = new Array(); +var rpc_failed = false; + + +// Calls a remote function on the server +function rpc_doCall(name, pArguments) +{ + if (rpc_failed) + return; + + var func = rpc_functions[name]; + if (!func) + { + rpc_alertFunction(name); + return; + } + + var args = new Array(); + var callback, i; + for (i=0;i0) + { + i--; + if (rpc_currentCalls[i].id == id) + return true; + } + return false; +} + + +// Reads the queue and returns the RPC call identified by the ID passed by the +// caller. Returns null if the RPC call cannot be found. +function rpc_getCall(id) +{ + for (i=0;i 1) { + document.getElementById('jsalliance').innerHTML = " [" + a.join('#') + "]"; + } else { + document.getElementById('jsalliance').innerHTML = ""; + } + thmcls_stTimer = setTimeout('thmcls_updateTime()', 1000); + thmcls_hdTimer = setTimeout('x_getHeaderData(thmcls_writeHeader)', 15000); +} + + +function updateHeader() { + if (thmcls_hdTimer) { + clearTimeout(thmcls_hdTimer); + } + if (thmcls_stTimer) { + clearTimeout(thmcls_stTimer); + } + x_getHeaderData(thmcls_writeHeader); +} diff --git a/site/static/beta5/js/thm_cripes.js b/site/static/beta5/js/thm_cripes.js new file mode 100644 index 0000000..de928a0 --- /dev/null +++ b/site/static/beta5/js/thm_cripes.js @@ -0,0 +1,107 @@ +var thmcrp_hdTimer; +var thmcrp_stTimer; +var thmcrp_msgIcon = null, thmcrp_milock = false, thmcrp_dIcon; +var thm_sTime; + + +function thmcrp_activateMsgBlink() { + if (thmcrp_milock) { + setTimeout('thmcrp_activateMsgBlink()', 500); + return; + } + + thmcrp_milock = true; + if (!thmcrp_msgIcon) { + thmcrp_dIcon = false; + thmcrp_msgIcon = setTimeout('thmcrp_blinkMsgBlink()', 200); + } + thmcrp_milock = false; +} + +function thmcrp_disableMsgBlink() { + if (thmcrp_milock) { + setTimeout('thmcrp_disableMsgBlink()', 500); + return; + } + + thmcrp_milock = true; + clearTimeout(thmcrp_msgIcon); + thmcrp_msgIcon = null; + + var e = document.getElementById('msgmenu'); + if (typeof e.oldbgc != 'undefined') { + e.bgColor = e.oldbgc; + } + + thmcrp_milock = false; +} + +function thmcrp_blinkMsgBlink() { + if (thmcrp_milock) { + setTimeout('thmcrp_blinkMsgBlink()', 50); + return; + } + thmcrp_milock = true; + + var e = document.getElementById('msgmenu').getElementsByTagName('h1'); + e = e[0]; + if (thmcrp_dIcon) { + e.oldbgc = e.style.backgroundColor; + e.oldc = e.style.color; + e.style.backgroundColor = '#6fafcf'; + e.style.color = '#000000'; + } else { + e.style.backgroundColor = e.oldbgc; + e.style.color = e.oldc; + } + thmcrp_dIcon = !thmcrp_dIcon; + thmcrp_msgIcon = setTimeout('thmcrp_blinkMsgBlink()', 1000); + thmcrp_milock = false; +} + + +function thmcrp_updateTime() { + thm_sTime ++; + document.getElementById('jsservtm').innerHTML = formatDate(thm_sTime); + thmcrp_stTimer = setTimeout('thmcrp_updateTime()', 1000); +} + + +function thmcrp_writeHeader(data) { + if (thmcrp_stTimer) { + clearTimeout(thmcrp_stTimer); + } + + var a = data.split("#"); + thm_sTime = parseInt(a.shift(), 10); + + document.getElementById('jsservtm').innerHTML = formatDate(thm_sTime); + document.getElementById('jspname').innerHTML = a.shift(); + document.getElementById('jscash').innerHTML = "€" + formatNumber(a.shift()); + + if (a[0] == "1") { + thmcrp_activateMsgBlink(); + } else { + thmcrp_disableMsgBlink(); + } + a.shift(); + + if (a[0] != "" || a.length > 1) { + document.getElementById('jsalliance').innerHTML = " [" + a.join('#') + "]"; + } else { + document.getElementById('jsalliance').innerHTML = ""; + } + thmcrp_stTimer = setTimeout('thmcrp_updateTime()', 1000); + thmcrp_hdTimer = setTimeout('x_getHeaderData(thmcrp_writeHeader)', 15000); +} + + +function updateHeader() { + if (thmcrp_hdTimer) { + clearTimeout(thmcrp_hdTimer); + } + if (thmcrp_stTimer) { + clearTimeout(thmcrp_stTimer); + } + x_getHeaderData(thmcrp_writeHeader); +} diff --git a/site/static/beta5/js/thm_default-en.js b/site/static/beta5/js/thm_default-en.js new file mode 100644 index 0000000..d388fbf --- /dev/null +++ b/site/static/beta5/js/thm_default-en.js @@ -0,0 +1 @@ +var thmdef_getPlanet = 'Get New Planet'; diff --git a/site/static/beta5/js/thm_default.js b/site/static/beta5/js/thm_default.js new file mode 100644 index 0000000..a634f2a --- /dev/null +++ b/site/static/beta5/js/thm_default.js @@ -0,0 +1,178 @@ +var thmdef_hdTimer; +var thmdef_stTimer; +var thmdef_plTimer; +var thmdef_msgIcon = null, thmdef_milock = false, thmdef_dIcon; +var thmdef_mFolders, thmdef_fdTimer; +var thm_sTime; + + +function thmdef_activateMsgIcon() +{ + if (thmdef_milock) + { + setTimeout('thmdef_activateMsgIcon()', 500); + return; + } + + thmdef_milock = true; + if (!thmdef_msgIcon) + { + thmdef_dIcon = false; + thmdef_msgIcon = setTimeout('thmdef_blinkMsgIcon()', 200); + } + thmdef_milock = false; +} + +function thmdef_disableMsgIcon() +{ + if (thmdef_milock) + { + setTimeout('thmdef_disableMsgIcon()', 500); + return; + } + + thmdef_milock = true; + clearTimeout(thmdef_msgIcon); + thmdef_msgIcon = null; + document.getElementById('msgicon').innerHTML = ' '; + document.getElementById('msgicon').onclick = null; + thmdef_milock = false; +} + +function thmdef_viewMessage() { + document.location.href = 'message.redirect'; +} + +function thmdef_blinkMsgIcon() +{ + if (thmdef_milock) + { + setTimeout('thmdef_blinkMsgIcon()', 50); + return; + } + thmdef_milock = true; + + var e = document.getElementById('msgicon'); + if (typeof e.onClick != 'function') + e.onclick = thmdef_viewMessage; + if (thmdef_dIcon) + e.innerHTML = ' '; + else + e.innerHTML = 'New message'; + thmdef_dIcon = !thmdef_dIcon; + thmdef_msgIcon = setTimeout('thmdef_blinkMsgIcon()', 1000); + thmdef_milock = false; +} + + +function thmdef_updateTime() +{ + thm_sTime ++; + document.getElementById('jsservtm').innerHTML = formatDate(thm_sTime); + thmdef_stTimer = setTimeout('thmdef_updateTime()', 1000); +} + + +function thmdef_writeHeader(data) +{ + if (thmdef_stTimer) + clearTimeout(thmdef_stTimer); + + var a = data.split("#"); + thm_sTime = parseInt(a.shift(), 10); + + document.getElementById('jsservtm').innerHTML = formatDate(thm_sTime); + document.getElementById('jspname').innerHTML = a.shift(); + document.getElementById('jscash').innerHTML = "€" + formatNumber(a.shift()); + + if (a[0] == "1") + thmdef_activateMsgIcon(); + else + thmdef_disableMsgIcon(); + a.shift(); + + if (a[0] != "" || a.length > 1) + document.getElementById('jsalliance').innerHTML = " [" + a.join('#') + "]"; + else + document.getElementById('jsalliance').innerHTML = ""; + thmdef_stTimer = setTimeout('thmdef_updateTime()', 1000); + thmdef_hdTimer = setTimeout('x_getHeaderData(thmdef_writeHeader)', 15000); +} + +function thmdef_writePlanets(data) +{ + if (!document.getElementById('jspmenu')) + return; + + var ms = ""; + if (data != '') + { + var i, a = data.split("\n"); + for (i=0;i"; + ms += p[1].replace(' ', ' ') + ""; + } + } + else + ms = '
  • ' + thmdef_getPlanet + '
  • '; + document.getElementById('jspmenu').innerHTML = ms; + thmdef_plTimer = setTimeout('x_getHeaderPList(thmdef_writePlanets)', 180000); +} + +function thmdef_ieDisplay(mid) +{ + document.getElementById(mid).style.display = 'block'; +} + +function thmdef_ieHide(mid) +{ + document.getElementById(mid).style.display = 'none'; +} + +function thmdef_writeFolders(data) +{ + if (!document.getElementById('jsfmenu')) + return; + + var ms = thmdef_mFolders; + if (data != '') + { + var i, a = data.split("\n"); + for (i=0;i"; + ms += p.join('#').replace(' ', ' ') + ""; + } + } + document.getElementById('jsfmenu').innerHTML = ms; + thmdef_fdTimer = setTimeout('x_getHeaderFolders(thmdef_writeFolders)', 180000); +} + +function thmdef_initFolders() +{ + var e = document.getElementById('jsfmenu'); + if (!e) + return; + thmdef_mFolders = e.innerHTML; + x_getHeaderFolders(thmdef_writeFolders); +} + + +function updateHeader() +{ + if (thmdef_hdTimer) + clearTimeout(thmdef_hdTimer); + if (thmdef_stTimer) + clearTimeout(thmdef_stTimer); + if (thmdef_plTimer) + clearTimeout(thmdef_plTimer); + if (thmdef_fdTimer) + clearTimeout(thmdef_fdTimer); + + x_getHeaderData(thmdef_writeHeader); + x_getHeaderPList(thmdef_writePlanets); + x_getHeaderFolders(thmdef_writeFolders); +} diff --git a/site/static/beta5/js/thm_invert-en.js b/site/static/beta5/js/thm_invert-en.js new file mode 100644 index 0000000..cb2454d --- /dev/null +++ b/site/static/beta5/js/thm_invert-en.js @@ -0,0 +1 @@ +var thminv_getPlanet = 'Get New Planet'; diff --git a/site/static/beta5/js/thm_invert.js b/site/static/beta5/js/thm_invert.js new file mode 100644 index 0000000..7262171 --- /dev/null +++ b/site/static/beta5/js/thm_invert.js @@ -0,0 +1,179 @@ +var thminv_hdTimer; +var thminv_stTimer; +var thminv_plTimer; +var thminv_msgIcon = null, thminv_milock = false, thminv_dIcon; +var thminv_mFolders, thminv_fdTimer; +var thm_sTime; + + +function thminv_activateMsgIcon() +{ + if (thminv_milock) + { + setTimeout('thminv_activateMsgIcon()', 500); + return; + } + + thminv_milock = true; + if (!thminv_msgIcon) + { + thminv_dIcon = false; + thminv_msgIcon = setTimeout('thminv_blinkMsgIcon()', 200); + } + thminv_milock = false; +} + +function thminv_disableMsgIcon() +{ + if (thminv_milock) + { + setTimeout('thminv_disableMsgIcon()', 500); + return; + } + + thminv_milock = true; + clearTimeout(thminv_msgIcon); + thminv_msgIcon = null; + document.getElementById('msgicon').innerHTML = ' '; + document.getElementById('msgicon').onclick = null; + thminv_milock = false; +} + +function thminv_viewMessage() +{ + document.location.href = 'message.redirect'; +} + +function thminv_blinkMsgIcon() +{ + if (thminv_milock) + { + setTimeout('thminv_blinkMsgIcon()', 50); + return; + } + thminv_milock = true; + + var e = document.getElementById('msgicon'); + if (typeof e.onClick != 'function') + e.onclick = thminv_viewMessage; + if (thminv_dIcon) + e.innerHTML = ' '; + else + e.innerHTML = 'New message'; + thminv_dIcon = !thminv_dIcon; + thminv_msgIcon = setTimeout('thminv_blinkMsgIcon()', 1000); + thminv_milock = false; +} + + +function thminv_updateTime() +{ + thm_sTime ++; + document.getElementById('jsservtm').innerHTML = formatDate(thm_sTime); + thminv_stTimer = setTimeout('thminv_updateTime()', 1000); +} + + +function thminv_writeHeader(data) +{ + if (thminv_stTimer) + clearTimeout(thminv_stTimer); + + var a = data.split("#"); + thm_sTime = parseInt(a.shift(), 10); + + document.getElementById('jsservtm').innerHTML = formatDate(thm_sTime); + document.getElementById('jspname').innerHTML = a.shift(); + document.getElementById('jscash').innerHTML = "€" + formatNumber(a.shift()); + + if (a[0] == "1") + thminv_activateMsgIcon(); + else + thminv_disableMsgIcon(); + a.shift(); + + if (a[0] != "" || a.length > 1) + document.getElementById('jsalliance').innerHTML = " [" + a.join('#') + "]"; + else + document.getElementById('jsalliance').innerHTML = ""; + thminv_stTimer = setTimeout('thminv_updateTime()', 1000); + thminv_hdTimer = setTimeout('x_getHeaderData(thminv_writeHeader)', 15000); +} + +function thminv_writePlanets(data) +{ + if (!document.getElementById('jspmenu')) + return; + + var ms = ""; + if (data != '') + { + var i, a = data.split("\n"); + for (i=0;i"; + ms += p[1].replace(' ', ' ') + ""; + } + } + else + ms = '
  • ' + thminv_getPlanet + '
  • '; + document.getElementById('jspmenu').innerHTML = ms; + thminv_plTimer = setTimeout('x_getHeaderPList(thminv_writePlanets)', 180000); +} + +function thminv_ieDisplay(mid) +{ + document.getElementById(mid).style.display = 'block'; +} + +function thminv_ieHide(mid) +{ + document.getElementById(mid).style.display = 'none'; +} + +function thminv_writeFolders(data) +{ + if (!document.getElementById('jsfmenu')) + return; + + var ms = thminv_mFolders; + if (data != '') + { + var i, a = data.split("\n"); + for (i=0;i"; + ms += p.join('#').replace(' ', ' ') + ""; + } + } + document.getElementById('jsfmenu').innerHTML = ms; + thminv_fdTimer = setTimeout('x_getHeaderFolders(thminv_writeFolders)', 180000); +} + +function thminv_initFolders() +{ + var e = document.getElementById('jsfmenu'); + if (!e) + return; + thminv_mFolders = e.innerHTML; + x_getHeaderFolders(thminv_writeFolders); +} + + +function updateHeader() +{ + if (thminv_hdTimer) + clearTimeout(thminv_hdTimer); + if (thminv_stTimer) + clearTimeout(thminv_stTimer); + if (thminv_plTimer) + clearTimeout(thminv_plTimer); + if (thminv_fdTimer) + clearTimeout(thminv_fdTimer); + + x_getHeaderData(thminv_writeHeader); + x_getHeaderPList(thminv_writePlanets); + x_getHeaderFolders(thminv_writeFolders); +} diff --git a/site/static/beta5/js/tooltips.js b/site/static/beta5/js/tooltips.js new file mode 100644 index 0000000..b3c1162 --- /dev/null +++ b/site/static/beta5/js/tooltips.js @@ -0,0 +1,616 @@ +/* This notice must be untouched at all times. + +wz_tooltip.js v. 3.38 + +The latest version is available at +http://www.walterzorn.com +or http://www.devira.com +or http://www.walterzorn.de + +Copyright (c) 2002-2005 Walter Zorn. All rights reserved. +Created 1. 12. 2002 by Walter Zorn (Web: http://www.walterzorn.com ) +Last modified: 9. 12. 2005 + +Cross-browser tooltips working even in Opera 5 and 6, +as well as in NN 4, Gecko-Browsers, IE4+, Opera 7+ and Konqueror. +No onmouseouts required. +Appearance of tooltips can be individually configured +via commands within the onmouseovers. + +LICENSE: LGPL + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License (LGPL) as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library 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. + +For more details on the GNU Lesser General Public License, +see http://www.gnu.org/copyleft/lesser.html +*/ + +/* Lots of LegacyWorlds related changes in here ... */ + + + +//////////////// GLOBAL TOOPTIP CONFIGURATION ///////////////////// +var ttAbove = false; // tooltip above mousepointer? Alternative: true +// Some definitions have been disabled for LW, the layout code should +// include code that does just that. +//var ttBgColor = "#e6ecff"; +//var ttBgImg = ""; // path to background image; +//var ttBorderColor = "#003399"; +var ttBorderWidth = 1; +//var ttDelay = 500; // time span until tooltip shows up [milliseconds] +//var ttFontColor = "#000066"; +var ttFontFace = "arial,helvetica,sans-serif"; +//var ttFontSize = "11px"; +var ttFontWeight = "normal"; // alternative: "bold"; +var ttLeft = false; // tooltip on the left of the mouse? Alternative: true +var ttOffsetX = 12; // horizontal offset of left-top corner from mousepointer +var ttOffsetY = 15; // vertical offset " +var ttOpacity = 100; // opacity of tooltip in percent (must be integer between 0 and 100) +var ttPadding = 3; // spacing between border and content +//var ttShadowColor = ""; +//var ttShadowWidth = 0; +var ttStatic = false; // tooltip NOT move with the mouse? Alternative: true +var ttSticky = false; // do NOT hide tooltip on mouseout? Alternative: true +var ttTemp = 0; // time span after which the tooltip disappears; 0 (zero) means "infinite timespan" +var ttTextAlign = "left"; +//var ttTitleColor = "#ffffff"; // color of caption text +var ttWidth = 300; +//////////////////// END OF TOOLTIP CONFIG //////////////////////// + + + +////////////// TAGS WITH TOOLTIP FUNCTIONALITY //////////////////// +// List may be extended or shortened: +var tt_tags = new Array("a","area","b","big","caption","center","code","dd","div","dl","dt","em","h1","h2","h3","h4","h5","h6","i","img","input","li","map","ol","p","pre","s", "select", "small","span","strike","strong","sub","sup","table","td","th","tr","tt","u","var","ul","layer"); +///////////////////////////////////////////////////////////////////// + + + +///////// DON'T CHANGE ANYTHING BELOW THIS LINE ///////////////////// +var tt_obj = null, // current tooltip +tt_ifrm = null, // iframe to cover windowed controls in IE +tt_objW = 0, tt_objH = 0, // width and height of tt_obj +tt_objX = 0, tt_objY = 0, +tt_offX = 0, tt_offY = 0, +xlim = 0, ylim = 0, // right and bottom borders of visible client area +tt_sup = false, // true if T_ABOVE cmd +tt_sticky = false, // tt_obj sticky? +tt_wait = false, +tt_act = false, // tooltip visibility flag +tt_sub = false, // true while tooltip below mousepointer +tt_u = "undefined", +tt_mf = null, // stores previous mousemove evthandler +tt_optag = null, // Opera: stores hovered dom node, and href and previous statusbar txt on tags +tt_tag = null, // stores hovered dom node +tt_offsetLeft, // Backup the offsetLeft property to determine changes in location for the hovered node +tt_disable = false; // Disable tt_Show +var tt_total = 0; // LW: make sure we don't define a dynamic tooltip twice +var tt_dyncode = ''; + + +var tt_db = (document.compatMode && document.compatMode != "BackCompat")? document.documentElement : document.body? document.body : null, +tt_n = navigator.userAgent.toLowerCase(), +tt_nv = navigator.appVersion; +// Browser flags +var tt_op = !!(window.opera && document.getElementById), +tt_op6 = tt_op && !document.defaultView, +tt_op7 = tt_op && !tt_op6, +tt_ie = tt_n.indexOf("msie") != -1 && document.all && tt_db && !tt_op, +tt_ie6 = tt_ie && parseFloat(tt_nv.substring(tt_nv.indexOf("MSIE")+5)) >= 5.5, +tt_n4 = (document.layers && typeof document.classes != tt_u), +tt_n6 = (!tt_op && document.defaultView && typeof document.defaultView.getComputedStyle != tt_u), +tt_w3c = !tt_ie && !tt_n6 && !tt_op && document.getElementById, +tt_konq = tt_n.indexOf('konqueror') != -1; + +function tt_Int(t_x) +{ + var t_y; + return isNaN(t_y = parseInt(t_x))? 0 : t_y; +} +function wzReplace(t_x, t_y) +{ + var t_ret = "", + t_str = this, + t_xI; + while((t_xI = t_str.indexOf(t_x)) != -1) + { + t_ret += t_str.substring(0, t_xI) + t_y; + t_str = t_str.substring(t_xI + t_x.length); + } + return t_ret+t_str; +} +String.prototype.wzReplace = wzReplace; +function tt_N4Tags(tagtyp, t_d, t_y) +{ + t_d = t_d || document; + t_y = t_y || new Array(); + var t_x = (tagtyp=="a")? t_d.links : t_d.layers; + for(var z = t_x.length; z--;) t_y[t_y.length] = t_x[z]; + for(z = t_d.layers.length; z--;) t_y = tt_N4Tags(tagtyp, t_d.layers[z].document, t_y); + return t_y; +} +function tt_Htm(tt, t_id, txt) +{ + var t_bgc = (typeof tt.T_BGCOLOR != tt_u)? tt.T_BGCOLOR : ttBgColor, + t_bgimg = (typeof tt.T_BGIMG != tt_u)? tt.T_BGIMG : ttBgImg, + t_bc = (typeof tt.T_BORDERCOLOR != tt_u)? tt.T_BORDERCOLOR : ttBorderColor, + t_bw = (typeof tt.T_BORDERWIDTH != tt_u)? tt.T_BORDERWIDTH : ttBorderWidth, + t_ff = (typeof tt.T_FONTFACE != tt_u)? tt.T_FONTFACE : ttFontFace, + t_fc = (typeof tt.T_FONTCOLOR != tt_u)? tt.T_FONTCOLOR : ttFontColor, + t_fsz = (typeof tt.T_FONTSIZE != tt_u)? tt.T_FONTSIZE : ttFontSize, + t_fwght = (typeof tt.T_FONTWEIGHT != tt_u)? tt.T_FONTWEIGHT : ttFontWeight, + t_opa = (typeof tt.T_OPACITY != tt_u)? tt.T_OPACITY : ttOpacity, + t_padd = (typeof tt.T_PADDING != tt_u)? tt.T_PADDING : ttPadding, + t_shc = (typeof tt.T_SHADOWCOLOR != tt_u)? tt.T_SHADOWCOLOR : (ttShadowColor || 0), + t_shw = (typeof tt.T_SHADOWWIDTH != tt_u)? tt.T_SHADOWWIDTH : (ttShadowWidth || 0), + t_algn = (typeof tt.T_TEXTALIGN != tt_u)? tt.T_TEXTALIGN : ttTextAlign, + t_tit = (typeof tt.T_TITLE != tt_u)? tt.T_TITLE : "", + t_titc = (typeof tt.T_TITLECOLOR != tt_u)? tt.T_TITLECOLOR : ttTitleColor, + t_w = (typeof tt.T_WIDTH != tt_u)? tt.T_WIDTH : ttWidth; + if(t_shc || t_shw) + { + t_shc = t_shc || "#cccccc"; + t_shw = t_shw || 5; + } + if(tt_n4 && (t_fsz == "10px" || t_fsz == "11px")) t_fsz = "12px"; + + var t_optx = (tt_n4? '' : tt_n6? ('-moz-opacity:'+(t_opa/100.0)) : tt_ie? ('filter:Alpha(opacity='+t_opa+')') : ('opacity:'+(t_opa/100.0))) + ';'; + var t_y = '
    ' + + ''; + if(t_tit) + { + t_y += ''; + } + t_y += '
    ' + + (tt_n4? ' ' : '')+t_tit+'
    ' + + ''; + if(t_fwght == 'bold') t_y += ''; + t_y += txt; + if(t_fwght == 'bold') t_y += ''; + t_y += '
    '; + if(t_shw) + { + var t_spct = Math.round(t_shw*1.3); + if(tt_n4) + { + t_y += '' + + ''; + } + else + { + t_optx = tt_n6? '-moz-opacity:0.85;' : tt_ie? 'filter:Alpha(opacity=85);' : 'opacity:0.85;'; + t_y += '
    ' + + '
    '; + } + } + return(t_y+'
    ' + + (tt_ie6 ? '' : '')); +} +function tt_EvX(t_e) +{ + var t_y = tt_Int(t_e.pageX || t_e.clientX || 0) + + tt_Int(tt_ie? tt_db.scrollLeft : 0) + + tt_offX; + if(t_y > xlim) t_y = xlim; + var t_scr = tt_Int(window.pageXOffset || (tt_db? tt_db.scrollLeft : 0) || 0); + if(t_y < t_scr) t_y = t_scr; + return t_y; +} +function tt_EvY(t_e) +{ + var t_y = tt_Int(t_e.pageY || t_e.clientY || 0) + + tt_Int(tt_ie? tt_db.scrollTop : 0); + if(tt_sup) t_y -= (tt_objH + tt_offY - 15); + else if(t_y > ylim || !tt_sub && t_y > ylim-24) + { + t_y -= (tt_objH + 5); + tt_sub = false; + } + else + { + t_y += tt_offY; + tt_sub = true; + } + return t_y; +} +function tt_ReleasMov() +{ + if(document.onmousemove == tt_Move) + { + if(!tt_mf && document.releaseEvents) document.releaseEvents(Event.MOUSEMOVE); + document.onmousemove = tt_mf; + } +} +function tt_ShowIfrm(t_x) +{ + if(!tt_obj || !tt_ifrm) return; + if(t_x) + { + tt_ifrm.style.width = tt_objW+'px'; + tt_ifrm.style.height = tt_objH+'px'; + tt_ifrm.style.display = "block"; + } + else tt_ifrm.style.display = "none"; +} +function tt_GetDiv(t_id) +{ + return( + tt_n4? (document.layers[t_id] || null) + : tt_ie? (document.all[t_id] || null) + : (document.getElementById(t_id) || null) + ); +} +function tt_GetDivW() +{ + return tt_Int( + tt_n4? tt_obj.clip.width + : (tt_obj.style.pixelWidth || tt_obj.offsetWidth) + ); +} +function tt_GetDivH() +{ + return tt_Int( + tt_n4? tt_obj.clip.height + : (tt_obj.style.pixelHeight || tt_obj.offsetHeight) + ); +} + +// Compat with DragDrop Lib: Ensure that z-index of tooltip is lifted beyond toplevel dragdrop element +function tt_SetDivZ() +{ + var t_i = tt_obj.style || tt_obj; + if(t_i) + { + if(window.dd && dd.z) + t_i.zIndex = Math.max(dd.z+1, t_i.zIndex); + if(tt_ifrm) tt_ifrm.style.zIndex = t_i.zIndex-1; + } +} +function tt_SetDivPos(t_x, t_y) +{ + var t_i = tt_obj.style || tt_obj; + var t_px = (tt_op6 || tt_n4)? '' : 'px'; + t_i.left = (tt_objX = t_x) + t_px; + t_i.top = (tt_objY = t_y) + t_px; + if(tt_ifrm) + { + tt_ifrm.style.left = t_i.left; + tt_ifrm.style.top = t_i.top; + } +} +function tt_ShowDiv(t_x) +{ + tt_ShowIfrm(t_x); + if(tt_n4) tt_obj.visibility = t_x? 'show' : 'hide'; + else tt_obj.style.visibility = t_x? 'visible' : 'hidden'; + tt_act = t_x; +} +function tt_OpDeHref(t_e) +{ + var t_tag; + if(t_e) + { + t_tag = t_e.target; + while(t_tag) + { + if(t_tag.hasAttribute("href")) + { + tt_optag = t_tag + tt_optag.t_href = tt_optag.getAttribute("href"); + tt_optag.removeAttribute("href"); + tt_optag.style.cursor = "hand"; + tt_optag.onmousedown = tt_OpReHref; + tt_optag.stats = window.status; + window.status = tt_optag.t_href; + break; + } + t_tag = t_tag.parentElement; + } + } +} +function tt_OpReHref() +{ + if(tt_optag) + { + tt_optag.setAttribute("href", tt_optag.t_href); + window.status = tt_optag.stats; + tt_optag = null; + } +} +function tt_whichElement(e) +{ + var targ; + if (e.target) targ = e.target + else if (e.srcElement) targ = e.srcElement + if (targ.nodeType == 3) // defeat Safari bug + targ = targ.parentNode; + return targ; +} +function tt_Show(t_e, t_id, t_sup, t_delay, t_fix, t_left, t_offx, t_offy, t_static, t_sticky, t_temp) +{ + if(tt_disable) return; + if(tt_obj) tt_Hide(); + tt_mf = document.onmousemove || null; + if(window.dd && (window.DRAG && tt_mf == DRAG || window.RESIZE && tt_mf == RESIZE)) return; + var t_sh, t_h; + + tt_obj = tt_GetDiv(t_id); + if(tt_obj) + { + t_e = t_e || window.event; + tt_sub = !(tt_sup = t_sup); + tt_sticky = t_sticky; + tt_objW = tt_GetDivW(); + tt_objH = tt_GetDivH(); + tt_offX = t_left? -(tt_objW+t_offx) : t_offx; + tt_offY = t_offy; + if(tt_op7) tt_OpDeHref(t_e); + tt_tag=tt_whichElement(t_e); + tt_offsetLeft = tt_tag.offsetLeft; + if(tt_n4) + { + if(tt_obj.document.layers.length) + { + t_sh = tt_obj.document.layers[0]; + t_sh.clip.height = tt_objH - Math.round(t_sh.clip.width*1.3); + } + } + else + { + t_sh = tt_GetDiv(t_id+'R'); + if(t_sh) + { + t_h = tt_objH - tt_Int(t_sh.style.pixelTop || t_sh.style.top || 0); + if(typeof t_sh.style.pixelHeight != tt_u) t_sh.style.pixelHeight = t_h; + else t_sh.style.height = t_h+'px'; + } + } + + xlim = tt_Int((tt_db && tt_db.clientWidth)? tt_db.clientWidth : window.innerWidth) + + tt_Int(window.pageXOffset || (tt_db? tt_db.scrollLeft : 0) || 0) - + tt_objW - + (tt_n4? 21 : 0); + ylim = tt_Int(window.innerHeight || tt_db.clientHeight) + + tt_Int(window.pageYOffset || (tt_db? tt_db.scrollTop : 0) || 0) - + tt_objH - tt_offY; + + tt_SetDivZ(); + if(t_fix) tt_SetDivPos(tt_Int((t_fix = t_fix.split(','))[0]), tt_Int(t_fix[1])); + else tt_SetDivPos(tt_EvX(t_e), tt_EvY(t_e)); + + var t_txt = 'tt_ShowDiv(\'true\');'; + if(t_sticky) t_txt += '{'+ + 'tt_ReleasMov();'+ + 'window.tt_upFunc = document.onmouseup || null;'+ + 'if(document.captureEvents) document.captureEvents(Event.MOUSEUP);'+ + 'document.onmouseup = new Function("window.setTimeout(\'tt_Hide();\', 10);");'+ + '}'; + else if(t_static) t_txt += 'tt_ReleasMov();'; + if(t_temp > 0) t_txt += 'window.tt_rtm = window.setTimeout(\'tt_sticky = false; tt_Hide();\','+t_temp+');'; + window.tt_rdl = window.setTimeout(t_txt, t_delay); + + if(!t_fix) + { + if(document.captureEvents) document.captureEvents(Event.MOUSEMOVE); + document.onmousemove = tt_Move; + } + } +} +var tt_area = false; +function tt_Move(t_ev) +{ + if(!tt_obj) return; + if(tt_n6 || tt_w3c) + { + if(tt_wait) return; + tt_wait = true; + setTimeout('tt_wait = false;', 5); + } + var t_e = t_ev || window.event; + tt_SetDivPos(tt_EvX(t_e), tt_EvY(t_e)); + if(tt_op6) + { + if(tt_area && t_e.target.tagName != 'AREA') tt_Hide(); + else if(t_e.target.tagName == 'AREA') tt_area = true; + } + + if(tt_konq&&typeof tt_tag != 'undefined') + { + var t = tt_tag; + while (t.tagName != 'HTML') + { + if (typeof t.offsetLeft == 'undefined') + { + tt_Hide(); + return; + } + t = t.parentElement; + } + } + else if(typeof tt_tag != 'undefined' && tt_tag.offsetLeft != tt_offsetLeft) + tt_Hide(); +} +function tt_Hide() +{ + if(window.tt_obj) + { + if(window.tt_rdl) window.clearTimeout(tt_rdl); + if(!tt_sticky || !tt_act) + { + if(window.tt_rtm) window.clearTimeout(tt_rtm); + tt_ShowDiv(false); + tt_SetDivPos(-tt_objW, -tt_objH); + tt_obj = null; + if(typeof window.tt_upFunc != tt_u) document.onmouseup = window.tt_upFunc; + } + tt_sticky = false; + if(tt_op6 && tt_area) tt_area = false; + tt_ReleasMov(); + if(tt_op7) tt_OpReHref(); + } +} + + +function tt_DynamicHtm(t_id, txt) +{ + var t_bgc = ttBgColor, + t_bgimg = ttBgImg, + t_bc = ttBorderColor, + t_bw = ttBorderWidth, + t_ff = ttFontFace, + t_fc = ttFontColor, + t_fsz = ttFontSize, + t_fwght = ttFontWeight, + t_padd = ttPadding, + t_shc = (ttShadowColor || 0), + t_shw = (ttShadowWidth || 0), + t_tit = '', + t_titc = ttTitleColor, + t_w = ttWidth; + if (t_shc || t_shw) + { + t_shc = t_shc || '#cccccc'; + t_shw = t_shw || 3; + } + if (tt_n4 && (t_fsz == '10px' || t_fsz == '11px')) t_fsz = '12px'; + + + var t_y = '
    '; + t_y += ''; + if (t_tit) + { + t_y += '","
    '; + t_y += t_tit + '<\/b><\/font><\/td><\/tr>'; + } + t_y += '
    '; + t_y += ''; + if (t_fwght == 'bold') t_y += ''; + t_y += txt; + if (t_fwght == 'bold') t_y += '<\/b>'; + t_y += '<\/font><\/td><\/tr><\/table><\/td><\/tr><\/table>'; + if (t_shw) + { + var t_spct = Math.round(t_shw*1.3); + if (tt_n4) + { + t_y += '<\/layer>'; + t_y += '<\/layer>'; + } + else + { + var t_opa = tt_n6? '-moz-opacity:0.85;' : tt_ie? 'filter:Alpha(opacity=85);' : ''; + t_y += '
    <\/div>'; + t_y += '
    <\/div>'; + } + } + t_y += '<\/div>'; + var ih = document.getElementById('ttPlaceHolderReserved').innerHTML; + if (ih == "") + tt_dyncode += t_y; + else + ih += t_y; +} +function tt_Init() +{ + if(!(tt_op || tt_n4 || tt_n6 || tt_ie || tt_w3c)) return; + + var htm = tt_n4? '
    ' : '', + tags, + t_tj, + over, + esc = 'return escape('; + var i = tt_tags.length; + htm += tt_dyncode; + while (i--) + { + tags = tt_ie? (document.all.tags(tt_tags[i]) || 1) + : document.getElementsByTagName? (document.getElementsByTagName(tt_tags[i]) || 1) + : (!tt_n4 && tt_tags[i]=="a")? document.links + : 1; + if(tt_n4 && (tt_tags[i] == "a" || tt_tags[i] == "layer")) tags = tt_N4Tags(tt_tags[i]); + var j = tags.length; while(j--) + { + if(typeof (t_tj = tags[j]).onmouseover == "function" && t_tj.onmouseover.toString().indexOf(esc) != -1 && !tt_n6 || tt_n6 && (over = t_tj.getAttribute("onmouseover")) && over.indexOf(esc) != -1) + { + if(over) t_tj.onmouseover = new Function(over); + var txt = unescape(t_tj.onmouseover()); + htm += tt_Htm( + t_tj, + "tOoLtIp"+i+""+j, + txt.wzReplace("& ","&") + ); + + t_tj.onmouseover = new Function('e', + 'tt_Show(e,'+ + '"tOoLtIp' +i+''+j+ '",'+ + ((typeof t_tj.T_ABOVE != tt_u)? t_tj.T_ABOVE : ttAbove)+','+ + ((typeof t_tj.T_DELAY != tt_u)? t_tj.T_DELAY : ttDelay)+','+ + ((typeof t_tj.T_FIX != tt_u)? '"'+t_tj.T_FIX+'"' : '""')+','+ + ((typeof t_tj.T_LEFT != tt_u)? t_tj.T_LEFT : ttLeft)+','+ + ((typeof t_tj.T_OFFSETX != tt_u)? t_tj.T_OFFSETX : ttOffsetX)+','+ + ((typeof t_tj.T_OFFSETY != tt_u)? t_tj.T_OFFSETY : ttOffsetY)+','+ + ((typeof t_tj.T_STATIC != tt_u)? t_tj.T_STATIC : ttStatic)+','+ + ((typeof t_tj.T_STICKY != tt_u)? t_tj.T_STICKY : ttSticky)+','+ + ((typeof t_tj.T_TEMP != tt_u)? t_tj.T_TEMP : ttTemp)+ + ');' + ); + t_tj.onmouseout = tt_Hide; + if(t_tj.alt) t_tj.alt = ""; + if(t_tj.title) t_tj.title = ""; + } + } + } + document.getElementById('ttPlaceHolderReserved').innerHTML=htm; + if(document.getElementById) tt_ifrm = document.getElementById("TTiEiFrM"); +} + +function tt_Dynamic(txt) +{ + tt_DynamicHtm('dyntOoLtIp'+tt_total, txt.wzReplace('& ','&')); + var code = ' onmouseover=\'tt_Show(event,"dyntOoLtIp'+tt_total+'",'+ + 'false,ttDelay,"",false,ttOffsetX,ttOffsetY,false,false);\''; + code += ' onmouseout=\'tt_Hide();\' '; + tt_total ++; + return code; +} + +function tt_Disable() +{ + var i = tt_tags.length; + while (i--) + { + var tags = tt_ie? (document.all.tags(tt_tags[i]) || 1) + : document.getElementsByTagName? (document.getElementsByTagName(tt_tags[i]) || 1) + : (!tt_n4 && tt_tags[i]=="a")? document.links + : 1; + if(tt_n4 && (tt_tags[i] == "a" || tt_tags[i] == "layer")) tags = tt_N4Tags(tt_tags[i]); + var j = tags.length; while(j--) + { + var t_tj; + if(typeof (t_tj = tags[j]).onmouseover == "function") + { + t_tj.onmouseover = undefined; + t_tj.onmouseout = undefined; + } + } + } + if(tt_obj) tt_Hide(); + tt_disable = true; +} diff --git a/site/static/beta5/js/tt_classic_blue.js b/site/static/beta5/js/tt_classic_blue.js new file mode 100644 index 0000000..9fdcdd7 --- /dev/null +++ b/site/static/beta5/js/tt_classic_blue.js @@ -0,0 +1,8 @@ +var ttBgColor = '#e6ecff'; +var ttBgImg = ''; +var ttBorderColor = '#003399'; +var ttFontColor = '#0033CC'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#ffffff'; + diff --git a/site/static/beta5/js/tt_classic_green.js b/site/static/beta5/js/tt_classic_green.js new file mode 100644 index 0000000..f462beb --- /dev/null +++ b/site/static/beta5/js/tt_classic_green.js @@ -0,0 +1,8 @@ +var ttBgColor = '#dfffef'; +var ttBgImg = ''; +var ttBorderColor = '#1fcf1f'; +var ttFontColor = '#1f7f1f'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#ffffff'; + diff --git a/site/static/beta5/js/tt_classic_grey.js b/site/static/beta5/js/tt_classic_grey.js new file mode 100644 index 0000000..193698a --- /dev/null +++ b/site/static/beta5/js/tt_classic_grey.js @@ -0,0 +1,8 @@ +var ttBgColor = '#ffffff'; +var ttBgImg = ''; +var ttBorderColor = '#666666'; +var ttFontColor = '#4f4f4f'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#000000'; + diff --git a/site/static/beta5/js/tt_classic_purple.js b/site/static/beta5/js/tt_classic_purple.js new file mode 100644 index 0000000..641cc89 --- /dev/null +++ b/site/static/beta5/js/tt_classic_purple.js @@ -0,0 +1,8 @@ +var ttBgColor = '#eeccff'; +var ttBgImg = ''; +var ttBorderColor = '#CC33FF'; +var ttFontColor = '#660099'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#ffffff'; + diff --git a/site/static/beta5/js/tt_classic_red.js b/site/static/beta5/js/tt_classic_red.js new file mode 100644 index 0000000..d34b75a --- /dev/null +++ b/site/static/beta5/js/tt_classic_red.js @@ -0,0 +1,8 @@ +var ttBgColor = '#ffece6'; +var ttBgImg = ''; +var ttBorderColor = '#993300'; +var ttFontColor = '#CC3300'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#ffffff'; + diff --git a/site/static/beta5/js/tt_classic_yellow.js b/site/static/beta5/js/tt_classic_yellow.js new file mode 100644 index 0000000..4e1a72c --- /dev/null +++ b/site/static/beta5/js/tt_classic_yellow.js @@ -0,0 +1,8 @@ +var ttBgColor = '#996600'; +var ttBgImg = ''; +var ttBorderColor = '#ffff3f'; +var ttFontColor = '#ffff3f'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#000000'; + diff --git a/site/static/beta5/js/tt_default_blue.js b/site/static/beta5/js/tt_default_blue.js new file mode 100644 index 0000000..9fdcdd7 --- /dev/null +++ b/site/static/beta5/js/tt_default_blue.js @@ -0,0 +1,8 @@ +var ttBgColor = '#e6ecff'; +var ttBgImg = ''; +var ttBorderColor = '#003399'; +var ttFontColor = '#0033CC'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#ffffff'; + diff --git a/site/static/beta5/js/tt_default_green.js b/site/static/beta5/js/tt_default_green.js new file mode 100644 index 0000000..f462beb --- /dev/null +++ b/site/static/beta5/js/tt_default_green.js @@ -0,0 +1,8 @@ +var ttBgColor = '#dfffef'; +var ttBgImg = ''; +var ttBorderColor = '#1fcf1f'; +var ttFontColor = '#1f7f1f'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#ffffff'; + diff --git a/site/static/beta5/js/tt_default_grey.js b/site/static/beta5/js/tt_default_grey.js new file mode 100644 index 0000000..193698a --- /dev/null +++ b/site/static/beta5/js/tt_default_grey.js @@ -0,0 +1,8 @@ +var ttBgColor = '#ffffff'; +var ttBgImg = ''; +var ttBorderColor = '#666666'; +var ttFontColor = '#4f4f4f'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#000000'; + diff --git a/site/static/beta5/js/tt_default_purple.js b/site/static/beta5/js/tt_default_purple.js new file mode 100644 index 0000000..641cc89 --- /dev/null +++ b/site/static/beta5/js/tt_default_purple.js @@ -0,0 +1,8 @@ +var ttBgColor = '#eeccff'; +var ttBgImg = ''; +var ttBorderColor = '#CC33FF'; +var ttFontColor = '#660099'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#ffffff'; + diff --git a/site/static/beta5/js/tt_default_red.js b/site/static/beta5/js/tt_default_red.js new file mode 100644 index 0000000..d34b75a --- /dev/null +++ b/site/static/beta5/js/tt_default_red.js @@ -0,0 +1,8 @@ +var ttBgColor = '#ffece6'; +var ttBgImg = ''; +var ttBorderColor = '#993300'; +var ttFontColor = '#CC3300'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#ffffff'; + diff --git a/site/static/beta5/js/tt_default_yellow.js b/site/static/beta5/js/tt_default_yellow.js new file mode 100644 index 0000000..4e1a72c --- /dev/null +++ b/site/static/beta5/js/tt_default_yellow.js @@ -0,0 +1,8 @@ +var ttBgColor = '#996600'; +var ttBgImg = ''; +var ttBorderColor = '#ffff3f'; +var ttFontColor = '#ffff3f'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#000000'; + diff --git a/site/static/beta5/js/tt_invert_blue.js b/site/static/beta5/js/tt_invert_blue.js new file mode 100644 index 0000000..9fdcdd7 --- /dev/null +++ b/site/static/beta5/js/tt_invert_blue.js @@ -0,0 +1,8 @@ +var ttBgColor = '#e6ecff'; +var ttBgImg = ''; +var ttBorderColor = '#003399'; +var ttFontColor = '#0033CC'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#ffffff'; + diff --git a/site/static/beta5/js/tt_invert_green.js b/site/static/beta5/js/tt_invert_green.js new file mode 100644 index 0000000..f462beb --- /dev/null +++ b/site/static/beta5/js/tt_invert_green.js @@ -0,0 +1,8 @@ +var ttBgColor = '#dfffef'; +var ttBgImg = ''; +var ttBorderColor = '#1fcf1f'; +var ttFontColor = '#1f7f1f'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#ffffff'; + diff --git a/site/static/beta5/js/tt_invert_grey.js b/site/static/beta5/js/tt_invert_grey.js new file mode 100644 index 0000000..193698a --- /dev/null +++ b/site/static/beta5/js/tt_invert_grey.js @@ -0,0 +1,8 @@ +var ttBgColor = '#ffffff'; +var ttBgImg = ''; +var ttBorderColor = '#666666'; +var ttFontColor = '#4f4f4f'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#000000'; + diff --git a/site/static/beta5/js/tt_invert_purple.js b/site/static/beta5/js/tt_invert_purple.js new file mode 100644 index 0000000..641cc89 --- /dev/null +++ b/site/static/beta5/js/tt_invert_purple.js @@ -0,0 +1,8 @@ +var ttBgColor = '#eeccff'; +var ttBgImg = ''; +var ttBorderColor = '#CC33FF'; +var ttFontColor = '#660099'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#ffffff'; + diff --git a/site/static/beta5/js/tt_invert_red.js b/site/static/beta5/js/tt_invert_red.js new file mode 100644 index 0000000..d34b75a --- /dev/null +++ b/site/static/beta5/js/tt_invert_red.js @@ -0,0 +1,8 @@ +var ttBgColor = '#ffece6'; +var ttBgImg = ''; +var ttBorderColor = '#993300'; +var ttFontColor = '#CC3300'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#ffffff'; + diff --git a/site/static/beta5/js/tt_invert_yellow.js b/site/static/beta5/js/tt_invert_yellow.js new file mode 100644 index 0000000..4e1a72c --- /dev/null +++ b/site/static/beta5/js/tt_invert_yellow.js @@ -0,0 +1,8 @@ +var ttBgColor = '#996600'; +var ttBgImg = ''; +var ttBorderColor = '#ffff3f'; +var ttFontColor = '#ffff3f'; +var ttShadowColor = ''; +var ttShadowWidth = 0; +var ttTitleColor = '#000000'; + diff --git a/site/static/beta5/pics/add1_blue.gif b/site/static/beta5/pics/add1_blue.gif new file mode 100644 index 0000000..f9d4335 Binary files /dev/null and b/site/static/beta5/pics/add1_blue.gif differ diff --git a/site/static/beta5/pics/add1_green.gif b/site/static/beta5/pics/add1_green.gif new file mode 100644 index 0000000..52f06d3 Binary files /dev/null and b/site/static/beta5/pics/add1_green.gif differ diff --git a/site/static/beta5/pics/add1_grey.gif b/site/static/beta5/pics/add1_grey.gif new file mode 100644 index 0000000..29b728b Binary files /dev/null and b/site/static/beta5/pics/add1_grey.gif differ diff --git a/site/static/beta5/pics/add1_purple.gif b/site/static/beta5/pics/add1_purple.gif new file mode 100644 index 0000000..dcec1a7 Binary files /dev/null and b/site/static/beta5/pics/add1_purple.gif differ diff --git a/site/static/beta5/pics/add1_red.gif b/site/static/beta5/pics/add1_red.gif new file mode 100644 index 0000000..806d770 Binary files /dev/null and b/site/static/beta5/pics/add1_red.gif differ diff --git a/site/static/beta5/pics/add1_yellow.gif b/site/static/beta5/pics/add1_yellow.gif new file mode 100644 index 0000000..733fbee Binary files /dev/null and b/site/static/beta5/pics/add1_yellow.gif differ diff --git a/site/static/beta5/pics/add2_blue.gif b/site/static/beta5/pics/add2_blue.gif new file mode 100644 index 0000000..906a2ae Binary files /dev/null and b/site/static/beta5/pics/add2_blue.gif differ diff --git a/site/static/beta5/pics/add2_green.gif b/site/static/beta5/pics/add2_green.gif new file mode 100644 index 0000000..1697590 Binary files /dev/null and b/site/static/beta5/pics/add2_green.gif differ diff --git a/site/static/beta5/pics/add2_grey.gif b/site/static/beta5/pics/add2_grey.gif new file mode 100644 index 0000000..f26c177 Binary files /dev/null and b/site/static/beta5/pics/add2_grey.gif differ diff --git a/site/static/beta5/pics/add2_purple.gif b/site/static/beta5/pics/add2_purple.gif new file mode 100644 index 0000000..03e5f2f Binary files /dev/null and b/site/static/beta5/pics/add2_purple.gif differ diff --git a/site/static/beta5/pics/add2_red.gif b/site/static/beta5/pics/add2_red.gif new file mode 100644 index 0000000..d490939 Binary files /dev/null and b/site/static/beta5/pics/add2_red.gif differ diff --git a/site/static/beta5/pics/add2_yellow.gif b/site/static/beta5/pics/add2_yellow.gif new file mode 100644 index 0000000..09c2463 Binary files /dev/null and b/site/static/beta5/pics/add2_yellow.gif differ diff --git a/site/static/beta5/pics/background.jpg b/site/static/beta5/pics/background.jpg new file mode 100644 index 0000000..9045e99 Binary files /dev/null and b/site/static/beta5/pics/background.jpg differ diff --git a/site/static/beta5/pics/dec1_blue.gif b/site/static/beta5/pics/dec1_blue.gif new file mode 100644 index 0000000..2600b69 Binary files /dev/null and b/site/static/beta5/pics/dec1_blue.gif differ diff --git a/site/static/beta5/pics/dec1_green.gif b/site/static/beta5/pics/dec1_green.gif new file mode 100644 index 0000000..7187565 Binary files /dev/null and b/site/static/beta5/pics/dec1_green.gif differ diff --git a/site/static/beta5/pics/dec1_grey.gif b/site/static/beta5/pics/dec1_grey.gif new file mode 100644 index 0000000..99c326b Binary files /dev/null and b/site/static/beta5/pics/dec1_grey.gif differ diff --git a/site/static/beta5/pics/dec1_purple.gif b/site/static/beta5/pics/dec1_purple.gif new file mode 100644 index 0000000..1b0be7e Binary files /dev/null and b/site/static/beta5/pics/dec1_purple.gif differ diff --git a/site/static/beta5/pics/dec1_red.gif b/site/static/beta5/pics/dec1_red.gif new file mode 100644 index 0000000..ab00737 Binary files /dev/null and b/site/static/beta5/pics/dec1_red.gif differ diff --git a/site/static/beta5/pics/dec1_yellow.gif b/site/static/beta5/pics/dec1_yellow.gif new file mode 100644 index 0000000..e77a89c Binary files /dev/null and b/site/static/beta5/pics/dec1_yellow.gif differ diff --git a/site/static/beta5/pics/dec2_blue.gif b/site/static/beta5/pics/dec2_blue.gif new file mode 100644 index 0000000..e987390 Binary files /dev/null and b/site/static/beta5/pics/dec2_blue.gif differ diff --git a/site/static/beta5/pics/dec2_green.gif b/site/static/beta5/pics/dec2_green.gif new file mode 100644 index 0000000..9a83995 Binary files /dev/null and b/site/static/beta5/pics/dec2_green.gif differ diff --git a/site/static/beta5/pics/dec2_grey.gif b/site/static/beta5/pics/dec2_grey.gif new file mode 100644 index 0000000..3b9ceec Binary files /dev/null and b/site/static/beta5/pics/dec2_grey.gif differ diff --git a/site/static/beta5/pics/dec2_purple.gif b/site/static/beta5/pics/dec2_purple.gif new file mode 100644 index 0000000..befe7bb Binary files /dev/null and b/site/static/beta5/pics/dec2_purple.gif differ diff --git a/site/static/beta5/pics/dec2_red.gif b/site/static/beta5/pics/dec2_red.gif new file mode 100644 index 0000000..2640f15 Binary files /dev/null and b/site/static/beta5/pics/dec2_red.gif differ diff --git a/site/static/beta5/pics/dec2_yellow.gif b/site/static/beta5/pics/dec2_yellow.gif new file mode 100644 index 0000000..249019c Binary files /dev/null and b/site/static/beta5/pics/dec2_yellow.gif differ diff --git a/site/static/beta5/pics/down_blue.gif b/site/static/beta5/pics/down_blue.gif new file mode 100644 index 0000000..ac45508 Binary files /dev/null and b/site/static/beta5/pics/down_blue.gif differ diff --git a/site/static/beta5/pics/down_green.gif b/site/static/beta5/pics/down_green.gif new file mode 100644 index 0000000..09c7755 Binary files /dev/null and b/site/static/beta5/pics/down_green.gif differ diff --git a/site/static/beta5/pics/down_grey.gif b/site/static/beta5/pics/down_grey.gif new file mode 100644 index 0000000..0b734ed Binary files /dev/null and b/site/static/beta5/pics/down_grey.gif differ diff --git a/site/static/beta5/pics/down_purple.gif b/site/static/beta5/pics/down_purple.gif new file mode 100644 index 0000000..ede1042 Binary files /dev/null and b/site/static/beta5/pics/down_purple.gif differ diff --git a/site/static/beta5/pics/down_red.gif b/site/static/beta5/pics/down_red.gif new file mode 100644 index 0000000..a3f223b Binary files /dev/null and b/site/static/beta5/pics/down_red.gif differ diff --git a/site/static/beta5/pics/down_yellow.gif b/site/static/beta5/pics/down_yellow.gif new file mode 100644 index 0000000..c054c3a Binary files /dev/null and b/site/static/beta5/pics/down_yellow.gif differ diff --git a/site/static/beta5/pics/icons/alliance.gif b/site/static/beta5/pics/icons/alliance.gif new file mode 100644 index 0000000..cd9fa4c Binary files /dev/null and b/site/static/beta5/pics/icons/alliance.gif differ diff --git a/site/static/beta5/pics/icons/alliance.png b/site/static/beta5/pics/icons/alliance.png new file mode 100644 index 0000000..704a133 Binary files /dev/null and b/site/static/beta5/pics/icons/alliance.png differ diff --git a/site/static/beta5/pics/icons/fleets.gif b/site/static/beta5/pics/icons/fleets.gif new file mode 100644 index 0000000..063c323 Binary files /dev/null and b/site/static/beta5/pics/icons/fleets.gif differ diff --git a/site/static/beta5/pics/icons/fleets.png b/site/static/beta5/pics/icons/fleets.png new file mode 100644 index 0000000..f04d11d Binary files /dev/null and b/site/static/beta5/pics/icons/fleets.png differ diff --git a/site/static/beta5/pics/icons/logout.gif b/site/static/beta5/pics/icons/logout.gif new file mode 100644 index 0000000..486f984 Binary files /dev/null and b/site/static/beta5/pics/icons/logout.gif differ diff --git a/site/static/beta5/pics/icons/logout.png b/site/static/beta5/pics/icons/logout.png new file mode 100644 index 0000000..3a558ea Binary files /dev/null and b/site/static/beta5/pics/icons/logout.png differ diff --git a/site/static/beta5/pics/icons/map.gif b/site/static/beta5/pics/icons/map.gif new file mode 100644 index 0000000..2eec334 Binary files /dev/null and b/site/static/beta5/pics/icons/map.gif differ diff --git a/site/static/beta5/pics/icons/map.png b/site/static/beta5/pics/icons/map.png new file mode 100644 index 0000000..e93e526 Binary files /dev/null and b/site/static/beta5/pics/icons/map.png differ diff --git a/site/static/beta5/pics/icons/message.gif b/site/static/beta5/pics/icons/message.gif new file mode 100644 index 0000000..1282635 Binary files /dev/null and b/site/static/beta5/pics/icons/message.gif differ diff --git a/site/static/beta5/pics/icons/planets.gif b/site/static/beta5/pics/icons/planets.gif new file mode 100644 index 0000000..a42c1cc Binary files /dev/null and b/site/static/beta5/pics/icons/planets.gif differ diff --git a/site/static/beta5/pics/icons/planets.png b/site/static/beta5/pics/icons/planets.png new file mode 100644 index 0000000..ac60699 Binary files /dev/null and b/site/static/beta5/pics/icons/planets.png differ diff --git a/site/static/beta5/pics/left_blue.gif b/site/static/beta5/pics/left_blue.gif new file mode 100644 index 0000000..f13c374 Binary files /dev/null and b/site/static/beta5/pics/left_blue.gif differ diff --git a/site/static/beta5/pics/left_green.gif b/site/static/beta5/pics/left_green.gif new file mode 100644 index 0000000..52967d9 Binary files /dev/null and b/site/static/beta5/pics/left_green.gif differ diff --git a/site/static/beta5/pics/left_grey.gif b/site/static/beta5/pics/left_grey.gif new file mode 100644 index 0000000..a8ed7ba Binary files /dev/null and b/site/static/beta5/pics/left_grey.gif differ diff --git a/site/static/beta5/pics/left_purple.gif b/site/static/beta5/pics/left_purple.gif new file mode 100644 index 0000000..b345d0b Binary files /dev/null and b/site/static/beta5/pics/left_purple.gif differ diff --git a/site/static/beta5/pics/left_red.gif b/site/static/beta5/pics/left_red.gif new file mode 100644 index 0000000..ff6beaa Binary files /dev/null and b/site/static/beta5/pics/left_red.gif differ diff --git a/site/static/beta5/pics/left_yellow.gif b/site/static/beta5/pics/left_yellow.gif new file mode 100644 index 0000000..01c0293 Binary files /dev/null and b/site/static/beta5/pics/left_yellow.gif differ diff --git a/site/static/beta5/pics/lock_blue.gif b/site/static/beta5/pics/lock_blue.gif new file mode 100644 index 0000000..318b2a5 Binary files /dev/null and b/site/static/beta5/pics/lock_blue.gif differ diff --git a/site/static/beta5/pics/lock_green.gif b/site/static/beta5/pics/lock_green.gif new file mode 100644 index 0000000..0522cf1 Binary files /dev/null and b/site/static/beta5/pics/lock_green.gif differ diff --git a/site/static/beta5/pics/lock_grey.gif b/site/static/beta5/pics/lock_grey.gif new file mode 100644 index 0000000..32dc716 Binary files /dev/null and b/site/static/beta5/pics/lock_grey.gif differ diff --git a/site/static/beta5/pics/lock_purple.gif b/site/static/beta5/pics/lock_purple.gif new file mode 100644 index 0000000..c828a3d Binary files /dev/null and b/site/static/beta5/pics/lock_purple.gif differ diff --git a/site/static/beta5/pics/lock_red.gif b/site/static/beta5/pics/lock_red.gif new file mode 100644 index 0000000..1ddfe61 Binary files /dev/null and b/site/static/beta5/pics/lock_red.gif differ diff --git a/site/static/beta5/pics/lock_yellow.gif b/site/static/beta5/pics/lock_yellow.gif new file mode 100644 index 0000000..f13b5a2 Binary files /dev/null and b/site/static/beta5/pics/lock_yellow.gif differ diff --git a/site/static/beta5/pics/lw-tot-tech-blue.gif b/site/static/beta5/pics/lw-tot-tech-blue.gif new file mode 100644 index 0000000..7e8d0ad Binary files /dev/null and b/site/static/beta5/pics/lw-tot-tech-blue.gif differ diff --git a/site/static/beta5/pics/lw-tot-tech-blue.png b/site/static/beta5/pics/lw-tot-tech-blue.png new file mode 100644 index 0000000..3154b67 Binary files /dev/null and b/site/static/beta5/pics/lw-tot-tech-blue.png differ diff --git a/site/static/beta5/pics/lw-tot-tech-green.gif b/site/static/beta5/pics/lw-tot-tech-green.gif new file mode 100644 index 0000000..aca6396 Binary files /dev/null and b/site/static/beta5/pics/lw-tot-tech-green.gif differ diff --git a/site/static/beta5/pics/lw-tot-tech-green.png b/site/static/beta5/pics/lw-tot-tech-green.png new file mode 100644 index 0000000..2d6c08a Binary files /dev/null and b/site/static/beta5/pics/lw-tot-tech-green.png differ diff --git a/site/static/beta5/pics/lw-tot-tech-grey.gif b/site/static/beta5/pics/lw-tot-tech-grey.gif new file mode 100644 index 0000000..47d3cf9 Binary files /dev/null and b/site/static/beta5/pics/lw-tot-tech-grey.gif differ diff --git a/site/static/beta5/pics/lw-tot-tech-grey.png b/site/static/beta5/pics/lw-tot-tech-grey.png new file mode 100644 index 0000000..3418c5e Binary files /dev/null and b/site/static/beta5/pics/lw-tot-tech-grey.png differ diff --git a/site/static/beta5/pics/lw-tot-tech-purple.gif b/site/static/beta5/pics/lw-tot-tech-purple.gif new file mode 100644 index 0000000..c89a41a Binary files /dev/null and b/site/static/beta5/pics/lw-tot-tech-purple.gif differ diff --git a/site/static/beta5/pics/lw-tot-tech-purple.png b/site/static/beta5/pics/lw-tot-tech-purple.png new file mode 100644 index 0000000..aa7906c Binary files /dev/null and b/site/static/beta5/pics/lw-tot-tech-purple.png differ diff --git a/site/static/beta5/pics/lw-tot-tech-red.gif b/site/static/beta5/pics/lw-tot-tech-red.gif new file mode 100644 index 0000000..5e0c886 Binary files /dev/null and b/site/static/beta5/pics/lw-tot-tech-red.gif differ diff --git a/site/static/beta5/pics/lw-tot-tech-red.png b/site/static/beta5/pics/lw-tot-tech-red.png new file mode 100644 index 0000000..af9d47e Binary files /dev/null and b/site/static/beta5/pics/lw-tot-tech-red.png differ diff --git a/site/static/beta5/pics/lw-tot-tech-yellow.gif b/site/static/beta5/pics/lw-tot-tech-yellow.gif new file mode 100644 index 0000000..edd089d Binary files /dev/null and b/site/static/beta5/pics/lw-tot-tech-yellow.gif differ diff --git a/site/static/beta5/pics/lw-tot-tech-yellow.png b/site/static/beta5/pics/lw-tot-tech-yellow.png new file mode 100644 index 0000000..036aec5 Binary files /dev/null and b/site/static/beta5/pics/lw-tot-tech-yellow.png differ diff --git a/site/static/beta5/pics/msgr.gif b/site/static/beta5/pics/msgr.gif new file mode 100644 index 0000000..3bc3e9b Binary files /dev/null and b/site/static/beta5/pics/msgr.gif differ diff --git a/site/static/beta5/pics/msgrep.gif b/site/static/beta5/pics/msgrep.gif new file mode 100644 index 0000000..e7ce88e Binary files /dev/null and b/site/static/beta5/pics/msgrep.gif differ diff --git a/site/static/beta5/pics/msgu.gif b/site/static/beta5/pics/msgu.gif new file mode 100644 index 0000000..1d9e4be Binary files /dev/null and b/site/static/beta5/pics/msgu.gif differ diff --git a/site/static/beta5/pics/nebula1.png b/site/static/beta5/pics/nebula1.png new file mode 100644 index 0000000..ab05128 Binary files /dev/null and b/site/static/beta5/pics/nebula1.png differ diff --git a/site/static/beta5/pics/nebula2.png b/site/static/beta5/pics/nebula2.png new file mode 100644 index 0000000..34f5fb3 Binary files /dev/null and b/site/static/beta5/pics/nebula2.png differ diff --git a/site/static/beta5/pics/nebula3.png b/site/static/beta5/pics/nebula3.png new file mode 100644 index 0000000..4b87ce1 Binary files /dev/null and b/site/static/beta5/pics/nebula3.png differ diff --git a/site/static/beta5/pics/nebula4.png b/site/static/beta5/pics/nebula4.png new file mode 100644 index 0000000..2baef5c Binary files /dev/null and b/site/static/beta5/pics/nebula4.png differ diff --git a/site/static/beta5/pics/prem_l.png b/site/static/beta5/pics/prem_l.png new file mode 100644 index 0000000..2f662c4 Binary files /dev/null and b/site/static/beta5/pics/prem_l.png differ diff --git a/site/static/beta5/pics/prem_s.png b/site/static/beta5/pics/prem_s.png new file mode 100644 index 0000000..6677bd8 Binary files /dev/null and b/site/static/beta5/pics/prem_s.png differ diff --git a/site/static/beta5/pics/read.gif b/site/static/beta5/pics/read.gif new file mode 100644 index 0000000..69efb3a Binary files /dev/null and b/site/static/beta5/pics/read.gif differ diff --git a/site/static/beta5/pics/read_sticky.gif b/site/static/beta5/pics/read_sticky.gif new file mode 100644 index 0000000..86c110f Binary files /dev/null and b/site/static/beta5/pics/read_sticky.gif differ diff --git a/site/static/beta5/pics/right_blue.gif b/site/static/beta5/pics/right_blue.gif new file mode 100644 index 0000000..ab8876d Binary files /dev/null and b/site/static/beta5/pics/right_blue.gif differ diff --git a/site/static/beta5/pics/right_green.gif b/site/static/beta5/pics/right_green.gif new file mode 100644 index 0000000..4c778d2 Binary files /dev/null and b/site/static/beta5/pics/right_green.gif differ diff --git a/site/static/beta5/pics/right_grey.gif b/site/static/beta5/pics/right_grey.gif new file mode 100644 index 0000000..48fb155 Binary files /dev/null and b/site/static/beta5/pics/right_grey.gif differ diff --git a/site/static/beta5/pics/right_purple.gif b/site/static/beta5/pics/right_purple.gif new file mode 100644 index 0000000..ea0b61f Binary files /dev/null and b/site/static/beta5/pics/right_purple.gif differ diff --git a/site/static/beta5/pics/right_red.gif b/site/static/beta5/pics/right_red.gif new file mode 100644 index 0000000..aee5870 Binary files /dev/null and b/site/static/beta5/pics/right_red.gif differ diff --git a/site/static/beta5/pics/right_yellow.gif b/site/static/beta5/pics/right_yellow.gif new file mode 100644 index 0000000..2acbf44 Binary files /dev/null and b/site/static/beta5/pics/right_yellow.gif differ diff --git a/site/static/beta5/pics/that-other-theme-blue.png b/site/static/beta5/pics/that-other-theme-blue.png new file mode 100644 index 0000000..32fe380 Binary files /dev/null and b/site/static/beta5/pics/that-other-theme-blue.png differ diff --git a/site/static/beta5/pics/that-other-theme-green.png b/site/static/beta5/pics/that-other-theme-green.png new file mode 100644 index 0000000..018f188 Binary files /dev/null and b/site/static/beta5/pics/that-other-theme-green.png differ diff --git a/site/static/beta5/pics/that-other-theme-grey.png b/site/static/beta5/pics/that-other-theme-grey.png new file mode 100644 index 0000000..e452935 Binary files /dev/null and b/site/static/beta5/pics/that-other-theme-grey.png differ diff --git a/site/static/beta5/pics/that-other-theme-purple.png b/site/static/beta5/pics/that-other-theme-purple.png new file mode 100644 index 0000000..c04e195 Binary files /dev/null and b/site/static/beta5/pics/that-other-theme-purple.png differ diff --git a/site/static/beta5/pics/that-other-theme-red.png b/site/static/beta5/pics/that-other-theme-red.png new file mode 100644 index 0000000..cd582c9 Binary files /dev/null and b/site/static/beta5/pics/that-other-theme-red.png differ diff --git a/site/static/beta5/pics/that-other-theme-yellow.png b/site/static/beta5/pics/that-other-theme-yellow.png new file mode 100644 index 0000000..914537c Binary files /dev/null and b/site/static/beta5/pics/that-other-theme-yellow.png differ diff --git a/site/static/beta5/pics/ttl/def/blue/menubg.png b/site/static/beta5/pics/ttl/def/blue/menubg.png new file mode 100644 index 0000000..60f9506 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/blue/menubg.png differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/alliance.gif b/site/static/beta5/pics/ttl/def/en/blue/alliance.gif new file mode 100644 index 0000000..b3868fe Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/alliance.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/allies.gif b/site/static/beta5/pics/ttl/def/en/blue/allies.gif new file mode 100644 index 0000000..054d11b Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/allies.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/comms.gif b/site/static/beta5/pics/ttl/def/en/blue/comms.gif new file mode 100644 index 0000000..b693ac7 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/comms.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/diplomacy.gif b/site/static/beta5/pics/ttl/def/en/blue/diplomacy.gif new file mode 100644 index 0000000..9bd28fe Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/diplomacy.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/empire.gif b/site/static/beta5/pics/ttl/def/en/blue/empire.gif new file mode 100644 index 0000000..eea23c1 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/empire.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/enemylist.gif b/site/static/beta5/pics/ttl/def/en/blue/enemylist.gif new file mode 100644 index 0000000..cd9ad66 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/enemylist.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/fleets.gif b/site/static/beta5/pics/ttl/def/en/blue/fleets.gif new file mode 100644 index 0000000..aeeacae Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/fleets.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/forums.gif b/site/static/beta5/pics/ttl/def/en/blue/forums.gif new file mode 100644 index 0000000..1070f76 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/forums.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/manual.gif b/site/static/beta5/pics/ttl/def/en/blue/manual.gif new file mode 100644 index 0000000..fc53dd0 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/manual.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/manual_notfound.gif b/site/static/beta5/pics/ttl/def/en/blue/manual_notfound.gif new file mode 100644 index 0000000..fc53dd0 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/manual_notfound.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/manual_search.gif b/site/static/beta5/pics/ttl/def/en/blue/manual_search.gif new file mode 100644 index 0000000..fc53dd0 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/manual_search.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/map.gif b/site/static/beta5/pics/ttl/def/en/blue/map.gif new file mode 100644 index 0000000..a737508 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/map.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/market.gif b/site/static/beta5/pics/ttl/def/en/blue/market.gif new file mode 100644 index 0000000..898948a Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/market.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/message.gif b/site/static/beta5/pics/ttl/def/en/blue/message.gif new file mode 100644 index 0000000..b1cb35a Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/message.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/money.gif b/site/static/beta5/pics/ttl/def/en/blue/money.gif new file mode 100644 index 0000000..84dc5f5 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/money.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/nplanet.gif b/site/static/beta5/pics/ttl/def/en/blue/nplanet.gif new file mode 100644 index 0000000..6a0ea67 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/nplanet.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/overview.gif b/site/static/beta5/pics/ttl/def/en/blue/overview.gif new file mode 100644 index 0000000..aee6c4b Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/overview.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/planet.gif b/site/static/beta5/pics/ttl/def/en/blue/planet.gif new file mode 100644 index 0000000..6a0ea67 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/planet.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/planetnf.gif b/site/static/beta5/pics/ttl/def/en/blue/planetnf.gif new file mode 100644 index 0000000..6a0ea67 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/planetnf.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/planets.gif b/site/static/beta5/pics/ttl/def/en/blue/planets.gif new file mode 100644 index 0000000..6a0ea67 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/planets.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/preferences.gif b/site/static/beta5/pics/ttl/def/en/blue/preferences.gif new file mode 100644 index 0000000..256ff48 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/preferences.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/probes.gif b/site/static/beta5/pics/ttl/def/en/blue/probes.gif new file mode 100644 index 0000000..2b99874 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/probes.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/rank.gif b/site/static/beta5/pics/ttl/def/en/blue/rank.gif new file mode 100644 index 0000000..600aa07 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/rank.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/research.gif b/site/static/beta5/pics/ttl/def/en/blue/research.gif new file mode 100644 index 0000000..2fe1f02 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/research.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/ticks.gif b/site/static/beta5/pics/ttl/def/en/blue/ticks.gif new file mode 100644 index 0000000..ab1edeb Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/ticks.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/blue/universe.gif b/site/static/beta5/pics/ttl/def/en/blue/universe.gif new file mode 100644 index 0000000..c9b3f11 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/blue/universe.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/alliance.gif b/site/static/beta5/pics/ttl/def/en/green/alliance.gif new file mode 100644 index 0000000..9978699 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/alliance.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/allies.gif b/site/static/beta5/pics/ttl/def/en/green/allies.gif new file mode 100644 index 0000000..c48303e Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/allies.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/comms.gif b/site/static/beta5/pics/ttl/def/en/green/comms.gif new file mode 100644 index 0000000..504da31 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/comms.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/diplomacy.gif b/site/static/beta5/pics/ttl/def/en/green/diplomacy.gif new file mode 100644 index 0000000..896d1cd Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/diplomacy.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/empire.gif b/site/static/beta5/pics/ttl/def/en/green/empire.gif new file mode 100644 index 0000000..2182608 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/empire.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/enemylist.gif b/site/static/beta5/pics/ttl/def/en/green/enemylist.gif new file mode 100644 index 0000000..b35e5b1 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/enemylist.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/fleets.gif b/site/static/beta5/pics/ttl/def/en/green/fleets.gif new file mode 100644 index 0000000..6255a7c Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/fleets.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/forums.gif b/site/static/beta5/pics/ttl/def/en/green/forums.gif new file mode 100644 index 0000000..9174400 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/forums.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/manual.gif b/site/static/beta5/pics/ttl/def/en/green/manual.gif new file mode 100644 index 0000000..0bd14c6 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/manual.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/manual_notfound.gif b/site/static/beta5/pics/ttl/def/en/green/manual_notfound.gif new file mode 100644 index 0000000..0bd14c6 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/manual_notfound.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/manual_search.gif b/site/static/beta5/pics/ttl/def/en/green/manual_search.gif new file mode 100644 index 0000000..0bd14c6 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/manual_search.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/map.gif b/site/static/beta5/pics/ttl/def/en/green/map.gif new file mode 100644 index 0000000..1f09546 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/map.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/market.gif b/site/static/beta5/pics/ttl/def/en/green/market.gif new file mode 100644 index 0000000..fa5a3e8 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/market.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/message.gif b/site/static/beta5/pics/ttl/def/en/green/message.gif new file mode 100644 index 0000000..f6cb53e Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/message.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/money.gif b/site/static/beta5/pics/ttl/def/en/green/money.gif new file mode 100644 index 0000000..bbe4ca3 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/money.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/nplanet.gif b/site/static/beta5/pics/ttl/def/en/green/nplanet.gif new file mode 100644 index 0000000..9c80434 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/nplanet.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/overview.gif b/site/static/beta5/pics/ttl/def/en/green/overview.gif new file mode 100644 index 0000000..fea4d7f Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/overview.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/planet.gif b/site/static/beta5/pics/ttl/def/en/green/planet.gif new file mode 100644 index 0000000..9c80434 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/planet.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/planetnf.gif b/site/static/beta5/pics/ttl/def/en/green/planetnf.gif new file mode 100644 index 0000000..9c80434 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/planetnf.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/planets.gif b/site/static/beta5/pics/ttl/def/en/green/planets.gif new file mode 100644 index 0000000..9c80434 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/planets.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/preferences.gif b/site/static/beta5/pics/ttl/def/en/green/preferences.gif new file mode 100644 index 0000000..754238d Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/preferences.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/probes.gif b/site/static/beta5/pics/ttl/def/en/green/probes.gif new file mode 100644 index 0000000..5ea42a7 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/probes.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/rank.gif b/site/static/beta5/pics/ttl/def/en/green/rank.gif new file mode 100644 index 0000000..ae32056 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/rank.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/research.gif b/site/static/beta5/pics/ttl/def/en/green/research.gif new file mode 100644 index 0000000..deb9887 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/research.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/ticks.gif b/site/static/beta5/pics/ttl/def/en/green/ticks.gif new file mode 100644 index 0000000..93497a2 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/ticks.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/green/universe.gif b/site/static/beta5/pics/ttl/def/en/green/universe.gif new file mode 100644 index 0000000..ae4fbce Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/green/universe.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/alliance.gif b/site/static/beta5/pics/ttl/def/en/grey/alliance.gif new file mode 100644 index 0000000..a99afc2 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/alliance.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/allies.gif b/site/static/beta5/pics/ttl/def/en/grey/allies.gif new file mode 100644 index 0000000..83727e7 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/allies.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/comms.gif b/site/static/beta5/pics/ttl/def/en/grey/comms.gif new file mode 100644 index 0000000..19fa59f Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/comms.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/diplomacy.gif b/site/static/beta5/pics/ttl/def/en/grey/diplomacy.gif new file mode 100644 index 0000000..3fffcca Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/diplomacy.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/empire.gif b/site/static/beta5/pics/ttl/def/en/grey/empire.gif new file mode 100644 index 0000000..bcc5bfe Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/empire.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/enemylist.gif b/site/static/beta5/pics/ttl/def/en/grey/enemylist.gif new file mode 100644 index 0000000..793992b Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/enemylist.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/fleets.gif b/site/static/beta5/pics/ttl/def/en/grey/fleets.gif new file mode 100644 index 0000000..7714276 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/fleets.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/forums.gif b/site/static/beta5/pics/ttl/def/en/grey/forums.gif new file mode 100644 index 0000000..5f89751 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/forums.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/manual.gif b/site/static/beta5/pics/ttl/def/en/grey/manual.gif new file mode 100644 index 0000000..dd66904 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/manual.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/manual_notfound.gif b/site/static/beta5/pics/ttl/def/en/grey/manual_notfound.gif new file mode 100644 index 0000000..dd66904 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/manual_notfound.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/manual_search.gif b/site/static/beta5/pics/ttl/def/en/grey/manual_search.gif new file mode 100644 index 0000000..dd66904 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/manual_search.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/map.gif b/site/static/beta5/pics/ttl/def/en/grey/map.gif new file mode 100644 index 0000000..76c8e00 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/map.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/market.gif b/site/static/beta5/pics/ttl/def/en/grey/market.gif new file mode 100644 index 0000000..3507327 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/market.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/message.gif b/site/static/beta5/pics/ttl/def/en/grey/message.gif new file mode 100644 index 0000000..b227701 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/message.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/money.gif b/site/static/beta5/pics/ttl/def/en/grey/money.gif new file mode 100644 index 0000000..ba78523 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/money.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/nplanet.gif b/site/static/beta5/pics/ttl/def/en/grey/nplanet.gif new file mode 100644 index 0000000..cffd74a Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/nplanet.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/overview.gif b/site/static/beta5/pics/ttl/def/en/grey/overview.gif new file mode 100644 index 0000000..c55bc2d Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/overview.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/planet.gif b/site/static/beta5/pics/ttl/def/en/grey/planet.gif new file mode 100644 index 0000000..cffd74a Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/planet.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/planetnf.gif b/site/static/beta5/pics/ttl/def/en/grey/planetnf.gif new file mode 100644 index 0000000..cffd74a Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/planetnf.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/planets.gif b/site/static/beta5/pics/ttl/def/en/grey/planets.gif new file mode 100644 index 0000000..cffd74a Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/planets.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/preferences.gif b/site/static/beta5/pics/ttl/def/en/grey/preferences.gif new file mode 100644 index 0000000..2f737fd Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/preferences.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/probes.gif b/site/static/beta5/pics/ttl/def/en/grey/probes.gif new file mode 100644 index 0000000..0619b1b Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/probes.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/rank.gif b/site/static/beta5/pics/ttl/def/en/grey/rank.gif new file mode 100644 index 0000000..69b7cae Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/rank.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/research.gif b/site/static/beta5/pics/ttl/def/en/grey/research.gif new file mode 100644 index 0000000..2951ab9 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/research.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/ticks.gif b/site/static/beta5/pics/ttl/def/en/grey/ticks.gif new file mode 100644 index 0000000..1d4198f Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/ticks.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/grey/universe.gif b/site/static/beta5/pics/ttl/def/en/grey/universe.gif new file mode 100644 index 0000000..ef54e9b Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/grey/universe.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/alliance.gif b/site/static/beta5/pics/ttl/def/en/purple/alliance.gif new file mode 100644 index 0000000..affd4b8 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/alliance.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/allies.gif b/site/static/beta5/pics/ttl/def/en/purple/allies.gif new file mode 100644 index 0000000..0ce7f17 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/allies.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/comms.gif b/site/static/beta5/pics/ttl/def/en/purple/comms.gif new file mode 100644 index 0000000..01c9e80 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/comms.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/diplomacy.gif b/site/static/beta5/pics/ttl/def/en/purple/diplomacy.gif new file mode 100644 index 0000000..5d7afa9 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/diplomacy.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/empire.gif b/site/static/beta5/pics/ttl/def/en/purple/empire.gif new file mode 100644 index 0000000..e0f15a4 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/empire.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/enemylist.gif b/site/static/beta5/pics/ttl/def/en/purple/enemylist.gif new file mode 100644 index 0000000..6e4c397 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/enemylist.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/fleets.gif b/site/static/beta5/pics/ttl/def/en/purple/fleets.gif new file mode 100644 index 0000000..9e44c6e Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/fleets.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/forums.gif b/site/static/beta5/pics/ttl/def/en/purple/forums.gif new file mode 100644 index 0000000..35bf793 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/forums.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/manual.gif b/site/static/beta5/pics/ttl/def/en/purple/manual.gif new file mode 100644 index 0000000..c243e02 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/manual.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/manual_notfound.gif b/site/static/beta5/pics/ttl/def/en/purple/manual_notfound.gif new file mode 100644 index 0000000..c243e02 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/manual_notfound.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/manual_search.gif b/site/static/beta5/pics/ttl/def/en/purple/manual_search.gif new file mode 100644 index 0000000..c243e02 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/manual_search.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/map.gif b/site/static/beta5/pics/ttl/def/en/purple/map.gif new file mode 100644 index 0000000..86d1057 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/map.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/market.gif b/site/static/beta5/pics/ttl/def/en/purple/market.gif new file mode 100644 index 0000000..8312b74 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/market.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/message.gif b/site/static/beta5/pics/ttl/def/en/purple/message.gif new file mode 100644 index 0000000..537ec67 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/message.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/money.gif b/site/static/beta5/pics/ttl/def/en/purple/money.gif new file mode 100644 index 0000000..b557dae Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/money.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/nplanet.gif b/site/static/beta5/pics/ttl/def/en/purple/nplanet.gif new file mode 100644 index 0000000..7cb8b37 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/nplanet.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/overview.gif b/site/static/beta5/pics/ttl/def/en/purple/overview.gif new file mode 100644 index 0000000..bde0113 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/overview.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/planet.gif b/site/static/beta5/pics/ttl/def/en/purple/planet.gif new file mode 100644 index 0000000..7cb8b37 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/planet.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/planetnf.gif b/site/static/beta5/pics/ttl/def/en/purple/planetnf.gif new file mode 100644 index 0000000..7cb8b37 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/planetnf.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/planets.gif b/site/static/beta5/pics/ttl/def/en/purple/planets.gif new file mode 100644 index 0000000..7cb8b37 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/planets.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/preferences.gif b/site/static/beta5/pics/ttl/def/en/purple/preferences.gif new file mode 100644 index 0000000..cfe0fc4 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/preferences.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/probes.gif b/site/static/beta5/pics/ttl/def/en/purple/probes.gif new file mode 100644 index 0000000..741fa2b Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/probes.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/rank.gif b/site/static/beta5/pics/ttl/def/en/purple/rank.gif new file mode 100644 index 0000000..2357a9c Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/rank.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/research.gif b/site/static/beta5/pics/ttl/def/en/purple/research.gif new file mode 100644 index 0000000..fb68968 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/research.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/ticks.gif b/site/static/beta5/pics/ttl/def/en/purple/ticks.gif new file mode 100644 index 0000000..d72a63f Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/ticks.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/purple/universe.gif b/site/static/beta5/pics/ttl/def/en/purple/universe.gif new file mode 100644 index 0000000..790c16c Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/purple/universe.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/alliance.gif b/site/static/beta5/pics/ttl/def/en/red/alliance.gif new file mode 100644 index 0000000..ef70213 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/alliance.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/allies.gif b/site/static/beta5/pics/ttl/def/en/red/allies.gif new file mode 100644 index 0000000..6fb74e6 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/allies.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/comms.gif b/site/static/beta5/pics/ttl/def/en/red/comms.gif new file mode 100644 index 0000000..a74a255 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/comms.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/diplomacy.gif b/site/static/beta5/pics/ttl/def/en/red/diplomacy.gif new file mode 100644 index 0000000..a3f27be Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/diplomacy.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/empire.gif b/site/static/beta5/pics/ttl/def/en/red/empire.gif new file mode 100644 index 0000000..6637b60 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/empire.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/enemylist.gif b/site/static/beta5/pics/ttl/def/en/red/enemylist.gif new file mode 100644 index 0000000..44e40a6 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/enemylist.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/fleets.gif b/site/static/beta5/pics/ttl/def/en/red/fleets.gif new file mode 100644 index 0000000..a3f9996 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/fleets.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/forums.gif b/site/static/beta5/pics/ttl/def/en/red/forums.gif new file mode 100644 index 0000000..fe4637b Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/forums.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/manual.gif b/site/static/beta5/pics/ttl/def/en/red/manual.gif new file mode 100644 index 0000000..f33f7c2 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/manual.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/manual_notfound.gif b/site/static/beta5/pics/ttl/def/en/red/manual_notfound.gif new file mode 100644 index 0000000..f33f7c2 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/manual_notfound.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/manual_search.gif b/site/static/beta5/pics/ttl/def/en/red/manual_search.gif new file mode 100644 index 0000000..f33f7c2 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/manual_search.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/map.gif b/site/static/beta5/pics/ttl/def/en/red/map.gif new file mode 100644 index 0000000..c152757 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/map.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/market.gif b/site/static/beta5/pics/ttl/def/en/red/market.gif new file mode 100644 index 0000000..61a8c97 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/market.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/message.gif b/site/static/beta5/pics/ttl/def/en/red/message.gif new file mode 100644 index 0000000..e2895da Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/message.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/money.gif b/site/static/beta5/pics/ttl/def/en/red/money.gif new file mode 100644 index 0000000..3986081 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/money.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/nplanet.gif b/site/static/beta5/pics/ttl/def/en/red/nplanet.gif new file mode 100644 index 0000000..2b7750f Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/nplanet.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/overview.gif b/site/static/beta5/pics/ttl/def/en/red/overview.gif new file mode 100644 index 0000000..0bfd910 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/overview.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/planet.gif b/site/static/beta5/pics/ttl/def/en/red/planet.gif new file mode 100644 index 0000000..2b7750f Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/planet.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/planetnf.gif b/site/static/beta5/pics/ttl/def/en/red/planetnf.gif new file mode 100644 index 0000000..2b7750f Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/planetnf.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/planets.gif b/site/static/beta5/pics/ttl/def/en/red/planets.gif new file mode 100644 index 0000000..2b7750f Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/planets.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/preferences.gif b/site/static/beta5/pics/ttl/def/en/red/preferences.gif new file mode 100644 index 0000000..5295945 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/preferences.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/probes.gif b/site/static/beta5/pics/ttl/def/en/red/probes.gif new file mode 100644 index 0000000..a85bf9f Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/probes.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/rank.gif b/site/static/beta5/pics/ttl/def/en/red/rank.gif new file mode 100644 index 0000000..0d9094d Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/rank.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/research.gif b/site/static/beta5/pics/ttl/def/en/red/research.gif new file mode 100644 index 0000000..3ae94f1 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/research.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/ticks.gif b/site/static/beta5/pics/ttl/def/en/red/ticks.gif new file mode 100644 index 0000000..14486a5 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/ticks.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/red/universe.gif b/site/static/beta5/pics/ttl/def/en/red/universe.gif new file mode 100644 index 0000000..1ca5707 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/red/universe.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/alliance.gif b/site/static/beta5/pics/ttl/def/en/yellow/alliance.gif new file mode 100644 index 0000000..179b7cd Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/alliance.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/allies.gif b/site/static/beta5/pics/ttl/def/en/yellow/allies.gif new file mode 100644 index 0000000..8bfb904 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/allies.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/comms.gif b/site/static/beta5/pics/ttl/def/en/yellow/comms.gif new file mode 100644 index 0000000..5908d8e Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/comms.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/diplomacy.gif b/site/static/beta5/pics/ttl/def/en/yellow/diplomacy.gif new file mode 100644 index 0000000..fd0c538 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/diplomacy.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/empire.gif b/site/static/beta5/pics/ttl/def/en/yellow/empire.gif new file mode 100644 index 0000000..c300f44 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/empire.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/enemylist.gif b/site/static/beta5/pics/ttl/def/en/yellow/enemylist.gif new file mode 100644 index 0000000..c308480 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/enemylist.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/fleets.gif b/site/static/beta5/pics/ttl/def/en/yellow/fleets.gif new file mode 100644 index 0000000..d793b19 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/fleets.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/forums.gif b/site/static/beta5/pics/ttl/def/en/yellow/forums.gif new file mode 100644 index 0000000..bc20422 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/forums.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/manual.gif b/site/static/beta5/pics/ttl/def/en/yellow/manual.gif new file mode 100644 index 0000000..7bffe63 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/manual.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/manual_notfound.gif b/site/static/beta5/pics/ttl/def/en/yellow/manual_notfound.gif new file mode 100644 index 0000000..7bffe63 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/manual_notfound.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/manual_search.gif b/site/static/beta5/pics/ttl/def/en/yellow/manual_search.gif new file mode 100644 index 0000000..7bffe63 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/manual_search.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/map.gif b/site/static/beta5/pics/ttl/def/en/yellow/map.gif new file mode 100644 index 0000000..1dc051a Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/map.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/market.gif b/site/static/beta5/pics/ttl/def/en/yellow/market.gif new file mode 100644 index 0000000..6bfa2f1 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/market.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/message.gif b/site/static/beta5/pics/ttl/def/en/yellow/message.gif new file mode 100644 index 0000000..606829d Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/message.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/money.gif b/site/static/beta5/pics/ttl/def/en/yellow/money.gif new file mode 100644 index 0000000..d96aa28 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/money.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/nplanet.gif b/site/static/beta5/pics/ttl/def/en/yellow/nplanet.gif new file mode 100644 index 0000000..4700284 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/nplanet.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/overview.gif b/site/static/beta5/pics/ttl/def/en/yellow/overview.gif new file mode 100644 index 0000000..097e039 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/overview.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/planet.gif b/site/static/beta5/pics/ttl/def/en/yellow/planet.gif new file mode 100644 index 0000000..4700284 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/planet.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/planetnf.gif b/site/static/beta5/pics/ttl/def/en/yellow/planetnf.gif new file mode 100644 index 0000000..4700284 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/planetnf.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/planets.gif b/site/static/beta5/pics/ttl/def/en/yellow/planets.gif new file mode 100644 index 0000000..4700284 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/planets.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/preferences.gif b/site/static/beta5/pics/ttl/def/en/yellow/preferences.gif new file mode 100644 index 0000000..7a773c7 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/preferences.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/probes.gif b/site/static/beta5/pics/ttl/def/en/yellow/probes.gif new file mode 100644 index 0000000..8f32c00 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/probes.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/rank.gif b/site/static/beta5/pics/ttl/def/en/yellow/rank.gif new file mode 100644 index 0000000..46e728a Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/rank.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/research.gif b/site/static/beta5/pics/ttl/def/en/yellow/research.gif new file mode 100644 index 0000000..13a1b32 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/research.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/ticks.gif b/site/static/beta5/pics/ttl/def/en/yellow/ticks.gif new file mode 100644 index 0000000..8dcf4b3 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/ticks.gif differ diff --git a/site/static/beta5/pics/ttl/def/en/yellow/universe.gif b/site/static/beta5/pics/ttl/def/en/yellow/universe.gif new file mode 100644 index 0000000..2956c09 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/en/yellow/universe.gif differ diff --git a/site/static/beta5/pics/ttl/def/green/menubg.png b/site/static/beta5/pics/ttl/def/green/menubg.png new file mode 100644 index 0000000..2c4b94b Binary files /dev/null and b/site/static/beta5/pics/ttl/def/green/menubg.png differ diff --git a/site/static/beta5/pics/ttl/def/grey/menubg.png b/site/static/beta5/pics/ttl/def/grey/menubg.png new file mode 100644 index 0000000..660009b Binary files /dev/null and b/site/static/beta5/pics/ttl/def/grey/menubg.png differ diff --git a/site/static/beta5/pics/ttl/def/purple/menubg.png b/site/static/beta5/pics/ttl/def/purple/menubg.png new file mode 100644 index 0000000..4035ebf Binary files /dev/null and b/site/static/beta5/pics/ttl/def/purple/menubg.png differ diff --git a/site/static/beta5/pics/ttl/def/red/menubg.png b/site/static/beta5/pics/ttl/def/red/menubg.png new file mode 100644 index 0000000..6f383ef Binary files /dev/null and b/site/static/beta5/pics/ttl/def/red/menubg.png differ diff --git a/site/static/beta5/pics/ttl/def/yellow/menubg.png b/site/static/beta5/pics/ttl/def/yellow/menubg.png new file mode 100644 index 0000000..3bffab6 Binary files /dev/null and b/site/static/beta5/pics/ttl/def/yellow/menubg.png differ diff --git a/site/static/beta5/pics/unlock_blue.gif b/site/static/beta5/pics/unlock_blue.gif new file mode 100644 index 0000000..0970a40 Binary files /dev/null and b/site/static/beta5/pics/unlock_blue.gif differ diff --git a/site/static/beta5/pics/unlock_green.gif b/site/static/beta5/pics/unlock_green.gif new file mode 100644 index 0000000..f4910f8 Binary files /dev/null and b/site/static/beta5/pics/unlock_green.gif differ diff --git a/site/static/beta5/pics/unlock_grey.gif b/site/static/beta5/pics/unlock_grey.gif new file mode 100644 index 0000000..2132419 Binary files /dev/null and b/site/static/beta5/pics/unlock_grey.gif differ diff --git a/site/static/beta5/pics/unlock_purple.gif b/site/static/beta5/pics/unlock_purple.gif new file mode 100644 index 0000000..5614f51 Binary files /dev/null and b/site/static/beta5/pics/unlock_purple.gif differ diff --git a/site/static/beta5/pics/unlock_red.gif b/site/static/beta5/pics/unlock_red.gif new file mode 100644 index 0000000..818ee02 Binary files /dev/null and b/site/static/beta5/pics/unlock_red.gif differ diff --git a/site/static/beta5/pics/unlock_yellow.gif b/site/static/beta5/pics/unlock_yellow.gif new file mode 100644 index 0000000..c125e25 Binary files /dev/null and b/site/static/beta5/pics/unlock_yellow.gif differ diff --git a/site/static/beta5/pics/unread.gif b/site/static/beta5/pics/unread.gif new file mode 100644 index 0000000..69710e2 Binary files /dev/null and b/site/static/beta5/pics/unread.gif differ diff --git a/site/static/beta5/pics/unread_sticky.gif b/site/static/beta5/pics/unread_sticky.gif new file mode 100644 index 0000000..4e3f8c6 Binary files /dev/null and b/site/static/beta5/pics/unread_sticky.gif differ diff --git a/site/static/beta5/pics/up_blue.gif b/site/static/beta5/pics/up_blue.gif new file mode 100644 index 0000000..12a47e0 Binary files /dev/null and b/site/static/beta5/pics/up_blue.gif differ diff --git a/site/static/beta5/pics/up_green.gif b/site/static/beta5/pics/up_green.gif new file mode 100644 index 0000000..95e2e4c Binary files /dev/null and b/site/static/beta5/pics/up_green.gif differ diff --git a/site/static/beta5/pics/up_grey.gif b/site/static/beta5/pics/up_grey.gif new file mode 100644 index 0000000..c7c9009 Binary files /dev/null and b/site/static/beta5/pics/up_grey.gif differ diff --git a/site/static/beta5/pics/up_purple.gif b/site/static/beta5/pics/up_purple.gif new file mode 100644 index 0000000..55ab2e0 Binary files /dev/null and b/site/static/beta5/pics/up_purple.gif differ diff --git a/site/static/beta5/pics/up_red.gif b/site/static/beta5/pics/up_red.gif new file mode 100644 index 0000000..d9d7cd7 Binary files /dev/null and b/site/static/beta5/pics/up_red.gif differ diff --git a/site/static/beta5/pics/up_yellow.gif b/site/static/beta5/pics/up_yellow.gif new file mode 100644 index 0000000..dbefc00 Binary files /dev/null and b/site/static/beta5/pics/up_yellow.gif differ diff --git a/site/static/main/css/account.css b/site/static/main/css/account.css new file mode 100644 index 0000000..6c68f02 --- /dev/null +++ b/site/static/main/css/account.css @@ -0,0 +1,69 @@ +#tabs { + width: 100%; + margin: 0px 0px 10px 0px; + height: 30px; +} + +#tabs a { + display: block; + position: absolute; + top: 0px; + width: 140px; + background-color: #1f1f1f; + border: 1px solid #3f3f3f; + padding: 5px; + text-align: center; + vertical-align: middle; + text-decoration: none; + font-size: 10pt; +} + +#tabs a:hover { + background-color: #5f5f5f; + border-color: white; +} + +#tabs a.selected { + background-color: #3f3f3f; + border-color: #5f5f5f; + font-weight: bold; +} + +#tab-games { left: 88px; } +#tab-vacation { left: 243px; } +#tab-quit { left: 398px; } + +#cframe .acctab h1 { + font-size: 12pt; +} +#cframe .acctab p { + padding: 0px 0px 10px 10px; +} +#cframe .acctab table { + width: 570px; + margin: 0px 0px 10px 30px; + font-size: 11pt; +} + +#cframe .acctab table a { background-color: #1f1f1f; text-decoration: none } +#cframe .acctab table a:hover { background-color: #3f3f3f; text-decoration: underline } + +.gname { + width: 300px; + text-align: left; +} + +.acctab .input { + width: 200px; + margin: 10px 0px 10px 40px; +} + +#cframe .acctab#quit h1 { + background-color: red; +} + +#cframe .acctab#quit p#header { + padding: 10px; + background-color: #ff7f7f; + color: white; +} diff --git a/site/static/main/css/annoy.css b/site/static/main/css/annoy.css new file mode 100644 index 0000000..a50ef50 --- /dev/null +++ b/site/static/main/css/annoy.css @@ -0,0 +1,34 @@ +#intframe { + background-image: url(__STATICURL__/main/pics/mp-content.jpg); +} + +#aframe { + position: absolute; + left: 15px; + top: 224px; + width: 921px; + height: 279px; + overflow: none; + background-color: transparent; +} + +#aframe h1 { + width: 100%; + font-size: 20pt; + font-variant: small-caps; + text-align: center; +} + +#aframe h1 span { + font-size: 20pt; +} + +#aframe p, #aframe b { + color: #aaa; + font-size: 14pt; +} + +#aframe p { + text-align: center; + margin: 10px 0px; +} diff --git a/site/static/main/css/b6pp.css b/site/static/main/css/b6pp.css new file mode 100644 index 0000000..8f5c7f3 --- /dev/null +++ b/site/static/main/css/b6pp.css @@ -0,0 +1,47 @@ +#cframe h1 { + font-size: 16pt; + margin: 5px 0px; +} + +#cframe h2 { + margin: 5px 0px 5px 10px; + font-size: 14pt; +} + +#cframe h3 { + margin: 5px 0px 5px 20px; + font-size: 12pt; + font-variant: small-caps; +} + +#cframe ul { list-style-type: square; } +#cframe li { font-size: 10pt; } +#cframe li:first-letter { + font-size: 11pt; + font-weight: bold; +} + +#cframe p { + margin: 5px 10px 5px 30px; + text-align: justify; + text-indent: 10px; + font-size: 10pt; +} +#cframe p:first-letter { + font-size: 11pt; + font-weight: bold; +} + +#cframe table { + border-collapse: collapse; + border: 1px solid #ffffff; + margin: 10px 0px; +} + +#cframe th, #cframe td { + border: 1px solid #7f7f7f; + text-align: center; + padding: 3px 1px; +} +#cframe th { vertical-align: bottom } +#cframe td { vertical-align: top } diff --git a/site/static/main/css/content.css b/site/static/main/css/content.css new file mode 100644 index 0000000..396cad4 --- /dev/null +++ b/site/static/main/css/content.css @@ -0,0 +1,72 @@ +#intframe { + background-image: url(__STATICURL__/main/pics/mp-content.jpg); +} + +#cframe { + position: absolute; + left: 299px; + top: 114px; + width: 637px; + height: 429px; + overflow: auto; + background-color: transparent; + color: white; +} + +a.mbutton { + display: block; + position: absolute; + height: 21px; + width: 250px; + left: 14px; + padding: 18px 0px; + background-repeat: no-repeat; + border-style: none; + border-width: 0px; + font-weight: bold; + text-align: center; + text-decoration: none; + color: #ddd; + background-position: 0px 0px; + font-size: 12pt; +} + +a.mbutton:hover { + color: white; + background-position: 0px -57px; +} + +a#b0 { + top: 136px; + background-image: url(__STATICURL__/main/pics/mp-content-b0.png); +} + +a#b1 { + top: 193px; + background-image: url(__STATICURL__/main/pics/mp-content-b1.png); +} + +a#b2 { + top: 250px; + background-image: url(__STATICURL__/main/pics/mp-content-b2.png); +} + +a#b3 { + top: 307px; + background-image: url(__STATICURL__/main/pics/mp-content-b3.png); +} + +a#b4 { + top: 364px; + background-image: url(__STATICURL__/main/pics/mp-content-b4.png); +} + +a#b5 { + top: 421px; + background-image: url(__STATICURL__/main/pics/mp-content-b5.png); +} + +a#b6 { + top: 478px; + background-image: url(__STATICURL__/main/pics/mp-content-b6.png); +} diff --git a/site/static/main/css/create.css b/site/static/main/css/create.css new file mode 100644 index 0000000..77d8ece --- /dev/null +++ b/site/static/main/css/create.css @@ -0,0 +1,89 @@ +#cframe h1 { + position: absolute; + top: 30px; + left: 5%; + font-size: 16pt; + text-align: left; + width: 95%; +} + +div.fsection { + position: absolute; + left: 0px; + width: 100%; +} + +.fsection#name-lang { + top: 80px; +} + +.fsection#email { + top: 150px; +} + +.fsection#passwd { + top: 220px; +} + +.fsection#planet { + top: 290px; +} + +.fsection#confirm { + top: 340px; +} + +.sechd { + position: absolute; + top: 0px; + width: 90%; + left: 10%; + height: 20px; + text-align: left; + font-size: 12pt; +} + +.error { + background-color: red; + font-weight: bold; +} + +.formline { + position: absolute; + top: 25px; + width: 80%; + height: 20px; + left: 10%; +} + +.formline .input { + width: 50%; +} + +.formline div { + text-align: right; +} + +.formline * { + font-size: 10pt; +} + +.fcol1 { + position: absolute; + top: 0px; + width: 50%; + left: 0px; + height: 100%; +} + +.fcol2 { + position: absolute; + top: 0px; + width: 50%; + left: 50%; + height: 100%; +} + +.pad { + padding: 2px 0px 0px 0px; +} diff --git a/site/static/main/css/credits.css b/site/static/main/css/credits.css new file mode 100644 index 0000000..5400fdb --- /dev/null +++ b/site/static/main/css/credits.css @@ -0,0 +1,37 @@ +#cframe div { + width: 100%; + text-align: center; +} + +#gamename { + font-size: 48pt; + font-variant: small-caps; + margin: 50px 0px 10px 0px; + color: white; +} + +#gameby, #gameby a { + font-size: 18pt; + text-decoration: none; + font-style: italic; + padding: 0px 0px 20px 0px; + color: #aaa; +} + +.category { + font-size: 18pt; + font-variant: small-caps; + margin: 30px 0px 5px 0px; +} + +.who, .who a { + font-size: 16pt; + color: #aaa; + text-decoration: none; +} + +.misc { + font-size: 14pt; + font-variant: small-caps; + margin: 50px 0px 5px 0px; +} diff --git a/site/static/main/css/home.css b/site/static/main/css/home.css new file mode 100644 index 0000000..524169d --- /dev/null +++ b/site/static/main/css/home.css @@ -0,0 +1,89 @@ +#intframe { + background-image: url(__STATICURL__/main/pics/mp-title.jpg); +} + +a.mbutton { + display: block; + position: absolute; + height: 24px; + padding: 18px 0px; + background-repeat: no-repeat; + border-style: none; + border-width: 0px; + font-weight: bold; + text-align: center; + text-decoration: none; + color: #ddd; + background-position: 0px 0px; + font-size: 14pt; +} + +a.mbutton:hover { + color: white; + background-position: 0px -60px; +} + +a#b0 { + top: 159px; + left: 319px; + width: 310px; + background-image: url(__STATICURL__/main/pics/mp-title-b0.png); +} + +a#b1 { + top: 220px; + left: 242px; + width: 232px; + background-image: url(__STATICURL__/main/pics/mp-title-b1.png); +} + +a#b2 { + top: 220px; + left: 475px; + width: 232px; + background-image: url(__STATICURL__/main/pics/mp-title-b2.png); +} + +a#b3 { + top: 280px; + left: 242px; + width: 232px; + background-image: url(__STATICURL__/main/pics/mp-title-b3.png); +} + +a#b4 { + top: 280px; + left: 475px; + width: 232px; + background-image: url(__STATICURL__/main/pics/mp-title-b4.png); +} + +a#b5 { + top: 340px; + left: 242px; + width: 232px; + background-image: url(__STATICURL__/main/pics/mp-title-b5.png); +} + +a#b6 { + top: 340px; + left: 475px; + width: 232px; + background-image: url(__STATICURL__/main/pics/mp-title-b6.png); +} + +#gamedesc { + position: absolute; + top: 430px; + left: 219px; + width: 510px; + text-align: center; + font-size: 10pt; + color: white; +} + +#gamedesc a, #gamedesc a:hover, #gamedesc a:visited { + font-style: normal; + text-decoration: none; + color: white; +} diff --git a/site/static/main/css/main.css b/site/static/main/css/main.css new file mode 100644 index 0000000..2e04b16 --- /dev/null +++ b/site/static/main/css/main.css @@ -0,0 +1,149 @@ +* { + font-family: Arial, sans-serif; + font-size: 10pt; + padding: 0px; + margin: 0px; + color: white; + scrollbar-face-color: #1f1f1f; + scrollbar-highlight-color: #1f1f1f; + scrollbar-3dlight-color: #3f3f3f; + scrollbar-darkshadow-color: #3f3f3f; + scrollbar-shadow-color: #1f1f1f; + scrollbar-arrow-color: #afafaf; + scrollbar-track-color: #1f1f1f; +} + +h1 { font-size: 16pt } +h2 { font-size: 14pt } +h3 { font-size: 12pt } +h4 { font-size: 11pt } +h5 { font-size: 11pt } +h6 { font-size: 10pt } + +body { + background-color: #000; + color: #888; + overflow: auto; +} + +#extframe { + position: absolute; + top: 50%; + left: 0px; + width: 100%; + height: 1px +} + +.internal { + margin-left: -475px; + position: absolute; + left: 50%; + width: 950px; +} + +#intframe { + top: -280px; + height: 560px; + background-repeat: no-repeat; +} + +#footer { + top: 280px; + text-align: center; + font-size: 8pt; + font-style: italic +} + +#footer a, #footer a:visited { + text-decoration: none; + color: white; +} + +#footer a:hover { + text-decoration: underline; +} + +#hbutton { + display: block; + position: absolute; + top: 12px; + left: 10px; + width: 272px; + height: 90px; + background-color: transparent; +} + +#hbutton span { + display: none; +} + +#version { + position: absolute; + top: 89px; + left: 299px; + width: 640px; + height: 22px; + font-size: 10pt; + text-align: center; + font-style: italic; + font-weight: bold; +} + +#lbox { + position: absolute; + top: 9px; + left: 399px; + width: 530px; + height: 60px; + text-align: right; + font-size: 10pt; +} + +#lbox a { + color: white; + text-decoration: none; + font-style: italic; +} + +#lbox a:hover { + text-decoration: underline; +} + +#players { + position: absolute; + top: 30px; + left: 330px; + width: 320px; + height: 30px; + font-weight: bold; + font-size: 13pt; + text-align: center; +} + +.input { + border-style: solid; + border-width: 1px; + border-color: #afafaf; + background-color: #3f3f3f; + color: white; + font-size: 10pt; + margin: 1px 0px +} +select.input { + width: 75%; +} + + +#cframe p { + color:#CCCCCC; +} + +#cframe ul, #cframe ol { + margin: 0px 0px 0px 20px; + padding: 0px 0px 0px 10px; +} + +#cframe li { + margin: 0px 0px 0px 20px; + padding: 0px 0px 0px 0px; +} diff --git a/site/static/main/css/manual.css b/site/static/main/css/manual.css new file mode 100644 index 0000000..9b26ad1 --- /dev/null +++ b/site/static/main/css/manual.css @@ -0,0 +1,153 @@ +/* Sidebox */ +#msidebox-top { + position: absolute; + left: 0px; + right: 0px; + width: 250px; + height: 90px; + overflow: none; +} + +#msidebox-top ul, #msidebox-top li { + display: block; + position: absolute; + margin: 0px; + padding: 0px; + height: 20px; + top: 0px; + left: 0px; + text-align: center; +} + +ul#msb-nav { width: 250px; height: 40px } +li#msb-top { top: 20px; width: 250px } +li#msb-left { left: 0px; width: 83px } +li#msb-up { left: 83px; width: 84px } +li#msb-right { left: 167px; width: 83px } + +#msb-form { + display: block; + position: absolute; + height: 20px; + top: 40px; + left: 0px; + width: 250px +} + +#msb-stext { width: 172px; } +#msb-sbutton { width: 69px; } + +#msb-title { + display: block; + position: absolute; + left: 0px; + top: 70px; + height: 20px; + width: 250px; + font-weight: bold; +} + +#msb-links { + position: absolute; + padding: 5px; + top: 90px; + left: 10px; + width: 218px; + height: 287px; + border: 1px solid #3f3f3f; + overflow: auto; +} + +/* Main page title */ +#manpage h1 { + font-size: 20pt; + margin: 0px 0px 20px 0px +} + +/* Table of contents: frame */ +#clist { + display: table; + font-size: 11pt; + margin: 0px 0px 0px 20px; + padding: 5px 50px 5px 5px; + border: 1px solid #7f7f7f; + background-color: #3f3f3f; + width: auto +} + +/* Table of contents: list */ +#pcontents { margin: 8px 0px 0px 0px } +#pcontents ul { margin: 0px 0px 0px 10px; } +#pcontents li { margin: 0px 0px 0px 10px; } + +/* Table of contents: hide/show */ +#clist span { + display: none; + text-decoration: underline; + font-style: italic; + cursor: pointer; +} + + +/* Links to top of the page */ +.toplnk { + display: block; + float: right; + margin: 5px 10px 5px 0px; + padding: 3px; + border: 1px solid #7f7f7f; + text-decoration: none; + font-style: italic; + font-size: 11pt; +} +a.toplnk:hover { + border: 1px solid #ffffff; +} + +/* Links */ +#manpage a { + background-color: #3f3f3f; + text-decoration: none +} +#manpage a:hover { + background-color: #7f7f7f; + text-decoration: underline +} +#manpage a.anchor { background-color: transparent; } +#manpage a.mlink { + display: table; + margin: 0px 0px 15px 40px; +} + + +/* Sections */ +.mtopsec { + border-color: white; + border-style: solid; + border-width: 1px 0px 0px 0px; + margin: 25px 10px 0px 0px; +} +.msec { + margin: 20px 0px 0px 15px; +} +div.mancontents { + padding: 5px 0px 0px 10px; + text-align: justify; + font-size: 11pt; +} + +div.mancontents table { + border-collapse: collapse; + border: 1px solid #ffffff; + margin: 10px 0px; +} + +div.mancontents th, div.mancontents td { + border: 1px solid #7f7f7f; + text-align: center; + padding: 3px 1px; +} +div.mancontents th { vertical-align: bottom } +div.mancontents td { vertical-align: top } + +div.mancontents tr:hover td { background-color: #3f3f3f } diff --git a/site/static/main/css/rankings.css b/site/static/main/css/rankings.css new file mode 100644 index 0000000..624437b --- /dev/null +++ b/site/static/main/css/rankings.css @@ -0,0 +1,3 @@ +select.input { + width: 200px; +} diff --git a/site/static/main/css/screenshots.css b/site/static/main/css/screenshots.css new file mode 100644 index 0000000..c72d337 --- /dev/null +++ b/site/static/main/css/screenshots.css @@ -0,0 +1,49 @@ +.category { + position: absolute; + width: 100%; + left: 0px; + height: 50px; + text-align: center; +} + +.catname, .catname a { + font-size: 24pt; + font-variant: small-caps; + color: white; + text-decoration: none; +} + +.catpics, .catpics a { + color: #aaa; + font-size: 12pt; + text-decoration: none; +} + +.thumbnail { + position: absolute; + width: 33%; + height: 110px; + text-align: center; +} + +.thumbpic { + width: 100%; + text-align: center; +} + +.thumbpic img { + border-style: none; +} + +.showpic { + position: absolute; + width: 100%; + top: 90px; + left: 0px; + height: 110px; + text-align: center; +} + +.showpic img { + border-style: none; +} diff --git a/site/static/main/css/sidebox.css b/site/static/main/css/sidebox.css new file mode 100644 index 0000000..61dc148 --- /dev/null +++ b/site/static/main/css/sidebox.css @@ -0,0 +1,134 @@ +#intframe { + background-image: url(__STATICURL__/main/pics/mp-content.jpg); +} + +#sbox { + position: absolute; + top: 110px; + left: 14px; + width: 250px; + height: 425px; +} + +#cframe { + position: absolute; + left: 299px; + top: 114px; + width: 637px; + height: 429px; + overflow: auto; + background-color: transparent; + color: white; +} + +#slbox { + position: absolute; + top: 0px; + left: 0px; + width: 250px; + height: 425px; +} + +#slbox li { + position: absolute; + width: 250px; + height: 415px; + top: 0px; + list-style: none; + font-size: 9pt; + background-repeat: no-repeat; +} + +li.slcol span { + position: absolute; + width: 125px; + top: 5px; + left: 0px; + text-align: center; + font-weight: normal; +} + +li#slright div { + display: none; +} + +li#slleft { + left: 7px; + background-image: url(__STATICURL__/main/pics/mp-tab-1.png); +} +li#slleft span { + font-weight: bold; +} + +li#slright { + height: 26px; + left: 134px; +} + +#slcontent { + display: block; + position: absolute; + left: 0px; + top: 31px; + width: 250px; + height: 394px; + overflow: auto; +} + +a.mbutton { + display: block; + position: absolute; + height: 21px; + width: 250px; + left: -134px; + padding: 18px 0px; + background-repeat: no-repeat; + border-style: none; + border-width: 0px; + font-weight: bold; + text-align: center; + text-decoration: none; + color: #ddd; + background-position: 0px 0px; + font-size: 12pt; +} + +a.mbutton:hover { + color: white; + background-position: 0px -57px; +} + +a#b0 { + top: 26px; + background-image: url(__STATICURL__/main/pics/mp-content-b0.png); +} + +a#b1 { + top: 83px; + background-image: url(__STATICURL__/main/pics/mp-content-b1.png); +} + +a#b2 { + top: 140px; + background-image: url(__STATICURL__/main/pics/mp-content-b2.png); +} + +a#b3 { + top: 197px; + background-image: url(__STATICURL__/main/pics/mp-content-b3.png); +} + +a#b4 { + top: 254px; + background-image: url(__STATICURL__/main/pics/mp-content-b4.png); +} + +a#b5 { + top: 311px; + background-image: url(__STATICURL__/main/pics/mp-content-b5.png); +} + +a#b6 { + top: 368px; + background-image: url(__STATICURL__/main/pics/mp-content-b6.png); +} diff --git a/site/static/main/css/text.css b/site/static/main/css/text.css new file mode 100644 index 0000000..d96393a --- /dev/null +++ b/site/static/main/css/text.css @@ -0,0 +1,49 @@ +#cframe h1 { + font-size: 16pt; + margin: 5px 0px; +} + +#cframe h2 { + margin: 5px 0px 5px 10px; + font-size: 14pt; +} + +#cframe h3 { + margin: 5px 0px 5px 20px; + font-size: 12pt; + font-variant: small-caps; +} + +#cframe ul { list-style-type: square; } +#cframe li { font-size: 10pt; } +#cframe li:first-letter { + font-size: 11pt; + font-weight: bold; +} + +#cframe p { + margin: 5px 10px 5px 30px; + text-align: justify; + text-indent: 10px; + font-size: 10pt; +} +#cframe p:first-letter { + font-size: 11pt; + font-weight: bold; +} + +#cframe table { + border-collapse: collapse; + border: 1px solid #ffffff; + margin: 10px 0px; +} + +#cframe th, #cframe td { + border: 1px solid #7f7f7f; + text-align: center; + padding: 3px 1px; +} +#cframe th { vertical-align: bottom } +#cframe td { vertical-align: top } + +#cframe tr:hover td { background-color: #3f3f3f } diff --git a/site/static/main/js/account.js b/site/static/main/js/account.js new file mode 100644 index 0000000..ceb5c94 --- /dev/null +++ b/site/static/main/js/account.js @@ -0,0 +1,29 @@ +$(function(){ + var _selected; + + var _getSelected = function (text) { + if (text == '#acc-quit' || $('#acc-vacation') && text == '#acc-vacation') { + _selected = text; + } else { + _selected = '#acc-games'; + } + }; + + var _display = function () { + $('#acc-quit').css('display', (_selected == '#acc-quit') ? 'block' : 'none'); + if ($('#acc-vacation')) { + $('#acc-vacation').css('display', (_selected == '#acc-vacation') ? 'block' : 'none'); + } + $('#acc-games').css('display', (_selected == '#acc-games') ? 'block' : 'none'); + $('#tabs a' + _selected.replace(/acc-/, 'tab-')).addClass('selected'); + }; + + _getSelected(location.hash); + _display(); + + $('#tabs a').click(function() { + $('#tabs a' + _selected.replace(/acc-/, 'tab-')).removeClass('selected'); + _getSelected($(this).attr('href')); + _display(); + }); +}); diff --git a/site/static/main/js/adapt.js b/site/static/main/js/adapt.js new file mode 100644 index 0000000..36fe1cd --- /dev/null +++ b/site/static/main/js/adapt.js @@ -0,0 +1,31 @@ +$(function() { + var _viewportHeight = function() { + return self.innerHeight + || jQuery.boxModel && document.documentElement.clientHeight + || document.body.clientHeight; + }; + var _viewportWidth = function() { + return self.innerWidth + || jQuery.boxModel && document.documentElement.clientWidth + || document.body.clientWidth; + }; + + var _handleSize = function () { + var _h = _viewportHeight(); + if (_h < 560) { + $("#extframe").css('top', '280px'); + } else { + $("#extframe").css('top', '50%'); + } + + var _w = _viewportWidth(); + if (_w < 950) { + $(".internal").css('left', '475px'); + } else { + $(".internal").css('left', '50%'); + } + }; + + $(window).resize(_handleSize); + _handleSize(); +}); diff --git a/site/static/main/js/annoy.js b/site/static/main/js/annoy.js new file mode 100644 index 0000000..e707010 --- /dev/null +++ b/site/static/main/js/annoy.js @@ -0,0 +1,14 @@ +function beAnnoying() { + var _t = parseInt(document.getElementById('redtime').innerHTML, 10); + _t --; + if (_t == 0) { + document.location.href = document.location.href.replace(/\.php.*$/, '.php/main/contrib'); + } else { + document.getElementById('redtime').innerHTML = _t; + setTimeout('beAnnoying()', 1000); + } +} + +$(function(){ + setTimeout('beAnnoying()', 1000); +}); diff --git a/site/static/main/js/jquery.js b/site/static/main/js/jquery.js new file mode 100644 index 0000000..3747929 --- /dev/null +++ b/site/static/main/js/jquery.js @@ -0,0 +1,32 @@ +/* + * jQuery 1.2.3 - New Wave Javascript + * + * Copyright (c) 2008 John Resig (jquery.com) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * $Date: 2008-02-06 00:21:25 -0500 (Wed, 06 Feb 2008) $ + * $Rev: 4663 $ + */ +(function(){if(window.jQuery)var _jQuery=window.jQuery;var jQuery=window.jQuery=function(selector,context){return new jQuery.prototype.init(selector,context);};if(window.$)var _$=window.$;window.$=jQuery;var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/;var isSimple=/^.[^:#\[\.]*$/;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(selector.nodeType){this[0]=selector;this.length=1;return this;}else if(typeof selector=="string"){var match=quickExpr.exec(selector);if(match&&(match[1]||!context)){if(match[1])selector=jQuery.clean([match[1]],context);else{var elem=document.getElementById(match[3]);if(elem)if(elem.id!=match[3])return jQuery().find(selector);else{this[0]=elem;this.length=1;return this;}else +selector=[];}}else +return new jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return new jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(selector.constructor==Array&&selector||(selector.jquery||selector.length&&selector!=window&&!selector.nodeType&&selector[0]!=undefined&&selector[0].nodeType)&&jQuery.makeArray(selector)||[selector]);},jquery:"1.2.3",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(elems){var ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function(elems){this.length=0;Array.prototype.push.apply(this,elems);return this;},each:function(callback,args){return jQuery.each(this,callback,args);},index:function(elem){var ret=-1;this.each(function(i){if(this==elem)ret=i;});return ret;},attr:function(name,value,type){var options=name;if(name.constructor==String)if(value==undefined)return this.length&&jQuery[type||"attr"](this[0],name)||undefined;else{options={};options[name]=value;}return this.each(function(i){for(name in options)jQuery.attr(type?this.style:this,name,jQuery.prop(this,options[name],type,i,name));});},css:function(key,value){if((key=='width'||key=='height')&&parseFloat(value)<0)value=undefined;return this.attr(key,value,"curCSS");},text:function(text){if(typeof text!="object"&&text!=null)return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(text));var ret="";jQuery.each(text||this,function(){jQuery.each(this.childNodes,function(){if(this.nodeType!=8)ret+=this.nodeType!=1?this.nodeValue:jQuery.fn.text([this]);});});return ret;},wrapAll:function(html){if(this[0])jQuery(html,this[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var elem=this;while(elem.firstChild)elem=elem.firstChild;return elem;}).append(this);return this;},wrapInner:function(html){return this.each(function(){jQuery(this).contents().wrapAll(html);});},wrap:function(html){return this.each(function(){jQuery(this).wrapAll(html);});},append:function(){return this.domManip(arguments,true,false,function(elem){if(this.nodeType==1)this.appendChild(elem);});},prepend:function(){return this.domManip(arguments,true,true,function(elem){if(this.nodeType==1)this.insertBefore(elem,this.firstChild);});},before:function(){return this.domManip(arguments,false,false,function(elem){this.parentNode.insertBefore(elem,this);});},after:function(){return this.domManip(arguments,false,true,function(elem){this.parentNode.insertBefore(elem,this.nextSibling);});},end:function(){return this.prevObject||jQuery([]);},find:function(selector){var elems=jQuery.map(this,function(elem){return jQuery.find(selector,elem);});return this.pushStack(/[^+>] [^+>]/.test(selector)||selector.indexOf("..")>-1?jQuery.unique(elems):elems);},clone:function(events){var ret=this.map(function(){if(jQuery.browser.msie&&!jQuery.isXMLDoc(this)){var clone=this.cloneNode(true),container=document.createElement("div");container.appendChild(clone);return jQuery.clean([container.innerHTML])[0];}else +return this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){if(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(selector){return this.pushStack(jQuery.isFunction(selector)&&jQuery.grep(this,function(elem,i){return selector.call(elem,i);})||jQuery.multiFilter(selector,this));},not:function(selector){if(selector.constructor==String)if(isSimple.test(selector))return this.pushStack(jQuery.multiFilter(selector,this,true));else +selector=jQuery.multiFilter(selector,this);var isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!selector.nodeType;return this.filter(function(){return isArrayLike?jQuery.inArray(this,selector)<0:this!=selector;});},add:function(selector){return!selector?this:this.pushStack(jQuery.merge(this.get(),selector.constructor==String?jQuery(selector).get():selector.length!=undefined&&(!selector.nodeName||jQuery.nodeName(selector,"form"))?selector:[selector]));},is:function(selector){return selector?jQuery.multiFilter(selector,this).length>0:false;},hasClass:function(selector){return this.is("."+selector);},val:function(value){if(value==undefined){if(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var index=elem.selectedIndex,values=[],options=elem.options,one=elem.type=="select-one";if(index<0)return null;for(var i=one?index:0,max=one?index+1:options.length;i=0||jQuery.inArray(this.name,value)>=0);else if(jQuery.nodeName(this,"select")){var values=value.constructor==Array?value:[value];jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this.text,values)>=0);});if(!values.length)this.selectedIndex=-1;}else +this.value=value;});},html:function(value){return value==undefined?(this.length?this[0].innerHTML:null):this.empty().append(value);},replaceWith:function(value){return this.after(value).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);},data:function(key,value){var parts=key.split(".");parts[1]=parts[1]?"."+parts[1]:"";if(value==null){var data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);if(data==undefined&&this.length)data=jQuery.data(this[0],key);return data==null&&parts[1]?this.data(parts[0]):data;}else +return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each(function(){jQuery.data(this,key,value);});},removeData:function(key){return this.each(function(){jQuery.removeData(this,key);});},domManip:function(args,table,reverse,callback){var clone=this.length>1,elems;return this.each(function(){if(!elems){elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByTagName("tbody")[0]||this.appendChild(this.ownerDocument.createElement("tbody"));var scripts=jQuery([]);jQuery.each(elems,function(){var elem=clone?jQuery(this).clone(true)[0]:this;if(jQuery.nodeName(elem,"script")){scripts=scripts.add(elem);}else{if(elem.nodeType==1)scripts=scripts.add(jQuery("script",elem).remove());callback.call(obj,elem);}});scripts.each(evalScript);});}};jQuery.prototype.init.prototype=jQuery.prototype;function evalScript(i,elem){if(elem.src)jQuery.ajax({url:elem.src,async:false,dataType:"script"});else +jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},i=1,length=arguments.length,deep=false,options;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}if(typeof target!="object"&&typeof target!="function")target={};if(length==1){target=this;i=0;}for(;i-1;}},swap:function(elem,options,callback){var old={};for(var name in options){old[name]=elem.style[name];elem.style[name]=options[name];}callback.call(elem);for(var name in options)elem.style[name]=old[name];},css:function(elem,name,force){if(name=="width"||name=="height"){var val,props={position:"absolute",visibility:"hidden",display:"block"},which=name=="width"?["Left","Right"]:["Top","Bottom"];function getWH(){val=name=="width"?elem.offsetWidth:elem.offsetHeight;var padding=0,border=0;jQuery.each(which,function(){padding+=parseFloat(jQuery.curCSS(elem,"padding"+this,true))||0;border+=parseFloat(jQuery.curCSS(elem,"border"+this+"Width",true))||0;});val-=Math.round(padding+border);}if(jQuery(elem).is(":visible"))getWH();else +jQuery.swap(elem,props,getWH);return Math.max(0,val);}return jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var ret;function color(elem){if(!jQuery.browser.safari)return false;var ret=document.defaultView.getComputedStyle(elem,null);return!ret||ret.getPropertyValue("color")=="";}if(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(elem.style,"opacity");return ret==""?"1":ret;}if(jQuery.browser.opera&&name=="display"){var save=elem.style.outline;elem.style.outline="0 solid black";elem.style.outline=save;}if(name.match(/float/i))name=styleFloat;if(!force&&elem.style&&elem.style[name])ret=elem.style[name];else if(document.defaultView&&document.defaultView.getComputedStyle){if(name.match(/float/i))name="float";name=name.replace(/([A-Z])/g,"-$1").toLowerCase();var getComputedStyle=document.defaultView.getComputedStyle(elem,null);if(getComputedStyle&&!color(elem))ret=getComputedStyle.getPropertyValue(name);else{var swap=[],stack=[];for(var a=elem;a&&color(a);a=a.parentNode)stack.unshift(a);for(var i=0;i]*?)\/>/g,function(all,front,tag){return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?all:front+">";});var tags=jQuery.trim(elem).toLowerCase(),div=context.createElement("div");var wrap=!tags.indexOf("",""]||!tags.indexOf("",""]||tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"
    ","
    "]||!tags.indexOf("
    "]||(!tags.indexOf("",""]||!tags.indexOf("",""]||jQuery.browser.msie&&[1,"div
    ","
    "]||[0,"",""];div.innerHTML=wrap[1]+elem+wrap[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){var tbody=!tags.indexOf(""&&tags.indexOf("=0;--j)if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length)tbody[j].parentNode.removeChild(tbody[j]);if(/^\s/.test(elem))div.insertBefore(context.createTextNode(elem.match(/^\s*/)[0]),div.firstChild);}elem=jQuery.makeArray(div.childNodes);}if(elem.length===0&&(!jQuery.nodeName(elem,"form")&&!jQuery.nodeName(elem,"select")))return;if(elem[0]==undefined||jQuery.nodeName(elem,"form")||elem.options)ret.push(elem);else +ret=jQuery.merge(ret,elem);});return ret;},attr:function(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return undefined;var fix=jQuery.isXMLDoc(elem)?{}:jQuery.props;if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(fix[name]){if(value!=undefined)elem[fix[name]]=value;return elem[fix[name]];}else if(jQuery.browser.msie&&name=="style")return jQuery.attr(elem.style,"cssText",value);else if(value==undefined&&jQuery.browser.msie&&jQuery.nodeName(elem,"form")&&(name=="action"||name=="method"))return elem.getAttributeNode(name).nodeValue;else if(elem.tagName){if(value!=undefined){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)throw"type property can't be changed";elem.setAttribute(name,""+value);}if(jQuery.browser.msie&&/href|src/.test(name)&&!jQuery.isXMLDoc(elem))return elem.getAttribute(name,2);return elem.getAttribute(name);}else{if(name=="opacity"&&jQuery.browser.msie){if(value!=undefined){elem.zoom=1;elem.filter=(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseFloat(value).toString()=="NaN"?"":"alpha(opacity="+value*100+")");}return elem.filter&&elem.filter.indexOf("opacity=")>=0?(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100).toString():"";}name=name.replace(/-([a-z])/ig,function(all,letter){return letter.toUpperCase();});if(value!=undefined)elem[name]=value;return elem[name];}},trim:function(text){return(text||"").replace(/^\s+|\s+$/g,"");},makeArray:function(array){var ret=[];if(typeof array!="array")for(var i=0,length=array.length;i*",this).remove();while(this.firstChild)this.removeChild(this.firstChild);}},function(name,fn){jQuery.fn[name]=function(){return this.each(fn,arguments);};});jQuery.each(["Height","Width"],function(i,name){var type=name.toLowerCase();jQuery.fn[type]=function(size){return this[0]==window?jQuery.browser.opera&&document.body["client"+name]||jQuery.browser.safari&&window["inner"+name]||document.compatMode=="CSS1Compat"&&document.documentElement["client"+name]||document.body["client"+name]:this[0]==document?Math.max(Math.max(document.body["scroll"+name],document.documentElement["scroll"+name]),Math.max(document.body["offset"+name],document.documentElement["offset"+name])):size==undefined?(this.length?jQuery.css(this[0],type):null):this.css(type,size.constructor==String?size:size+"px");};});var chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars+"*)");jQuery.extend({expr:{"":function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},"#":function(a,i,m){return a.getAttribute("id")==m[2];},":":{lt:function(a,i,m){return im[3]-0;},nth:function(a,i,m){return m[3]-0==i;},eq:function(a,i,m){return m[3]-0==i;},first:function(a,i){return i==0;},last:function(a,i,m,r){return i==r.length-1;},even:function(a,i){return i%2==0;},odd:function(a,i){return i%2;},"first-child":function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},"last-child":function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},"only-child":function(a){return!jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},parent:function(a){return a.firstChild;},empty:function(a){return!a.firstChild;},contains:function(a,i,m){return(a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},visible:function(a){return"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},hidden:function(a){return"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},enabled:function(a){return!a.disabled;},disabled:function(a){return a.disabled;},checked:function(a){return a.checked;},selected:function(a){return a.selected||jQuery.attr(a,"selected");},text:function(a){return"text"==a.type;},radio:function(a){return"radio"==a.type;},checkbox:function(a){return"checkbox"==a.type;},file:function(a){return"file"==a.type;},password:function(a){return"password"==a.type;},submit:function(a){return"submit"==a.type;},image:function(a){return"image"==a.type;},reset:function(a){return"reset"==a.type;},button:function(a){return"button"==a.type||jQuery.nodeName(a,"button");},input:function(a){return/input|select|textarea|button/i.test(a.nodeName);},has:function(a,i,m){return jQuery.find(m[3],a).length;},header:function(a){return/h\d/i.test(a.nodeName);},animated:function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}}},parse:[/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,new RegExp("^([:.#]*)("+chars+"+)")],multiFilter:function(expr,elems,not){var old,cur=[];while(expr&&expr!=old){old=expr;var f=jQuery.filter(expr,elems,not);expr=f.t.replace(/^\s*,\s*/,"");cur=not?elems=f.r:jQuery.merge(cur,f.r);}return cur;},find:function(t,context){if(typeof t!="string")return[t];if(context&&context.nodeType!=1&&context.nodeType!=9)return[];context=context||document;var ret=[context],done=[],last,nodeName;while(t&&last!=t){var r=[];last=t;t=jQuery.trim(t);var foundToken=false;var re=quickChild;var m=re.exec(t);if(m){nodeName=m[1].toUpperCase();for(var i=0;ret[i];i++)for(var c=ret[i].firstChild;c;c=c.nextSibling)if(c.nodeType==1&&(nodeName=="*"||c.nodeName.toUpperCase()==nodeName))r.push(c);ret=r;t=t.replace(re,"");if(t.indexOf(" ")==0)continue;foundToken=true;}else{re=/^([>+~])\s*(\w*)/i;if((m=re.exec(t))!=null){r=[];var merge={};nodeName=m[2].toUpperCase();m=m[1];for(var j=0,rl=ret.length;j=0;if(!not&&pass||not&&!pass)tmp.push(r[i]);}return tmp;},filter:function(t,r,not){var last;while(t&&t!=last){last=t;var p=jQuery.parse,m;for(var i=0;p[i];i++){m=p[i].exec(t);if(m){t=t.substring(m[0].length);m[2]=m[2].replace(/\\/g,"");break;}}if(!m)break;if(m[1]==":"&&m[2]=="not")r=isSimple.test(m[3])?jQuery.filter(m[3],r,true).r:jQuery(r).not(m[3]);else if(m[1]==".")r=jQuery.classFilter(r,m[2],not);else if(m[1]=="["){var tmp=[],type=m[3];for(var i=0,rl=r.length;i=0)^not)tmp.push(a);}r=tmp;}else if(m[1]==":"&&m[2]=="nth-child"){var merge={},tmp=[],test=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[3]=="even"&&"2n"||m[3]=="odd"&&"2n+1"||!/\D/.test(m[3])&&"0n+"+m[3]||m[3]),first=(test[1]+(test[2]||1))-0,last=test[3]-0;for(var i=0,rl=r.length;i=0)add=true;if(add^not)tmp.push(node);}r=tmp;}else{var fn=jQuery.expr[m[1]];if(typeof fn=="object")fn=fn[m[2]];if(typeof fn=="string")fn=eval("false||function(a,i){return "+fn+";}");r=jQuery.grep(r,function(elem,i){return fn(elem,i,m,r);},not);}}return{r:r,t:t};},dir:function(elem,dir){var matched=[];var cur=elem[dir];while(cur&&cur!=document){if(cur.nodeType==1)matched.push(cur);cur=cur[dir];}return matched;},nth:function(cur,result,dir,elem){result=result||1;var num=0;for(;cur;cur=cur[dir])if(cur.nodeType==1&&++num==result)break;return cur;},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType==1&&(!elem||n!=elem))r.push(n);}return r;}});jQuery.event={add:function(elem,types,handler,data){if(elem.nodeType==3||elem.nodeType==8)return;if(jQuery.browser.msie&&elem.setInterval!=undefined)elem=window;if(!handler.guid)handler.guid=this.guid++;if(data!=undefined){var fn=handler;handler=function(){return fn.apply(this,arguments);};handler.data=data;handler.guid=fn.guid;}var events=jQuery.data(elem,"events")||jQuery.data(elem,"events",{}),handle=jQuery.data(elem,"handle")||jQuery.data(elem,"handle",function(){var val;if(typeof jQuery=="undefined"||jQuery.event.triggered)return val;val=jQuery.event.handle.apply(arguments.callee.elem,arguments);return val;});handle.elem=elem;jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];handler.type=parts[1];var handlers=events[type];if(!handlers){handlers=events[type]={};if(!jQuery.event.special[type]||jQuery.event.special[type].setup.call(elem)===false){if(elem.addEventListener)elem.addEventListener(type,handle,false);else if(elem.attachEvent)elem.attachEvent("on"+type,handle);}}handlers[handler.guid]=handler;jQuery.event.global[type]=true;});elem=null;},guid:1,global:{},remove:function(elem,types,handler){if(elem.nodeType==3||elem.nodeType==8)return;var events=jQuery.data(elem,"events"),ret,index;if(events){if(types==undefined||(typeof types=="string"&&types.charAt(0)=="."))for(var type in events)this.remove(elem,type+(types||""));else{if(types.type){handler=types.handler;types=types.type;}jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];if(events[type]){if(handler)delete events[type][handler.guid];else +for(handler in events[type])if(!parts[1]||events[type][handler].type==parts[1])delete events[type][handler];for(ret in events[type])break;if(!ret){if(!jQuery.event.special[type]||jQuery.event.special[type].teardown.call(elem)===false){if(elem.removeEventListener)elem.removeEventListener(type,jQuery.data(elem,"handle"),false);else if(elem.detachEvent)elem.detachEvent("on"+type,jQuery.data(elem,"handle"));}ret=null;delete events[type];}}});}for(ret in events)break;if(!ret){var handle=jQuery.data(elem,"handle");if(handle)handle.elem=null;jQuery.removeData(elem,"events");jQuery.removeData(elem,"handle");}}},trigger:function(type,data,elem,donative,extra){data=jQuery.makeArray(data||[]);if(type.indexOf("!")>=0){type=type.slice(0,-1);var exclusive=true;}if(!elem){if(this.global[type])jQuery("*").add([window,document]).trigger(type,data);}else{if(elem.nodeType==3||elem.nodeType==8)return undefined;var val,ret,fn=jQuery.isFunction(elem[type]||null),event=!data[0]||!data[0].preventDefault;if(event)data.unshift(this.fix({type:type,target:elem}));data[0].type=type;if(exclusive)data[0].exclusive=true;if(jQuery.isFunction(jQuery.data(elem,"handle")))val=jQuery.data(elem,"handle").apply(elem,data);if(!fn&&elem["on"+type]&&elem["on"+type].apply(elem,data)===false)val=false;if(event)data.shift();if(extra&&jQuery.isFunction(extra)){ret=extra.apply(elem,val==null?data:data.concat(val));if(ret!==undefined)val=ret;}if(fn&&donative!==false&&val!==false&&!(jQuery.nodeName(elem,'a')&&type=="click")){this.triggered=true;try{elem[type]();}catch(e){}}this.triggered=false;}return val;},handle:function(event){var val;event=jQuery.event.fix(event||window.event||{});var parts=event.type.split(".");event.type=parts[0];var handlers=jQuery.data(this,"events")&&jQuery.data(this,"events")[event.type],args=Array.prototype.slice.call(arguments,1);args.unshift(event);for(var j in handlers){var handler=handlers[j];args[0].handler=handler;args[0].data=handler.data;if(!parts[1]&&!event.exclusive||handler.type==parts[1]){var ret=handler.apply(this,args);if(val!==false)val=ret;if(ret===false){event.preventDefault();event.stopPropagation();}}}if(jQuery.browser.msie)event.target=event.preventDefault=event.stopPropagation=event.handler=event.data=null;return val;},fix:function(event){var originalEvent=event;event=jQuery.extend({},originalEvent);event.preventDefault=function(){if(originalEvent.preventDefault)originalEvent.preventDefault();originalEvent.returnValue=false;};event.stopPropagation=function(){if(originalEvent.stopPropagation)originalEvent.stopPropagation();originalEvent.cancelBubble=true;};if(!event.target)event.target=event.srcElement||document;if(event.target.nodeType==3)event.target=originalEvent.target.parentNode;if(!event.relatedTarget&&event.fromElement)event.relatedTarget=event.fromElement==event.target?event.toElement:event.fromElement;if(event.pageX==null&&event.clientX!=null){var doc=document.documentElement,body=document.body;event.pageX=event.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc.clientLeft||0);event.pageY=event.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc.clientTop||0);}if(!event.which&&((event.charCode||event.charCode===0)?event.charCode:event.keyCode))event.which=event.charCode||event.keyCode;if(!event.metaKey&&event.ctrlKey)event.metaKey=event.ctrlKey;if(!event.which&&event.button)event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)));return event;},special:{ready:{setup:function(){bindReady();return;},teardown:function(){return;}},mouseenter:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseover",jQuery.event.special.mouseenter.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseover",jQuery.event.special.mouseenter.handler);return true;},handler:function(event){if(withinElement(event,this))return true;arguments[0].type="mouseenter";return jQuery.event.handle.apply(this,arguments);}},mouseleave:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseout",jQuery.event.special.mouseleave.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseout",jQuery.event.special.mouseleave.handler);return true;},handler:function(event){if(withinElement(event,this))return true;arguments[0].type="mouseleave";return jQuery.event.handle.apply(this,arguments);}}}};jQuery.fn.extend({bind:function(type,data,fn){return type=="unload"?this.one(type,data,fn):this.each(function(){jQuery.event.add(this,type,fn||data,fn&&data);});},one:function(type,data,fn){return this.each(function(){jQuery.event.add(this,type,function(event){jQuery(this).unbind(event);return(fn||data).apply(this,arguments);},fn&&data);});},unbind:function(type,fn){return this.each(function(){jQuery.event.remove(this,type,fn);});},trigger:function(type,data,fn){return this.each(function(){jQuery.event.trigger(type,data,this,true,fn);});},triggerHandler:function(type,data,fn){if(this[0])return jQuery.event.trigger(type,data,this[0],false,fn);return undefined;},toggle:function(){var args=arguments;return this.click(function(event){this.lastToggle=0==this.lastToggle?1:0;event.preventDefault();return args[this.lastToggle].apply(this,arguments)||false;});},hover:function(fnOver,fnOut){return this.bind('mouseenter',fnOver).bind('mouseleave',fnOut);},ready:function(fn){bindReady();if(jQuery.isReady)fn.call(document,jQuery);else +jQuery.readyList.push(function(){return fn.call(this,jQuery);});return this;}});jQuery.extend({isReady:false,readyList:[],ready:function(){if(!jQuery.isReady){jQuery.isReady=true;if(jQuery.readyList){jQuery.each(jQuery.readyList,function(){this.apply(document);});jQuery.readyList=null;}jQuery(document).triggerHandler("ready");}}});var readyBound=false;function bindReady(){if(readyBound)return;readyBound=true;if(document.addEventListener&&!jQuery.browser.opera)document.addEventListener("DOMContentLoaded",jQuery.ready,false);if(jQuery.browser.msie&&window==top)(function(){if(jQuery.isReady)return;try{document.documentElement.doScroll("left");}catch(error){setTimeout(arguments.callee,0);return;}jQuery.ready();})();if(jQuery.browser.opera)document.addEventListener("DOMContentLoaded",function(){if(jQuery.isReady)return;for(var i=0;i=0){var selector=url.slice(off,url.length);url=url.slice(0,off);}callback=callback||function(){};var type="GET";if(params)if(jQuery.isFunction(params)){callback=params;params=null;}else{params=jQuery.param(params);type="POST";}var self=this;jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(res,status){if(status=="success"||status=="notmodified")self.html(selector?jQuery("
    ").append(res.responseText.replace(//g,"")).find(selector):res.responseText);self.each(callback,[res.responseText,status,res]);}});return this;},serialize:function(){return jQuery.param(this.serializeArray());},serializeArray:function(){return this.map(function(){return jQuery.nodeName(this,"form")?jQuery.makeArray(this.elements):this;}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type));}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:val.constructor==Array?jQuery.map(val,function(val,i){return{name:elem.name,value:val};}):{name:elem.name,value:val};}).get();}});jQuery.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(i,o){jQuery.fn[o]=function(f){return this.bind(o,f);};});var jsc=(new Date).getTime();jQuery.extend({get:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data=null;}return jQuery.ajax({type:"GET",url:url,data:data,success:callback,dataType:type});},getScript:function(url,callback){return jQuery.get(url,null,callback,"script");},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json");},post:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data={};}return jQuery.ajax({type:"POST",url:url,data:data,success:callback,dataType:type});},ajaxSetup:function(settings){jQuery.extend(jQuery.ajaxSettings,settings);},ajaxSettings:{global:true,type:"GET",timeout:0,contentType:"application/x-www-form-urlencoded",processData:true,async:true,data:null,username:null,password:null,accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(s){var jsonp,jsre=/=\?(&|$)/g,status,data;s=jQuery.extend(true,s,jQuery.extend(true,{},jQuery.ajaxSettings,s));if(s.data&&s.processData&&typeof s.data!="string")s.data=jQuery.param(s.data);if(s.dataType=="jsonp"){if(s.type.toLowerCase()=="get"){if(!s.url.match(jsre))s.url+=(s.url.match(/\?/)?"&":"?")+(s.jsonp||"callback")+"=?";}else if(!s.data||!s.data.match(jsre))s.data=(s.data?s.data+"&":"")+(s.jsonp||"callback")+"=?";s.dataType="json";}if(s.dataType=="json"&&(s.data&&s.data.match(jsre)||s.url.match(jsre))){jsonp="jsonp"+jsc++;if(s.data)s.data=(s.data+"").replace(jsre,"="+jsonp+"$1");s.url=s.url.replace(jsre,"="+jsonp+"$1");s.dataType="script";window[jsonp]=function(tmp){data=tmp;success();complete();window[jsonp]=undefined;try{delete window[jsonp];}catch(e){}if(head)head.removeChild(script);};}if(s.dataType=="script"&&s.cache==null)s.cache=false;if(s.cache===false&&s.type.toLowerCase()=="get"){var ts=(new Date()).getTime();var ret=s.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+ts+"$2");s.url=ret+((ret==s.url)?(s.url.match(/\?/)?"&":"?")+"_="+ts:"");}if(s.data&&s.type.toLowerCase()=="get"){s.url+=(s.url.match(/\?/)?"&":"?")+s.data;s.data=null;}if(s.global&&!jQuery.active++)jQuery.event.trigger("ajaxStart");if((!s.url.indexOf("http")||!s.url.indexOf("//"))&&s.dataType=="script"&&s.type.toLowerCase()=="get"){var head=document.getElementsByTagName("head")[0];var script=document.createElement("script");script.src=s.url;if(s.scriptCharset)script.charset=s.scriptCharset;if(!jsonp){var done=false;script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){done=true;success();complete();head.removeChild(script);}};}head.appendChild(script);return undefined;}var requestDone=false;var xml=window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();xml.open(s.type,s.url,s.async,s.username,s.password);try{if(s.data)xml.setRequestHeader("Content-Type",s.contentType);if(s.ifModified)xml.setRequestHeader("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970 00:00:00 GMT");xml.setRequestHeader("X-Requested-With","XMLHttpRequest");xml.setRequestHeader("Accept",s.dataType&&s.accepts[s.dataType]?s.accepts[s.dataType]+", */*":s.accepts._default);}catch(e){}if(s.beforeSend)s.beforeSend(xml);if(s.global)jQuery.event.trigger("ajaxSend",[xml,s]);var onreadystatechange=function(isTimeout){if(!requestDone&&xml&&(xml.readyState==4||isTimeout=="timeout")){requestDone=true;if(ival){clearInterval(ival);ival=null;}status=isTimeout=="timeout"&&"timeout"||!jQuery.httpSuccess(xml)&&"error"||s.ifModified&&jQuery.httpNotModified(xml,s.url)&&"notmodified"||"success";if(status=="success"){try{data=jQuery.httpData(xml,s.dataType);}catch(e){status="parsererror";}}if(status=="success"){var modRes;try{modRes=xml.getResponseHeader("Last-Modified");}catch(e){}if(s.ifModified&&modRes)jQuery.lastModified[s.url]=modRes;if(!jsonp)success();}else +jQuery.handleError(s,xml,status);complete();if(s.async)xml=null;}};if(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)setTimeout(function(){if(xml){xml.abort();if(!requestDone)onreadystatechange("timeout");}},s.timeout);}try{xml.send(s.data);}catch(e){jQuery.handleError(s,xml,null,e);}if(!s.async)onreadystatechange();function success(){if(s.success)s.success(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xml,s]);}function complete(){if(s.complete)s.complete(xml,status);if(s.global)jQuery.event.trigger("ajaxComplete",[xml,s]);if(s.global&&!--jQuery.active)jQuery.event.trigger("ajaxStop");}return xml;},handleError:function(s,xml,status,e){if(s.error)s.error(xml,status,e);if(s.global)jQuery.event.trigger("ajaxError",[xml,s,e]);},active:0,httpSuccess:function(r){try{return!r.status&&location.protocol=="file:"||(r.status>=200&&r.status<300)||r.status==304||r.status==1223||jQuery.browser.safari&&r.status==undefined;}catch(e){}return false;},httpNotModified:function(xml,url){try{var xmlRes=xml.getResponseHeader("Last-Modified");return xml.status==304||xmlRes==jQuery.lastModified[url]||jQuery.browser.safari&&xml.status==undefined;}catch(e){}return false;},httpData:function(r,type){var ct=r.getResponseHeader("content-type");var xml=type=="xml"||!type&&ct&&ct.indexOf("xml")>=0;var data=xml?r.responseXML:r.responseText;if(xml&&data.documentElement.tagName=="parsererror")throw"parsererror";if(type=="script")jQuery.globalEval(data);if(type=="json")data=eval("("+data+")");return data;},param:function(a){var s=[];if(a.constructor==Array||a.jquery)jQuery.each(a,function(){s.push(encodeURIComponent(this.name)+"="+encodeURIComponent(this.value));});else +for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent(this));});else +s.push(encodeURIComponent(j)+"="+encodeURIComponent(a[j]));return s.join("&").replace(/%20/g,"+");}});jQuery.fn.extend({show:function(speed,callback){return speed?this.animate({height:"show",width:"show",opacity:"show"},speed,callback):this.filter(":hidden").each(function(){this.style.display=this.oldblock||"";if(jQuery.css(this,"display")=="none"){var elem=jQuery("<"+this.tagName+" />").appendTo("body");this.style.display=elem.css("display");if(this.style.display=="none")this.style.display="block";elem.remove();}}).end();},hide:function(speed,callback){return speed?this.animate({height:"hide",width:"hide",opacity:"hide"},speed,callback):this.filter(":visible").each(function(){this.oldblock=this.oldblock||jQuery.css(this,"display");this.style.display="none";}).end();},_toggle:jQuery.fn.toggle,toggle:function(fn,fn2){return jQuery.isFunction(fn)&&jQuery.isFunction(fn2)?this._toggle(fn,fn2):fn?this.animate({height:"toggle",width:"toggle",opacity:"toggle"},fn,fn2):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();});},slideDown:function(speed,callback){return this.animate({height:"show"},speed,callback);},slideUp:function(speed,callback){return this.animate({height:"hide"},speed,callback);},slideToggle:function(speed,callback){return this.animate({height:"toggle"},speed,callback);},fadeIn:function(speed,callback){return this.animate({opacity:"show"},speed,callback);},fadeOut:function(speed,callback){return this.animate({opacity:"hide"},speed,callback);},fadeTo:function(speed,to,callback){return this.animate({opacity:to},speed,callback);},animate:function(prop,speed,easing,callback){var optall=jQuery.speed(speed,easing,callback);return this[optall.queue===false?"each":"queue"](function(){if(this.nodeType!=1)return false;var opt=jQuery.extend({},optall);var hidden=jQuery(this).is(":hidden"),self=this;for(var p in prop){if(prop[p]=="hide"&&hidden||prop[p]=="show"&&!hidden)return jQuery.isFunction(opt.complete)&&opt.complete.apply(this);if(p=="height"||p=="width"){opt.display=jQuery.css(this,"display");opt.overflow=this.style.overflow;}}if(opt.overflow!=null)this.style.overflow="hidden";opt.curAnim=jQuery.extend({},prop);jQuery.each(prop,function(name,val){var e=new jQuery.fx(self,opt,name);if(/toggle|show|hide/.test(val))e[val=="toggle"?hidden?"show":"hide":val](prop);else{var parts=val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),start=e.cur(true)||0;if(parts){var end=parseFloat(parts[2]),unit=parts[3]||"px";if(unit!="px"){self.style[name]=(end||1)+unit;start=((end||1)/e.cur(true))*start;self.style[name]=start+unit;}if(parts[1])end=((parts[1]=="-="?-1:1)*end)+start;e.custom(start,end,unit);}else +e.custom(start,val,"");}});return true;});},queue:function(type,fn){if(jQuery.isFunction(type)||(type&&type.constructor==Array)){fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue(this[0],type);return this.each(function(){if(fn.constructor==Array)queue(this,type,fn);else{queue(this,type).push(fn);if(queue(this,type).length==1)fn.apply(this);}});},stop:function(clearQueue,gotoEnd){var timers=jQuery.timers;if(clearQueue)this.queue([]);this.each(function(){for(var i=timers.length-1;i>=0;i--)if(timers[i].elem==this){if(gotoEnd)timers[i](true);timers.splice(i,1);}});if(!gotoEnd)this.dequeue();return this;}});var queue=function(elem,type,array){if(!elem)return undefined;type=type||"fx";var q=jQuery.data(elem,type+"queue");if(!q||array)q=jQuery.data(elem,type+"queue",array?jQuery.makeArray(array):[]);return q;};jQuery.fn.dequeue=function(type){type=type||"fx";return this.each(function(){var q=queue(this,type);q.shift();if(q.length)q[0].apply(this);});};jQuery.extend({speed:function(speed,easing,fn){var opt=speed&&speed.constructor==Object?speed:{complete:fn||!fn&&easing||jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||easing&&easing.constructor!=Function&&easing};opt.duration=(opt.duration&&opt.duration.constructor==Number?opt.duration:{slow:600,fast:200}[opt.duration])||400;opt.old=opt.complete;opt.complete=function(){if(opt.queue!==false)jQuery(this).dequeue();if(jQuery.isFunction(opt.old))opt.old.apply(this);};return opt;},easing:{linear:function(p,n,firstNum,diff){return firstNum+diff*p;},swing:function(p,n,firstNum,diff){return((-Math.cos(p*Math.PI)/2)+0.5)*diff+firstNum;}},timers:[],timerId:null,fx:function(elem,options,prop){this.options=options;this.elem=elem;this.prop=prop;if(!options.orig)options.orig={};}});jQuery.fx.prototype={update:function(){if(this.options.step)this.options.step.apply(this.elem,[this.now,this]);(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this);if(this.prop=="height"||this.prop=="width")this.elem.style.display="block";},cur:function(force){if(this.elem[this.prop]!=null&&this.elem.style[this.prop]==null)return this.elem[this.prop];var r=parseFloat(jQuery.css(this.elem,this.prop,force));return r&&r>-10000?r:parseFloat(jQuery.curCSS(this.elem,this.prop))||0;},custom:function(from,to,unit){this.startTime=(new Date()).getTime();this.start=from;this.end=to;this.unit=unit||this.unit||"px";this.now=this.start;this.pos=this.state=0;this.update();var self=this;function t(gotoEnd){return self.step(gotoEnd);}t.elem=this.elem;jQuery.timers.push(t);if(jQuery.timerId==null){jQuery.timerId=setInterval(function(){var timers=jQuery.timers;for(var i=0;ithis.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var done=true;for(var i in this.options.curAnim)if(this.options.curAnim[i]!==true)done=false;if(done){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(jQuery.css(this.elem,"display")=="none")this.elem.style.display="block";}if(this.options.hide)this.elem.style.display="none";if(this.options.hide||this.options.show)for(var p in this.options.curAnim)jQuery.attr(this.elem.style,p,this.options.orig[p]);}if(done&&jQuery.isFunction(this.options.complete))this.options.complete.apply(this.elem);return false;}else{var n=t-this.startTime;this.state=n/this.options.duration;this.pos=jQuery.easing[this.options.easing||(jQuery.easing.swing?"swing":"linear")](this.state,n,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update();}return true;}};jQuery.fx.step={scrollLeft:function(fx){fx.elem.scrollLeft=fx.now;},scrollTop:function(fx){fx.elem.scrollTop=fx.now;},opacity:function(fx){jQuery.attr(fx.elem.style,"opacity",fx.now);},_default:function(fx){fx.elem.style[fx.prop]=fx.now+fx.unit;}};jQuery.fn.offset=function(){var left=0,top=0,elem=this[0],results;if(elem)with(jQuery.browser){var parent=elem.parentNode,offsetChild=elem,offsetParent=elem.offsetParent,doc=elem.ownerDocument,safari2=safari&&parseInt(version)<522&&!/adobeair/i.test(userAgent),fixed=jQuery.css(elem,"position")=="fixed";if(elem.getBoundingClientRect){var box=elem.getBoundingClientRect();add(box.left+Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),box.top+Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));add(-doc.documentElement.clientLeft,-doc.documentElement.clientTop);}else{add(elem.offsetLeft,elem.offsetTop);while(offsetParent){add(offsetParent.offsetLeft,offsetParent.offsetTop);if(mozilla&&!/^t(able|d|h)$/i.test(offsetParent.tagName)||safari&&!safari2)border(offsetParent);if(!fixed&&jQuery.css(offsetParent,"position")=="fixed")fixed=true;offsetChild=/^body$/i.test(offsetParent.tagName)?offsetChild:offsetParent;offsetParent=offsetParent.offsetParent;}while(parent&&parent.tagName&&!/^body|html$/i.test(parent.tagName)){if(!/^inline|table.*$/i.test(jQuery.css(parent,"display")))add(-parent.scrollLeft,-parent.scrollTop);if(mozilla&&jQuery.css(parent,"overflow")!="visible")border(parent);parent=parent.parentNode;}if((safari2&&(fixed||jQuery.css(offsetChild,"position")=="absolute"))||(mozilla&&jQuery.css(offsetChild,"position")!="absolute"))add(-doc.body.offsetLeft,-doc.body.offsetTop);if(fixed)add(Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));}results={top:top,left:left};}function border(elem){add(jQuery.curCSS(elem,"borderLeftWidth",true),jQuery.curCSS(elem,"borderTopWidth",true));}function add(l,t){left+=parseInt(l)||0;top+=parseInt(t)||0;}return results;};})(); \ No newline at end of file diff --git a/site/static/main/js/manual.js b/site/static/main/js/manual.js new file mode 100644 index 0000000..87cd7ea --- /dev/null +++ b/site/static/main/js/manual.js @@ -0,0 +1,13 @@ +$(function(){ + $("#showcontents").click(function() { + $("#pcontents").css("display", "block"); + $("#hidecontents").css("display", "inline"); + $("#showcontents").css("display", "none"); + }); + $("#hidecontents").click(function() { + $("#pcontents").css("display", "none"); + $("#showcontents").css("display", "inline"); + $("#hidecontents").css("display", "none"); + }); + $("#hidecontents").css("display", "inline"); +}); diff --git a/site/static/main/js/sidebox.js b/site/static/main/js/sidebox.js new file mode 100644 index 0000000..d5b307d --- /dev/null +++ b/site/static/main/js/sidebox.js @@ -0,0 +1,30 @@ +$(function(){ + $("li#slright span").mouseover(function() { + $("li#slleft").css({ + height: "26px", + backgroundImage: "none" + }); + $("li#slleft div").css("display", "none"); + $("li#slleft span").css("fontWeight", "normal"); + $("li#slright").css({ + height: "425px", + backgroundImage: "url(__STATICURL__/main/pics/mp-tab-2.png)" + }); + $("li#slright span").css("fontWeight", "bold"); + $("li#slright div").css("display", "block"); + }); + $("li#slleft span").mouseover(function() { + $("li#slright").css({ + height: "26px", + backgroundImage: "none" + }); + $("li#slright div").css("display", "none"); + $("li#slright span").css("fontWeight", "normal"); + $("li#slleft").css({ + height: "425px", + backgroundImage: "url(__STATICURL__/main/pics/mp-tab-1.png)" + }); + $("li#slleft span").css("fontWeight", "bold"); + $("li#slleft div").css("display", "block"); + }); +}); diff --git a/site/static/main/pics/mp-content-b0.png b/site/static/main/pics/mp-content-b0.png new file mode 100644 index 0000000..ed18c52 Binary files /dev/null and b/site/static/main/pics/mp-content-b0.png differ diff --git a/site/static/main/pics/mp-content-b1.png b/site/static/main/pics/mp-content-b1.png new file mode 100644 index 0000000..c62487e Binary files /dev/null and b/site/static/main/pics/mp-content-b1.png differ diff --git a/site/static/main/pics/mp-content-b2.png b/site/static/main/pics/mp-content-b2.png new file mode 100644 index 0000000..2ffec4f Binary files /dev/null and b/site/static/main/pics/mp-content-b2.png differ diff --git a/site/static/main/pics/mp-content-b3.png b/site/static/main/pics/mp-content-b3.png new file mode 100644 index 0000000..f35a722 Binary files /dev/null and b/site/static/main/pics/mp-content-b3.png differ diff --git a/site/static/main/pics/mp-content-b4.png b/site/static/main/pics/mp-content-b4.png new file mode 100644 index 0000000..e2c9548 Binary files /dev/null and b/site/static/main/pics/mp-content-b4.png differ diff --git a/site/static/main/pics/mp-content-b5.png b/site/static/main/pics/mp-content-b5.png new file mode 100644 index 0000000..92560ea Binary files /dev/null and b/site/static/main/pics/mp-content-b5.png differ diff --git a/site/static/main/pics/mp-content-b6.png b/site/static/main/pics/mp-content-b6.png new file mode 100644 index 0000000..81a8a92 Binary files /dev/null and b/site/static/main/pics/mp-content-b6.png differ diff --git a/site/static/main/pics/mp-content.jpg b/site/static/main/pics/mp-content.jpg new file mode 100644 index 0000000..b5bd6df Binary files /dev/null and b/site/static/main/pics/mp-content.jpg differ diff --git a/site/static/main/pics/mp-tab-1.png b/site/static/main/pics/mp-tab-1.png new file mode 100644 index 0000000..1355e11 Binary files /dev/null and b/site/static/main/pics/mp-tab-1.png differ diff --git a/site/static/main/pics/mp-tab-2.png b/site/static/main/pics/mp-tab-2.png new file mode 100644 index 0000000..e132fa2 Binary files /dev/null and b/site/static/main/pics/mp-tab-2.png differ diff --git a/site/static/main/pics/mp-title-b0.png b/site/static/main/pics/mp-title-b0.png new file mode 100644 index 0000000..5b48209 Binary files /dev/null and b/site/static/main/pics/mp-title-b0.png differ diff --git a/site/static/main/pics/mp-title-b1.png b/site/static/main/pics/mp-title-b1.png new file mode 100644 index 0000000..052e8b0 Binary files /dev/null and b/site/static/main/pics/mp-title-b1.png differ diff --git a/site/static/main/pics/mp-title-b2.png b/site/static/main/pics/mp-title-b2.png new file mode 100644 index 0000000..54b6248 Binary files /dev/null and b/site/static/main/pics/mp-title-b2.png differ diff --git a/site/static/main/pics/mp-title-b3.png b/site/static/main/pics/mp-title-b3.png new file mode 100644 index 0000000..ed55a26 Binary files /dev/null and b/site/static/main/pics/mp-title-b3.png differ diff --git a/site/static/main/pics/mp-title-b4.png b/site/static/main/pics/mp-title-b4.png new file mode 100644 index 0000000..cabb179 Binary files /dev/null and b/site/static/main/pics/mp-title-b4.png differ diff --git a/site/static/main/pics/mp-title-b5.png b/site/static/main/pics/mp-title-b5.png new file mode 100644 index 0000000..5ea749e Binary files /dev/null and b/site/static/main/pics/mp-title-b5.png differ diff --git a/site/static/main/pics/mp-title-b6.png b/site/static/main/pics/mp-title-b6.png new file mode 100644 index 0000000..79e9b23 Binary files /dev/null and b/site/static/main/pics/mp-title-b6.png differ diff --git a/site/static/main/pics/mp-title.jpg b/site/static/main/pics/mp-title.jpg new file mode 100644 index 0000000..37e291a Binary files /dev/null and b/site/static/main/pics/mp-title.jpg differ diff --git a/site/static/main/pics/smiles/icon_arrow.gif b/site/static/main/pics/smiles/icon_arrow.gif new file mode 100644 index 0000000..2880055 Binary files /dev/null and b/site/static/main/pics/smiles/icon_arrow.gif differ diff --git a/site/static/main/pics/smiles/icon_biggrin.gif b/site/static/main/pics/smiles/icon_biggrin.gif new file mode 100644 index 0000000..d352772 Binary files /dev/null and b/site/static/main/pics/smiles/icon_biggrin.gif differ diff --git a/site/static/main/pics/smiles/icon_confused.gif b/site/static/main/pics/smiles/icon_confused.gif new file mode 100644 index 0000000..0c49e06 Binary files /dev/null and b/site/static/main/pics/smiles/icon_confused.gif differ diff --git a/site/static/main/pics/smiles/icon_cool.gif b/site/static/main/pics/smiles/icon_cool.gif new file mode 100644 index 0000000..cead030 Binary files /dev/null and b/site/static/main/pics/smiles/icon_cool.gif differ diff --git a/site/static/main/pics/smiles/icon_cry.gif b/site/static/main/pics/smiles/icon_cry.gif new file mode 100644 index 0000000..7d54b1f Binary files /dev/null and b/site/static/main/pics/smiles/icon_cry.gif differ diff --git a/site/static/main/pics/smiles/icon_eek.gif b/site/static/main/pics/smiles/icon_eek.gif new file mode 100644 index 0000000..5d39781 Binary files /dev/null and b/site/static/main/pics/smiles/icon_eek.gif differ diff --git a/site/static/main/pics/smiles/icon_evil.gif b/site/static/main/pics/smiles/icon_evil.gif new file mode 100644 index 0000000..ab1aa8e Binary files /dev/null and b/site/static/main/pics/smiles/icon_evil.gif differ diff --git a/site/static/main/pics/smiles/icon_exclaim.gif b/site/static/main/pics/smiles/icon_exclaim.gif new file mode 100644 index 0000000..6e50e2e Binary files /dev/null and b/site/static/main/pics/smiles/icon_exclaim.gif differ diff --git a/site/static/main/pics/smiles/icon_frown.gif b/site/static/main/pics/smiles/icon_frown.gif new file mode 100644 index 0000000..d2ac78c Binary files /dev/null and b/site/static/main/pics/smiles/icon_frown.gif differ diff --git a/site/static/main/pics/smiles/icon_idea.gif b/site/static/main/pics/smiles/icon_idea.gif new file mode 100644 index 0000000..a40ae0d Binary files /dev/null and b/site/static/main/pics/smiles/icon_idea.gif differ diff --git a/site/static/main/pics/smiles/icon_lol.gif b/site/static/main/pics/smiles/icon_lol.gif new file mode 100644 index 0000000..374ba15 Binary files /dev/null and b/site/static/main/pics/smiles/icon_lol.gif differ diff --git a/site/static/main/pics/smiles/icon_mad.gif b/site/static/main/pics/smiles/icon_mad.gif new file mode 100644 index 0000000..1f6c3c2 Binary files /dev/null and b/site/static/main/pics/smiles/icon_mad.gif differ diff --git a/site/static/main/pics/smiles/icon_mrgreen.gif b/site/static/main/pics/smiles/icon_mrgreen.gif new file mode 100644 index 0000000..b54cd0f Binary files /dev/null and b/site/static/main/pics/smiles/icon_mrgreen.gif differ diff --git a/site/static/main/pics/smiles/icon_neutral.gif b/site/static/main/pics/smiles/icon_neutral.gif new file mode 100644 index 0000000..4f31156 Binary files /dev/null and b/site/static/main/pics/smiles/icon_neutral.gif differ diff --git a/site/static/main/pics/smiles/icon_question.gif b/site/static/main/pics/smiles/icon_question.gif new file mode 100644 index 0000000..9d07226 Binary files /dev/null and b/site/static/main/pics/smiles/icon_question.gif differ diff --git a/site/static/main/pics/smiles/icon_razz.gif b/site/static/main/pics/smiles/icon_razz.gif new file mode 100644 index 0000000..29da2a2 Binary files /dev/null and b/site/static/main/pics/smiles/icon_razz.gif differ diff --git a/site/static/main/pics/smiles/icon_redface.gif b/site/static/main/pics/smiles/icon_redface.gif new file mode 100644 index 0000000..ad76283 Binary files /dev/null and b/site/static/main/pics/smiles/icon_redface.gif differ diff --git a/site/static/main/pics/smiles/icon_rolleyes.gif b/site/static/main/pics/smiles/icon_rolleyes.gif new file mode 100644 index 0000000..d7f5f2f Binary files /dev/null and b/site/static/main/pics/smiles/icon_rolleyes.gif differ diff --git a/site/static/main/pics/smiles/icon_sad.gif b/site/static/main/pics/smiles/icon_sad.gif new file mode 100644 index 0000000..d2ac78c Binary files /dev/null and b/site/static/main/pics/smiles/icon_sad.gif differ diff --git a/site/static/main/pics/smiles/icon_smile.gif b/site/static/main/pics/smiles/icon_smile.gif new file mode 100644 index 0000000..7b1f6d3 Binary files /dev/null and b/site/static/main/pics/smiles/icon_smile.gif differ diff --git a/site/static/main/pics/smiles/icon_surprised.gif b/site/static/main/pics/smiles/icon_surprised.gif new file mode 100644 index 0000000..cb21424 Binary files /dev/null and b/site/static/main/pics/smiles/icon_surprised.gif differ diff --git a/site/static/main/pics/smiles/icon_twisted.gif b/site/static/main/pics/smiles/icon_twisted.gif new file mode 100644 index 0000000..502fe24 Binary files /dev/null and b/site/static/main/pics/smiles/icon_twisted.gif differ diff --git a/site/static/main/pics/smiles/icon_wink.gif b/site/static/main/pics/smiles/icon_wink.gif new file mode 100644 index 0000000..d148288 Binary files /dev/null and b/site/static/main/pics/smiles/icon_wink.gif differ diff --git a/site/static/main/screens/b4-fleets-m.jpg b/site/static/main/screens/b4-fleets-m.jpg new file mode 100644 index 0000000..8250841 Binary files /dev/null and b/site/static/main/screens/b4-fleets-m.jpg differ diff --git a/site/static/main/screens/b4-fleets-s.jpg b/site/static/main/screens/b4-fleets-s.jpg new file mode 100644 index 0000000..f38990e Binary files /dev/null and b/site/static/main/screens/b4-fleets-s.jpg differ diff --git a/site/static/main/screens/b4-fleets.jpg b/site/static/main/screens/b4-fleets.jpg new file mode 100644 index 0000000..3e246e3 Binary files /dev/null and b/site/static/main/screens/b4-fleets.jpg differ diff --git a/site/static/main/screens/b4-map-m.jpg b/site/static/main/screens/b4-map-m.jpg new file mode 100644 index 0000000..cd0c53a Binary files /dev/null and b/site/static/main/screens/b4-map-m.jpg differ diff --git a/site/static/main/screens/b4-map-s.jpg b/site/static/main/screens/b4-map-s.jpg new file mode 100644 index 0000000..ce2a9b9 Binary files /dev/null and b/site/static/main/screens/b4-map-s.jpg differ diff --git a/site/static/main/screens/b4-map.jpg b/site/static/main/screens/b4-map.jpg new file mode 100644 index 0000000..1df1335 Binary files /dev/null and b/site/static/main/screens/b4-map.jpg differ diff --git a/site/static/main/screens/b4-money-m.jpg b/site/static/main/screens/b4-money-m.jpg new file mode 100644 index 0000000..f68436b Binary files /dev/null and b/site/static/main/screens/b4-money-m.jpg differ diff --git a/site/static/main/screens/b4-money-s.jpg b/site/static/main/screens/b4-money-s.jpg new file mode 100644 index 0000000..5175932 Binary files /dev/null and b/site/static/main/screens/b4-money-s.jpg differ diff --git a/site/static/main/screens/b4-money.jpg b/site/static/main/screens/b4-money.jpg new file mode 100644 index 0000000..c92c77d Binary files /dev/null and b/site/static/main/screens/b4-money.jpg differ diff --git a/site/static/main/screens/b4-ov-m.jpg b/site/static/main/screens/b4-ov-m.jpg new file mode 100644 index 0000000..8517c02 Binary files /dev/null and b/site/static/main/screens/b4-ov-m.jpg differ diff --git a/site/static/main/screens/b4-ov-s.jpg b/site/static/main/screens/b4-ov-s.jpg new file mode 100644 index 0000000..fa353af Binary files /dev/null and b/site/static/main/screens/b4-ov-s.jpg differ diff --git a/site/static/main/screens/b4-ov.jpg b/site/static/main/screens/b4-ov.jpg new file mode 100644 index 0000000..abf56db Binary files /dev/null and b/site/static/main/screens/b4-ov.jpg differ diff --git a/site/static/main/screens/b4-planets-m.jpg b/site/static/main/screens/b4-planets-m.jpg new file mode 100644 index 0000000..cb5c503 Binary files /dev/null and b/site/static/main/screens/b4-planets-m.jpg differ diff --git a/site/static/main/screens/b4-planets-s.jpg b/site/static/main/screens/b4-planets-s.jpg new file mode 100644 index 0000000..28882a8 Binary files /dev/null and b/site/static/main/screens/b4-planets-s.jpg differ diff --git a/site/static/main/screens/b4-planets.jpg b/site/static/main/screens/b4-planets.jpg new file mode 100644 index 0000000..37eee8c Binary files /dev/null and b/site/static/main/screens/b4-planets.jpg differ diff --git a/site/static/main/screens/b4-ranking-m.jpg b/site/static/main/screens/b4-ranking-m.jpg new file mode 100644 index 0000000..07c1a99 Binary files /dev/null and b/site/static/main/screens/b4-ranking-m.jpg differ diff --git a/site/static/main/screens/b4-ranking-s.jpg b/site/static/main/screens/b4-ranking-s.jpg new file mode 100644 index 0000000..c8ab8bc Binary files /dev/null and b/site/static/main/screens/b4-ranking-s.jpg differ diff --git a/site/static/main/screens/b4-ranking.jpg b/site/static/main/screens/b4-ranking.jpg new file mode 100644 index 0000000..3e1c08b Binary files /dev/null and b/site/static/main/screens/b4-ranking.jpg differ diff --git a/site/static/main/screens/b4-ticks-m.jpg b/site/static/main/screens/b4-ticks-m.jpg new file mode 100644 index 0000000..266bd43 Binary files /dev/null and b/site/static/main/screens/b4-ticks-m.jpg differ diff --git a/site/static/main/screens/b4-ticks-s.jpg b/site/static/main/screens/b4-ticks-s.jpg new file mode 100644 index 0000000..538c754 Binary files /dev/null and b/site/static/main/screens/b4-ticks-s.jpg differ diff --git a/site/static/main/screens/b4-ticks.jpg b/site/static/main/screens/b4-ticks.jpg new file mode 100644 index 0000000..4911d97 Binary files /dev/null and b/site/static/main/screens/b4-ticks.jpg differ diff --git a/site/static/main/screens/b5-allies-m.jpg b/site/static/main/screens/b5-allies-m.jpg new file mode 100644 index 0000000..63f1a5e Binary files /dev/null and b/site/static/main/screens/b5-allies-m.jpg differ diff --git a/site/static/main/screens/b5-allies-s.jpg b/site/static/main/screens/b5-allies-s.jpg new file mode 100644 index 0000000..74c02b7 Binary files /dev/null and b/site/static/main/screens/b5-allies-s.jpg differ diff --git a/site/static/main/screens/b5-allies.jpg b/site/static/main/screens/b5-allies.jpg new file mode 100644 index 0000000..fd0e1fb Binary files /dev/null and b/site/static/main/screens/b5-allies.jpg differ diff --git a/site/static/main/screens/b5-fleets-m.jpg b/site/static/main/screens/b5-fleets-m.jpg new file mode 100644 index 0000000..64a9499 Binary files /dev/null and b/site/static/main/screens/b5-fleets-m.jpg differ diff --git a/site/static/main/screens/b5-fleets-s.jpg b/site/static/main/screens/b5-fleets-s.jpg new file mode 100644 index 0000000..879f0b8 Binary files /dev/null and b/site/static/main/screens/b5-fleets-s.jpg differ diff --git a/site/static/main/screens/b5-fleets.jpg b/site/static/main/screens/b5-fleets.jpg new file mode 100644 index 0000000..b94db3a Binary files /dev/null and b/site/static/main/screens/b5-fleets.jpg differ diff --git a/site/static/main/screens/b5-manual-m.jpg b/site/static/main/screens/b5-manual-m.jpg new file mode 100644 index 0000000..cf61b11 Binary files /dev/null and b/site/static/main/screens/b5-manual-m.jpg differ diff --git a/site/static/main/screens/b5-manual-s.jpg b/site/static/main/screens/b5-manual-s.jpg new file mode 100644 index 0000000..9f95696 Binary files /dev/null and b/site/static/main/screens/b5-manual-s.jpg differ diff --git a/site/static/main/screens/b5-manual.jpg b/site/static/main/screens/b5-manual.jpg new file mode 100644 index 0000000..7c54fc7 Binary files /dev/null and b/site/static/main/screens/b5-manual.jpg differ diff --git a/site/static/main/screens/b5-map-m.jpg b/site/static/main/screens/b5-map-m.jpg new file mode 100644 index 0000000..f2279a2 Binary files /dev/null and b/site/static/main/screens/b5-map-m.jpg differ diff --git a/site/static/main/screens/b5-map-s.jpg b/site/static/main/screens/b5-map-s.jpg new file mode 100644 index 0000000..982524f Binary files /dev/null and b/site/static/main/screens/b5-map-s.jpg differ diff --git a/site/static/main/screens/b5-map.jpg b/site/static/main/screens/b5-map.jpg new file mode 100644 index 0000000..4693272 Binary files /dev/null and b/site/static/main/screens/b5-map.jpg differ diff --git a/site/static/main/screens/b5-market-m.jpg b/site/static/main/screens/b5-market-m.jpg new file mode 100644 index 0000000..f19d75f Binary files /dev/null and b/site/static/main/screens/b5-market-m.jpg differ diff --git a/site/static/main/screens/b5-market-s.jpg b/site/static/main/screens/b5-market-s.jpg new file mode 100644 index 0000000..39083ab Binary files /dev/null and b/site/static/main/screens/b5-market-s.jpg differ diff --git a/site/static/main/screens/b5-market.jpg b/site/static/main/screens/b5-market.jpg new file mode 100644 index 0000000..0d6d652 Binary files /dev/null and b/site/static/main/screens/b5-market.jpg differ diff --git a/site/static/main/screens/b5-messages-m.jpg b/site/static/main/screens/b5-messages-m.jpg new file mode 100644 index 0000000..e6d0c69 Binary files /dev/null and b/site/static/main/screens/b5-messages-m.jpg differ diff --git a/site/static/main/screens/b5-messages-s.jpg b/site/static/main/screens/b5-messages-s.jpg new file mode 100644 index 0000000..a2dc3a0 Binary files /dev/null and b/site/static/main/screens/b5-messages-s.jpg differ diff --git a/site/static/main/screens/b5-messages.jpg b/site/static/main/screens/b5-messages.jpg new file mode 100644 index 0000000..b8eda76 Binary files /dev/null and b/site/static/main/screens/b5-messages.jpg differ diff --git a/site/static/main/screens/b5-money-m.jpg b/site/static/main/screens/b5-money-m.jpg new file mode 100644 index 0000000..58e69c5 Binary files /dev/null and b/site/static/main/screens/b5-money-m.jpg differ diff --git a/site/static/main/screens/b5-money-s.jpg b/site/static/main/screens/b5-money-s.jpg new file mode 100644 index 0000000..f390f74 Binary files /dev/null and b/site/static/main/screens/b5-money-s.jpg differ diff --git a/site/static/main/screens/b5-money.jpg b/site/static/main/screens/b5-money.jpg new file mode 100644 index 0000000..e1ba5ef Binary files /dev/null and b/site/static/main/screens/b5-money.jpg differ diff --git a/site/static/main/screens/b5-ov-m.jpg b/site/static/main/screens/b5-ov-m.jpg new file mode 100644 index 0000000..1d7430d Binary files /dev/null and b/site/static/main/screens/b5-ov-m.jpg differ diff --git a/site/static/main/screens/b5-ov-s.jpg b/site/static/main/screens/b5-ov-s.jpg new file mode 100644 index 0000000..f2230be Binary files /dev/null and b/site/static/main/screens/b5-ov-s.jpg differ diff --git a/site/static/main/screens/b5-ov.jpg b/site/static/main/screens/b5-ov.jpg new file mode 100644 index 0000000..5b5442a Binary files /dev/null and b/site/static/main/screens/b5-ov.jpg differ diff --git a/site/static/main/screens/b5-planet-m.jpg b/site/static/main/screens/b5-planet-m.jpg new file mode 100644 index 0000000..c9e4b55 Binary files /dev/null and b/site/static/main/screens/b5-planet-m.jpg differ diff --git a/site/static/main/screens/b5-planet-s.jpg b/site/static/main/screens/b5-planet-s.jpg new file mode 100644 index 0000000..fd7c721 Binary files /dev/null and b/site/static/main/screens/b5-planet-s.jpg differ diff --git a/site/static/main/screens/b5-planet.jpg b/site/static/main/screens/b5-planet.jpg new file mode 100644 index 0000000..2fd3f70 Binary files /dev/null and b/site/static/main/screens/b5-planet.jpg differ diff --git a/site/static/main/screens/b5-planets-m.jpg b/site/static/main/screens/b5-planets-m.jpg new file mode 100644 index 0000000..c64c2f8 Binary files /dev/null and b/site/static/main/screens/b5-planets-m.jpg differ diff --git a/site/static/main/screens/b5-planets-s.jpg b/site/static/main/screens/b5-planets-s.jpg new file mode 100644 index 0000000..693026e Binary files /dev/null and b/site/static/main/screens/b5-planets-s.jpg differ diff --git a/site/static/main/screens/b5-planets.jpg b/site/static/main/screens/b5-planets.jpg new file mode 100644 index 0000000..3e82865 Binary files /dev/null and b/site/static/main/screens/b5-planets.jpg differ diff --git a/site/static/main/screens/b5-ranking-m.jpg b/site/static/main/screens/b5-ranking-m.jpg new file mode 100644 index 0000000..b51f1a4 Binary files /dev/null and b/site/static/main/screens/b5-ranking-m.jpg differ diff --git a/site/static/main/screens/b5-ranking-s.jpg b/site/static/main/screens/b5-ranking-s.jpg new file mode 100644 index 0000000..845bf9c Binary files /dev/null and b/site/static/main/screens/b5-ranking-s.jpg differ diff --git a/site/static/main/screens/b5-ranking.jpg b/site/static/main/screens/b5-ranking.jpg new file mode 100644 index 0000000..a8b14ec Binary files /dev/null and b/site/static/main/screens/b5-ranking.jpg differ diff --git a/site/static/main/screens/b5-research-m.jpg b/site/static/main/screens/b5-research-m.jpg new file mode 100644 index 0000000..efd676b Binary files /dev/null and b/site/static/main/screens/b5-research-m.jpg differ diff --git a/site/static/main/screens/b5-research-s.jpg b/site/static/main/screens/b5-research-s.jpg new file mode 100644 index 0000000..ba8de6c Binary files /dev/null and b/site/static/main/screens/b5-research-s.jpg differ diff --git a/site/static/main/screens/b5-research.jpg b/site/static/main/screens/b5-research.jpg new file mode 100644 index 0000000..11af5c8 Binary files /dev/null and b/site/static/main/screens/b5-research.jpg differ diff --git a/sql/00-init.sql b/sql/00-init.sql new file mode 100644 index 0000000..c7b121b --- /dev/null +++ b/sql/00-init.sql @@ -0,0 +1,70 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 00-init.sql +-- +-- Initialises the various roles and the database itself +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- Connect to the main system database +\c postgres postgres +SET search_path=public; + +-- Create the LW users +CREATE ROLE legacyworlds WITH LOGIN ENCRYPTED PASSWORD 'main user password'; +CREATE ROLE legacyworlds_admin WITH LOGIN ENCRYPTED PASSWORD 'administration user password' CONNECTION LIMIT 2; + +-- Create the database +CREATE DATABASE legacyworlds WITH OWNER=legacyworlds_admin ENCODING='UTF8'; + + + +-- Connect to the LW database with the PostgreSQL admin user +\c legacyworlds postgres + +-- Register PL/PgSQL's handler function +CREATE FUNCTION plpgsql_call_handler() RETURNS language_handler AS + '$libdir/plpgsql' LANGUAGE C; + +-- Register PL/PgSQL's validator function +CREATE FUNCTION plpgsql_validator(oid) RETURNS void AS + '$libdir/plpgsql' LANGUAGE C; + +-- Register PL/PgSQL +CREATE TRUSTED PROCEDURAL LANGUAGE plpgsql + HANDLER plpgsql_call_handler + VALIDATOR plpgsql_validator; + + + +-- Connect to the LW database with the LW admin user +\c legacyworlds legacyworlds_admin + + +-- +-- The following function returns the last inserted identifier for some table. +-- +CREATE OR REPLACE FUNCTION last_inserted(tbl NAME) RETURNS BIGINT AS $$ +DECLARE + i BIGINT; +BEGIN + SELECT INTO i currval(tbl || '_id_seq'); + RETURN i; +EXCEPTION +WHEN undefined_table OR object_not_in_prerequisite_state THEN + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +-- +-- UNIX_TIMESPAMP() function for MySQL compatibility +-- +CREATE OR REPLACE FUNCTION unix_timestamp(t TIMESTAMP WITH TIME ZONE) RETURNS INT AS $$ + SELECT CAST(EXTRACT(EPOCH FROM $1) AS INT); +$$ LANGUAGE SQL; +CREATE OR REPLACE FUNCTION unix_timestamp(t TIMESTAMP WITHOUT TIME ZONE) RETURNS INT AS $$ + SELECT CAST(EXTRACT(EPOCH FROM $1) AS INT); +$$ LANGUAGE SQL; diff --git a/sql/01-inheritance.sql b/sql/01-inheritance.sql new file mode 100644 index 0000000..a1d254c --- /dev/null +++ b/sql/01-inheritance.sql @@ -0,0 +1,1373 @@ +-- -------------------------------------------------------------------------- +-- +-- PostgreSQL Extended Inheritance Library +-- +-- +-- This library contains a set of functions that extend PostgreSQL's table +-- inheritance in order to work around some of the model's limitations: it +-- propagates indexes, constraints, foreign keys and triggers, ensures +-- uniqueness for primary and unique key in the whole hierarchy, and allows +-- foreign keys referencing tables with children to reference both elements +-- of the referenced tables AND elements defined in child tables. +-- +-- +-- A DeepClone Development/Nocternity project +-- Copyright(C) 2007, E. Benoit +-- +-- -------------------------------------------------------------------------- + + +-- +-- Create the inheritance schema in which the various system tables +-- and related functions will reside. +-- +CREATE SCHEMA inheritance; + + +-- -------------------------------------------------------------------------- +-- PUBLIC FUNCTIONS +-- -------------------------------------------------------------------------- + +-- +-- Function that adds a table to be ignored +-- +CREATE OR REPLACE FUNCTION inheritance.ignore_table(NAME, NAME) RETURNS BOOLEAN AS $$ +BEGIN + INSERT INTO inheritance.ignore(ign_schema,ign_table) VALUES($1, $2); + RETURN TRUE; +EXCEPTION WHEN unique_violation THEN + RETURN FALSE; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Adds a schema to be handled to the list of schemas +-- +CREATE OR REPLACE FUNCTION inheritance.add_schema(NAME) RETURNS BOOLEAN AS $$ +BEGIN + INSERT INTO inheritance.schemas (sch_name) VALUES ($1); + RETURN TRUE; +EXCEPTION WHEN unique_violation THEN + RETURN FALSE; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Register a foreign key that can't be handled using built-in +-- SQL because of indexes not being propagated automatically. +-- +-- Usage: SELECT inheritance.foreign_key('"namespace".src_table', '"field1",field2', 'namespace."dst_table"', 'dest1,dest2', 'CASCADE', 'SET NULL'); +-- +CREATE OR REPLACE FUNCTION inheritance.foreign_key(on_table TEXT, src_fields TEXT, to_table TEXT, dst_fields TEXT, on_update TEXT, on_delete TEXT) RETURNS VOID AS $$ +DECLARE + f_ns NAME; + f_tb NAME; + f_fl NAME[]; + t_ns NAME; + t_tb NAME; + t_fl NAME[]; +BEGIN + SELECT INTO f_ns, f_tb * FROM inheritance.split_table_name(on_table); + SELECT INTO t_ns, t_tb * FROM inheritance.split_table_name(to_table); + + SELECT INTO f_fl * FROM inheritance.split_field_list(src_fields); + SELECT INTO t_fl * FROM inheritance.split_field_list(dst_fields); + + INSERT INTO inheritance.manual_fkeys(mfk_from_ns,mfk_from_table,mfk_from_fields,mfk_to_ns,mfk_to_table,mfk_to_fields,mfk_on_update,mfk_on_delete) + VALUES (f_ns, f_tb, f_fl, t_ns, t_tb, t_fl, upper(on_update), upper(on_delete)); +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that handles the whole thing +-- +CREATE OR REPLACE FUNCTION inheritance.init() RETURNS VOID AS $$ +BEGIN + -- Destroy any existing data + PERFORM inheritance.kill(); + + -- Update the table cache + PERFORM inheritance.update_table_cache(); + + -- Read existing constraints, indexes and triggers on tables + PERFORM inheritance.cache_initial_triggers(); + PERFORM inheritance.cache_initial_indexes(); + PERFORM inheritance.cache_initial_constraints(); + + -- Propagate constraints, indexes and triggers + PERFORM inheritance.propagate(); + + -- Handle foreign keys + PERFORM inheritance.handle_foreign_keys(); +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that removes everything the inheritance code did previously +-- This function should be called before proceeding to any modifications +-- on a database's existing structure or before updating the inheritance +-- system. +-- +CREATE OR REPLACE FUNCTION inheritance.kill() RETURNS VOID AS $$ +BEGIN + -- Flush foreign keys + DELETE FROM inheritance.fk_cache; + -- Flush the trigger cache + DELETE FROM inheritance.tgr_cache; + -- Destroy the constraint cache + DELETE FROM inheritance.con_cache; + -- Destroy the index cache + DELETE FROM inheritance.idx_cache; + -- Destroy the inheritance cache + DELETE FROM inheritance.tbl_cache; + -- Destroy primary key / unique key handlers + PERFORM inheritance.destroy_unique_functions(); +END; +$$ LANGUAGE plpgsql; + + +-- -------------------------------------------------------------------------- + + + +-- -------------------------------------------------------------------------- +-- TABLES USED INTERNALLY FOR CACHING PURPOSES +-- -------------------------------------------------------------------------- + + +-- +-- Create the table that lists tables to be ignored +-- +CREATE TABLE inheritance.ignore ( + ign_schema NAME NOT NULL, + ign_table NAME NOT NULL, + ign_oid OID, + PRIMARY KEY(ign_schema,ign_table) +); + + +-- +-- Table that lists the schemas to be considered when handling inherited tables +-- +CREATE TABLE inheritance.schemas ( + sch_name NAME PRIMARY KEY +); + + +-- +-- Table that will contain the inheritance cache +-- +CREATE TABLE inheritance.tbl_cache ( + tch_parent OID NOT NULL, + tch_child OID NOT NULL, + tch_direct BOOLEAN NOT NULL, + PRIMARY KEY(tch_parent,tch_child,tch_direct) +); + + +-- +-- Table that will cache constraints on tables the inheritance code handles +-- +CREATE TABLE inheritance.con_cache ( + cch_table OID NOT NULL, + cch_constraint OID NOT NULL, + cch_inherited BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY(cch_table, cch_constraint) +); + + +-- +-- Table that will list functions created in order to handle primary keys +-- or unique keys +-- +CREATE TABLE inheritance.ufn_cache ( + uch_fname NAME NOT NULL PRIMARY KEY +); + + +-- +-- Table that will list indexes that have been propagated from base tables; +-- it also lists indexes that are present from the beginning with the +-- ich_inherited field set to FALSE. +-- +CREATE TABLE inheritance.idx_cache ( + ich_table OID NOT NULL, + ich_index OID NOT NULL, + ich_inherited BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY(ich_table, ich_index) +); + + +-- +-- Table that will list both initial triggers and triggers added by the +-- inheritance code. +-- +CREATE TABLE inheritance.tgr_cache ( + tch_table OID NOT NULL, + tch_trigger OID NOT NULL, + tch_initial BOOLEAN NOT NULL DEFAULT TRUE, + PRIMARY KEY(tch_table, tch_trigger) +); + + +-- +-- Table that stores the list of supported foreign key actions +-- +CREATE TABLE inheritance.fk_actions ( + txt VARCHAR(9) NOT NULL UNIQUE, + ch CHAR NOT NULL UNIQUE +); +COPY inheritance.fk_actions FROM STDIN; +CASCADE c +SET NULL n +NO ACTION a +\. + + +-- +-- Table to store "foreign keys" to child tables which can't be created +-- with the REFERENCES keyword due to the fact that child tables have +-- no indexes when they are created (unless these indexes are added +-- manually, which we don't want). +-- +CREATE TABLE inheritance.manual_fkeys ( + mfk_from_ns NAME NOT NULL, + mfk_from_table NAME NOT NULL, + mfk_from_fields NAME[] NOT NULL CHECK(mfk_from_fields <> '{}'), + mfk_to_ns NAME NOT NULL, + mfk_to_table NAME NOT NULL, + mfk_to_fields NAME[] NOT NULL CHECK(mfk_to_fields <> '{}'), + mfk_on_update VARCHAR(9) NOT NULL REFERENCES inheritance.fk_actions (txt), + mfk_on_delete VARCHAR(9) NOT NULL REFERENCES inheritance.fk_actions (txt), + PRIMARY KEY(mfk_from_ns,mfk_from_table,mfk_from_fields), + CHECK(array_dims(mfk_from_fields) = array_dims(mfk_to_fields)) +); + + +-- +-- Table to store foreign keys that have been removed because they were +-- referencing base tables and were replaced by sets of triggers, and +-- "manual" foreign keys. +-- +CREATE TABLE inheritance.fk_cache ( + fkc_from_oid OID NOT NULL, + fkc_from_attr INT2[] NOT NULL, + fkc_to_oid OID NOT NULL, + fkc_to_attr INT2[] NOT NULL, + fkc_on_update CHAR NOT NULL REFERENCES inheritance.fk_actions (ch), + fkc_on_delete CHAR NOT NULL REFERENCES inheritance.fk_actions (ch), + fkc_manual BOOLEAN NOT NULL DEFAULT FALSE, + fkc_constraint OID, + fkc_create TEXT, + PRIMARY KEY(fkc_from_oid,fkc_from_attr) +); + + +-- -------------------------------------------------------------------------- + + + +-- -------------------------------------------------------------------------- +-- INTERNAL FUNCTIONS +-- -------------------------------------------------------------------------- + + +-- +-- Function that looks up a table's OID using its namespace and table name +-- +CREATE OR REPLACE FUNCTION inheritance.get_table_oid(namespace NAME, tablename NAME) RETURNS OID STABLE AS $$ + SELECT c.oid FROM pg_class c, pg_namespace n + WHERE c.relname = $2 AND n.nspname = $1 AND c.relnamespace = n.oid +$$ LANGUAGE SQL; + + +-- +-- Function that looks up a table's name and namespace using its OID +-- +CREATE OR REPLACE FUNCTION inheritance.get_table_name(tableoid OID, OUT namespace NAME, OUT tablename NAME) STABLE AS $$ + SELECT n.nspname, c.relname FROM pg_class c, pg_namespace n WHERE c.oid = $1 AND n.oid = c.relnamespace +$$ LANGUAGE SQL; + + +-- +-- Function that looks up the names of a set of attributes +-- +CREATE OR REPLACE FUNCTION inheritance.get_attr_names(tableoid OID, attrids INT2[]) RETURNS NAME[] STABLE AS $$ +DECLARE + tmp NAME; + attr NAME[]; + i INT; +BEGIN + attr := '{}'; + FOR i IN array_lower(attrids, 1) .. array_upper(attrids, 1) + LOOP + SELECT INTO tmp attname FROM pg_attribute WHERE attrelid = tableoid AND attnum = attrids[i]; + IF NOT FOUND + THEN RETURN NULL; + END IF; + attr[i] := tmp; + END LOOP; + RETURN attr; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that sets the OIDs in the list of tables to be ignored +-- +CREATE OR REPLACE FUNCTION inheritance.update_ignore_oids() RETURNS VOID AS $$ +DECLARE + rec RECORD; + rd_oid OID; +BEGIN + FOR rec IN SELECT ign_schema, ign_table FROM inheritance.ignore + WHERE ign_oid IS NULL + LOOP + rd_oid := inheritance.get_table_oid(rec.ign_schema, rec.ign_table); + IF rd_oid IS NOT NULL THEN + UPDATE inheritance.ignore SET ign_oid=rd_oid + WHERE ign_schema=rec.ign_schema AND ign_table=rec.ign_table; + END IF; + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that stores a table's inheritance data +-- +CREATE OR REPLACE FUNCTION inheritance.add_to_cache(parent OID, child OID) RETURNS VOID AS $$ +DECLARE + rec RECORD; +BEGIN + -- Generate indirect inheritance + FOR rec IN SELECT tch_parent FROM inheritance.tbl_cache WHERE tch_child=parent + LOOP + INSERT INTO inheritance.tbl_cache(tch_parent,tch_child,tch_direct) + VALUES (rec.tch_parent, child, FALSE); + END LOOP; + + -- Generate direct inheritance + INSERT INTO inheritance.tbl_cache(tch_parent,tch_child,tch_direct) + VALUES (parent, child, TRUE); +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that looks up a table's children and stores them in the cache +-- +CREATE OR REPLACE FUNCTION inheritance.find_children(parent OID) RETURNS VOID AS $$ +DECLARE + child RECORD; +BEGIN + FOR child IN SELECT c.oid FROM pg_class c, pg_inherits i + WHERE c.oid = i.inhrelid AND i.inhparent = parent + AND c.oid NOT IN (SELECT ign_oid FROM inheritance.ignore WHERE ign_oid IS NOT NULL) + LOOP + -- Add to cache + PERFORM inheritance.add_to_cache(parent, child.oid); + -- Find child tables + PERFORM inheritance.find_children(child.oid); + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that will update the inheritance cache +-- +CREATE OR REPLACE FUNCTION inheritance.update_table_cache() RETURNS VOID AS $$ +DECLARE + base RECORD; +BEGIN + -- Make sure the ignore list is up-to-date + PERFORM inheritance.update_ignore_oids(); + + -- Find tables that do not inherit from any other table, are not set to be ignored and + -- are in the list of schemas we are supposed to examine + FOR base IN SELECT oid FROM pg_class + WHERE relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (SELECT sch_name FROM inheritance.schemas)) + AND oid NOT IN (SELECT ign_oid FROM inheritance.ignore WHERE ign_oid IS NOT NULL) + AND oid NOT IN (SELECT inhrelid FROM pg_inherits) + AND relhassubclass + LOOP + PERFORM inheritance.find_children(base.oid); + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that drops a constraint using its OID +-- +CREATE OR REPLACE FUNCTION inheritance.drop_constraint(cst OID) RETURNS VOID AS $$ +DECLARE + rec RECORD; +BEGIN + SELECT INTO rec c.conname AS cn, r.relname AS tn, n.nspname AS nn + FROM pg_constraint c, pg_class r, pg_namespace n + WHERE c.oid = cst AND r.oid = c.conrelid AND n.oid = r.relnamespace; + IF FOUND + THEN + EXECUTE 'ALTER TABLE "' || rec.nn || '"."' || rec.tn + || '" DROP CONSTRAINT "' || rec.cn || '"'; + END IF; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that drops a trigger using its OID +-- +CREATE OR REPLACE FUNCTION inheritance.drop_trigger(tg OID) RETURNS VOID AS $$ +DECLARE + rec RECORD; +BEGIN + SELECT INTO rec t.tgname AS n, r.relname AS tn, n.nspname AS nn + FROM pg_trigger t, pg_class r, pg_namespace n + WHERE t.oid = tg AND r.oid = t.tgrelid AND n.oid = r.relnamespace; + IF FOUND THEN + EXECUTE 'DROP TRIGGER "' || rec.n || '" ON "' || rec.nn || '"."' || rec.tn || '"'; +-- RAISE NOTICE 'DROP TRIGGER "%" ON "%"."%"', rec.n, rec.nn, rec.tn; + END IF; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that drops an index using its OID +-- +CREATE OR REPLACE FUNCTION inheritance.drop_index(idx OID) RETURNS VOID AS $$ +DECLARE + rec RECORD; +BEGIN + SELECT INTO rec * FROM inheritance.get_table_name(idx); + IF rec.namespace IS NOT NULL THEN + EXECUTE 'DROP INDEX "' || rec.namespace || '"."' || rec.tablename || '"'; + END IF; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Trigger function that drops constraints added by the inheritance code +-- +CREATE OR REPLACE FUNCTION inheritance.drop_inherited_constraints() RETURNS TRIGGER AS $$ +BEGIN + IF OLD.cch_inherited THEN + PERFORM inheritance.drop_constraint(OLD.cch_constraint); + END IF; + RETURN OLD; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER con_cache_drop_inherited AFTER DELETE ON inheritance.con_cache + FOR EACH ROW EXECUTE PROCEDURE inheritance.drop_inherited_constraints(); + + +-- +-- Trigger function that drops triggers added by the inheritance code +-- +CREATE OR REPLACE FUNCTION inheritance.drop_inherited_triggers() RETURNS TRIGGER AS $$ +BEGIN + IF NOT OLD.tch_initial THEN + PERFORM inheritance.drop_trigger(OLD.tch_trigger); + END IF; + RETURN OLD; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER tgr_cache_drop_inherited AFTER DELETE ON inheritance.tgr_cache + FOR EACH ROW EXECUTE PROCEDURE inheritance.drop_inherited_triggers(); + + +-- +-- Trigger function that drops indexes added by the inheritance code +-- +CREATE OR REPLACE FUNCTION inheritance.drop_inherited_indexes() RETURNS TRIGGER AS $$ +BEGIN + IF OLD.ich_inherited THEN + PERFORM inheritance.drop_index(OLD.ich_index); + END IF; + RETURN OLD; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER idx_cache_drop_inherited AFTER DELETE ON inheritance.idx_cache + FOR EACH ROW EXECUTE PROCEDURE inheritance.drop_inherited_indexes(); + + +-- +-- Function that looks up a table's original constrains and stores them in +-- the constraint cache +-- +CREATE OR REPLACE FUNCTION inheritance.cache_table_constraints(tbl OID) RETURNS VOID AS $$ +BEGIN + INSERT INTO inheritance.con_cache (cch_table, cch_constraint) + SELECT tbl,c.oid FROM pg_constraint c WHERE c.conrelid = tbl; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that caches indexes for all existing tables handled by the inheritance code +-- +CREATE OR REPLACE FUNCTION inheritance.cache_initial_indexes() RETURNS VOID AS $$ +BEGIN + INSERT INTO inheritance.idx_cache (ich_table, ich_index) + SELECT indrelid,indexrelid FROM pg_index WHERE indrelid IN ( + SELECT DISTINCT tch_parent AS tid FROM inheritance.tbl_cache + UNION SELECT DISTINCT tch_child AS tid FROM inheritance.tbl_cache + ); +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that caches triggers for all existing tables handled by the inheritance code +-- +CREATE OR REPLACE FUNCTION inheritance.cache_initial_triggers() RETURNS VOID AS $$ +BEGIN + INSERT INTO inheritance.tgr_cache (tch_table, tch_trigger) + SELECT tgrelid,oid FROM pg_trigger WHERE tgrelid IN ( + SELECT DISTINCT tch_parent AS tid FROM inheritance.tbl_cache + UNION SELECT DISTINCT tch_child AS tid FROM inheritance.tbl_cache + ) AND NOT tgisconstraint; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that caches constraints for all existing tables handled by the inheritance code +-- +CREATE OR REPLACE FUNCTION inheritance.cache_initial_constraints() RETURNS VOID AS $$ +BEGIN + INSERT INTO inheritance.con_cache (cch_table, cch_constraint) + SELECT conrelid,oid FROM pg_constraint WHERE conrelid IN ( + SELECT DISTINCT tch_parent AS tid FROM inheritance.tbl_cache + UNION SELECT DISTINCT tch_child AS tid FROM inheritance.tbl_cache + ); +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that copies foreign key constraints from a table to another +-- +CREATE OR REPLACE FUNCTION inheritance.copy_fkey_constraints(src OID, dst OID) RETURNS VOID AS $$ +DECLARE + trec RECORD; + rec RECORD; + at TEXT; + i INT; +BEGIN + -- Get the destination table's name and namespace name + SELECT INTO trec * FROM inheritance.get_table_name(dst); + + -- Get the constraints to copy + FOR rec IN SELECT oid FROM pg_constraint WHERE contype = 'f' AND oid IN ( + SELECT cch_constraint FROM inheritance.con_cache WHERE cch_table=src AND NOT cch_inherited) + LOOP + EXECUTE 'ALTER TABLE "' || trec.namespace || '"."' || trec.tablename || '" ADD ' || pg_get_constraintdef(rec.oid); + END LOOP; + + -- Update the constraint cache + INSERT INTO inheritance.con_cache (cch_table, cch_constraint, cch_inherited) + SELECT dst,c.oid,TRUE FROM pg_constraint c WHERE c.conrelid = dst AND c.oid NOT IN ( + SELECT cch_constraint FROM inheritance.con_cache WHERE cch_table=dst + ); +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that generates a function (how perverse!) to check for a table's inherited +-- primary key. +-- +CREATE OR REPLACE FUNCTION inheritance.make_key_function(fn NAME, tbl OID, fl SMALLINT[], kt TEXT) RETURNS VOID AS $$ +DECLARE + tdet RECORD; + fdet RECORD; + code TEXT; + ncode TEXT; + ocode TEXT; + i INT; +BEGIN + SELECT INTO tdet * FROM inheritance.get_table_name(tbl); + + ncode := ''; + ocode := ''; + FOR i IN array_lower(fl, 1) .. array_upper(fl, 1) + LOOP + IF ocode <> '' + THEN + ocode := ocode || ' AND '; + ncode := ncode || ' AND '; + END IF; + + SELECT INTO fdet attname FROM pg_attribute WHERE attrelid=tbl AND attnum=fl[i]; + ncode := ncode || '"' || fdet.attname || '"=NEW."' || fdet.attname || '"'; + ocode := ocode || '"' || fdet.attname || '"<>OLD."' || fdet.attname || '"'; + END LOOP; + + EXECUTE 'CREATE OR REPLACE FUNCTION inheritance.' || fn || '() RETURNS TRIGGER AS ''' + || 'DECLARE c INT; ' + || 'BEGIN ' + || 'IF TG_OP=''''INSERT'''' THEN ' + || 'SELECT INTO c COUNT(*) FROM "' || tdet.namespace || '"."' || tdet.tablename || '" WHERE ' || ncode || ';' + || 'ELSE ' + || 'SELECT INTO c COUNT(*) FROM "' || tdet.namespace || '"."' || tdet.tablename || '" WHERE ' || ncode || ' AND ' || ocode || ';' + || 'END IF;' + || 'IF FOUND AND c>0 THEN ' + || 'RAISE EXCEPTION ''''Duplicate ' || kt || ' key value in table "%" inherited from "' || tdet.namespace + || '"."' || tdet.tablename || '"'''', TG_RELNAME;' + || 'END IF;' + || 'RETURN NEW;' + || 'END;' + || ''' LANGUAGE plpgsql'; + INSERT INTO inheritance.ufn_cache(uch_fname) VALUES (fn); +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that propagates the base tables' primary keys +-- +CREATE OR REPLACE FUNCTION inheritance.propagate_primary_keys() RETURNS VOID AS $$ +DECLARE + pkey RECORD; + ctbl RECORD; + fn NAME; +BEGIN + -- Loop on all primary keys found for the base tables + FOR pkey IN SELECT DISTINCT i.tch_parent AS t,c.conkey AS k + FROM inheritance.tbl_cache i, pg_constraint c + WHERE i.tch_parent NOT IN (SELECT DISTINCT tch_child FROM inheritance.tbl_cache) + AND c.conrelid=i.tch_parent AND c.contype='p' + LOOP + -- Generate the function that checks for this specific primary key + fn := 'pkey_check_' || pkey.t; + PERFORM inheritance.make_key_function(fn, pkey.t, pkey.k, 'primary'); + + -- Create the trigger on the table itself + SELECT INTO ctbl * FROM inheritance.get_table_name(pkey.t); + EXECUTE 'CREATE TRIGGER ' || fn || ' BEFORE INSERT OR UPDATE ' + || 'ON "' || ctbl.namespace || '"."' || ctbl.tablename || '" ' + || 'FOR EACH ROW EXECUTE PROCEDURE inheritance.' || fn || '()'; + INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) + SELECT oid, pkey.t, FALSE FROM pg_trigger + WHERE tgname = fn AND tgrelid = pkey.t; + + -- Create the trigger for all tables that inherit the base + FOR ctbl IN SELECT r.relname AS tn, n.nspname AS nn, r.oid AS o + FROM inheritance.tbl_cache i, pg_class r, pg_namespace n + WHERE i.tch_parent = pkey.t AND r.oid = i.tch_child AND n.oid = r.relnamespace + LOOP + EXECUTE 'CREATE TRIGGER ' || fn || ' BEFORE INSERT OR UPDATE ' + || 'ON "' || ctbl.nn || '"."' || ctbl.tn || '" ' + || 'FOR EACH ROW EXECUTE PROCEDURE inheritance.' || fn || '()'; + INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) + SELECT t.oid, ctbl.o, FALSE FROM pg_trigger t + WHERE t.tgname = fn AND tgrelid = ctbl.o; + END LOOP; + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that propagates the tables' unique keys +-- +CREATE OR REPLACE FUNCTION inheritance.propagate_unique_keys() RETURNS VOID AS $$ +DECLARE + ukey RECORD; + ctbl RECORD; + fn NAME; +BEGIN + -- Loop on all primary keys found for the base tables + FOR ukey IN SELECT DISTINCT i.tch_parent AS t,c.oid AS c,c.conkey AS k + FROM inheritance.tbl_cache i, pg_constraint c + WHERE c.conrelid=i.tch_parent AND c.contype='u' + LOOP + -- Generate the function that checks for this specific key + fn := 'ukey_check_' || ukey.c; + PERFORM inheritance.make_key_function(fn, ukey.t, ukey.k, 'unique'); + + -- Create the trigger on the table itself + SELECT INTO ctbl * FROM inheritance.get_table_name(ukey.t); + EXECUTE 'CREATE TRIGGER ' || fn || ' BEFORE INSERT OR UPDATE ' + || 'ON "' || ctbl.namespace || '"."' || ctbl.tablename || '" ' + || 'FOR EACH ROW EXECUTE PROCEDURE inheritance.' || fn || '()'; + INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) + SELECT oid, ukey.t, FALSE FROM pg_trigger + WHERE tgname = fn AND tgrelid = ukey.t; + + -- Create the trigger for all tables that inherit the base + FOR ctbl IN SELECT r.relname AS tn, n.nspname AS nn, r.oid AS o + FROM inheritance.tbl_cache i, pg_class r, pg_namespace n + WHERE i.tch_parent = ukey.t AND r.oid = i.tch_child AND n.oid = r.relnamespace + LOOP + EXECUTE 'CREATE TRIGGER ' || fn || ' BEFORE INSERT OR UPDATE ' + || 'ON "' || ctbl.nn || '"."' || ctbl.tn || '" ' + || 'FOR EACH ROW EXECUTE PROCEDURE inheritance.' || fn || '()'; + INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) + SELECT oid, ctbl.o, FALSE FROM pg_trigger + WHERE tgname = fn AND tgrelid = ctbl.o; + END LOOP; + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that duplicates an index +-- +CREATE OR REPLACE FUNCTION inheritance.duplicate_index(idx OID, tbl OID) RETURNS VOID AS $$ +DECLARE + irec RECORD; + trec RECORD; + attr RECORD; + fl TEXT; + i INT; +BEGIN + SELECT INTO irec CASE indisunique WHEN TRUE THEN ' UNIQUE' ELSE '' END AS unq, indkey::int2[] AS fls, indrelid + FROM pg_index WHERE indexrelid = idx; + SELECT INTO trec * FROM inheritance.get_table_name(tbl); + + fl := ''; + FOR i IN array_lower(irec.fls, 1) .. array_upper(irec.fls, 1) + LOOP + IF fl <> '' + THEN fl := fl || ','; + END IF; + SELECT INTO attr attname FROM pg_attribute WHERE attrelid = irec.indrelid AND attnum = irec.fls[i]; + fl := fl || '"' || attr.attname || '"'; + END LOOP; + + EXECUTE 'CREATE' || irec.unq || ' INDEX inh_idx_dup_' || idx || '_' || tbl + || ' ON "' || trec.namespace || '"."' || trec.tablename || '" (' || fl || ')'; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that propagates indexes from tables to their children +-- +CREATE OR REPLACE FUNCTION inheritance.propagate_indexes() RETURNS VOID AS $$ +DECLARE + idx RECORD; +BEGIN + FOR idx IN SELECT i.ich_index, t.tch_child FROM inheritance.idx_cache i, inheritance.tbl_cache t + WHERE NOT i.ich_inherited AND t.tch_parent = i.ich_table + LOOP + PERFORM inheritance.duplicate_index(idx.ich_index, idx.tch_child); + END LOOP; + + INSERT INTO inheritance.idx_cache (ich_table, ich_index, ich_inherited) + SELECT indrelid,indexrelid,TRUE FROM pg_index WHERE indrelid IN ( + SELECT DISTINCT tch_parent AS tid FROM inheritance.tbl_cache + UNION SELECT DISTINCT tch_child AS tid FROM inheritance.tbl_cache + ) AND indexrelid NOT IN (SELECT ich_index FROM inheritance.idx_cache WHERE NOT ich_inherited); +END; +$$ LANGUAGE plpgsql; + + + +-- +-- Function that copies all of the base tables' constraints to their children +-- +CREATE OR REPLACE FUNCTION inheritance.propagate() RETURNS VOID AS $$ +DECLARE + rec RECORD; +BEGIN + -- Propagate indexes + PERFORM inheritance.propagate_indexes(); + + -- Propagate the base tables' primary keys + PERFORM inheritance.propagate_primary_keys(); + + -- Propagate the tables' unique keys + PERFORM inheritance.propagate_unique_keys(); + + -- Propagate triggers + PERFORM inheritance.propagate_triggers(); + + -- Copy CHECK and FOREIGN KEY constraints + FOR rec IN SELECT tch_parent, tch_child FROM inheritance.tbl_cache + LOOP + PERFORM inheritance.copy_fkey_constraints(rec.tch_parent, rec.tch_child); + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that deletes functions used for primary key and unique key +-- enforcement. +-- +CREATE OR REPLACE FUNCTION inheritance.destroy_unique_functions() RETURNS VOID AS $$ +DECLARE + rec RECORD; +BEGIN + FOR rec IN SELECT * FROM inheritance.ufn_cache + LOOP + EXECUTE 'DROP FUNCTION inheritance."' || rec.uch_fname || '"()'; -- CASCADE'; + END LOOP; + + DELETE FROM inheritance.ufn_cache; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that propagates triggers from base tables to their children +-- +CREATE OR REPLACE FUNCTION inheritance.propagate_triggers() RETURNS VOID AS $$ +DECLARE + rec RECORD; +BEGIN + FOR rec IN SELECT t.tch_trigger AS tg, i.tch_child AS tb + FROM inheritance.tgr_cache t, inheritance.tbl_cache i + WHERE i.tch_parent = t.tch_table AND t.tch_initial + LOOP + PERFORM inheritance.copy_trigger(rec.tg, rec.tb); + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that copies a trigger to some table +-- +CREATE OR REPLACE FUNCTION inheritance.copy_trigger(tg OID, tbl OID) RETURNS VOID AS $$ +DECLARE + tbr RECORD; -- Table record + tgr RECORD; -- Trigger record + prr RECORD; -- Procedure record + code TEXT; + need_or BOOLEAN; + astring BYTEA; +BEGIN + -- Get trigger, table and procedure data + SELECT INTO tbr * FROM inheritance.get_table_name(tbl); + SELECT INTO tgr tgfoid AS fo, tgtype::int::bit(5) AS t, tgargs AS a FROM pg_trigger WHERE oid = tg; + SELECT INTO prr p.proname AS pn, n.nspname AS nn FROM pg_proc p, pg_namespace n + WHERE p.oid = tgr.fo AND n.oid = p.pronamespace; + + -- Generate the beginning of the CREATE TRIGGER instruction + code := 'CREATE TRIGGER tgr_dup_' || tg || '_' || tbl || ' ' + || (CASE (tgr.t << 3)::bit(1) WHEN B'1' THEN 'BEFORE' ELSE 'AFTER' END) + || ' '; + + -- Check the operations to which the trigger applies + need_or := FALSE; + IF (tgr.t << 2)::bit(1) = B'1' THEN + code := code || 'INSERT '; + need_or := TRUE; + END IF; + IF (tgr.t << 1)::bit(1) = B'1' THEN + code := code || (CASE need_or WHEN TRUE THEN 'OR ' ELSE '' END) || 'DELETE '; + need_or := TRUE; + END IF; + IF tgr.t::bit(1) = B'1' THEN + code := code || (CASE need_or WHEN TRUE THEN 'OR ' ELSE '' END) || 'UPDATE '; + END IF; + + -- Add table name, FOR EACH thingy and procedure name + code := code || 'ON "' || tbr.namespace || '"."' || tbr.tablename || '" FOR EACH ' + || (CASE (tgr.t << 4)::bit(1) WHEN B'1' THEN 'ROW' ELSE 'STATEMENT' END) + || ' EXECUTE PROCEDURE "' || prr.nn || '"."' || prr.pn || '"('; + + -- Generate the arguments list + astring := tgr.a; need_or := FALSE; + WHILE position('\\000'::bytea IN astring) > 0 + LOOP + IF need_or + THEN code := code || ','; + ELSE need_or := TRUE; + END IF; + + code := code || quote_literal(encode(substring(astring FROM 1 FOR position('\\000'::bytea IN astring) - 1), 'escape')); + astring := substring(astring FROM position('\\000'::bytea IN astring) + 1); + END LOOP; + code := code || ')'; + + -- Create the trigger and add it to the list + EXECUTE code; + INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) + SELECT oid, tbl, FALSE FROM pg_trigger + WHERE tgname = 'tgr_dup_' || tg || '_' || tbl AND tgrelid = tbl; +END; +$$ LANGUAGE plpgsql; + + +-- +-- This function splits a string containing a namespace and table name into +-- two strings, the namespace's name and the table's name. +-- +CREATE OR REPLACE FUNCTION inheritance.split_table_name(complete TEXT, OUT nsname NAME, OUT tblname NAME) AS $$ +DECLARE + s TEXT; +BEGIN + s := split_part(complete, '.', 1); + IF substr(s, 1, 1) = '"' + THEN nsname := substr(s, 2, char_length(s) - 2); + ELSE nsname := lower(s); + END IF; + + s := split_part(complete, '.', 2); + IF substr(s, 1, 1) = '"' + THEN tblname := substr(s, 2, char_length(s) - 2); + ELSE tblname := lower(s); + END IF; +END; +$$ LANGUAGE plpgsql; + + +-- +-- This function splits a string of coma-separated field names into an array +-- of names. +-- +CREATE OR REPLACE FUNCTION inheritance.split_field_list(fstr TEXT, OUT lst NAME[]) AS $$ +DECLARE + s TEXT; + i INT; +BEGIN + lst := '{}'; + i := 1; + LOOP + s := split_part(fstr, ',', i); + IF s = '' + THEN EXIT; + END IF; + + IF substr(s, 1, 1) = '"' + THEN lst[i] := substr(s, 2, char_length(s) - 2); + ELSE lst[i] := lower(s); + END IF; + + i := i + 1; + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +-- +-- This function handles foreign keys (be them "real" SQL foreign keys or manual +-- foreign keys created by inheritance.foreign_key()). +-- +CREATE OR REPLACE FUNCTION inheritance.handle_foreign_keys() RETURNS VOID AS $$ +BEGIN + -- Cache real SQL foreign keys + PERFORM inheritance.cache_real_fkeys(); + -- Cache manual foreign keys + PERFORM inheritance.cache_manual_fkeys(); + -- Create the functions and triggers + PERFORM inheritance.make_fkey_triggers(); +END; +$$ LANGUAGE plpgsql; + + +-- +-- This function reads the list of real foreign keys that refer to tables +-- handled by the inheritance code. If the foreign keys have been added by +-- inheritance, it removes them silently and stores them into the cache as +-- if they were "manual" foreign keys +-- +CREATE OR REPLACE FUNCTION inheritance.cache_real_fkeys() RETURNS VOID AS $$ +DECLARE + rec RECORD; +BEGIN + -- List the foreign keys + INSERT INTO inheritance.fk_cache (fkc_from_oid,fkc_from_attr,fkc_to_oid,fkc_to_attr,fkc_constraint,fkc_on_update,fkc_on_delete,fkc_manual) + SELECT c.conrelid, c.conkey, c.confrelid, c.confkey, c.oid, c.confupdtype, c.confdeltype, ( + SELECT COUNT(*)=1 FROM inheritance.con_cache + WHERE cch_table=c.conrelid AND cch_inherited='t' AND cch_constraint=c.oid) + FROM pg_constraint c WHERE c.contype='f' AND c.confrelid IN ( + SELECT DISTINCT tch_parent AS tid FROM inheritance.tbl_cache + UNION SELECT DISTINCT tch_child AS tid FROM inheritance.tbl_cache); + + -- Drop constraints that were copied by the inheritance code + DELETE FROM inheritance.con_cache WHERE cch_constraint IN ( + SELECT fkc_constraint FROM inheritance.fk_cache WHERE fkc_manual); + DELETE FROM inheritance.fk_cache WHERE fkc_manual; + + -- Drop the other constraints + FOR rec IN SELECT fkc_constraint FROM inheritance.fk_cache WHERE NOT fkc_manual + LOOP + UPDATE inheritance.fk_cache SET fkc_create = pg_get_constraintdef(rec.fkc_constraint) + WHERE fkc_constraint = rec.fkc_constraint; + PERFORM inheritance.drop_constraint(rec.fkc_constraint); + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +-- +-- This function fetches the list of manual foreign keys and adds them to +-- the cache if possible AND necessary. +-- +CREATE OR REPLACE FUNCTION inheritance.cache_manual_fkeys() RETURNS VOID AS $$ +DECLARE + mfk RECORD; + stbl OID; + dtbl OID; + sfld INT2[]; + dfld INT2[]; + tmp INT2; + i INT; +BEGIN + <> + FOR mfk IN SELECT * FROM inheritance.manual_fkeys + LOOP + -- Get the source table's OID + stbl := inheritance.get_table_oid(mfk.mfk_from_ns, mfk.mfk_from_table); + CONTINUE WHEN stbl IS NULL; + + -- Get the destination table's OID + dtbl := inheritance.get_table_oid(mfk.mfk_to_ns, mfk.mfk_to_table); + CONTINUE WHEN dtbl IS NULL; + + -- Get the list of fields + sfld := '{}'; dfld := '{}'; + FOR i IN array_lower(mfk.mfk_from_fields, 1) .. array_upper(mfk.mfk_from_fields, 1) + LOOP + SELECT INTO tmp attnum FROM pg_attribute + WHERE attrelid = stbl AND attname = mfk.mfk_from_fields[i]; + CONTINUE cmfk_main_loop WHEN NOT FOUND; + sfld[i] := tmp; + + SELECT INTO tmp attnum FROM pg_attribute + WHERE attrelid = dtbl AND attname = mfk.mfk_to_fields[i]; + CONTINUE cmfk_main_loop WHEN NOT FOUND; + dfld[i] := tmp; + END LOOP; + + -- Insert data + INSERT INTO inheritance.fk_cache (fkc_from_oid,fkc_from_attr,fkc_to_oid,fkc_to_attr,fkc_manual,fkc_on_update,fkc_on_delete) + VALUES (stbl, sfld, dtbl, dfld, TRUE, + (SELECT ch FROM inheritance.fk_actions WHERE txt=mfk.mfk_on_update), + (SELECT ch FROM inheritance.fk_actions WHERE txt=mfk.mfk_on_delete) + ); + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Trigger function that restore foreign keys that had been deleted +-- +CREATE OR REPLACE FUNCTION inheritance.restore_deleted_fkeys() RETURNS TRIGGER AS $$ +DECLARE + rec RECORD; +BEGIN + IF NOT OLD.fkc_manual THEN + SELECT INTO rec * FROM inheritance.get_table_name(OLD.fkc_from_oid); + EXECUTE 'ALTER TABLE "' || rec.namespace || '"."' || rec.tablename || '" ADD ' || OLD.fkc_create; + END IF; + RETURN OLD; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER fk_cache_restore_deleted AFTER DELETE ON inheritance.fk_cache + FOR EACH ROW EXECUTE PROCEDURE inheritance.restore_deleted_fkeys(); + + +-- +-- Function that will generate trigger functions that will be applied +-- before deletion or update on referenced tables and their children. +-- +CREATE OR REPLACE FUNCTION inheritance.make_referenced_functions(tbl OID) RETURNS VOID AS $$ +DECLARE + dcode TEXT; + ucode TEXT; + trec RECORD; + trec2 RECORD; + rec RECORD; + rec2 RECORD; + sattr NAME[]; + dattr NAME[]; + i INT; + acode TEXT; +BEGIN + dcode := 'CREATE FUNCTION inheritance.tgr_fk_' || tbl || '_del() RETURNS TRIGGER AS $a$DECLARE c INT;BEGIN '; + ucode := 'CREATE FUNCTION inheritance.tgr_fk_' || tbl || '_upd_d() RETURNS TRIGGER AS $a$DECLARE c INT;BEGIN '; + + SELECT INTO trec * FROM inheritance.get_table_name(tbl); + + -- For each set of referenced keys + FOR rec IN SELECT DISTINCT fkc_to_attr AS attr FROM inheritance.fk_cache WHERE fkc_to_oid = tbl + LOOP + dattr := inheritance.get_attr_names(tbl, rec.attr); + + -- Generate condition for the UPDATE trigger + ucode := ucode || 'IF '; + FOR i IN array_lower(dattr, 1) .. array_upper(dattr, 1) + LOOP + ucode := ucode || 'NEW."' || dattr[i] || '" <> OLD."' || dattr[i] || '" '; + IF i < array_upper(dattr, 1) + THEN ucode := ucode || 'OR '; + END IF; + END LOOP; + ucode := ucode || 'THEN '; + + -- For each referencing table + FOR rec2 IN SELECT fkc_from_oid AS oid, fkc_from_attr AS attr, fkc_on_delete AS od, fkc_on_update AS ou + FROM inheritance.fk_cache WHERE fkc_to_oid=tbl AND fkc_to_attr=rec.attr + LOOP + -- Fetch table and namespace + SELECT INTO trec2 * FROM inheritance.get_table_name(rec2.oid); + + -- Fetch attribute names + sattr := inheritance.get_attr_names(rec2.oid, rec2.attr); + acode := ''; + FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) + LOOP + acode := acode || '"' || sattr[i] || '"=OLD."' || dattr[i] || '"'; + IF i < array_upper(sattr, 1) + THEN acode := acode || ' AND '; + END IF; + END LOOP; + + -- Handle "ON DELETE CASCADE" + IF rec2.od = 'c' THEN + dcode := dcode || 'DELETE FROM "' || trec2.namespace || '"."' || trec2.tablename || '" WHERE ' || acode || ';'; + -- Handle "ON DELETE SET NULL" + ELSIF rec2.od = 'n' THEN + dcode := dcode || 'UPDATE "' || trec2.namespace || '"."' || trec2.tablename || '" SET '; + FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) + LOOP + dcode := dcode || '"' || sattr[i] || '"=NULL'; + IF i < array_upper(rec2.attr, 1) + THEN dcode := dcode || ','; + END IF; + END LOOP; + dcode := dcode || ' WHERE ' || acode || ';'; + -- Handle "ON DELETE NO ACTION" + ELSIF rec2.od = 'a' THEN + dcode := dcode || 'SELECT INTO c COUNT(*) FROM "' || trec2.namespace || '"."' || trec2.tablename || '" WHERE ' || acode + || '; IF c > 0 THEN RAISE EXCEPTION ''Foreign key on "' || trec2.namespace || '"."' || trec2.tablename + || '" failed while deleting from "' || trec.namespace || '"."' || trec.tablename || '"''; END IF;'; + END IF; + + -- Handle "ON UPDATE CASCADE" + IF rec2.ou = 'c' THEN + ucode := ucode || 'UPDATE "' || trec2.namespace || '"."' || trec2.tablename || '" SET '; + FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) + LOOP + ucode := ucode || '"' || sattr[i] || '"=NEW."' || dattr[i] || '"'; + IF i < array_upper(rec2.attr, 1) + THEN ucode := ucode || ','; + END IF; + END LOOP; + ucode := ucode || ' WHERE ' || acode || ';'; + -- Handle "ON UPDATE SET NULL" + ELSIF rec2.ou = 'n' THEN + ucode := ucode || 'UPDATE "' || trec2.namespace || '"."' || trec2.tablename || '" SET '; + FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) + LOOP + ucode := ucode || '"' || sattr[i] || '"=NULL'; + IF i < array_upper(rec2.attr, 1) + THEN ucode := ucode || ','; + END IF; + END LOOP; + ucode := ucode || ' WHERE ' || acode || ';'; + -- Handle "ON UPDATE NO ACTION" + ELSIF rec2.ou = 'a' THEN + ucode := ucode || 'SELECT INTO c COUNT(*) FROM "' || trec2.namespace || '"."' || trec2.tablename || '" WHERE ' || acode + || '; IF c > 0 THEN RAISE EXCEPTION ''Foreign key on "' || trec2.namespace || '"."' || trec2.tablename + || '" failed while updating from "' || trec.namespace || '"."' || trec.tablename || '"''; END IF;'; + END IF; + END LOOP; + ucode := ucode || 'END IF;'; + END LOOP; + + dcode := dcode || 'RETURN OLD;END;$a$ LANGUAGE plpgsql'; + ucode := ucode || 'RETURN NEW;END;$a$ LANGUAGE plpgsql'; + + EXECUTE dcode; + EXECUTE ucode; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that will generate the trigger functions to be applied +-- before insertion or update on referencing tables. +-- +CREATE OR REPLACE FUNCTION inheritance.make_referencing_functions(tbl OID) RETURNS VOID AS $$ +DECLARE + icode TEXT; + ucode TEXT; + rec RECORD; + trec RECORD; + trec2 RECORD; + sattr NAME[]; + dattr NAME[]; + i INT; +BEGIN + icode := 'CREATE FUNCTION inheritance.tgr_fk_' || tbl || '_ins() RETURNS TRIGGER AS $a$DECLARE c INT;BEGIN '; + ucode := 'CREATE FUNCTION inheritance.tgr_fk_' || tbl || '_upd_s() RETURNS TRIGGER AS $a$DECLARE c INT;BEGIN '; + + SELECT INTO trec * FROM inheritance.get_table_name(tbl); + + -- For each set of referencing keys + FOR rec IN SELECT fkc_from_attr AS attr, fkc_to_oid AS ttbl, fkc_to_attr AS tattr + FROM inheritance.fk_cache WHERE fkc_from_oid = tbl + LOOP + sattr := inheritance.get_attr_names(tbl, rec.attr); + + -- Get referenced table and attribute + SELECT INTO trec2 * FROM inheritance.get_table_name(rec.ttbl); + dattr := inheritance.get_attr_names(rec.ttbl, rec.tattr); + + -- Create the code to be run on insertion + icode := icode || 'IF'; + FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) + LOOP + icode := icode || ' NEW."' || sattr[i] || '" IS NOT NULL'; + IF i <> array_upper(sattr, 1) + THEN icode := icode || ' AND'; + END IF; + END LOOP; + icode := icode || ' THEN SELECT INTO c COUNT(*) FROM "' || trec2.namespace || '"."' || trec2.tablename || '" WHERE'; + FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) + LOOP + icode := icode || ' NEW."' || sattr[i] || '"="' || dattr[i] || '"'; + IF i <> array_upper(sattr, 1) + THEN icode := icode || ' AND'; + END IF; + END LOOP; + icode := icode || '; IF c=0 THEN RAISE EXCEPTION ''Foreign key to "' || trec2.namespace || '"."' || trec2.tablename + || '" failed while inserting into "' || trec.namespace || '"."' || trec.tablename + || '"''; END IF; END IF;'; + + + -- Create the code to be run on update + ucode := ucode || 'IF ('; + FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) + LOOP + ucode := ucode || 'NEW."' || sattr[i] || '"<>OLD."' || sattr[i] || '"'; + IF i <> array_upper(sattr, 1) + THEN ucode := ucode || ' OR '; + END IF; + END LOOP; + ucode := ucode || ') AND'; + FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) + LOOP + ucode := ucode || ' NEW."' || sattr[i] || '" IS NOT NULL'; + IF i <> array_upper(sattr, 1) + THEN ucode := ucode || ' AND'; + END IF; + END LOOP; + ucode := ucode || ' THEN SELECT INTO c COUNT(*) FROM "' || trec2.namespace || '"."' || trec2.tablename || '" WHERE'; + FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) + LOOP + ucode := ucode || ' NEW."' || sattr[i] || '"="' || dattr[i] || '"'; + IF i <> array_upper(sattr, 1) + THEN ucode := ucode || ' AND'; + END IF; + END LOOP; + ucode := ucode || '; IF c=0 THEN RAISE EXCEPTION ''Foreign key to "' || trec2.namespace || '"."' || trec2.tablename + || '" failed while updating "' || trec.namespace || '"."' || trec.tablename + || '"''; END IF; END IF;'; + END LOOP; + + icode := icode || 'RETURN NEW;END;$a$ LANGUAGE plpgsql'; + ucode := ucode || 'RETURN NEW;END;$a$ LANGUAGE plpgsql'; + + EXECUTE icode; + EXECUTE ucode; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that will generate foreign key triggers +-- +CREATE OR REPLACE FUNCTION inheritance.make_fkey_triggers() RETURNS VOID AS $$ +DECLARE + rec RECORD; +BEGIN + -- Generate functions for all referencing tables + FOR rec IN SELECT DISTINCT fkc_from_oid FROM inheritance.fk_cache + LOOP + PERFORM inheritance.make_referencing_functions(rec.fkc_from_oid); + PERFORM inheritance.create_referencing_triggers(rec.fkc_from_oid); + END LOOP; + + -- Generate functions and triggers for all referenced tables + FOR rec IN SELECT DISTINCT fkc_to_oid FROM inheritance.fk_cache + LOOP + PERFORM inheritance.make_referenced_functions(rec.fkc_to_oid); + PERFORM inheritance.create_referenced_triggers(rec.fkc_to_oid); + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that creates the foreign key triggers for referenced tables and their children +-- +CREATE OR REPLACE FUNCTION inheritance.create_referenced_triggers(dst OID) RETURNS VOID AS $$ +DECLARE + rec RECORD; + trec RECORD; +BEGIN + -- Create triggers for the referenced table and its children + FOR rec IN SELECT dst AS tid UNION SELECT tch_child AS tid FROM inheritance.tbl_cache WHERE tch_parent = dst + LOOP + SELECT INTO trec * FROM inheritance.get_table_name(rec.tid); + + EXECUTE 'CREATE TRIGGER tgr_fk_' || dst || '_del_' || rec.tid + || ' BEFORE DELETE ON "' || trec.namespace || '"."' || trec.tablename || '" ' + || 'FOR EACH ROW EXECUTE PROCEDURE inheritance.tgr_fk_' || dst || '_del()'; + INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) + SELECT oid, rec.tid, FALSE FROM pg_trigger + WHERE tgname = 'tgr_fk_' || dst || '_del_' || rec.tid + AND tgrelid = rec.tid; + + EXECUTE 'CREATE TRIGGER tgr_fk_' || dst || '_updd_' || rec.tid + || ' BEFORE UPDATE ON "' || trec.namespace || '"."' || trec.tablename || '" ' + || 'FOR EACH ROW EXECUTE PROCEDURE inheritance.tgr_fk_' || dst || '_upd_d()'; + INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) + SELECT oid, rec.tid, FALSE FROM pg_trigger + WHERE tgname = 'tgr_fk_' || dst || '_updd_' || rec.tid + AND tgrelid = rec.tid; + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +-- +-- Function that creates the foreign key triggers for referencing tables and their children +-- +CREATE OR REPLACE FUNCTION inheritance.create_referencing_triggers(src OID) RETURNS VOID AS $$ +DECLARE + rec RECORD; + trec RECORD; +BEGIN + -- Create triggers for the referencing table and its children + FOR rec IN SELECT src AS tid UNION SELECT tch_child AS tid FROM inheritance.tbl_cache WHERE tch_parent = src + LOOP + SELECT INTO trec * FROM inheritance.get_table_name(rec.tid); + + EXECUTE 'CREATE TRIGGER tgr_fk_' || src || '_ins_' || rec.tid + || ' BEFORE INSERT ON "' || trec.namespace || '"."' || trec.tablename || '" ' + || 'FOR EACH ROW EXECUTE PROCEDURE inheritance.tgr_fk_' || src || '_ins()'; + INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) + SELECT oid, rec.tid, FALSE FROM pg_trigger + WHERE tgname = 'tgr_fk_' || src || '_ins_' || rec.tid + AND tgrelid = rec.tid; + + EXECUTE 'CREATE TRIGGER tgr_fk_' || src || '_upds_' || rec.tid + || ' BEFORE UPDATE ON "' || trec.namespace || '"."' || trec.tablename || '" ' + || 'FOR EACH ROW EXECUTE PROCEDURE inheritance.tgr_fk_' || src || '_upd_s()'; + INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) + SELECT oid, rec.tid, FALSE FROM pg_trigger + WHERE tgname = 'tgr_fk_' || src || '_upds_' || rec.tid + AND tgrelid = rec.tid; + END LOOP; +END; +$$ LANGUAGE plpgsql; diff --git a/sql/10-main.sql b/sql/10-main.sql new file mode 100644 index 0000000..98cea84 --- /dev/null +++ b/sql/10-main.sql @@ -0,0 +1,19 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 10-main.sql +-- +-- Initialises the main schema +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + + +-- Connect to the database +\c legacyworlds legacyworlds_admin + +-- Create the main schema +CREATE SCHEMA main; +GRANT USAGE ON SCHEMA main TO legacyworlds; + diff --git a/sql/11-main-enums.sql b/sql/11-main-enums.sql new file mode 100644 index 0000000..dc0b610 --- /dev/null +++ b/sql/11-main-enums.sql @@ -0,0 +1,71 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 11-main-enums.sql +-- +-- Initialises the tables to be used as enumerations for +-- the main schema +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + + +-- Connect to the database +\c legacyworlds legacyworlds_admin + + +-- +-- Create the account status table +-- + +CREATE TABLE main.acnt_status ( + txt VARCHAR(6) PRIMARY KEY +); +GRANT SELECT ON TABLE main.acnt_status TO legacyworlds; + +-- Fill the account status table +COPY main.acnt_status FROM STDIN; +NEW +STD +KICKED +QUIT +INAC +VAC +\. + + +-- +-- Create the account log entry types list +-- + +CREATE TABLE main.acnt_log_entry_type ( + txt VARCHAR(6) PRIMARY KEY +); +GRANT SELECT ON TABLE main.acnt_log_entry_type TO legacyworlds; + +-- Fill the account log entry type table +COPY main.acnt_log_entry_type FROM STDIN; +IN +OUT +CREATE +CONF +QUIT +VSTART +VEND +\. + + +-- +-- Create the supported language list +-- + +CREATE TABLE main.lang ( + txt VARCHAR(4) PRIMARY KEY +); +GRANT SELECT ON TABLE main.lang TO legacyworlds; + +-- Fill the supported language table +COPY main.lang FROM STDIN; +en +\. diff --git a/sql/12-main-tables.sql b/sql/12-main-tables.sql new file mode 100644 index 0000000..2780cd3 --- /dev/null +++ b/sql/12-main-tables.sql @@ -0,0 +1,223 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 12-main-tables.sql +-- +-- Initialises the part of the database that contains +-- the user accounts, logs ... +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + + +-- Connect to the database +\c legacyworlds legacyworlds_admin + + + +-- +-- Create the account table +-- + +CREATE TABLE main.account ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(15) NOT NULL UNIQUE, + email VARCHAR(128) NOT NULL UNIQUE, + password VARCHAR(64) NOT NULL, + status VARCHAR(6) NOT NULL DEFAULT 'NEW' REFERENCES main.acnt_status (txt), + conf_code VARCHAR(16) NULL, + reason TEXT NULL, + vac_credits INT NOT NULL DEFAULT 30 CHECK(vac_credits BETWEEN 0 AND 240), + vac_start INT NULL CHECK(vac_start IS NULL OR (vac_start IS NOT NULL AND vac_credits > 0)), + quit_ts INT NULL CHECK(quit_ts IS NULL OR quit_ts > 0), + last_login INT NULL CHECK(last_login IS NULL OR last_login > 0), + last_logout INT NULL CHECK(last_logout IS NULL OR last_logout > 0), + pw_conf VARCHAR(16) NULL, + admin BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE INDEX account_status ON main.account(status); + +GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE main.account TO legacyworlds; +GRANT SELECT,UPDATE ON main.account_id_seq TO legacyworlds; + + +-- +-- The following table stores queued game registrations. +-- + +CREATE TABLE main.reg_queue ( + account BIGINT NOT NULL PRIMARY KEY REFERENCES main.account (id) + ON DELETE CASCADE, + game VARCHAR(16) NOT NULL +); +GRANT SELECT,INSERT,DELETE ON main.reg_queue TO legacyworlds; + + +-- +-- Create the table that stores kick requests +-- + +CREATE TABLE main.adm_kick ( + id BIGSERIAL PRIMARY KEY, + to_kick BIGINT NOT NULL REFERENCES main.account (id), + requested_by BIGINT NOT NULL REFERENCES main.account (id), + requested_at INT NOT NULL DEFAULT INT4(EXTRACT(EPOCH FROM NOW())), + reason TEXT NOT NULL, + status CHAR(1) NOT NULL CHECK(status IN ('P','Y','N')), + examined_by BIGINT REFERENCES main.account (id) +); + +CREATE INDEX adm_kick_to_kick ON main.adm_kick (to_kick); +CREATE INDEX adm_kick_req_by ON main.adm_kick (requested_by); +CREATE INDEX adm_kick_exam_by ON main.adm_kick (examined_by); + +GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE main.adm_kick TO legacyworlds; +GRANT SELECT,UPDATE ON main.adm_kick_id_seq TO legacyworlds; + + +-- +-- Create the table to handle tracking cookies +-- + +CREATE TABLE main.web_tracking ( + id BIGSERIAL PRIMARY KEY, + cookie VARCHAR(32) NOT NULL UNIQUE, + created INT NOT NULL DEFAULT INT4(EXTRACT(EPOCH FROM NOW())), + last_used INT NOT NULL DEFAULT INT4(EXTRACT(EPOCH FROM NOW())), + ip_addr VARCHAR(15) NOT NULL, + browser VARCHAR(255) NOT NULL, + stored_data TEXT NOT NULL DEFAULT 'a:0:{}' +); + +GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE main.web_tracking TO legacyworlds; +GRANT SELECT,UPDATE ON main.web_tracking_id_seq TO legacyworlds; + + +-- +-- Create the session storage table +-- + +CREATE TABLE main.web_session ( + id BIGSERIAL PRIMARY KEY, + cookie VARCHAR(32) NOT NULL UNIQUE, + created INT NOT NULL DEFAULT INT4(EXTRACT(EPOCH FROM NOW())), + last_used INT NOT NULL DEFAULT INT4(EXTRACT(EPOCH FROM NOW())), + ip_addr VARCHAR(15) NOT NULL, + account BIGINT REFERENCES main.account(id), + stored_data TEXT NOT NULL DEFAULT 'a:0:{}', + tracking BIGINT NOT NULL REFERENCES main.web_tracking(id) ON DELETE CASCADE +); + +CREATE INDEX web_session_tracking ON main.web_session (tracking); +CREATE INDEX web_session_account ON main.web_session (account); + +GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE main.web_session TO legacyworlds; +GRANT SELECT,UPDATE ON main.web_session_id_seq TO legacyworlds; + + + +-- +-- Create the account log table +-- +CREATE TABLE main.account_log ( + id BIGSERIAL PRIMARY KEY, + account BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + tracking BIGINT NULL REFERENCES main.web_tracking (id) ON DELETE SET NULL, + ip_addr VARCHAR(18) NOT NULL DEFAULT 'AUTO', + action VARCHAR(6) NOT NULL REFERENCES main.acnt_log_entry_type (txt), + t INT NOT NULL DEFAULT INT4(EXTRACT(EPOCH FROM NOW())) +); + +CREATE INDEX acnt_log_account ON main.account_log (account); +CREATE INDEX acnt_log_trk ON main.account_log (tracking); +CREATE INDEX acnt_log_act ON main.account_log (action); + +GRANT SELECT,INSERT,DELETE ON TABLE main.account_log TO legacyworlds; +GRANT SELECT,UPDATE ON main.account_log_id_seq TO legacyworlds; + + +-- +-- Create the table that stores cache references +-- + +CREATE TABLE main.web_cache ( + id BIGSERIAL PRIMARY KEY, + rtype VARCHAR(5) NOT NULL, + md5 VARCHAR(32) NOT NULL, + last_used BIGINT NOT NULL DEFAULT INT4(EXTRACT(EPOCH FROM NOW())), + UNIQUE (rtype, md5) +); + +GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE main.web_cache TO legacyworlds; +GRANT SELECT,UPDATE ON main.web_cache_id_seq TO legacyworlds; + + +-- +-- Tables that will store rankings definitions, texts, and +-- game relations +-- + +-- Definitions +CREATE TABLE main.ranking_def ( + id BIGSERIAL PRIMARY KEY, + version VARCHAR(16) NOT NULL, + name VARCHAR(16) NOT NULL, + more BOOLEAN NOT NULL DEFAULT FALSE, + UNIQUE (version, name) +); + +GRANT SELECT ON TABLE main.ranking_def TO legacyworlds; + +-- Descriptions +CREATE TABLE main.ranking_text ( + ranking BIGINT NOT NULL REFERENCES main.ranking_def (id), + lang VARCHAR(4) NOT NULL REFERENCES main.lang (txt), + name VARCHAR(32) NOT NULL, + description text NOT NULL, + PRIMARY KEY (ranking, lang) +); + +CREATE INDEX rk_txt_lang ON main.ranking_text (lang); +GRANT SELECT ON TABLE main.ranking_text TO legacyworlds; + + +-- Games +CREATE TABLE main.ranking_game ( + id BIGSERIAL NOT NULL PRIMARY KEY, + ranking BIGINT NOT NULL REFERENCES main.ranking_def (id), + game VARCHAR(16) NOT NULL, + UNIQUE (ranking, game) +); + +GRANT SELECT ON TABLE main.ranking_game TO legacyworlds; + + +-- +-- Table that will store the rankings themselves +-- +CREATE TABLE main.ranking ( + r_type BIGINT NOT NULL REFERENCES main.ranking_game (id) ON DELETE CASCADE, + id VARCHAR(32) NOT NULL, + additional TEXT, + points BIGINT NOT NULL DEFAULT 0, + ranking BIGINT NOT NULL, + PRIMARY KEY (r_type, id) +); + +GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE main.ranking TO legacyworlds; + + +-- +-- Table for user preferences +-- +CREATE TABLE main.user_preferences ( + id VARCHAR(32) NOT NULL, + version VARCHAR(16) NOT NULL, + account BIGINT NULL REFERENCES main.account (id) ON DELETE CASCADE, + value VARCHAR(255) NOT NULL, + UNIQUE (id, version, account) +); + +GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE main.user_preferences TO legacyworlds; diff --git a/sql/13-main-donations.sql b/sql/13-main-donations.sql new file mode 100644 index 0000000..2d43fba --- /dev/null +++ b/sql/13-main-donations.sql @@ -0,0 +1,70 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 13-main-donations.sql +-- +-- Initialises the part of the database that contains +-- data related to PayPal donations +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + + +-- Connect to the database +\c legacyworlds legacyworlds_admin + + + +-- +-- Create the ticket table +-- +CREATE TABLE main.pp_ticket ( + md5_id VARCHAR(64) NOT NULL PRIMARY KEY, + account BIGINT NOT NULL REFERENCES main.account (id), + created INT NOT NULL DEFAULT UNIX_TIMESTAMP(NOW()) +); + +CREATE INDEX pp_tick_acnt ON main.pp_ticket(account); +GRANT SELECT,INSERT,DELETE ON TABLE main.pp_ticket TO legacyworlds; + + +-- +-- Create the donations history table +-- +CREATE TABLE main.pp_history ( + account BIGINT NOT NULL REFERENCES main.account (id), + donated INT NOT NULL DEFAULT UNIX_TIMESTAMP(NOW()), + amount FLOAT NOT NULL, + PRIMARY KEY (account, donated) +); + +GRANT SELECT,INSERT ON TABLE main.pp_history TO legacyworlds; + + +-- +-- Create the Paypal IPN log table +-- +CREATE TABLE main.pp_ipn ( + id BIGSERIAL NOT NULL PRIMARY KEY, + received INT NOT NULL DEFAULT UNIX_TIMESTAMP(NOW()), + receiver_email VARCHAR(127), + item_number VARCHAR(127), + payment_status VARCHAR(20), + pending_reason VARCHAR(10), + payment_date VARCHAR(20), + mc_gross VARCHAR(20), + mc_fee VARCHAR(20), + tax VARCHAR(20), + mc_currency VARCHAR(3), + txn_id VARCHAR(20), + txn_type VARCHAR(10), + payer_email VARCHAR(127), + payer_status VARCHAR(10), + payment_type VARCHAR(10), + verify_sign VARCHAR(10), + referrer_id VARCHAR(10) +); + +GRANT SELECT,INSERT ON TABLE main.pp_ipn TO legacyworlds; +GRANT SELECT,UPDATE ON main.pp_ipn_id_seq TO legacyworlds; diff --git a/sql/13-main-forums.sql b/sql/13-main-forums.sql new file mode 100644 index 0000000..835aecb --- /dev/null +++ b/sql/13-main-forums.sql @@ -0,0 +1,149 @@ +-- 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 --git a/sql/13-main-links.sql b/sql/13-main-links.sql new file mode 100644 index 0000000..8c8eaf1 --- /dev/null +++ b/sql/13-main-links.sql @@ -0,0 +1,69 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 13-main-links.sql +-- +-- Tables for the links to external sites +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + + +-- Connect to the database +\c legacyworlds legacyworlds_admin + + + +-- +-- Categories +-- +CREATE TABLE main.lk_category ( + id BIGSERIAL NOT NULL PRIMARY KEY, + position INT NOT NULL UNIQUE CHECK(position >= 0), + title VARCHAR(64) NOT NULL UNIQUE, + description TEXT +); + +GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE main.lk_category TO legacyworlds; +GRANT SELECT,UPDATE ON main.lk_category_id_seq TO legacyworlds; + + +-- +-- Links +-- +CREATE TABLE main.lk_link ( + id BIGSERIAL NOT NULL PRIMARY KEY, + category BIGINT NOT NULL REFERENCES main.lk_category (id) ON DELETE CASCADE, + title VARCHAR(64) NOT NULL, + url VARCHAR(128) NOT NULL UNIQUE, + description TEXT +); + +CREATE UNIQUE INDEX lk_link_cat_title ON main.lk_link (category, title); +GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE main.lk_link TO legacyworlds; +GRANT SELECT,UPDATE ON main.lk_link_id_seq TO legacyworlds; + + +-- +-- Reports of broken links +-- +CREATE TABLE main.lk_broken ( + link BIGINT NOT NULL REFERENCES main.lk_link (id) ON DELETE CASCADE, + reported_by BIGINT NOT NULL REFERENCES main.account (id), + PRIMARY KEY (link, reported_by) +); +GRANT SELECT,INSERT,DELETE ON TABLE main.lk_broken TO legacyworlds; + + +-- +-- Submitted links +-- +CREATE TABLE main.lk_submitted ( + url VARCHAR(128) NOT NULL, + submitted_by BIGINT NOT NULL REFERENCES main.account (id), + title VARCHAR(64) NOT NULL, + description TEXT, + PRIMARY KEY (url, submitted_by) +); +GRANT SELECT,INSERT,DELETE ON TABLE main.lk_submitted TO legacyworlds; diff --git a/sql/13-main-manual.sql b/sql/13-main-manual.sql new file mode 100644 index 0000000..5644206 --- /dev/null +++ b/sql/13-main-manual.sql @@ -0,0 +1,66 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 13-main-manual.sql +-- +-- Tables for the manual and its index +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + + +-- Connect to the database +\c legacyworlds legacyworlds_admin + + + +-- +-- Manual sections +-- +CREATE TABLE main.man_section ( + id BIGSERIAL NOT NULL PRIMARY KEY, + version VARCHAR(16) NOT NULL, + lang VARCHAR(4) NOT NULL REFERENCES main.lang (txt), + in_section BIGINT REFERENCES main.man_section (id) ON DELETE SET NULL, + after_section BIGINT REFERENCES main.man_section (id) ON DELETE SET NULL, + link_to BIGINT REFERENCES main.man_section (id) ON DELETE SET NULL, + name VARCHAR(64) NOT NULL, + last_update INT NOT NULL, + is_page BOOLEAN NOT NULL, + in_menu BOOLEAN NOT NULL, + title VARCHAR(128) NOT NULL, + contents TEXT NOT NULL +); + +CREATE UNIQUE INDEX man_section_unique ON main.man_section (version, lang, name); +CREATE INDEX man_section_in_section ON main.man_section (in_section); +CREATE INDEX man_section_after_section ON main.man_section (after_section); +CREATE INDEX man_section_link_to ON main.man_section (link_to); + +GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE main.man_section TO legacyworlds; +GRANT SELECT,UPDATE ON main.man_section_id_seq TO legacyworlds; + + +-- +-- Manual index +-- +CREATE TABLE main.man_index ( + word VARCHAR(32) NOT NULL, + wcount INT NOT NULL CHECK(wcount > 0), + lang VARCHAR(4) NOT NULL REFERENCES main.lang (txt), + section BIGINT NOT NULL REFERENCES main.man_section (id) ON DELETE CASCADE, + PRIMARY KEY (word, lang, section) +); +GRANT SELECT,INSERT,DELETE ON TABLE main.man_index TO legacyworlds; + + +-- +-- Banned words +-- +CREATE TABLE main.man_index_ban ( + lang VARCHAR(4) NOT NULL REFERENCES main.lang (txt), + word VARCHAR(32) NOT NULL, + PRIMARY KEY (lang,word) +); +GRANT SELECT ON TABLE main.man_index_ban TO legacyworlds; diff --git a/sql/13-main-proxy.sql b/sql/13-main-proxy.sql new file mode 100644 index 0000000..c6ccc64 --- /dev/null +++ b/sql/13-main-proxy.sql @@ -0,0 +1,25 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 13-main-proxy.sql +-- +-- Table for open proxy detector's cache +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + + +-- Connect to the database +\c legacyworlds legacyworlds_admin + + +-- +-- Cache table +-- +CREATE TABLE main.proxy_detector ( + host VARCHAR(15) PRIMARY KEY, + last_check INT NOT NULL, + is_proxy BOOLEAN NOT NULL +); +GRANT SELECT,INSERT,DELETE ON main.proxy_detector TO legacyworlds; diff --git a/sql/18-main-functions.sql b/sql/18-main-functions.sql new file mode 100644 index 0000000..77ce9df --- /dev/null +++ b/sql/18-main-functions.sql @@ -0,0 +1,34 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 18-main-functions.sql +-- +-- Creates SQL functions to be used when registering new +-- games +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + + +-- Connect to the database +\c legacyworlds legacyworlds_admin + + +-- +-- Function that adds a ranking description for some ranking type in some language +-- +CREATE OR REPLACE FUNCTION main.add_ranking_description (version TEXT, name TEXT, lang TEXT, title TEXT, description TEXT) RETURNS VOID AS $$ + INSERT INTO main.ranking_text (ranking, lang, name, description) VALUES ( + (SELECT id FROM main.ranking_def WHERE version=$1 AND name=$2), $3, $4, $5) +$$ LANGUAGE SQL; + + +-- +-- Function that registers a game +-- +CREATE OR REPLACE FUNCTION main.register_game (version TEXT, game_name TEXT) RETURNS VOID AS $$ + INSERT INTO main.ranking_game (ranking, game) + SELECT id, $2 FROM main.ranking_def + WHERE version = $1; +$$ LANGUAGE SQL; diff --git a/sql/19-main-values.sql b/sql/19-main-values.sql new file mode 100644 index 0000000..93f77cf --- /dev/null +++ b/sql/19-main-values.sql @@ -0,0 +1,140 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 19-main-values.sql +-- +-- Insert data into some of the main tables +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + + +-- Connect to the database in ADMIN mode +\c legacyworlds legacyworlds_admin + + +-- +-- Peacekeepers AI +-- + +INSERT INTO main.account (name, email, password, status, vac_credits, last_login, last_logout, admin) + VALUES ('AI>Peacekeepers', 'not-a-valid-email@not-a-valid-domain.com', + '[-jv&VCR3B-b}F75qS["lpBDk8C[v~DU78Oc}=6WROXt)b+&U7[ZbNRb[d0', 'STD', 240, + UNIX_TIMESTAMP(NOW()), UNIX_TIMESTAMP(NOW()) + 1, 't'); + + +-- +-- Words banned from the manual's index +-- +COPY main.man_index_ban FROM STDIN; +en - +en a +en am +en an +en as +en at +en and +en are +en be +en being +en by +en for +en had +en has +en have +en he +en if +en in +en into +en is +en it +en its +en it's +en of +en on +en or +en out +en she +en that +en the +en then +en these +en this +en those +en thus +en to +en was +en were +en with +en what +en when +en where +en which +en who +en you +en your +\. + + +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*)*
    • +\\[manual\\](.*?)\\[\\/manual\\] $1 +\\[manual=(\\w+)(#\\w+)?\\](.*?)\\[\\/manual\\] $3 +\\[topic=(\\d+)\\](.*?)\\[\\/topic\\] $2 +\. + + +-- Connect to the database in USER mode +\c legacyworlds legacyworlds + + +-- +-- Default values for user preferences +-- +COPY main.user_preferences (id, version, value) FROM STDIN; +colour main red +font_size main 2 +forums_nitems main 20 +forums_ntopics main 20 +forums_reversed main 1 +forums_threaded main 1 +forum_code main 1 +smileys main 1 +tooltips main 2 +\. diff --git a/sql/20-credits.sql b/sql/20-credits.sql new file mode 100644 index 0000000..86295b7 --- /dev/null +++ b/sql/20-credits.sql @@ -0,0 +1,18 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 20-credits.sql +-- +-- Initialises the tables that store credits +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +CREATE TABLE main.credits ( + account BIGINT NOT NULL PRIMARY KEY REFERENCES main.account (id) ON DELETE CASCADE, + resources_used INT NOT NULL DEFAULT 0 CHECK(resources_used >= 0), + credits_obtained INT NOT NULL DEFAULT 9000 +); + +GRANT SELECT,INSERT,UPDATE ON TABLE main.credits TO legacyworlds; diff --git a/sql/25-ctf-maps.sql b/sql/25-ctf-maps.sql new file mode 100644 index 0000000..bf16c3d --- /dev/null +++ b/sql/25-ctf-maps.sql @@ -0,0 +1,57 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 25-ctf-maps.sql +-- +-- Create the tables for predefined maps. +-- +-- Copyright(C) 2004-2008, DeepClone Development +-- -------------------------------------------------------- + + +-- Connect to the database in ADMIN mode +\c legacyworlds legacyworlds_admin + + +-- +-- Create the definition table +-- + +CREATE TABLE main.ctf_map_def ( + id SERIAL PRIMARY KEY, + name VARCHAR(32) NOT NULL UNIQUE, + description TEXT, + alliances INT NOT NULL CHECK(alliances > 1), + width INT NOT NULL CHECK(width > 1), + height INT NOT NULL CHECK(height > 1) +); + +GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE main.ctf_map_def TO legacyworlds; +GRANT SELECT,UPDATE ON main.ctf_map_def_id_seq TO legacyworlds; + + +-- +-- Create the map table +-- +-- sys_type is either 'S' for normal systems or '1' .. '4' for nebulae +-- alloc_for is either 0 (target) or a number that corresponds to an alliance +-- + +CREATE TABLE main.ctf_map_layout ( + map INT NOT NULL REFERENCES main.ctf_map_def (id) ON DELETE CASCADE, + sys_x INT NOT NULL, + sys_y INT NOT NULL, + sys_type CHAR(1) NOT NULL CHECK(sys_type IN ('S', '1', '2', '3', '4')), + alloc_for INT, + spawn_here BOOLEAN, + + CHECK( (sys_type = 'S' AND alloc_for IS NOT NULL) + OR (sys_type <> 'S' AND alloc_for IS NULL) ), + + CHECK( ((alloc_for IS NULL OR alloc_for = 0) AND spawn_here IS NULL) + OR (alloc_for > 0 AND spawn_here IS NOT NULL) ), + + PRIMARY KEY(map, sys_x, sys_y) +); + +GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE main.ctf_map_layout TO legacyworlds; diff --git a/sql/25-death-of-rats.sql b/sql/25-death-of-rats.sql new file mode 100644 index 0000000..61c4d77 --- /dev/null +++ b/sql/25-death-of-rats.sql @@ -0,0 +1,161 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 25-death-of-rats.sql +-- +-- Tables for the AMS +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + + +-- +-- Execution logs +-- +CREATE TABLE main.dor_exec ( + ts INT NOT NULL PRIMARY KEY, + entries INT NOT NULL CHECK(entries >= 0), + events INT NOT NULL CHECK(events >= 0) +); +GRANT SELECT,INSERT,DELETE ON main.dor_exec TO legacyworlds; + + +-- +-- Single player log +-- +CREATE TABLE main.dor_single( + message VARCHAR(10) NOT NULL, + account BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + ts INT NOT NULL +); + +CREATE INDEX dor_single_message ON main.dor_single (message); +CREATE INDEX dor_single_account ON main.dor_single (account); +CREATE INDEX dor_single_ts ON main.dor_single (ts); + +GRANT SELECT, INSERT, DELETE ON main.dor_single TO legacyworlds; + + +-- +-- Multiplayer log +-- +CREATE TABLE main.dor_multi( + message VARCHAR(10) NOT NULL, + account1 BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + account2 BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + ts INT NOT NULL +); + +CREATE INDEX dor_multi_message ON main.dor_multi (message); +CREATE INDEX dor_multi_account1 ON main.dor_multi (account1); +CREATE INDEX dor_multi_account2 ON main.dor_multi (account2); +CREATE INDEX dor_multi_ts ON main.dor_multi (ts); + +GRANT SELECT, INSERT, DELETE ON main.dor_multi TO legacyworlds; + + +-- +-- Log of people who try to connect with banned accounts +-- +CREATE TABLE main.banned_attempt ( + ip_addr VARCHAR(15) NOT NULL, + ts INT NOT NULL DEFAULT UNIX_TIMESTAMP(NOW()) +); +CREATE INDEX banned_attempt_ip_addr ON main.banned_attempt (ip_addr); +CREATE INDEX banned_attempt_ts ON main.banned_attempt (ts); +GRANT SELECT, INSERT, DELETE ON main.banned_attempt TO legacyworlds; + + +-- +-- Log of password changes +-- +CREATE TABLE main.pass_change ( + account BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + ts INT NOT NULL DEFAULT UNIX_TIMESTAMP(NOW()), + old_pass VARCHAR(64) NOT NULL, + new_pass VARCHAR(64) NOT NULL +); +CREATE INDEX pass_change_account ON main.pass_change (account); +CREATE INDEX pass_change_ts ON main.pass_change (ts); +GRANT SELECT, INSERT, DELETE ON main.pass_change TO legacyworlds; + + +-- +-- Single player "badness points" +-- +CREATE TABLE main.dor_single_points ( + account BIGINT NOT NULL PRIMARY KEY REFERENCES main.account (id) ON DELETE CASCADE, + points INT NOT NULL CHECK(points > 0) +); +GRANT SELECT, INSERT, DELETE ON main.dor_single_points TO legacyworlds; + +-- +-- Multiplayer "badness points" +-- +CREATE TABLE main.dor_multi_points ( + account1 BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + account2 BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + points INT NOT NULL CHECK(points > 0), + PRIMARY KEY (account1, account2) +); +GRANT SELECT, INSERT, DELETE ON main.dor_multi_points TO legacyworlds; + + +-- +-- Punishments +-- +CREATE TABLE main.dor_punishment ( + account BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + other_account BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + ts INT NOT NULL, + PRIMARY KEY(account,ts) +); +CREATE INDEX dor_punishment_ts ON main.dor_punishment (ts); +CREATE INDEX dor_punishment_oaccount ON main.dor_punishment (other_account); +GRANT SELECT, INSERT, DELETE ON main.dor_punishment TO legacyworlds; + + +-- +-- Warnings +-- +CREATE TABLE main.dor_warning ( + account1 BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + account2 BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + ts INT NOT NULL, + PRIMARY KEY(account1, account2) +); +CREATE INDEX dor_warning_ts ON main.dor_warning (ts); +GRANT SELECT, INSERT, DELETE ON main.dor_warning TO legacyworlds; + + +-- +-- In-game checks +-- +CREATE TABLE main.dor_ingame_check ( + account1 BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + account2 BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + message VARCHAR(16) NOT NULL, + ts INT NOT NULL, + game VARCHAR(10) NOT NULL +); + +CREATE INDEX dor_ingame_check_ac1 ON main.dor_ingame_check (account1); +CREATE INDEX dor_ingame_check_ac2 ON main.dor_ingame_check (account2); +CREATE INDEX dor_ingame_check_game ON main.dor_ingame_check (game); +CREATE INDEX dor_ingame_check_message ON main.dor_ingame_check (message); +CREATE INDEX dor_ingame_check_ts ON main.dor_ingame_check (ts); + +GRANT SELECT, INSERT, DELETE ON main.dor_ingame_check TO legacyworlds; + + +-- +-- Final badness points +-- +CREATE TABLE main.dor_final_points ( + account1 BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + account2 BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + points INT NOT NULL CHECK(points > 0), + PRIMARY KEY(account1, account2) +); +GRANT SELECT, INSERT, DELETE ON main.dor_final_points TO legacyworlds; diff --git a/sql/25-predefined-alliances.sql b/sql/25-predefined-alliances.sql new file mode 100644 index 0000000..d1fc26a --- /dev/null +++ b/sql/25-predefined-alliances.sql @@ -0,0 +1,42 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 25-predefined-alliances.sql +-- +-- Create the table for predefined alliances and insert +-- its contents. +-- +-- Copyright(C) 2004-2008, DeepClone Development +-- -------------------------------------------------------- + + +-- Connect to the database in ADMIN mode +\c legacyworlds legacyworlds_admin + + +-- +-- Create the definition table +-- + +CREATE TABLE main.default_alliance ( + tag VARCHAR(5) NOT NULL PRIMARY KEY, + name VARCHAR(64) NOT NULL, + html_color CHAR(6) NOT NULL UNIQUE +); + +GRANT SELECT ON main.default_alliance TO legacyworlds; + + +-- +-- Insert default alliances +-- +COPY main.default_alliance FROM STDIN; +-R- Red team ff0000 +-G- Green team 00ff00 +-B- Blue team 0000ff +-C- Teal team 007f7f +-P- Purple team 7f007f +-Y- Yellow team afaf00 +-O- Orange team ffaf3f +-A- Aquamarine team 003f7f +\. diff --git a/sql/30-beta5.sql b/sql/30-beta5.sql new file mode 100644 index 0000000..9ed8f00 --- /dev/null +++ b/sql/30-beta5.sql @@ -0,0 +1,13 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 30-beta5.sql +-- +-- Run the Beta 5 SQL scripts +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + +\i beta5/00-beta5.sql +\i beta5/10-beta5-b5.sql +\i beta5/11-beta5-b5m0.sql diff --git a/sql/50-beta6-planet-pictures.sql b/sql/50-beta6-planet-pictures.sql new file mode 100644 index 0000000..6b4de8a --- /dev/null +++ b/sql/50-beta6-planet-pictures.sql @@ -0,0 +1,33 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 50-beta6-planet-pictures.sql +-- +-- Tables that store votes about Beta 6's planet pictures +-- +-- Copyright(C) 2004-2008, DeepClone Development +-- -------------------------------------------------------- + + +-- Connect to the database in ADMIN mode +\c legacyworlds legacyworlds_admin + + +CREATE TABLE main.b6_planet_pics ( + id SERIAL PRIMARY KEY, + p_size INT NOT NULL CHECK(p_size BETWEEN 1 AND 10), + p_type INT NOT NULL CHECK(p_type BETWEEN 0 AND 4) +); + +GRANT SELECT,INSERT ON TABLE main.b6_planet_pics TO legacyworlds; +GRANT SELECT,UPDATE ON main.b6_planet_pics_id_seq TO legacyworlds; + + +CREATE TABLE main.b6_planet_votes ( + account BIGINT NOT NULL REFERENCES main.account (id), + picture INT NOT NULL REFERENCES main.b6_planet_pics (id), + vote INT NOT NULL CHECK(vote BETWEEN 1 AND 5), + PRIMARY KEY(account, picture) +); + +GRANT SELECT,INSERT ON TABLE main.b6_planet_votes TO legacyworlds; diff --git a/sql/INSTALL.sql b/sql/INSTALL.sql new file mode 100644 index 0000000..742e5aa --- /dev/null +++ b/sql/INSTALL.sql @@ -0,0 +1,25 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- INSTALL.sql +-- +-- Install the Legacy Worlds database +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + +\i 00-init.sql +\i 01-inheritance.sql +\i 10-main.sql +\i 11-main-enums.sql +\i 12-main-tables.sql +\i 13-main-donations.sql +\i 13-main-forums.sql +\i 13-main-links.sql +\i 13-main-manual.sql +\i 13-main-proxy.sql +\i 18-main-functions.sql +\i 19-main-values.sql +\i 25-ctf-maps.sql +\i 25-predefined-alliances.sql +\i 30-beta5.sql diff --git a/sql/beta5/00-beta5.sql b/sql/beta5/00-beta5.sql new file mode 100644 index 0000000..f98d370 --- /dev/null +++ b/sql/beta5/00-beta5.sql @@ -0,0 +1,46 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/00-beta5.sql +-- +-- Install the Beta 5-specific data into the main database +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +\c legacyworlds legacyworlds_admin + +SET search_path=main; + +COPY ranking_def (version, name, more) FROM STDIN; +beta5 p_general FALSE +beta5 a_general TRUE +beta5 p_financial FALSE +beta5 p_military FALSE +beta5 p_civ FALSE +beta5 p_round FALSE +beta5 p_idr FALSE +\. + + +SELECT add_ranking_description('beta5', 'p_general', 'en', 'General Ranking', + 'This ranking corresponds to a combination of civilisation, military and financial rankings. It represents a ' || + 'player''s current overall advancement and strength in the game.'); +SELECT add_ranking_description('beta5', 'a_general', 'en', 'Alliance Ranking', + 'An alliance''s ranking indicates its overall strength; the points for an alliance are the sum of the general ' || + 'ranking points for all of its members.'); +SELECT add_ranking_description('beta5', 'p_financial', 'en', 'Financial Ranking', + 'This ranking corresponds to the economic health of a player''s empire. It takes into account the player''s ' || + 'banked cash, his income and the number of industrial factories he owns.'); +SELECT add_ranking_description('beta5', 'p_military', 'en', 'Military Ranking', + 'This ranking allows to assess the military strength of a player''s empire. Its calculation is based on the ' || + 'number of turrets and military factories the player owns along with his fleet fire power.'); +SELECT add_ranking_description('beta5', 'p_civ', 'en', 'Civilization Ranking', + 'This ranking represents the advancement level of the society in a player''s empire. It takes into account ' || + 'technology level, population and happiness.'); +SELECT add_ranking_description('beta5', 'p_round', 'en', 'Overall Round Ranking', + 'This ranking is calculated based on players'' previous general rankings for the top 15 players. It allows ' || + 'for a long term estimate of the bets players'' accomplishments.'); +SELECT add_ranking_description('beta5', 'p_idr', 'en', 'Inflicted Damage Ranking', + 'This ranking represents the amount of damage a player has inflicted on other players'' fleets.'); diff --git a/sql/beta5/10-beta5-b5.sql b/sql/beta5/10-beta5-b5.sql new file mode 100644 index 0000000..7704603 --- /dev/null +++ b/sql/beta5/10-beta5-b5.sql @@ -0,0 +1,56 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/10-beta5.sql +-- +-- Install the Beta 5 'b5' game database +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- Connect as administrator +\c legacyworlds legacyworlds_admin + +-- Create the game's schema +CREATE SCHEMA "b5"; +GRANT USAGE ON SCHEMA "b5" TO legacyworlds; + +-- Include the files defining the game's database structure +SET search_path="b5",main,public; +\i beta5/structure/00-gdata.sql +\i beta5/structure/00-system.sql +\i beta5/structure/00-ecm-eccm.sql +\i beta5/structure/00-rule-base.sql +\i beta5/structure/00-player-table.sql +\i beta5/structure/01-research-base.sql +\i beta5/structure/01-alliance.sql +\i beta5/structure/01-planet.sql +\i beta5/structure/01-player-dipl.sql +\i beta5/structure/01-player-rules.sql +\i beta5/structure/01-message-base.sql +\i beta5/structure/02-alliance-forums.sql +\i beta5/structure/02-orders.sql +\i beta5/structure/02-warehouse.sql +\i beta5/structure/03-fleets.sql +\i beta5/structure/04-sales.sql +\i beta5/structure/05-message-player.sql +\i beta5/structure/06-message-internal.sql +\i beta5/structure/06-message-battle.sql +\i beta5/structure/99-player-fk.sql + +-- Include the file containing the game's read-only data +\i beta5/data/standard.sql + +-- Include the file containing initial game data in USER mode +\c - legacyworlds +SET search_path="b5",main,public; +\i beta5/data/game.sql + +-- Finalise the structure +\c - legacyworlds_admin +SET search_path="b5",main,public; +\i beta5/structure/finalise.sql + +-- Register the game itself +SELECT register_game ('beta5', 'beta5'); diff --git a/sql/beta5/11-beta5-b5m0.sql b/sql/beta5/11-beta5-b5m0.sql new file mode 100644 index 0000000..785bf8e --- /dev/null +++ b/sql/beta5/11-beta5-b5m0.sql @@ -0,0 +1,56 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/11-beta5-b5m0.sql +-- +-- Install the Beta 5 'b5m0' game database +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- Connect as administrator +\c legacyworlds legacyworlds_admin + +-- Create the game's schema +CREATE SCHEMA "b5m0"; +GRANT USAGE ON SCHEMA "b5m0" TO legacyworlds; + +-- Include the files defining the game's database structure +SET search_path="b5m0",main,public; +\i beta5/structure/00-gdata.sql +\i beta5/structure/00-system.sql +\i beta5/structure/00-ecm-eccm.sql +\i beta5/structure/00-rule-base.sql +\i beta5/structure/00-player-table.sql +\i beta5/structure/01-research-base.sql +\i beta5/structure/01-alliance.sql +\i beta5/structure/01-planet.sql +\i beta5/structure/01-player-dipl.sql +\i beta5/structure/01-player-rules.sql +\i beta5/structure/01-message-base.sql +\i beta5/structure/02-alliance-forums.sql +\i beta5/structure/02-orders.sql +\i beta5/structure/02-warehouse.sql +\i beta5/structure/03-fleets.sql +\i beta5/structure/04-sales.sql +\i beta5/structure/05-message-player.sql +\i beta5/structure/06-message-internal.sql +\i beta5/structure/06-message-battle.sql +\i beta5/structure/99-player-fk.sql + +-- Include the file containing the game's read-only data +\i beta5/data/match.sql + +-- Include the file containing initial game data in USER mode +\c - legacyworlds +SET search_path="b5m0",main,public; +\i beta5/data/game.sql + +-- Finalise the structure +\c - legacyworlds_admin +SET search_path="b5m0",main,public; +\i beta5/structure/finalise.sql + +-- Register the game itself +SELECT register_game ('beta5', 'b5match'); diff --git a/sql/beta5/beta5-ctf.sql b/sql/beta5/beta5-ctf.sql new file mode 100644 index 0000000..7de4ba0 --- /dev/null +++ b/sql/beta5/beta5-ctf.sql @@ -0,0 +1,61 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/beta5-match.sql +-- +-- Generic installation script for matches, to be parsed +-- by administrative scripts +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- Connect as administrator +\c legacyworlds legacyworlds_admin + +-- Create the game's schema +CREATE SCHEMA "b5mX"; +GRANT USAGE ON SCHEMA "b5mX" TO legacyworlds; + +-- Include the files defining the game's database structure +SET search_path="b5mX",main,public; +\i beta5/structure/00-gdata.sql +\i beta5/structure/00-system.sql +\i beta5/structure/00-ecm-eccm.sql +\i beta5/structure/00-rule-base.sql +\i beta5/structure/00-player-table.sql +\i beta5/structure/01-research-base.sql +\i beta5/structure/01-alliance.sql +\i beta5/structure/01-planet.sql +\i beta5/structure/01-player-dipl.sql +\i beta5/structure/01-player-rules.sql +\i beta5/structure/01-message-base.sql +\i beta5/structure/02-alliance-forums.sql +\i beta5/structure/02-alliance-tech.sql +\i beta5/structure/02-orders.sql +\i beta5/structure/02-warehouse.sql +\i beta5/structure/03-fleets.sql +\i beta5/structure/04-sales.sql +\i beta5/structure/05-message-player.sql +\i beta5/structure/06-message-internal.sql +\i beta5/structure/06-message-battle.sql +\i beta5/structure/07-message-admin.sql +\i beta5/structure/07-beacons.sql +\i beta5/structure/10-ctf-tables.sql +\i beta5/structure/99-player-fk.sql + +-- Include the file containing the game's read-only data +\i beta5/data/match.sql + +-- Include the file containing initial game data in USER mode +\c - legacyworlds +SET search_path="b5mX",main,public; +\i beta5/data/game.sql + +-- Finalise the structure +\c - legacyworlds_admin +SET search_path="b5mX",main,public; +\i beta5/structure/finalise.sql + +-- Register the game itself +SELECT register_game ('beta5', 'b5mX'); diff --git a/sql/beta5/beta5-match.sql b/sql/beta5/beta5-match.sql new file mode 100644 index 0000000..8167a79 --- /dev/null +++ b/sql/beta5/beta5-match.sql @@ -0,0 +1,60 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/beta5-match.sql +-- +-- Generic installation script for matches, to be parsed +-- by administrative scripts +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- Connect as administrator +\c legacyworlds legacyworlds_admin + +-- Create the game's schema +CREATE SCHEMA "b5mX"; +GRANT USAGE ON SCHEMA "b5mX" TO legacyworlds; + +-- Include the files defining the game's database structure +SET search_path="b5mX",main,public; +\i beta5/structure/00-gdata.sql +\i beta5/structure/00-system.sql +\i beta5/structure/00-ecm-eccm.sql +\i beta5/structure/00-rule-base.sql +\i beta5/structure/00-player-table.sql +\i beta5/structure/01-research-base.sql +\i beta5/structure/01-alliance.sql +\i beta5/structure/01-planet.sql +\i beta5/structure/01-player-dipl.sql +\i beta5/structure/01-player-rules.sql +\i beta5/structure/01-message-base.sql +\i beta5/structure/02-alliance-forums.sql +\i beta5/structure/02-alliance-tech.sql +\i beta5/structure/02-orders.sql +\i beta5/structure/02-warehouse.sql +\i beta5/structure/03-fleets.sql +\i beta5/structure/04-sales.sql +\i beta5/structure/05-message-player.sql +\i beta5/structure/06-message-internal.sql +\i beta5/structure/06-message-battle.sql +\i beta5/structure/07-message-admin.sql +\i beta5/structure/07-beacons.sql +\i beta5/structure/99-player-fk.sql + +-- Include the file containing the game's read-only data +\i beta5/data/match.sql + +-- Include the file containing initial game data in USER mode +\c - legacyworlds +SET search_path="b5mX",main,public; +\i beta5/data/game.sql + +-- Finalise the structure +\c - legacyworlds_admin +SET search_path="b5mX",main,public; +\i beta5/structure/finalise.sql + +-- Register the game itself +SELECT register_game ('beta5', 'b5mX'); diff --git a/sql/beta5/beta5-round.sql b/sql/beta5/beta5-round.sql new file mode 100644 index 0000000..5be3105 --- /dev/null +++ b/sql/beta5/beta5-round.sql @@ -0,0 +1,61 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/beta5-round.sql +-- +-- Generic installation script for rounds, to be parsed +-- by administrative scripts +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- Connect as administrator +\c legacyworlds legacyworlds_admin + +-- Create the game's schema +CREATE SCHEMA "b5rX"; +GRANT USAGE ON SCHEMA "b5rX" TO legacyworlds; + +-- Include the files defining the game's database structure +SET search_path="b5rX",main,public; +\i beta5/structure/00-gdata.sql +\i beta5/structure/00-system.sql +\i beta5/structure/00-ecm-eccm.sql +\i beta5/structure/00-rule-base.sql +\i beta5/structure/00-player-table.sql +\i beta5/structure/01-research-base.sql +\i beta5/structure/01-alliance.sql +\i beta5/structure/01-planet.sql +\i beta5/structure/01-player-dipl.sql +\i beta5/structure/01-player-rules.sql +\i beta5/structure/01-message-base.sql +\i beta5/structure/02-alliance-forums.sql +\i beta5/structure/02-alliance-tech.sql +\i beta5/structure/02-orders.sql +\i beta5/structure/02-warehouse.sql +\i beta5/structure/03-fleets.sql +\i beta5/structure/04-sales.sql +\i beta5/structure/05-message-player.sql +\i beta5/structure/06-message-internal.sql +\i beta5/structure/06-message-battle.sql +\i beta5/structure/07-message-admin.sql +\i beta5/structure/07-beacons.sql +\i beta5/structure/10-prot-tables.sql +\i beta5/structure/99-player-fk.sql + +-- Include the file containing the game's read-only data +\i beta5/data/standard.sql + +-- Include the file containing initial game data in USER mode +\c - legacyworlds +SET search_path="b5rX",main,public; +\i beta5/data/game.sql + +-- Finalise the structure +\c - legacyworlds_admin +SET search_path="b5rX",main,public; +\i beta5/structure/finalise.sql + +-- Register the game itself +SELECT register_game ('beta5', 'b5rX'); diff --git a/sql/beta5/data/game.sql b/sql/beta5/data/game.sql new file mode 100644 index 0000000..0956d19 --- /dev/null +++ b/sql/beta5/data/game.sql @@ -0,0 +1,15 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/game.sql +-- +-- Beta 5 games: +-- User-mode data insertion +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- Universe generator parameters +INSERT INTO gdata (id, value) VALUES ('sg_dir', '0'); +INSERT INTO gdata (id, value) VALUES ('sg_len', '0'); diff --git a/sql/beta5/data/match.sql b/sql/beta5/data/match.sql new file mode 100644 index 0000000..fb8598d --- /dev/null +++ b/sql/beta5/data/match.sql @@ -0,0 +1,602 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/data/readonly.sql +-- +-- Beta 5 games: +-- Read-only data insertion +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- ECM probability table +COPY ecm FROM STDIN; +0 1 0 +0 2 0 +0 3 0 +0 4 1 +0 5 99 +1 1 0 +1 2 0 +1 3 10 +1 4 70 +1 5 20 +2 1 0 +2 2 10 +2 3 70 +2 4 15 +2 5 5 +3 1 5 +3 2 70 +3 3 15 +3 4 8 +3 5 2 +4 1 60 +4 2 30 +4 3 6 +4 4 4 +4 5 0 +\. + +-- ECCM probability table +COPY eccm FROM STDIN; +0 0 99 +0 1 1 +0 2 0 +0 3 0 +0 4 0 +1 0 20 +1 1 70 +1 2 10 +1 3 0 +1 4 0 +2 0 10 +2 1 20 +2 2 60 +2 3 10 +2 4 0 +3 0 5 +3 1 10 +3 2 20 +3 3 60 +3 4 5 +4 0 0 +4 1 5 +4 2 10 +4 3 40 +4 4 45 +\. + + + +-- Default rules +COPY rule_def FROM stdin; +military_level 0 +pop_growth_factor 2 +if_productivity 30 +if_productivity_factor 10 +base_income 3 +factory_cost 10 +turret_cost 20 +mf_productivity 12 +mf_productivity_factor 10 +workunits_turret 1500 +workunits_gaship 4500 +workunits_fighter 2000 +workunits_cruiser 20000 +workunits_bcruiser 22000 +build_cost_turret 400 +build_cost_gaship 750 +build_cost_fighter 500 +build_cost_cruiser 5000 +build_cost_bcruiser 15000 +if_cost 400 +mf_cost 400 +battle_losses 100 +unhappiness_factor 95 +effective_fleet_power 100 +planet_max_pop 10000 +capital_ship_speed 1 +prevent_hs_exit 0 +planet_destruction 0 +turret_power 10 +gaship_power 5 +fighter_power 10 +cruiser_power 40 +bcruiser_power 80 +gaship_upkeep 40 +fighter_upkeep 50 +cruiser_upkeep 500 +bcruiser_upkeep 1500 +research_percent 350 +gaship_space 3 +fighter_space 1 +cruiser_haul 20 +bcruiser_haul 15 +ecm_level 0 +eccm_level 0 +gaship_pop 200 +hs_beacon_level 0 +probe_tech 0 +\. + + +-- Rule handlers +COPY rule_handler FROM STDIN; +unhappiness_factor UpdateHappiness +planet_max_pop UpdateMaxPopulation +effective_fleet_power UpdateFleetPower +gaship_power UpdateFleetPower +fighter_power UpdateFleetPower +cruiser_power UpdateFleetPower +bcruiser_power UpdateFleetPower +ecm_level UpdateCommTech +eccm_level UpdateCommTech +\. + + +-- Research definitions +COPY research FROM STDIN; +1 7000 50000 0 1 FALSE +2 5000 15000 0 0 FALSE +3 4000 20000 0 2 FALSE +4 6000 30000 0 2 FALSE +5 13333 5000 0 1 TRUE +6 29000 300000 0 1 FALSE +7 44667 50000 0 2 TRUE +8 10333 40000 0 2 FALSE +9 10333 40000 0 2 FALSE +10 18000 50000 0 2 FALSE +11 18000 50000 0 0 FALSE +12 38000 80000 2 2 FALSE +13 57667 15000 1 2 TRUE +14 54000 100000 1 0 FALSE +15 37554 100000 1 2 FALSE +16 14667 100000 0 0 FALSE +17 34889 100000 1 0 FALSE +18 21777 150000 1 0 FALSE +20 99628 500000 1 2 FALSE +21 211873 1000000 2 2 FALSE +22 99628 500000 1 0 FALSE +23 51519 30000 1 0 TRUE +24 28777 100000 1 2 FALSE +25 87925 500000 1 0 FALSE +26 23777 100000 2 1 FALSE +27 135777 500000 1 0 FALSE +28 241036 800000 1 0 FALSE +29 112000 200000 1 2 FALSE +30 68000 1500000 2 2 FALSE +31 44000 400000 1 2 FALSE +32 68667 60000 1 2 TRUE +33 300000 400000 2 2 FALSE +34 174333 80000 1 2 TRUE +35 127036 800000 2 2 FALSE +36 72146 600000 1 2 FALSE +37 68369 300000 1 2 FALSE +38 98072 500000 1 1 FALSE +39 212466 700000 2 1 FALSE +40 172837 1000000 1 0 FALSE +41 280449 2000000 1 0 FALSE +42 554218 1500000 1 2 FALSE +43 860476 100000 1 0 TRUE +44 49036 500000 2 1 FALSE +45 49036 500000 1 1 FALSE +46 112048 1000000 2 2 FALSE +48 238218 1500000 1 1 FALSE +49 238218 1500000 1 1 FALSE +50 160195 800000 1 2 FALSE +51 302260 1500000 2 1 FALSE +52 875013 3000000 2 1 FALSE +53 342195 1500000 1 0 FALSE +54 839548 2000000 1 1 FALSE +55 517576 2000000 1 2 FALSE +56 740101 70000 1 2 TRUE +57 263996 1500000 1 0 FALSE +58 241036 1500000 1 0 FALSE +59 403005 120000 1 2 TRUE +61 136195 500000 1 1 FALSE +62 1106581 2500000 1 2 FALSE +63 583758 2500000 1 1 FALSE +64 628073 5000000 1 1 FALSE +65 628073 5000000 1 1 FALSE +68 319418 2000000 1 1 FALSE +69 1019937 150000 1 1 TRUE +70 1949373 5000000 1 0 FALSE +71 630811 4000000 2 1 FALSE +72 529228 2500000 2 0 FALSE +73 965308 3800000 1 2 FALSE +74 1295217 4000000 1 2 FALSE +75 2537057 7000000 1 2 FALSE +76 3725240 200000 1 2 TRUE +77 2228337 10000000 1 1 FALSE +78 1251897 4000000 1 2 FALSE +79 845313 4000000 1 0 FALSE +80 2719164 10000000 1 1 FALSE +81 2709164 10000000 1 2 FALSE +82 3616595 12000000 1 1 FALSE +83 4321117 10000000 1 2 FALSE +84 805637 5000000 1 1 FALSE +85 2088549 8000000 1 1 FALSE +86 4642983 12000000 1 1 FALSE +87 3642219 170000 1 2 TRUE +88 7457771 15000000 1 1 FALSE +89 4000692 10000000 1 1 FALSE +90 2627540 10000000 1 1 FALSE +\. + + +-- Research dependencies +COPY research_dep FROM STDIN; +5 1 +6 1 +6 2 +16 2 +46 2 +8 3 +9 3 +17 3 +10 4 +11 4 +38 4 +7 6 +51 6 +63 6 +71 6 +15 8 +18 8 +36 8 +15 9 +24 9 +26 9 +27 9 +14 10 +30 10 +50 10 +12 11 +30 11 +31 11 +13 12 +27 14 +29 14 +20 15 +22 15 +38 15 +17 16 +20 16 +22 16 +25 16 +23 17 +43 17 +21 18 +44 18 +45 18 +68 46 +21 20 +42 20 +48 20 +76 21 +40 22 +49 22 +57 22 +25 24 +35 24 +36 24 +37 24 +72 25 +39 26 +28 27 +53 27 +58 27 +42 28 +55 28 +33 29 +34 29 +33 30 +32 31 +35 31 +52 33 +73 35 +50 36 +55 36 +61 36 +53 37 +57 37 +63 37 +39 38 +71 38 +54 39 +41 40 +64 40 +65 40 +63 41 +70 41 +73 41 +79 41 +43 42 +62 42 +74 42 +49 44 +59 44 +46 45 +48 45 +59 48 +62 48 +65 48 +64 49 +51 50 +52 51 +54 53 +74 53 +78 53 +56 55 +75 55 +72 57 +73 57 +71 58 +77 58 +79 58 +68 61 +69 61 +85 61 +70 62 +69 63 +89 63 +90 63 +86 64 +82 65 +80 70 +81 70 +82 70 +89 71 +78 72 +84 72 +83 73 +75 74 +77 74 +83 74 +85 74 +76 75 +90 78 +83 79 +89 79 +86 80 +88 80 +87 81 +88 81 +89 84 +\. + + +-- Research effects +COPY research_effect FROM STDIN; +1 military_level 1 +3 battle_losses -2 +4 if_cost 80 +4 if_productivity_factor 2 +5 if_productivity_factor -2 +5 mf_productivity_factor 3 +5 unhappiness_factor 5 +6 military_level 1 +7 effective_fleet_power -5 +7 unhappiness_factor -4 +8 if_cost 40 +8 if_productivity_factor 1 +8 mf_cost 40 +8 mf_productivity_factor 1 +9 if_cost 40 +9 if_productivity_factor 1 +9 mf_cost 40 +9 mf_productivity_factor 1 +10 pop_growth_factor 1 +12 unhappiness_factor -1 +13 if_productivity_factor -2 +13 mf_productivity_factor -3 +13 unhappiness_factor -5 +15 research_percent 10 +17 research_percent 10 +18 unhappiness_factor -2 +20 research_percent 10 +21 base_income 2 +23 base_income -1 +23 if_productivity_factor -2 +23 research_percent 25 +24 battle_losses -5 +24 if_cost 80 +24 if_productivity_factor 2 +24 mf_cost 80 +24 mf_productivity_factor 2 +26 gaship_pop 75 +29 if_cost 120 +29 if_productivity_factor 3 +30 pop_growth_factor 1 +31 if_productivity_factor -1 +31 mf_productivity_factor -1 +31 unhappiness_factor -2 +32 if_productivity_factor -1 +32 mf_productivity_factor -1 +32 unhappiness_factor -4 +33 pop_growth_factor 1 +34 pop_growth_factor 1 +34 unhappiness_factor 5 +35 planet_max_pop 10000 +36 if_cost 120 +36 if_productivity_factor 3 +36 mf_cost 120 +36 mf_productivity_factor 3 +37 battle_losses -5 +37 if_cost 80 +37 if_productivity_factor 2 +37 mf_cost 80 +37 mf_productivity_factor 2 +38 effective_fleet_power 10 +39 gaship_pop 75 +41 capital_ship_speed 1 +42 research_percent 10 +43 if_productivity_factor -3 +43 mf_productivity_factor -3 +43 research_percent 50 +44 ecm_level 1 +45 eccm_level 1 +46 hs_beacon_level 1 +48 eccm_level 1 +49 ecm_level 1 +51 battle_losses -2 +52 battle_losses -2 +53 battle_losses -2 +53 if_cost 80 +53 if_productivity_factor 2 +53 mf_cost 80 +53 mf_productivity_factor 2 +54 gaship_pop 75 +55 if_cost 80 +55 if_productivity_factor 2 +55 mf_cost 80 +55 mf_productivity_factor 2 +55 unhappiness_factor 7 +56 if_productivity_factor -2 +56 mf_productivity_factor -2 +56 unhappiness_factor -7 +57 battle_losses -4 +59 eccm_level -1 +59 ecm_level -1 +59 unhappiness_factor -4 +61 turret_power 3 +62 research_percent 10 +63 military_level 1 +64 ecm_level 1 +65 eccm_level 1 +68 turret_power 3 +69 if_productivity_factor -4 +69 mf_productivity_factor 6 +69 unhappiness_factor 10 +70 capital_ship_speed 1 +71 fighter_power 4 +71 gaship_power 2 +73 planet_max_pop 10000 +74 battle_losses -2 +74 if_cost 80 +74 if_productivity_factor 2 +74 mf_cost 80 +74 mf_productivity_factor 2 +75 if_cost 160 +75 if_productivity_factor 4 +75 mf_cost 120 +75 mf_productivity_factor 3 +76 base_income 3 +76 if_productivity_factor 10 +76 mf_productivity_factor -5 +76 research_percent -50 +76 unhappiness_factor 15 +77 battle_losses -5 +78 if_cost 80 +78 if_productivity_factor 2 +78 mf_cost 80 +78 mf_productivity_factor 2 +80 prevent_hs_exit 1 +81 if_productivity_factor -3 +81 unhappiness_factor -5 +82 eccm_level 1 +83 planet_max_pop 10000 +84 capital_ship_speed 1 +84 effective_fleet_power 5 +85 turret_power 3 +86 ecm_level 1 +87 if_cost 120 +87 if_productivity_factor 3 +87 unhappiness_factor 7 +88 planet_destruction 1 +89 bcruiser_power 20 +89 cruiser_power 10 +90 effective_fleet_power 10 +\. + + +-- Research descriptions +COPY research_txt FROM STDIN; +1 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. +2 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. +3 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. +4 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. +5 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! +6 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. +7 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. +8 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. +9 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. +10 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. +11 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. +12 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. +13 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! +14 en Cloning Techniques Sir! Their stem cells analysis have finally brought our scientists a new breakthrough. Our researchers have succeeded in cloning various lifeforms. +15 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. +16 en Quantum Gravitation These advances in the field of quantum theory will open a new area for further studies. +17 en Miniaturised Particle Colliders These new and small particle colliders increase research in many areas, increasing lab outputs. +18 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. +20 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. +21 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. +22 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. +23 en Increased Research Grants This law allows to divert a higher percentage of income towards research thus allowing faster discoveries but reducing income. +24 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. +25 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. +26 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. +27 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! +28 en Sentient Lifeform Engineering Sir! Our biologists have finally managed to create intelligent lifeforms from scratch, thus opening a brand new field of studies. +29 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! +30 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. +31 en Green Production These new production methods are safer for planetary ecology, making the population happier but slightly reducing factory productivity. +32 en Biosphere Protection Pact Enforcing this law forces the industrial sector to use greener production methods that make the population happier but reduce productivity. +33 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. +34 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... +35 en Arcologies Sir! We are now able to build arcologies, which will allow us to house loads more citizens on our empire's planets! +36 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. +37 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 resistent alloys. +38 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. +39 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.. +40 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. +41 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... +42 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. +43 en Science Golden Age Enacting this law permits us to divert more resources towards research at the expense of other necessities. +44 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. +45 en Fast Burst Transmission This new technology renders our stellar and interstellar communications less prone to jamming and interception. +46 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. +48 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. +49 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. +50 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. +51 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. +52 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. +53 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. +54 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. +55 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. +56 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. +57 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. +58 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. +59 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! +61 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. +62 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. +63 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. +64 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. +65 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. +68 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. +69 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. +70 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. +71 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. +72 en Mass Anti-matter Production Using newly acquired technologies our scientists have acheived mass production of anti-matter thus opening a brand new field of applications. +73 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. +74 en Intelligent Materials Bringing sentience to the materials they use, our scientists provide us with wonderful new ways of building stuff. +75 en Automated Factories Sir! Using sentient materials in our factories will allow us to gain production efficiency. +76 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. +77 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. +78 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. +79 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. +80 en Wormhole Collapsing Sir, our scientists have devised a new defence. 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. +81 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. +82 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. +83 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. +84 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. +85 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. +86 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. +87 en Wormhole Lockdown This law prevents your citizens from using the planetary gateways, cancelling the effects of the wormhole technology. +88 en Wormhole Super Nova 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. +89 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. +90 en Matter Anti-matter Missiles Using matter / anti-matter reactions in our warheads should greatly increase the damage caused by our ships. +\. diff --git a/sql/beta5/data/standard.sql b/sql/beta5/data/standard.sql new file mode 100644 index 0000000..5bc8cce --- /dev/null +++ b/sql/beta5/data/standard.sql @@ -0,0 +1,801 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/data/readonly.sql +-- +-- Beta 5 games: +-- Read-only data insertion +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- ECM probability table +COPY ecm FROM STDIN; +0 1 0 +0 2 0 +0 3 0 +0 4 1 +0 5 99 +1 1 0 +1 2 0 +1 3 10 +1 4 70 +1 5 20 +2 1 0 +2 2 10 +2 3 70 +2 4 15 +2 5 5 +3 1 5 +3 2 70 +3 3 15 +3 4 8 +3 5 2 +4 1 60 +4 2 30 +4 3 6 +4 4 4 +4 5 0 +\. + +-- ECCM probability table +COPY eccm FROM STDIN; +0 0 99 +0 1 1 +0 2 0 +0 3 0 +0 4 0 +1 0 20 +1 1 70 +1 2 10 +1 3 0 +1 4 0 +2 0 10 +2 1 20 +2 2 60 +2 3 10 +2 4 0 +3 0 5 +3 1 10 +3 2 20 +3 3 60 +3 4 5 +4 0 0 +4 1 5 +4 2 10 +4 3 40 +4 4 45 +\. + + +-- Default rules +COPY rule_def FROM stdin; +military_level 0 +pop_growth_factor 1 +if_productivity 30 +if_productivity_factor 10 +base_income 2 +factory_cost 10 +turret_cost 20 +mf_productivity 10 +mf_productivity_factor 10 +workunits_turret 1500 +workunits_gaship 4500 +workunits_fighter 2000 +workunits_cruiser 20000 +workunits_bcruiser 22000 +build_cost_turret 400 +build_cost_gaship 750 +build_cost_fighter 500 +build_cost_cruiser 5000 +build_cost_bcruiser 15000 +if_cost 400 +mf_cost 400 +battle_losses 100 +unhappiness_factor 100 +effective_fleet_power 100 +planet_max_pop 10000 +capital_ship_speed 1 +prevent_hs_exit 0 +planet_destruction 0 +turret_power 10 +gaship_power 5 +fighter_power 10 +cruiser_power 40 +bcruiser_power 80 +gaship_upkeep 40 +fighter_upkeep 50 +cruiser_upkeep 500 +bcruiser_upkeep 1500 +research_percent 200 +gaship_space 3 +fighter_space 1 +cruiser_haul 20 +bcruiser_haul 15 +ecm_level 0 +eccm_level 0 +gaship_pop 200 +hs_beacon_level 0 +probe_tech 0 +\. + + +-- Rule handlers +COPY rule_handler FROM STDIN; +unhappiness_factor UpdateHappiness +planet_max_pop UpdateMaxPopulation +effective_fleet_power UpdateFleetPower +gaship_power UpdateFleetPower +fighter_power UpdateFleetPower +cruiser_power UpdateFleetPower +bcruiser_power UpdateFleetPower +ecm_level UpdateCommTech +eccm_level UpdateCommTech +\. + + +-- Research definitions +COPY research FROM STDIN; +1 7000 50000 0 1 FALSE +2 5000 15000 0 0 FALSE +3 4000 20000 0 2 FALSE +4 6000 30000 0 2 FALSE +5 13333 5000 0 1 TRUE +6 29000 300000 0 1 FALSE +7 44667 50000 0 2 TRUE +8 10333 40000 0 2 FALSE +9 10333 40000 0 2 FALSE +10 18000 50000 0 2 FALSE +11 18000 50000 0 0 FALSE +12 38000 80000 2 2 FALSE +13 57667 15000 1 2 TRUE +14 54000 100000 1 0 FALSE +15 37554 100000 1 2 FALSE +16 14667 100000 0 0 FALSE +17 34889 100000 1 0 FALSE +18 21777 150000 1 0 FALSE +20 99628 500000 1 2 FALSE +21 211873 1000000 2 2 FALSE +22 99628 500000 1 0 FALSE +23 51519 30000 1 0 TRUE +24 28777 100000 1 2 FALSE +25 87925 500000 1 0 FALSE +26 23777 100000 2 1 FALSE +27 135777 500000 1 0 FALSE +28 241036 800000 1 0 FALSE +29 112000 200000 1 2 FALSE +30 68000 1500000 2 2 FALSE +31 44000 400000 1 2 FALSE +32 68667 60000 1 2 TRUE +33 300000 400000 2 2 FALSE +34 174333 80000 1 2 TRUE +35 127036 800000 2 2 FALSE +36 72146 600000 1 2 FALSE +37 68369 300000 1 2 FALSE +38 98072 500000 1 1 FALSE +39 212466 700000 2 1 FALSE +40 172837 1000000 1 0 FALSE +41 280449 2000000 1 0 FALSE +42 554218 1500000 1 2 FALSE +43 860476 100000 1 0 TRUE +44 49036 500000 2 1 FALSE +45 49036 500000 1 1 FALSE +46 112048 1000000 2 1 FALSE +48 238218 1500000 1 1 FALSE +49 238218 1500000 1 1 FALSE +50 160195 800000 1 2 FALSE +51 302260 1500000 2 1 FALSE +52 875013 3000000 2 1 FALSE +53 342195 1500000 1 0 FALSE +54 839548 2000000 1 1 FALSE +55 517576 2000000 1 2 FALSE +56 740101 70000 1 2 TRUE +57 263996 1500000 1 0 FALSE +58 241036 1500000 1 0 FALSE +59 403005 120000 1 2 TRUE +60 307222 3000000 2 1 FALSE +61 136195 500000 1 1 FALSE +62 1106581 2500000 1 2 FALSE +63 583758 2500000 1 1 FALSE +64 628073 5000000 1 1 FALSE +65 628073 5000000 1 1 FALSE +68 319418 2000000 1 1 FALSE +69 1019937 150000 1 1 TRUE +70 1949373 5000000 1 0 FALSE +71 630811 4000000 2 1 FALSE +72 529228 2500000 2 0 FALSE +73 965308 3800000 1 2 FALSE +74 1295217 4000000 1 2 FALSE +75 2537057 7000000 1 2 FALSE +76 3725240 200000 1 2 TRUE +77 2228337 10000000 1 1 FALSE +78 1251897 4000000 1 2 FALSE +79 845313 4000000 1 0 FALSE +80 2719164 10000000 1 1 FALSE +81 2709164 10000000 1 2 FALSE +82 3616595 12000000 1 1 FALSE +83 4321117 10000000 1 2 FALSE +84 805637 5000000 1 1 FALSE +85 2088549 8000000 1 1 FALSE +86 4642983 12000000 1 1 FALSE +87 3642219 170000 1 2 TRUE +88 7457771 15000000 1 1 FALSE +89 4000692 10000000 1 1 FALSE +90 2627540 10000000 1 1 FALSE +\. + + +-- Research dependencies +COPY research_dep FROM STDIN; +5 1 +6 1 +6 2 +16 2 +46 2 +8 3 +9 3 +17 3 +10 4 +11 4 +38 4 +7 6 +51 6 +63 6 +71 6 +15 8 +18 8 +36 8 +15 9 +24 9 +26 9 +27 9 +14 10 +30 10 +50 10 +12 11 +30 11 +31 11 +13 12 +27 14 +29 14 +20 15 +22 15 +38 15 +17 16 +20 16 +22 16 +25 16 +23 17 +43 17 +21 18 +44 18 +45 18 +68 46 +21 20 +42 20 +48 20 +76 21 +40 22 +49 22 +57 22 +25 24 +35 24 +36 24 +37 24 +72 25 +39 26 +28 27 +53 27 +58 27 +42 28 +55 28 +33 29 +34 29 +33 30 +32 31 +35 31 +52 33 +73 35 +50 36 +55 36 +61 36 +53 37 +57 37 +63 37 +39 38 +71 38 +54 39 +41 40 +64 40 +65 40 +63 41 +70 41 +73 41 +79 41 +43 42 +62 42 +74 42 +49 44 +59 44 +46 45 +48 45 +59 48 +62 48 +65 48 +64 49 +51 50 +52 51 +54 53 +74 53 +78 53 +56 55 +75 55 +72 57 +73 57 +71 58 +77 58 +79 58 +68 61 +69 61 +85 61 +70 62 +69 63 +89 63 +90 63 +86 64 +82 65 +80 70 +81 70 +82 70 +89 71 +78 72 +84 72 +83 73 +75 74 +77 74 +83 74 +85 74 +76 75 +90 78 +83 79 +89 79 +86 80 +88 80 +87 81 +88 81 +89 84 +60 46 +60 22 +\. + + +-- Research effects +COPY research_effect FROM STDIN; +1 military_level 1 +3 battle_losses -2 +4 if_cost 80 +4 if_productivity_factor 2 +5 if_productivity_factor -2 +5 mf_productivity_factor 3 +5 unhappiness_factor 5 +6 military_level 1 +7 effective_fleet_power -5 +7 unhappiness_factor -4 +8 if_cost 40 +8 if_productivity_factor 1 +8 mf_cost 40 +8 mf_productivity_factor 1 +9 if_cost 40 +9 if_productivity_factor 1 +9 mf_cost 40 +9 mf_productivity_factor 1 +10 pop_growth_factor 1 +12 unhappiness_factor -1 +13 if_productivity_factor -2 +13 mf_productivity_factor -3 +13 unhappiness_factor -5 +15 research_percent 10 +17 research_percent 10 +18 unhappiness_factor -2 +20 research_percent 10 +21 base_income 2 +23 base_income -1 +23 if_productivity_factor -2 +23 research_percent 25 +24 battle_losses -5 +24 if_cost 80 +24 if_productivity_factor 2 +24 mf_cost 80 +24 mf_productivity_factor 2 +26 gaship_pop 75 +29 if_cost 120 +29 if_productivity_factor 3 +30 pop_growth_factor 1 +31 if_productivity_factor -1 +31 mf_productivity_factor -1 +31 unhappiness_factor -2 +32 if_productivity_factor -1 +32 mf_productivity_factor -1 +32 unhappiness_factor -4 +33 pop_growth_factor 1 +34 pop_growth_factor 1 +34 unhappiness_factor 5 +35 planet_max_pop 10000 +36 if_cost 120 +36 if_productivity_factor 3 +36 mf_cost 120 +36 mf_productivity_factor 3 +37 battle_losses -5 +37 if_cost 80 +37 if_productivity_factor 2 +37 mf_cost 80 +37 mf_productivity_factor 2 +38 effective_fleet_power 10 +39 gaship_pop 75 +41 capital_ship_speed 1 +42 research_percent 10 +43 if_productivity_factor -3 +43 mf_productivity_factor -3 +43 research_percent 50 +44 ecm_level 1 +45 eccm_level 1 +46 hs_beacon_level 1 +48 eccm_level 1 +49 ecm_level 1 +51 battle_losses -2 +52 battle_losses -2 +53 battle_losses -2 +53 if_cost 80 +53 if_productivity_factor 2 +53 mf_cost 80 +53 mf_productivity_factor 2 +54 gaship_pop 75 +55 if_cost 80 +55 if_productivity_factor 2 +55 mf_cost 80 +55 mf_productivity_factor 2 +55 unhappiness_factor 7 +56 if_productivity_factor -2 +56 mf_productivity_factor -2 +56 unhappiness_factor -7 +57 battle_losses -4 +59 eccm_level -1 +59 ecm_level -1 +59 unhappiness_factor -4 +60 hs_beacon_level 1 +61 turret_power 3 +62 research_percent 10 +63 military_level 1 +64 ecm_level 1 +65 eccm_level 1 +68 turret_power 3 +69 if_productivity_factor -4 +69 mf_productivity_factor 6 +69 unhappiness_factor 10 +70 capital_ship_speed 1 +71 fighter_power 4 +71 gaship_power 2 +73 planet_max_pop 10000 +74 battle_losses -2 +74 if_cost 80 +74 if_productivity_factor 2 +74 mf_cost 80 +74 mf_productivity_factor 2 +75 if_cost 160 +75 if_productivity_factor 4 +75 mf_cost 120 +75 mf_productivity_factor 3 +76 base_income 3 +76 if_productivity_factor 10 +76 mf_productivity_factor -5 +76 research_percent -50 +76 unhappiness_factor 15 +77 battle_losses -5 +78 if_cost 80 +78 if_productivity_factor 2 +78 mf_cost 80 +78 mf_productivity_factor 2 +80 prevent_hs_exit 1 +81 if_productivity_factor -3 +81 unhappiness_factor -5 +82 eccm_level 1 +83 planet_max_pop 10000 +84 capital_ship_speed 1 +84 effective_fleet_power 5 +85 turret_power 3 +86 ecm_level 1 +87 if_cost 120 +87 if_productivity_factor 3 +87 unhappiness_factor 7 +88 planet_destruction 1 +89 bcruiser_power 20 +89 cruiser_power 10 +90 effective_fleet_power 10 +\. + + +-- Research descriptions +COPY research_txt FROM STDIN; +1 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. +2 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. +3 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. +4 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. +5 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! +6 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. +7 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. +8 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. +9 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. +10 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. +11 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. +12 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. +13 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! +14 en Cloning Techniques Sir! Their stem cells analysis have finally brought our scientists a new breakthrough. Our researchers have succeeded in cloning various lifeforms. +15 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. +16 en Quantum Gravitation These advances in the field of quantum theory will open a new area for further studies. +17 en Miniaturised Particle Colliders These new and small particle colliders increase research in many areas, increasing lab outputs. +18 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. +20 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. +21 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. +22 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. +23 en Increased Research Grants This law allows to divert a higher percentage of income towards research thus allowing faster discoveries but reducing income. +24 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. +25 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. +26 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. +27 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! +28 en Sentient Lifeform Engineering Sir! Our biologists have finally managed to create intelligent lifeforms from scratch, thus opening a brand new field of studies. +29 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! +30 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. +31 en Green Production These new production methods are safer for planetary ecology, making the population happier but slightly reducing factory productivity. +32 en Biosphere Protection Pact Enforcing this law forces the industrial sector to use greener production methods that make the population happier but reduce productivity. +33 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. +34 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... +35 en Arcologies Sir! We are now able to build arcologies, which will allow us to house loads more citizens on our empire's planets! +36 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. +37 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 resistent alloys. +38 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. +39 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.. +40 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. +41 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... +42 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. +43 en Science Golden Age Enacting this law permits us to divert more resources towards research at the expense of other necessities. +44 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. +45 en Fast Burst Transmission This new technology renders our stellar and interstellar communications less prone to jamming and interception. +46 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. +48 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. +49 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. +50 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. +51 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. +52 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. +53 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. +54 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. +55 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. +56 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. +57 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. +58 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. +59 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! +60 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. +61 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. +62 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. +63 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. +64 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. +65 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. +68 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. +69 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. +70 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. +71 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. +72 en Mass Anti-matter Production Using newly acquired technologies our scientists have acheived mass production of anti-matter thus opening a brand new field of applications. +73 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. +74 en Intelligent Materials Bringing sentience to the materials they use, our scientists provide us with wonderful new ways of building stuff. +75 en Automated Factories Sir! Using sentient materials in our factories will allow us to gain production efficiency. +76 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. +77 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. +78 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. +79 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. +80 en Wormhole Collapsing Sir, our scientists have devised a new defence. 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. +81 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. +82 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. +83 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. +84 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. +85 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. +86 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. +87 en Wormhole Lockdown This law prevents your citizens from using the planetary gateways, cancelling the effects of the wormhole technology. +88 en Wormhole Super Nova 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. +89 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. +90 en Matter Anti-matter Missiles Using matter / anti-matter reactions in our warheads should greatly increase the damage caused by our ships. +\. + + + +-- Peacekeepers AI +INSERT INTO player (userid, hidden) VALUES ( + (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers'), 't' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'military_level', '0' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'pop_growth_factor', '1' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'if_productivity', '30' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'if_productivity_factor', '10' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'base_income', '2' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'factory_cost', '10' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'turret_cost', '20' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'mf_productivity', '10' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'mf_productivity_factor', '10' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'workunits_turret', '1500' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'workunits_gaship', '4500' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'workunits_fighter', '2000' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'workunits_cruiser', '20000' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'workunits_bcruiser', '22000' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'build_cost_turret', '400' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'build_cost_gaship', '750' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'build_cost_fighter', '500' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'build_cost_cruiser', '5000' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'build_cost_bcruiser', '15000' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'if_cost', '400' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'mf_cost', '400' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'battle_losses', '10' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'unhappiness_factor', '100' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'effective_fleet_power', '200' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'planet_max_pop', '10000' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'capital_ship_speed', '3' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'prevent_hs_exit', '0' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'planet_destruction', '0' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'turret_power', '10' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'gaship_power', '5' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'fighter_power', '30' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'cruiser_power', '120' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'bcruiser_power', '240' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'gaship_upkeep', '0' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'fighter_upkeep', '0' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'cruiser_upkeep', '0' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'bcruiser_upkeep', '0' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'research_percent', '200' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'gaship_space', '1' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'fighter_space', '1' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'cruiser_haul', '50' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'bcruiser_haul', '50' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'ecm_level', '4' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'eccm_level', '4' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'gaship_pop', '200' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'hs_beacon_level', '0' +); +INSERT INTO rule (player, name, value) VALUES ( + (SELECT id FROM player WHERE userid = (SELECT id FROM main.account WHERE name = 'AI>Peacekeepers')), + 'probe_tech', '0' +); diff --git a/sql/beta5/structure/00-ecm-eccm.sql b/sql/beta5/structure/00-ecm-eccm.sql new file mode 100644 index 0000000..e215851 --- /dev/null +++ b/sql/beta5/structure/00-ecm-eccm.sql @@ -0,0 +1,28 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/00-ecm-eccm.sql +-- +-- Beta 5 games: +-- Tables that store ECM and ECCM settings +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +CREATE TABLE eccm ( + eccm_level SMALLINT NOT NULL, + gain SMALLINT NOT NULL, + probability SMALLINT NOT NULL, + PRIMARY KEY (eccm_level, gain) +); + +CREATE TABLE ecm ( + ecm_level SMALLINT NOT NULL, + info_level SMALLINT NOT NULL, + probability SMALLINT NOT NULL, + PRIMARY KEY (ecm_level, info_level) +); + +GRANT SELECT ON eccm TO legacyworlds; +GRANT SELECT ON ecm TO legacyworlds; diff --git a/sql/beta5/structure/00-gdata.sql b/sql/beta5/structure/00-gdata.sql new file mode 100644 index 0000000..0b6d7be --- /dev/null +++ b/sql/beta5/structure/00-gdata.sql @@ -0,0 +1,18 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/00-gdata.sql +-- +-- Beta 5 games: +-- Table that stores game data +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +CREATE TABLE gdata ( + id VARCHAR(8) PRIMARY KEY, + value VARCHAR(60) NOT NULL +); + +GRANT INSERT,SELECT,UPDATE ON gdata TO legacyworlds; diff --git a/sql/beta5/structure/00-player-table.sql b/sql/beta5/structure/00-player-table.sql new file mode 100644 index 0000000..e1742e0 --- /dev/null +++ b/sql/beta5/structure/00-player-table.sql @@ -0,0 +1,42 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/00-player-table.sql +-- +-- Beta 5 games: +-- Table that stores player data, minus its foreign keys +-- which will be defined in 99-player-fk.sql +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +CREATE TABLE player ( + id BIGSERIAL NOT NULL PRIMARY KEY, + userid BIGINT NOT NULL REFERENCES main.account (id), + name VARCHAR(15), + quit INT, + cash INT NOT NULL DEFAULT 20000, + alliance INT, + a_status CHAR(3) NULL CHECK(a_status IS NULL OR a_status IN ('REQ', 'IN')), + a_grade BIGINT, + a_vote BIGINT, + first_planet BIGINT, + restrain SMALLINT NOT NULL DEFAULT 10, + research VARCHAR(8) NOT NULL DEFAULT '20!40!40', + res_assistance BIGINT REFERENCES player (id), + bh_unhappiness INT NOT NULL DEFAULT 0, + probe_policy CHAR(4) NOT NULL DEFAULT '2110', + hidden BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE INDEX player_alliance ON player (alliance); +CREATE INDEX player_name ON player (name); +CREATE INDEX player_userid ON player (userid); +CREATE INDEX player_a_vote ON player (a_vote); +CREATE INDEX player_a_grade ON player (a_grade); +CREATE INDEX player_res_assistance ON player (res_assistance); +CREATE INDEX player_first_planet ON player (first_planet); + +GRANT SELECT,INSERT,UPDATE ON player TO legacyworlds; +GRANT SELECT,UPDATE ON player_id_seq TO legacyworlds; diff --git a/sql/beta5/structure/00-rule-base.sql b/sql/beta5/structure/00-rule-base.sql new file mode 100644 index 0000000..b3cf2a5 --- /dev/null +++ b/sql/beta5/structure/00-rule-base.sql @@ -0,0 +1,25 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/00-rule-base.sql +-- +-- Beta 5 games: +-- Base tables for the rule system +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + +CREATE TABLE rule_def ( + name VARCHAR(32) NOT NULL PRIMARY KEY, + value INT NOT NULL +); + +GRANT SELECT ON rule_def TO legacyworlds; + + +CREATE TABLE rule_handler ( + rule VARCHAR(32) NOT NULL PRIMARY KEY REFERENCES rule_def (name), + handler VARCHAR(32) NOT NULL +); + +GRANT SELECT ON rule_handler TO legacyworlds; diff --git a/sql/beta5/structure/00-system.sql b/sql/beta5/structure/00-system.sql new file mode 100644 index 0000000..f5921be --- /dev/null +++ b/sql/beta5/structure/00-system.sql @@ -0,0 +1,26 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/00-system.sql +-- +-- Beta 5 games: +-- Table that stores systems +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +CREATE TABLE system ( + id SERIAL PRIMARY KEY, + x INT NOT NULL, + y INT NOT NULL, + prot INT NOT NULL, + assigned BOOLEAN NOT NULL DEFAULT FALSE, + nebula SMALLINT NOT NULL +); + +CREATE UNIQUE INDEX system_coords ON system (x, y); +CREATE INDEX system_prot ON system (prot); + +GRANT SELECT,INSERT,UPDATE ON system TO legacyworlds; +GRANT SELECT,UPDATE ON system_id_seq TO legacyworlds; diff --git a/sql/beta5/structure/01-alliance.sql b/sql/beta5/structure/01-alliance.sql new file mode 100644 index 0000000..29562d8 --- /dev/null +++ b/sql/beta5/structure/01-alliance.sql @@ -0,0 +1,121 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/01-alliance.sql +-- +-- Beta 5 games: +-- Main alliance tables including definitions for an +-- alliance itself as well as candidates for presidency +-- and ranks +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +CREATE TABLE alliance ( + id SERIAL NOT NULL PRIMARY KEY, + tag VARCHAR(5) NOT NULL UNIQUE, + name VARCHAR(64) NOT NULL, + leader BIGINT REFERENCES player (id), + successor BIGINT REFERENCES player (id), + democracy BOOLEAN NOT NULL DEFAULT FALSE, + default_grade BIGINT NOT NULL, + enable_tt CHAR(1) NOT NULL DEFAULT 'N' CHECK(enable_tt IN ('N', 'S', 'R')) +); + +CREATE INDEX alliance_leader ON alliance (leader); +CREATE INDEX alliance_successor ON alliance (successor); +CREATE INDEX alliance_def_grade ON alliance (default_grade); + +GRANT SELECT,INSERT,UPDATE,DELETE ON alliance TO legacyworlds; +GRANT SELECT,UPDATE ON alliance_id_seq TO legacyworlds; + + + +-- +-- Table for alliance ranks +-- +-- list_access -> level of access to the alliance's listings +-- NO - no access +-- PY - member list +-- PL - planet list +-- PLD - detailed planet list +-- +-- tech_trade -> level of access to the alliance's tech trading system +-- NO - no access +-- SL - submit tech list / view orders +-- SR - submit tech list and requests / view orders +-- VL - submit tech list and requests / view orders / view list +-- MR - submit tech list and requests / view orders / view list / manage recommandations +-- + + +CREATE TABLE alliance_grade ( + id BIGSERIAL NOT NULL PRIMARY KEY, + alliance INT REFERENCES alliance (id) ON DELETE CASCADE, + name VARCHAR(32), + list_access CHAR(3) NOT NULL DEFAULT 'PLD' CHECK(list_access IN ('NO','PY','PL','PLD')), + attacks BOOLEAN NOT NULL DEFAULT TRUE, + can_set_grades BOOLEAN NOT NULL DEFAULT FALSE, + can_kick BOOLEAN NOT NULL DEFAULT FALSE, + can_accept BOOLEAN NOT NULL DEFAULT FALSE, + can_be_kicked BOOLEAN NOT NULL DEFAULT TRUE, + forum_admin BOOLEAN NOT NULL DEFAULT FALSE, + dipl_contact BOOLEAN NOT NULL DEFAULT FALSE, + can_vote BOOLEAN NOT NULL DEFAULT TRUE, + can_be_cand BOOLEAN NOT NULL DEFAULT TRUE, + tech_trade CHAR(2) NOT NULL DEFAULT 'NO' CHECK(tech_trade IN ('NO','SL','SR','VL','MR')) +); + +CREATE INDEX algr_alliance ON alliance_grade (alliance); +ALTER TABLE alliance ADD FOREIGN KEY (default_grade) REFERENCES alliance_grade (id); + +GRANT SELECT,INSERT,UPDATE,DELETE ON alliance_grade TO legacyworlds; +GRANT SELECT,UPDATE ON alliance_grade_id_seq TO legacyworlds; + + + +CREATE TABLE algr_chgrade ( + grade BIGINT NOT NULL REFERENCES alliance_grade (id) ON DELETE CASCADE, + can_change BIGINT NOT NULL REFERENCES alliance_grade (id) ON DELETE CASCADE, + PRIMARY KEY (grade, can_change) +); + +CREATE INDEX algr_chgrade_target ON algr_chgrade (can_change); +GRANT SELECT,INSERT,UPDATE,DELETE ON algr_chgrade TO legacyworlds; + + + +CREATE TABLE algr_kick ( + grade BIGINT NOT NULL REFERENCES alliance_grade (id) ON DELETE CASCADE, + kick BIGINT NOT NULL REFERENCES alliance_grade (id) ON DELETE CASCADE, + PRIMARY KEY (grade, kick) +); + +CREATE INDEX algr_kick_target ON algr_kick (kick); +GRANT SELECT,INSERT,UPDATE,DELETE ON algr_kick TO legacyworlds; + + + +CREATE TABLE alliance_candidate ( + id BIGSERIAL NOT NULL PRIMARY KEY, + alliance INT NOT NULL REFERENCES alliance (id) ON DELETE CASCADE, + candidate BIGINT NOT NULL REFERENCES player (id), + UNIQUE (alliance, candidate) +); + +CREATE INDEX alcand_candidate ON alliance_candidate (candidate); +GRANT SELECT,INSERT,UPDATE,DELETE ON alliance_candidate TO legacyworlds; +GRANT SELECT,UPDATE ON alliance_candidate_id_seq TO legacyworlds; + + +-- +-- Alliance status regarding the victory conditions +-- in test match 1. +-- +CREATE TABLE alliance_victory ( + alliance INT NOT NULL PRIMARY KEY REFERENCES alliance(id) ON DELETE CASCADE, + time_of_victory INT NULL +); + +GRANT SELECT,INSERT,UPDATE,DELETE ON alliance_victory TO legacyworlds; diff --git a/sql/beta5/structure/01-message-base.sql b/sql/beta5/structure/01-message-base.sql new file mode 100644 index 0000000..d26d0d6 --- /dev/null +++ b/sql/beta5/structure/01-message-base.sql @@ -0,0 +1,45 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/01-message-base.sql +-- +-- Beta 5 games: +-- The base tables for the PM system: messages and custom +-- folders +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +CREATE TABLE custom_folder ( + id BIGSERIAL NOT NULL PRIMARY KEY, + player BIGINT NOT NULL REFERENCES player (id), + name VARCHAR(32) NOT NULL, + UNIQUE (player, name) +); + +GRANT INSERT,SELECT,UPDATE,DELETE ON custom_folder TO legacyworlds; +GRANT SELECT,UPDATE ON custom_folder_id_seq TO legacyworlds; + + + +CREATE TABLE message ( + id BIGSERIAL NOT NULL PRIMARY KEY, + player BIGINT NOT NULL REFERENCES player (id), + sent_on INT NOT NULL, + mtype VARCHAR(16) NOT NULL, + ftype CHAR(3) NOT NULL CHECK(ftype IN ('IN','INT','OUT','CUS')), + fcustom BIGINT REFERENCES custom_folder (id) ON DELETE SET NULL, + is_new BOOLEAN NOT NULL DEFAULT TRUE, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + original BIGINT REFERENCES message (id), + reply_to BIGINT REFERENCES message (id) +); + +CREATE INDEX message_player ON message (player); +CREATE INDEX message_fcustom ON message (fcustom); +CREATE INDEX message_reply_to ON message (reply_to); +CREATE INDEX message_original ON message (original); + +GRANT INSERT,SELECT,UPDATE ON message TO legacyworlds; +GRANT INSERT,SELECT,UPDATE ON message_id_seq TO legacyworlds; diff --git a/sql/beta5/structure/01-planet.sql b/sql/beta5/structure/01-planet.sql new file mode 100644 index 0000000..5f005af --- /dev/null +++ b/sql/beta5/structure/01-planet.sql @@ -0,0 +1,139 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/01-planet.sql +-- +-- Beta 5 games: +-- Planet data and associated tables (buildqueue, etc...) +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +CREATE TABLE planet ( + id BIGSERIAL NOT NULL PRIMARY KEY, + owner BIGINT REFERENCES player (id), + system INT NOT NULL REFERENCES system (id), + orbit SMALLINT NOT NULL CHECK(orbit BETWEEN 0 AND 5), + name VARCHAR(15) NOT NULL UNIQUE, + status SMALLINT NOT NULL DEFAULT 0 CHECK(status BETWEEN 0 AND 5), + pop INT NOT NULL DEFAULT 2000, + max_pop INT NOT NULL, + ifact INT NOT NULL DEFAULT 3, + mfact INT NOT NULL DEFAULT 3, + turrets INT NOT NULL DEFAULT 0, + renamed INT NOT NULL DEFAULT 0, + happiness SMALLINT NOT NULL DEFAULT 70 CHECK(happiness BETWEEN 0 AND 100), + beacon SMALLINT NOT NULL DEFAULT 0, + abandon SMALLINT, + bh_prep SMALLINT, + bh_unhappiness SMALLINT NOT NULL DEFAULT 0, + sale SMALLINT, + built_probe BOOLEAN NOT NULL DEFAULT FALSE, + probe_policy CHAR(4), + corruption SMALLINT NOT NULL DEFAULT 0, + vacation CHAR(4) NOT NULL DEFAULT 'NO' CHECK(vacation IN ('NO','PEND','YES')), + mod_check BOOLEAN NOT NULL DEFAULT TRUE, + force_rename INT, + UNIQUE (system, orbit) +); + +CREATE INDEX planet_owner ON planet (owner); + +GRANT SELECT,INSERT,UPDATE ON planet TO legacyworlds; +GRANT SELECT,UPDATE ON planet_id_seq TO legacyworlds; + + +CREATE TABLE planet_abandon_time ( + id BIGINT PRIMARY KEY REFERENCES planet (id), + time_required INT NOT NULL CHECK(time_required BETWEEN 6 AND 24) +); + +CREATE INDEX planet_abandon_time_req ON planet_abandon_time (time_required); +GRANT SELECT,INSERT,DELETE,UPDATE ON planet_abandon_time TO legacyworlds; + + +CREATE TABLE planet_max_pop ( + planet BIGINT NOT NULL REFERENCES planet(id), + tech_level SMALLINT NOT NULL CHECK(tech_level BETWEEN 0 AND 3), + max_pop INT NOT NULL, + PRIMARY KEY (planet, tech_level) +); +GRANT SELECT,INSERT,DELETE ON planet_max_pop TO legacyworlds; + + + +CREATE TABLE facthist ( + planet BIGINT NOT NULL REFERENCES planet (id), + moment INT NOT NULL DEFAULT INT4(EXTRACT(EPOCH FROM NOW())), + is_military BOOLEAN NOT NULL, + change INT NOT NULL CHECK(change <> 0) +); + +CREATE INDEX facthist_idx ON facthist (planet, moment, is_military); +GRANT SELECT,INSERT,DELETE ON facthist TO legacyworlds; + + + +CREATE TABLE turhist ( + planet BIGINT NOT NULL REFERENCES planet (id), + moment INT NOT NULL DEFAULT INT4(EXTRACT(EPOCH FROM NOW())), + change INT NOT NULL CHECK(change <> 0) +); + +CREATE INDEX turhist_idx ON turhist (planet, moment); +GRANT SELECT,INSERT,DELETE ON turhist TO legacyworlds; + + + +CREATE TABLE buildqueue ( + planet BIGINT NOT NULL REFERENCES planet (id), + bq_order INT NOT NULL CHECK(bq_order >= 0), + item SMALLINT NOT NULL CHECK(item BETWEEN 0 AND 5), + quantity INT NOT NULL CHECK(quantity > 0), + workunits BIGINT NOT NULL DEFAULT 0 CHECK(workunits >= 0), + PRIMARY KEY (planet, bq_order) +); +GRANT SELECT,INSERT,UPDATE,DELETE ON buildqueue TO legacyworlds; + + + +CREATE TABLE abandon_log ( + planet BIGINT NOT NULL REFERENCES planet (id), + abandon_time INT NOT NULL DEFAULT INT4(EXTRACT(EPOCH FROM NOW())), + former_owner BIGINT NOT NULL REFERENCES player (id), + retake_time INT, + retake_owner BIGINT REFERENCES player (id), + PRIMARY KEY (planet, abandon_time) +); + +CREATE INDEX abandon_former ON abandon_log (former_owner); +CREATE INDEX abandon_retake ON abandon_log (retake_owner); + +GRANT INSERT,UPDATE,SELECT ON abandon_log TO legacyworlds; + + + +CREATE TABLE attacks ( + planet BIGINT NOT NULL REFERENCES planet (id) PRIMARY KEY, + ecm_level SMALLINT NOT NULL CHECK(ecm_level BETWEEN 0 AND 4), + eccm_level SMALLINT NOT NULL CHECK(ecm_level BETWEEN 0 AND 4), + friendly INT NOT NULL, + enemy INT NOT NULL, + v_players BOOLEAN NOT NULL, + v_friendly INT, + v_enemy INT +); +GRANT SELECT,INSERT,UPDATE,DELETE ON attacks TO legacyworlds; + + +-- +-- Table to store planet names for new players +-- + +CREATE TABLE planet_reg_queue ( + account BIGINT NOT NULL PRIMARY KEY REFERENCES main.reg_queue (account) + ON DELETE CASCADE, + p_name VARCHAR(15) NOT NULL UNIQUE +); +GRANT SELECT,INSERT,DELETE ON planet_reg_queue TO legacyworlds; diff --git a/sql/beta5/structure/01-player-dipl.sql b/sql/beta5/structure/01-player-dipl.sql new file mode 100644 index 0000000..86f359c --- /dev/null +++ b/sql/beta5/structure/01-player-dipl.sql @@ -0,0 +1,89 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/01-player-dipl.sql +-- +-- Beta 5 games: +-- Data structures that store everything related to a +-- player's diplomatic relations +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +CREATE TABLE research_assistance ( + id BIGSERIAL NOT NULL PRIMARY KEY, + player BIGINT NOT NULL REFERENCES player (id), + price INT NOT NULL CHECK(price >= 0), + offer_to BIGINT NOT NULL REFERENCES player (id), + technology INT REFERENCES research (id), + moment INT NOT NULL DEFAULT INT4(EXTRACT(EPOCH FROM NOW())), + accepted BOOLEAN +); + +CREATE INDEX res_as_player ON research_assistance (player); +CREATE INDEX res_as_offer_to ON research_assistance (offer_to); +CREATE INDEX res_as_technology ON research_assistance (technology); +CREATE INDEX res_as_moment ON research_assistance (moment); + +GRANT SELECT,INSERT,UPDATE ON research_assistance TO legacyworlds; +GRANT SELECT,UPDATE ON research_assistance_id_seq TO legacyworlds; + + + +CREATE TABLE enemy_alliance ( + player BIGINT NOT NULL REFERENCES player (id), + alliance INT NOT NULL REFERENCES alliance (id) ON DELETE CASCADE, + PRIMARY KEY (player, alliance) +); + +CREATE INDEX enemy_alliance_idx ON enemy_alliance (alliance); +GRANT SELECT,INSERT,DELETE ON enemy_alliance TO legacyworlds; + + + +CREATE TABLE enemy_player ( + player BIGINT NOT NULL REFERENCES player (id), + enemy BIGINT NOT NULL REFERENCES player (id), + PRIMARY KEY (player, enemy) +); + +CREATE INDEX enemy_player_idx ON enemy_player (enemy); +GRANT SELECT,INSERT,DELETE ON enemy_player TO legacyworlds; + + + +CREATE TABLE trusted ( + player BIGINT NOT NULL REFERENCES player (id), + level SMALLINT NOT NULL CHECK(level >= 0), + friend BIGINT NOT NULL REFERENCES player (id), + PRIMARY KEY (player, level), + UNIQUE (player, level) +); +GRANT SELECT,INSERT,DELETE,UPDATE ON trusted TO legacyworlds; + + + +CREATE TABLE trusted_ban ( + player BIGINT NOT NULL REFERENCES player (id), + ban_player BIGINT NOT NULL REFERENCES player (id), + PRIMARY KEY (player, ban_player) +); + +CREATE INDEX trusted_ban_idx ON trusted_ban (ban_player); +GRANT SELECT,INSERT,DELETE ON trusted_ban TO legacyworlds; + + + +CREATE TABLE donation_log ( + time INT NOT NULL DEFAULT UNIX_TIMESTAMP(NOW()), + source BIGINT NOT NULL REFERENCES player (id), + target BIGINT NOT NULL REFERENCES player (id), + amount INT NOT NULL CHECK(amount > 0), + PRIMARY KEY (time, source) +); + +CREATE INDEX donation_source ON donation_log (source); +CREATE INDEX donation_target ON donation_log (target); + +GRANT INSERT,SELECT ON donation_log TO legacyworlds; diff --git a/sql/beta5/structure/01-player-rules.sql b/sql/beta5/structure/01-player-rules.sql new file mode 100644 index 0000000..be8e225 --- /dev/null +++ b/sql/beta5/structure/01-player-rules.sql @@ -0,0 +1,40 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/01-player-rules.sql +-- +-- Beta 5 games: +-- Data structures that store the rules that apply for +-- each player, as well as the list of implemented techs +-- for each player +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +CREATE TABLE research_player ( + player BIGINT NOT NULL REFERENCES player (id), + research INT NOT NULL REFERENCES research (id), + possible BOOLEAN NOT NULL, + in_effect SMALLINT NOT NULL DEFAULT 0, + points INT NOT NULL DEFAULT 0 CHECK(points >= 0), + given_by BIGINT REFERENCES player (id), + PRIMARY KEY (player, research) +); + +CREATE INDEX resplayer_research ON research_player (research); +CREATE INDEX resplayer_giver ON research_player (given_by); + +GRANT SELECT,INSERT,UPDATE,DELETE ON research_player TO legacyworlds; + + + +CREATE TABLE rule ( + name VARCHAR(32) NOT NULL REFERENCES rule_def (name), + player BIGINT NOT NULL REFERENCES player (id), + value INT NOT NULL, + PRIMARY KEY (name, player) +); + +CREATE INDEX rule_player ON rule (player); +GRANT SELECT,INSERT,UPDATE,DELETE ON rule TO legacyworlds; diff --git a/sql/beta5/structure/01-research-base.sql b/sql/beta5/structure/01-research-base.sql new file mode 100644 index 0000000..ac2f166 --- /dev/null +++ b/sql/beta5/structure/01-research-base.sql @@ -0,0 +1,59 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/01-research-base.sql +-- +-- Beta 5 games: +-- Tables that store tech definitions and functions to +-- ease inserting the data +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + +-- Research definitions +CREATE TABLE research ( + id INT NOT NULL PRIMARY KEY, + points INT NOT NULL, + cost INT NOT NULL, + optional SMALLINT NOT NULL CHECK(optional IN (0,1,2)), + category SMALLINT NOT NULL CHECK(category IN (0,1,2)), + is_law BOOLEAN NOT NULL +); + +GRANT SELECT ON research TO legacyworlds; + + +-- Dependencies +CREATE TABLE research_dep ( + research INT NOT NULL REFERENCES research (id), + depends_on INT NOT NULL REFERENCES research (id), + PRIMARY KEY (research, depends_on) +); + +CREATE INDEX research_depends_on ON research_dep (depends_on); +GRANT SELECT ON research_dep TO legacyworlds; + + +-- Research names and descriptions +CREATE TABLE research_txt ( + research INT NOT NULL REFERENCES research (id), + lang VARCHAR(4) NOT NULL REFERENCES main.lang (txt), + name VARCHAR(64) NOT NULL, + description TEXT NOT NULL, + PRIMARY KEY (research, lang) +); + +CREATE INDEX research_txt_lang ON research_txt (lang); +GRANT SELECT ON research_txt TO legacyworlds; + + +-- Research effects +CREATE TABLE research_effect ( + research INT NOT NULL REFERENCES research (id), + rule VARCHAR(32) NOT NULL REFERENCES rule_def (name), + modifier INT NOT NULL, + PRIMARY KEY (research, rule) +); + +CREATE INDEX research_fx_rule ON research_effect (rule); +GRANT SELECT ON research_effect TO legacyworlds; diff --git a/sql/beta5/structure/02-alliance-forums.sql b/sql/beta5/structure/02-alliance-forums.sql new file mode 100644 index 0000000..51ed285 --- /dev/null +++ b/sql/beta5/structure/02-alliance-forums.sql @@ -0,0 +1,100 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/02-alliance-forums.sql +-- +-- Beta 5 games: +-- Tables for alliance forums +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +CREATE TABLE af_forum ( + id SERIAL PRIMARY KEY, + alliance INT NOT NULL REFERENCES alliance(id) ON DELETE CASCADE, + forder INT NOT NULL CHECK(forder >= 0), + title VARCHAR(64) NOT NULL, + description TEXT, + topics INT NOT NULL DEFAULT 0 CHECK(topics >= 0), + posts INT NOT NULL DEFAULT 0 CHECK(posts >= 0), + last_post BIGINT, + user_post BOOLEAN NOT NULL DEFAULT TRUE, + UNIQUE(alliance, forder), + UNIQUE(alliance, title) +); + +CREATE INDEX af_forum_last_post ON af_forum (last_post); + +GRANT SELECT,INSERT,UPDATE,DELETE ON af_forum TO legacyworlds; +GRANT SELECT,UPDATE ON af_forum_id_seq TO legacyworlds; + + + +CREATE TABLE af_topic ( + id BIGSERIAL NOT NULL PRIMARY KEY, + forum INT NOT NULL REFERENCES af_forum (id) ON DELETE CASCADE, + first_post BIGINT NOT NULL, + last_post BIGINT, + sticky BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE INDEX af_topic_forum ON af_topic (forum); +CREATE INDEX af_topic_first_post ON af_topic (first_post); +CREATE INDEX af_topic_last_post ON af_topic (last_post); + +GRANT SELECT,INSERT,UPDATE,DELETE ON af_topic TO legacyworlds; +GRANT SELECT,UPDATE ON af_topic_id_seq TO legacyworlds; + + + +CREATE TABLE af_post ( + id BIGSERIAL NOT NULL PRIMARY KEY, + forum INT NOT NULL REFERENCES af_forum (id) ON DELETE CASCADE, + topic BIGINT REFERENCES af_topic (id) ON DELETE CASCADE, + author BIGINT NOT NULL REFERENCES player (id), + reply_to BIGINT REFERENCES af_post (id) ON DELETE NO ACTION, + 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, + edited_by BIGINT REFERENCES player(id) +); + +CREATE INDEX af_post_forum ON af_post (forum); +CREATE INDEX af_post_topic ON af_post (topic); +CREATE INDEX af_post_author ON af_post (author); +CREATE INDEX af_post_reply_to ON af_post (reply_to); +CREATE INDEX af_post_edited_by ON af_post (edited_by); + +ALTER TABLE af_forum ADD FOREIGN KEY (last_post) REFERENCES af_post (id) ON DELETE SET NULL; +ALTER TABLE af_topic ADD FOREIGN KEY (first_post) REFERENCES af_post (id) ON DELETE CASCADE; +ALTER TABLE af_topic ADD FOREIGN KEY (last_post) REFERENCES af_post (id) ON DELETE SET NULL; + +GRANT SELECT,INSERT,UPDATE,DELETE ON af_post TO legacyworlds; +GRANT SELECT,UPDATE ON af_post_id_seq TO legacyworlds; + + + +CREATE TABLE af_read ( + reader BIGINT NOT NULL REFERENCES player (id), + topic BIGINT NOT NULL REFERENCES af_topic (id) ON DELETE CASCADE, + PRIMARY KEY (reader, topic) +); + +CREATE INDEX af_read_topic ON af_read (topic); +GRANT SELECT,INSERT,DELETE ON af_read TO legacyworlds; + + + +CREATE TABLE algr_forums ( + grade BIGINT NOT NULL REFERENCES alliance_grade (id) ON DELETE CASCADE, + forum INT NOT NULL REFERENCES af_forum (id) ON DELETE CASCADE, + is_mod BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY (grade, forum) +); + +CREATE INDEX algr_forums_forum ON algr_forums (forum); +GRANT SELECT,INSERT,DELETE,UPDATE ON algr_forums TO legacyworlds; diff --git a/sql/beta5/structure/02-alliance-tech.sql b/sql/beta5/structure/02-alliance-tech.sql new file mode 100644 index 0000000..32e8d17 --- /dev/null +++ b/sql/beta5/structure/02-alliance-tech.sql @@ -0,0 +1,81 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/02-alliance-tech.sql +-- +-- Beta 5 games: +-- Tables for the alliance technology trading tool. +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- +-- Table tech_trade_list +-- +-- Contains the lists for all players in an alliance +-- +-- status: indicates the player's status w/r to a tech +-- N - New technology +-- L - Law +-- I - Implemented technology +-- F - Foreseen breakthrough +-- + +CREATE TABLE tech_trade_list ( + alliance INT NOT NULL REFERENCES alliance (id) ON DELETE CASCADE, + member BIGINT NOT NULL REFERENCES player (id) ON DELETE CASCADE, + tech INT NOT NULL REFERENCES research (id), + submitted INT NOT NULL DEFAULT UNIX_TIMESTAMP( NOW() ), + status CHAR(1) NOT NULL CHECK(status IN ('N', 'L', 'I', 'F')), + PRIMARY KEY(member, tech) +); + +CREATE INDEX tech_trade_list_alliance ON tech_trade_list (alliance); +CREATE INDEX tech_trade_list_tech ON tech_trade_list (tech); + +GRANT INSERT,SELECT,DELETE ON tech_trade_list TO legacyworlds; + + +-- +-- Table tech_trade_request +-- +-- Contains the list of requests made by alliance members +-- + +CREATE TABLE tech_trade_request ( + alliance INT NOT NULL REFERENCES alliance (id) ON DELETE CASCADE, + player BIGINT NOT NULL REFERENCES player (id) ON DELETE CASCADE, + priority INT NOT NULL CHECK(priority BETWEEN 0 AND 6), + tech INT NOT NULL REFERENCES research (id), + + PRIMARY KEY(player, priority), + UNIQUE(player, tech) +); + +CREATE INDEX tech_trade_req_alliance ON tech_trade_request (alliance); +CREATE INDEX tech_trade_req_tech ON tech_trade_request (tech); + +GRANT INSERT,SELECT,DELETE,UPDATE ON tech_trade_request TO legacyworlds; + + +-- +-- Table tech_trade_order +-- +-- Contains the orders issued for tech trades +-- + +CREATE TABLE tech_trade_order ( + alliance INT NOT NULL REFERENCES alliance (id) ON DELETE CASCADE, + player BIGINT NOT NULL REFERENCES player (id) ON DELETE CASCADE, + send_to BIGINT NOT NULL UNIQUE REFERENCES player (id) ON DELETE CASCADE, + tech INT NOT NULL REFERENCES research (id), + submitted INT NOT NULL DEFAULT UNIX_TIMESTAMP( NOW() ), + obeyed INT, + PRIMARY KEY(player) +); + +CREATE INDEX tech_trade_order_tech ON tech_trade_order (tech); +CREATE INDEX tech_trade_order_alliance ON tech_trade_order (alliance); + +GRANT SELECT,INSERT,UPDATE,DELETE ON tech_trade_order TO legacyworlds; diff --git a/sql/beta5/structure/02-orders.sql b/sql/beta5/structure/02-orders.sql new file mode 100644 index 0000000..8125edb --- /dev/null +++ b/sql/beta5/structure/02-orders.sql @@ -0,0 +1,57 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/02-orders.sql +-- +-- Beta 5 games: +-- Data structures for moving objects and hyperspace +-- stand-by +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +CREATE TABLE hs_wait ( + id BIGSERIAL NOT NULL PRIMARY KEY, + time_left INT NOT NULL CHECK(time_left BETWEEN 0 AND 48), + time_spent INT NOT NULL DEFAULT 0 CHECK(time_spent >= 0), + origin BIGINT REFERENCES planet (id), + drop_point BIGINT NOT NULL REFERENCES planet (id) +); + +CREATE INDEX wait_origin ON hs_wait (origin); +CREATE INDEX wait_drop_point ON hs_wait (drop_point); + +GRANT SELECT,INSERT,UPDATE,DELETE ON hs_wait TO legacyworlds; +GRANT SELECT,UPDATE ON hs_wait_id_seq TO legacyworlds; + + + +CREATE TABLE moving_object ( + id BIGSERIAL NOT NULL PRIMARY KEY, + m_from BIGINT NOT NULL REFERENCES planet (id), + m_to BIGINT NOT NULL REFERENCES planet (id), + changed SMALLINT NOT NULL DEFAULT 0 CHECK(changed >= 0), + time_left INT NOT NULL CHECK(time_left >= 0), + hyperspace BOOLEAN NOT NULL, + wait_order BIGINT REFERENCES hs_wait (id) ON DELETE SET NULL +); + +CREATE INDEX move_from ON moving_object (m_from); +CREATE INDEX move_to ON moving_object (m_to); +CREATE INDEX move_hssb ON moving_object (wait_order); + +GRANT SELECT,INSERT,UPDATE,DELETE ON moving_object TO legacyworlds; +GRANT SELECT,UPDATE ON moving_object_id_seq TO legacyworlds; + + + +CREATE TABLE waypoint ( + move_id BIGINT NOT NULL REFERENCES moving_object (id) ON DELETE CASCADE, + location BIGINT NOT NULL REFERENCES planet (id), + until_eta INT NOT NULL CHECK(until_eta >= 0), + PRIMARY KEY (move_id, until_eta) +); + +CREATE INDEX waypoint_location ON waypoint (location); +GRANT SELECT,INSERT,UPDATE,DELETE ON waypoint TO legacyworlds; diff --git a/sql/beta5/structure/02-warehouse.sql b/sql/beta5/structure/02-warehouse.sql new file mode 100644 index 0000000..49823e2 --- /dev/null +++ b/sql/beta5/structure/02-warehouse.sql @@ -0,0 +1,70 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/02-warehouse.sql +-- +-- Beta 5 games: +-- The tables in which the day ticks store the game's +-- history +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + +CREATE TABLE bt_debug ( + id BIGSERIAL NOT NULL PRIMARY KEY, + tick_time INT NOT NULL DEFAULT UNIX_TIMESTAMP(NOW()), + txt TEXT NOT NULL +); + +CREATE INDEX bt_time ON bt_debug (tick_time); +CREATE RULE bt_debug_cleaner AS + ON INSERT TO bt_debug DO ALSO + DELETE FROM bt_debug WHERE UNIX_TIMESTAMP(NOW()) - tick_time > 3 * 86400; +GRANT INSERT,DELETE ON bt_debug TO legacyworlds; +GRANT SELECT,UPDATE ON bt_debug_id_seq TO legacyworlds; + + + +CREATE TABLE warehouse ( + id BIGSERIAL NOT NULL PRIMARY KEY, + player BIGINT NOT NULL REFERENCES player (id), + tick_at INT NOT NULL DEFAULT UNIX_TIMESTAMP(NOW()), + cash BIGINT NOT NULL, + a_tag VARCHAR(5), + g_rank BIGINT NOT NULL, + c_rank BIGINT NOT NULL, + m_rank BIGINT NOT NULL, + f_rank BIGINT NOT NULL, + d_rank BIGINT NOT NULL, + o_rank BIGINT, + gaships INT NOT NULL, + fighters INT NOT NULL, + cruisers INT NOT NULL, + bcruisers INT NOT NULL, + fleet INT NOT NULL, + tech_list TEXT NOT NULL, + tech_points BIGINT NOT NULL, + UNIQUE (player, tick_at) +); + +GRANT INSERT,SELECT,DELETE ON warehouse TO legacyworlds; +GRANT SELECT,UPDATE ON warehouse_id_seq TO legacyworlds; + + + +CREATE TABLE wh_planet ( + id BIGINT NOT NULL REFERENCES warehouse (id) ON DELETE CASCADE, + planet BIGINT NOT NULL REFERENCES planet (id), + name VARCHAR(15) NOT NULL, + pop INT NOT NULL, + max_pop INT NOT NULL, + ifact INT NOT NULL, + mfact INT NOT NULL, + turrets INT NOT NULL, + happiness SMALLINT NOT NULL, + beacon SMALLINT NOT NULL, + abandon SMALLINT, + corruption INT, + PRIMARY KEY (id, planet) +); +GRANT INSERT,SELECT,DELETE ON wh_planet TO legacyworlds; diff --git a/sql/beta5/structure/03-fleets.sql b/sql/beta5/structure/03-fleets.sql new file mode 100644 index 0000000..c2c865a --- /dev/null +++ b/sql/beta5/structure/03-fleets.sql @@ -0,0 +1,38 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/03-fleets.sql +-- +-- Beta 5 games: +-- The table that containst fleet-related data +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +CREATE TABLE fleet ( + id BIGSERIAL NOT NULL PRIMARY KEY, + owner BIGINT NOT NULL REFERENCES player (id), + name VARCHAR(64) NOT NULL DEFAULT 'No Name', + location BIGINT REFERENCES planet (id), + gaships INT NOT NULL DEFAULT 0 CHECK (gaships >= 0), + fighters INT NOT NULL DEFAULT 0 CHECK (fighters >= 0), + cruisers INT NOT NULL DEFAULT 0 CHECK (cruisers >= 0), + bcruisers INT NOT NULL DEFAULT 0 CHECK (bcruisers >= 0), + attacking BOOLEAN NOT NULL DEFAULT FALSE, + can_move CHAR(1) NOT NULL DEFAULT 'Y' CHECK(can_move IN ('Y','H','B')), + moving BIGINT REFERENCES moving_object (id) ON DELETE SET NULL, + waiting BIGINT REFERENCES hs_wait (id) ON DELETE SET NULL, + time_spent INT NOT NULL DEFAULT 0, + sale SMALLINT, + CHECK(gaships + fighters + cruisers + bcruisers > 0) +); + +CREATE INDEX fleet_owner ON fleet (owner); +CREATE INDEX fleet_location ON fleet (location); +CREATE INDEX fleet_moving ON fleet (moving); +CREATE INDEX fleet_waiting ON fleet (waiting); + +GRANT INSERT,SELECT,DELETE,UPDATE ON fleet TO legacyworlds; +GRANT SELECT,UPDATE ON fleet_id_seq TO legacyworlds; + diff --git a/sql/beta5/structure/04-sales.sql b/sql/beta5/structure/04-sales.sql new file mode 100644 index 0000000..f8551f8 --- /dev/null +++ b/sql/beta5/structure/04-sales.sql @@ -0,0 +1,101 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/04-sales.sql +-- +-- Beta 5 games: +-- The various tables that store information associated +-- with fleet and planet sales +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +CREATE TABLE sale ( + id BIGSERIAL NOT NULL PRIMARY KEY, + player BIGINT NOT NULL REFERENCES player (id), + started INT NOT NULL DEFAULT UNIX_TIMESTAMP(NOW()), + expires INT, + fleet BIGINT REFERENCES fleet (id), + planet BIGINT REFERENCES planet (id), + finalized INT, + sold_to BIGINT REFERENCES player (id), + CHECK(expires IS NULL OR expires > started), + CHECK(fleet IS NOT NULL OR planet IS NOT NULL), + CHECK(finalized IS NULL AND sold_to IS NULL OR finalized IS NOT NULL AND sold_to IS NOT NULL) +); + +CREATE INDEX sale_player ON sale (player); +CREATE INDEX sale_fleet ON sale (fleet); +CREATE INDEX sale_planet ON sale (planet); +CREATE INDEX sale_sold_to ON sale (sold_to); + +GRANT INSERT,SELECT,DELETE,UPDATE ON sale TO legacyworlds; +GRANT SELECT,UPDATE ON sale_id_seq TO legacyworlds; + + + +CREATE TABLE private_offer ( + offer BIGINT NOT NULL REFERENCES sale (id) ON DELETE CASCADE PRIMARY KEY, + to_player BIGINT NOT NULL REFERENCES player (id), + price INT NOT NULL DEFAULT 0 CHECK(price >= 0) +); + +CREATE INDEX p_offer_to ON private_offer (to_player); +GRANT INSERT,SELECT,DELETE,UPDATE ON private_offer TO legacyworlds; + + + +CREATE TABLE public_offer ( + offer BIGINT NOT NULL REFERENCES sale (id) ON DELETE CASCADE PRIMARY KEY, + price INT NOT NULL DEFAULT 0 CHECK(price > 0), + auction BOOLEAN NOT NULL DEFAULT FALSE +); +GRANT INSERT,SELECT,DELETE,UPDATE ON public_offer TO legacyworlds; + + + +CREATE TABLE auction ( + offer BIGINT NOT NULL REFERENCES sale (id) ON DELETE CASCADE, + player BIGINT NOT NULL REFERENCES player (id), + moment INT NOT NULL DEFAULT UNIX_TIMESTAMP(NOW()), + price INT NOT NULL CHECK(price > 0), + PRIMARY KEY(offer, player, moment) +); + +CREATE INDEX auction_player ON auction (player); +GRANT SELECT,INSERT,DELETE ON auction TO legacyworlds; + + + +CREATE TABLE sale_history ( + id BIGSERIAL NOT NULL PRIMARY KEY, + offer BIGINT REFERENCES sale (id) ON DELETE SET NULL, + from_player BIGINT NOT NULL REFERENCES player (id), + to_player BIGINT REFERENCES player (id), + started INT NOT NULL DEFAULT UNIX_TIMESTAMP(NOW()), + ended INT, + mode SMALLINT NOT NULL, + end_mode SMALLINT, + price INT NOT NULL CHECK(price >= 0), + sell_price INT CHECK(sell_price IS NULL OR sell_price >= 0), + p_id BIGINT REFERENCES planet (id), + p_name VARCHAR(15) NOT NULL, + is_planet BOOLEAN NOT NULL, + p_pop INT, + p_turrets INT, + p_factories INT, + f_gaships INT NOT NULL DEFAULT 0, + f_fighters INT NOT NULL DEFAULT 0, + f_cruisers INT NOT NULL DEFAULT 0, + f_bcruisers INT NOT NULL DEFAULT 0, + CHECK(ended IS NULL OR ended > started) +); + +CREATE INDEX shist_offer ON sale_history (offer); +CREATE INDEX shist_from_player ON sale_history (from_player); +CREATE INDEX shist_to_player ON sale_history (to_player); +CREATE INDEX shist_planet ON sale_history (p_id); + +GRANT INSERT,UPDATE,SELECT,DELETE ON sale_history TO legacyworlds; +GRANT SELECT,UPDATE ON sale_history_id_seq TO legacyworlds; diff --git a/sql/beta5/structure/05-message-player.sql b/sql/beta5/structure/05-message-player.sql new file mode 100644 index 0000000..42d0f6e --- /dev/null +++ b/sql/beta5/structure/05-message-player.sql @@ -0,0 +1,71 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/05-message-player.sql +-- +-- Beta 5 games: +-- Tables for the player-controlled messages +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + + +CREATE TABLE msg_std ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + sender BIGINT NOT NULL REFERENCES player (id), + recipient BIGINT NOT NULL REFERENCES player (id), + subject VARCHAR(64) NOT NULL, + message text NOT NULL +); + +CREATE INDEX msg_std_sender ON msg_std (sender); +CREATE INDEX msg_std_recipient ON msg_std (recipient); +GRANT INSERT,DELETE,SELECT ON msg_std TO legacyworlds; + + + +CREATE TABLE msg_planet ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + planet BIGINT NOT NULL REFERENCES planet (id), + pname VARCHAR(15) NOT NULL, + sender BIGINT NOT NULL REFERENCES player (id), + subject VARCHAR(64) NOT NULL, + message text NOT NULL +); + +CREATE INDEX msg_planet_sender ON msg_planet (sender); +CREATE INDEX msg_planet_planet ON msg_planet (planet); +GRANT INSERT,DELETE,SELECT ON msg_planet TO legacyworlds; + + + +CREATE TABLE msg_alliance ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + sender BIGINT NOT NULL REFERENCES player (id), + alliance INT REFERENCES alliance (id) ON DELETE SET NULL, + tag VARCHAR(5) NOT NULL, + subject VARCHAR(64) NOT NULL, + message TEXT NOT NULL +); + +CREATE INDEX msg_alliance_sender ON msg_alliance (sender); +CREATE INDEX msg_alliance_alliance ON msg_alliance (alliance); +GRANT INSERT,DELETE,SELECT ON msg_alliance TO legacyworlds; + + + +CREATE TABLE msg_diplchan ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + sender BIGINT NOT NULL REFERENCES player (id), + recipient BIGINT NOT NULL REFERENCES player (id), + alliance INT REFERENCES alliance (id) ON DELETE SET NULL, + tag VARCHAR(5) NOT NULL, + subject VARCHAR(64) NOT NULL, + message TEXT NOT NULL +); + +CREATE INDEX msg_diplchan_sender ON msg_diplchan (sender); +CREATE INDEX msg_diplchan_recipient ON msg_diplchan (recipient); +CREATE INDEX msg_diplchan_alliance ON msg_diplchan (alliance); +GRANT INSERT,DELETE,SELECT ON msg_diplchan TO legacyworlds; diff --git a/sql/beta5/structure/06-message-battle.sql b/sql/beta5/structure/06-message-battle.sql new file mode 100644 index 0000000..4f3ab70 --- /dev/null +++ b/sql/beta5/structure/06-message-battle.sql @@ -0,0 +1,56 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/06-message-battle.sql +-- +-- Beta 5 games: +-- Table to store battle reports +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + + +CREATE TABLE msg_battle ( + id BIGINT NOT NULL REFERENCES message (id) ON DELETE CASCADE, + planet_id BIGINT NOT NULL REFERENCES planet (id), + planet VARCHAR(15) NOT NULL, + o_gaships INT NOT NULL DEFAULT 0, + o_fighters INT NOT NULL DEFAULT 0, + o_cruisers INT NOT NULL DEFAULT 0, + o_bcruisers INT NOT NULL DEFAULT 0, + o_power INT NOT NULL DEFAULT 0, + ol_gaships INT NOT NULL DEFAULT 0, + ol_fighters INT NOT NULL DEFAULT 0, + ol_cruisers INT NOT NULL DEFAULT 0, + ol_bcruisers INT NOT NULL DEFAULT 0, + ol_power INT NOT NULL DEFAULT 0, + a_gaships INT NOT NULL DEFAULT 0, + a_fighters INT NOT NULL DEFAULT 0, + a_cruisers INT NOT NULL DEFAULT 0, + a_bcruisers INT NOT NULL DEFAULT 0, + a_power INT NOT NULL DEFAULT 0, + al_gaships INT NOT NULL DEFAULT 0, + al_fighters INT NOT NULL DEFAULT 0, + al_cruisers INT NOT NULL DEFAULT 0, + al_bcruisers INT NOT NULL DEFAULT 0, + al_power INT NOT NULL DEFAULT 0, + e_gaships INT NOT NULL DEFAULT 0, + e_fighters INT NOT NULL DEFAULT 0, + e_cruisers INT NOT NULL DEFAULT 0, + e_bcruisers INT NOT NULL DEFAULT 0, + e_power INT NOT NULL DEFAULT 0, + el_gaships INT NOT NULL DEFAULT 0, + el_fighters INT NOT NULL DEFAULT 0, + el_cruisers INT NOT NULL DEFAULT 0, + el_bcruisers INT NOT NULL DEFAULT 0, + el_power INT NOT NULL DEFAULT 0, + turrets INT NOT NULL DEFAULT 0, + tpower INT NOT NULL DEFAULT 0, + l_turrets INT NOT NULL DEFAULT 0, + l_tpower INT NOT NULL DEFAULT 0, + tmode SMALLINT NOT NULL DEFAULT 0, + heroic_def INT NOT NULL CHECK(heroic_def >= -1 AND heroic_def <= 1) +); +CREATE INDEX msg_battle_planet ON msg_battle (planet_id); +GRANT INSERT,DELETE,SELECT ON msg_battle TO legacyworlds; diff --git a/sql/beta5/structure/06-message-internal.sql b/sql/beta5/structure/06-message-internal.sql new file mode 100644 index 0000000..60f26d3 --- /dev/null +++ b/sql/beta5/structure/06-message-internal.sql @@ -0,0 +1,341 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/06-message-internal.sql +-- +-- Beta 5 games: +-- Tables for all types of internal messages except for +-- battle reports +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + + +-- +-- Planets being abandoned +-- +CREATE TABLE msg_abandon ( + id BIGINT NOT NULL REFERENCES message (id) ON DELETE CASCADE, + p_id BIGINT NOT NULL REFERENCES planet (id), + p_name VARCHAR(15) NOT NULL, + PRIMARY KEY (id, p_id) +); +CREATE INDEX msg_abandon_planet ON msg_abandon (p_id); +GRANT INSERT,DELETE,SELECT ON msg_abandon TO legacyworlds; + + + +-- +-- Alliance internal messages +-- +CREATE TABLE msg_alint ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + alliance INT REFERENCES alliance (id) ON DELETE SET NULL, + tag VARCHAR(5) NOT NULL, + player BIGINT REFERENCES player (id), + msg_type SMALLINT NOT NULL CHECK(msg_type >= 0) +); +CREATE INDEX msg_alint_alliance ON msg_alint (alliance); +CREATE INDEX msg_alint_player ON msg_alint (player); +GRANT INSERT,DELETE,SELECT ON msg_alint TO legacyworlds; + + + +-- +-- Bids on auction sales +-- +CREATE TABLE msg_bid ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + offer BIGINT REFERENCES public_offer (offer) ON DELETE SET NULL, + is_planet BOOLEAN NOT NULL, + pname VARCHAR(15) NOT NULL, + planet BIGINT NOT NULL REFERENCES planet (id), + f_gas INT NOT NULL DEFAULT 0, + f_fighters INT NOT NULL DEFAULT 0, + f_cruisers INT NOT NULL DEFAULT 0, + f_bcruisers INT NOT NULL DEFAULT 0, + new_price INT NOT NULL DEFAULT 0, + last_bidder BIGINT REFERENCES player (id) +); +CREATE INDEX msg_bid_offer ON msg_bid (offer); +CREATE INDEX msg_bid_planet ON msg_bid (planet); +CREATE INDEX msg_bid_last_bidder ON msg_bid (last_bidder); +GRANT INSERT,DELETE,SELECT ON msg_bid TO legacyworlds; + + + +-- +-- Cash donations +-- +CREATE TABLE msg_cash ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + p_id BIGINT NOT NULL REFERENCES player (id), + amount INT NOT NULL CHECK(amount > 0) +); +CREATE INDEX msg_cash_player ON msg_cash (p_id); +GRANT INSERT,DELETE,SELECT ON msg_cash TO legacyworlds; + + +-- +-- Fleet movements +-- +CREATE TABLE msg_flmove ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + p_id BIGINT NOT NULL REFERENCES planet (id), + p_name VARCHAR(15) NOT NULL +); +CREATE INDEX msg_flmove_planet ON msg_flmove (p_id); +GRANT INSERT,DELETE,SELECT ON msg_flmove TO legacyworlds; + +CREATE TABLE flmove_data ( + id BIGINT NOT NULL REFERENCES msg_flmove (id) ON DELETE CASCADE, + f_name VARCHAR(64) NOT NULL, + f_owner BIGINT NOT NULL REFERENCES player (id), + f_gaships INT NOT NULL DEFAULT 0, + f_fighters INT NOT NULL DEFAULT 0, + f_cruisers INT NOT NULL DEFAULT 0, + f_bcruisers INT NOT NULL DEFAULT 0, + f_power INT NOT NULL, + hostile BOOLEAN NOT NULL DEFAULT FALSE, + arrived BOOLEAN NOT NULL DEFAULT FALSE, + from_id BIGINT REFERENCES planet (id), + from_name VARCHAR(15) +); +CREATE INDEX flmove_id ON flmove_data (id); +CREATE INDEX flmove_owner ON flmove_data (f_owner); +CREATE INDEX flmove_origin ON flmove_data (from_id); +GRANT INSERT,DELETE,SELECT ON flmove_data TO legacyworlds; + + + +-- +-- Fleets that get switched to attack due to enemy lists +-- +CREATE TABLE msg_flswitch ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + planet BIGINT NOT NULL REFERENCES planet (id), + pname VARCHAR(15) NOT NULL, + n_fleets INT NOT NULL CHECK(n_fleets > 0), + fpower INT NOT NULL CHECK(fpower > 0) +); +CREATE INDEX msg_flswitch_planet ON msg_flswitch (planet); +GRANT INSERT,DELETE,SELECT ON msg_flswitch TO legacyworlds; + + + +-- +-- Hyperspace standby losses +-- +CREATE TABLE msg_hsloss ( + id BIGINT NOT NULL REFERENCES message (id) ON DELETE CASCADE, + p_id BIGINT NOT NULL REFERENCES planet (id), + p_name VARCHAR(15) NOT NULL, + f_name VARCHAR(64) NOT NULL, + gaships INT NOT NULL DEFAULT 0, + fighters INT NOT NULL DEFAULT 0, + cruisers INT NOT NULL DEFAULT 0, + bcruisers INT NOT NULL DEFAULT 0, + power INT NOT NULL CHECK(power > 0) +); +CREATE INDEX msg_hsloss_idx ON msg_hsloss (id,p_id); +CREATE INDEX msg_hsloss_planet ON msg_hsloss (p_id); +GRANT INSERT,DELETE,SELECT ON msg_hsloss TO legacyworlds; + + + +-- +-- Fleet losses due to insufficient income +-- +CREATE TABLE msg_kfleet ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + gaships INT NOT NULL DEFAULT 0, + fighters INT NOT NULL DEFAULT 0, + cruisers INT NOT NULL DEFAULT 0, + bcruisers INT NOT NULL DEFAULT 0 +); +GRANT INSERT,DELETE,SELECT ON msg_kfleet TO legacyworlds; + + + +-- +-- Planetary improvents losses due to insufficient income +-- +CREATE TABLE msg_kimpr ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + turrets INT NOT NULL DEFAULT 0, + factories INT NOT NULL DEFAULT 0 +); +GRANT INSERT,DELETE,SELECT ON msg_kimpr TO legacyworlds; + + + +-- +-- Players leaving the game +-- +CREATE TABLE msg_leave ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + player BIGINT NOT NULL REFERENCES player (id), + reason CHAR(4) NOT NULL DEFAULT 'QUIT' CHECK(reason IN ('QUIT', 'INAC', 'KICK')), + tag VARCHAR(5), + ally BOOLEAN NOT NULL DEFAULT FALSE, + sales_to INT NOT NULL DEFAULT 0, + sales_from INT NOT NULL DEFAULT 0 +); +CREATE INDEX msg_leave_player ON msg_leave (player); +GRANT INSERT,DELETE,SELECT ON msg_leave TO legacyworlds; + + + +-- +-- Planet owner changes +-- +CREATE TABLE msg_ownerch ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + owner BIGINT NULL REFERENCES player (id), + p_id BIGINT NOT NULL REFERENCES planet (id), + p_name VARCHAR(15) NOT NULL, + status CHAR(4) NOT NULL CHECK(status IN ('LOSE', 'TAKE', 'VIEW')) +); +CREATE INDEX msg_ownerch_owner ON msg_ownerch (owner); +CREATE INDEX msg_ownerch_planet ON msg_ownerch (p_id); +GRANT INSERT,DELETE,SELECT ON msg_ownerch TO legacyworlds; + + + +-- +-- Planet sales cancelled due to owner changes +-- +CREATE TABLE msg_plsc ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + seller BIGINT NOT NULL REFERENCES player (id), + taker BIGINT NOT NULL REFERENCES player (id), + p_id BIGINT NOT NULL REFERENCES planet (id), + p_name VARCHAR(15) NOT NULL +); +CREATE INDEX msg_plsc_seller ON msg_plsc (seller); +CREATE INDEX msg_plsc_taker ON msg_plsc (taker); +CREATE INDEX msg_plsc_planet ON msg_plsc (p_id); +GRANT INSERT,DELETE,SELECT ON msg_plsc TO legacyworlds; + + + +-- +-- Planets being renamed +-- +CREATE TABLE msg_rename ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + planet BIGINT NOT NULL REFERENCES planet (id), + status CHAR(5) NOT NULL DEFAULT 'ORBIT' CHECK(status IN ('ORBIT','MOVE','PROBE')), + old_name VARCHAR(15) NOT NULL, + new_name VARCHAR(15) NOT NULL +); +CREATE INDEX msg_rename_planet ON msg_rename (planet); +GRANT INSERT,DELETE,SELECT ON msg_rename TO legacyworlds; + + + +-- +-- Research diplomacy messages +-- +CREATE TABLE msg_resdipl ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + offer BIGINT NOT NULL REFERENCES research_assistance (id), + msg_id SMALLINT NOT NULL CHECK(msg_id >= 0) +); +CREATE INDEX msg_resdipl_offer ON msg_resdipl (offer); +GRANT INSERT,DELETE,SELECT ON msg_resdipl TO legacyworlds; + + + +-- +-- Damage to infrastructure because of revolts +-- +CREATE TABLE msg_revdmg ( + id BIGINT NOT NULL REFERENCES message (id) ON DELETE CASCADE, + planet BIGINT NOT NULL REFERENCES planet (id), + pname VARCHAR(15) NOT NULL, + ifact INT NOT NULL DEFAULT 0, + mfact INT NOT NULL DEFAULT 0, + turrets INT NOT NULL DEFAULT 0, + PRIMARY KEY (id, planet) +); +CREATE INDEX msg_revdmg_planet ON msg_revdmg (planet); +GRANT INSERT,DELETE,SELECT ON msg_revdmg TO legacyworlds; + + + +-- +-- Beginning and end of revolts +-- +CREATE TABLE msg_revolt ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + planet BIGINT NOT NULL REFERENCES planet (id), + pname VARCHAR(15) NOT NULL, + started BOOLEAN NOT NULL +); +CREATE INDEX msg_revolt_planet ON msg_revolt (planet); +GRANT INSERT,DELETE,SELECT ON msg_revolt TO legacyworlds; + + + +-- +-- Sales +-- +CREATE TABLE msg_sale ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + p_id BIGINT REFERENCES planet (id), + p_name VARCHAR(15), + f_name VARCHAR(64), + f_power INT, + pl_id BIGINT NOT NULL REFERENCES player (id), + pl_name VARCHAR(15) NOT NULL, + is_sale BOOLEAN NOT NULL DEFAULT FALSE +); +CREATE INDEX msg_sale_planet ON msg_sale (p_id); +CREATE INDEX msg_sale_player ON msg_sale (pl_id); +GRANT INSERT,DELETE,SELECT ON msg_sale TO legacyworlds; + + + +-- +-- Warnings for incorrect planet names +-- +CREATE TABLE msg_warnname ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + moderator BIGINT NOT NULL REFERENCES player (id), + planet BIGINT NOT NULL REFERENCES planet (id), + p_name VARCHAR(15) NOT NULL, + event CHAR(5) NOT NULL DEFAULT 'WARN' CHECK(event IN ('WARN', 'NEUT', 'ANEUT')) +); +CREATE INDEX msg_warnname_moderator ON msg_warnname (moderator); +CREATE INDEX msg_warnname_planet ON msg_warnname (planet); +GRANT INSERT,DELETE,SELECT ON msg_warnname TO legacyworlds; + + + +-- +-- Planets that get blown up by the WHSN tech +-- +CREATE TABLE msg_whsn ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + p_id BIGINT NOT NULL REFERENCES planet (id), + p_name VARCHAR(15) NOT NULL, + was_owner BOOLEAN NOT NULL DEFAULT FALSE, + f_power INT NOT NULL DEFAULT 0, + e_power INT NOT NULL DEFAULT 0 +); +CREATE INDEX msg_whsn_planet ON msg_whsn (p_id); +GRANT INSERT,DELETE,SELECT ON msg_whsn TO legacyworlds; + +CREATE TABLE whsn_fleet ( + id BIGINT NOT NULL REFERENCES msg_whsn (id) ON DELETE CASCADE, + name VARCHAR(64) NOT NULL, + gaships INT NOT NULL DEFAULT 0, + fighters INT NOT NULL DEFAULT 0, + cruisers INT NOT NULL DEFAULT 0, + bcruisers INT NOT NULL DEFAULT 0, + power INT NOT NULL CHECK(power > 0) +); +CREATE INDEX whsn_fleet_msg ON whsn_fleet (id); +GRANT INSERT,DELETE,SELECT ON whsn_fleet TO legacyworlds; diff --git a/sql/beta5/structure/07-beacons.sql b/sql/beta5/structure/07-beacons.sql new file mode 100644 index 0000000..d74e4d9 --- /dev/null +++ b/sql/beta5/structure/07-beacons.sql @@ -0,0 +1,58 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/07-beacons.sql +-- +-- Beta 5 games: +-- Tables that handle detection of fleets in HSSB by +-- beacons. +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- +-- Detection status +-- +-- Fleets should be listed in this table when they get +-- detected. +-- + +CREATE TABLE beacon_detection ( + planet BIGINT NOT NULL REFERENCES planet (id), + fleet BIGINT NOT NULL REFERENCES fleet (id) ON DELETE CASCADE, + i_level INT NOT NULL CHECK( i_level >= 0 AND i_level <= 4 ), + fl_size INT, + fl_owner BIGINT REFERENCES player (id) ON DELETE CASCADE, + PRIMARY KEY (planet, fleet), + + CHECK( i_level = 0 AND fl_size IS NULL OR i_level > 0 AND fl_size IS NOT NULL ), + CHECK( i_level < 4 AND fl_owner IS NULL OR i_level = 4 AND fl_owner IS NOT NULL ) +); + +CREATE INDEX beacon_detection_fleet ON beacon_detection (fleet); +CREATE INDEX beacon_detection_owner ON beacon_detection (fl_owner); +GRANT SELECT,INSERT,DELETE ON beacon_detection TO legacyworlds; + + +-- +-- Message table +-- + +CREATE TABLE msg_detect ( + id BIGINT PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + planet BIGINT REFERENCES planet (id) ON DELETE CASCADE, + p_name VARCHAR(15) NOT NULL, + is_owner BOOLEAN NOT NULL, + i_level INT NOT NULL CHECK( i_level >= 0 AND i_level <= 4 ), + fl_size INT, + flo_id BIGINT REFERENCES player (id) ON DELETE SET NULL, + flo_name VARCHAR(15), + + CHECK( is_owner OR NOT is_owner AND (i_level = 0 AND fl_size IS NULL OR i_level > 0 AND fl_size IS NOT NULL) ), + CHECK( is_owner OR NOT is_owner AND (i_level < 4 AND flo_name IS NULL OR i_level = 4 AND flo_name IS NOT NULL) ) +); + +CREATE INDEX msg_detect_planet ON msg_detect (planet); +CREATE INDEX msg_detect_owner ON msg_detect (flo_id); +GRANT SELECT,INSERT,DELETE ON msg_detect TO legacyworlds; diff --git a/sql/beta5/structure/07-message-admin.sql b/sql/beta5/structure/07-message-admin.sql new file mode 100644 index 0000000..69f5b45 --- /dev/null +++ b/sql/beta5/structure/07-message-admin.sql @@ -0,0 +1,41 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/07-message-admin.sql +-- +-- Beta 5 games: +-- Tables required to send "administrative spam" to all +-- players. +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- +-- Contents table +-- + +CREATE TABLE admin_spam ( + id SERIAL PRIMARY KEY, + sent_by BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + subject VARCHAR(64) NOT NULL, + contents TEXT NOT NULL +); + +CREATE INDEX admin_spam_sender ON admin_spam (sent_by); + +GRANT SELECT,INSERT ON admin_spam TO legacyworlds; +GRANT SELECT,UPDATE ON admin_spam_id_seq TO legacyworlds; + + +-- +-- Message table +-- + +CREATE TABLE msg_admin ( + id BIGINT PRIMARY KEY REFERENCES message(id) ON DELETE CASCADE, + spam INT NOT NULL REFERENCES admin_spam (id) ON DELETE CASCADE +); + +CREATE INDEX msg_admin_spam ON msg_admin (spam); +GRANT INSERT,DELETE,SELECT ON msg_admin TO legacyworlds; diff --git a/sql/beta5/structure/10-ctf-tables.sql b/sql/beta5/structure/10-ctf-tables.sql new file mode 100644 index 0000000..270d8d8 --- /dev/null +++ b/sql/beta5/structure/10-ctf-tables.sql @@ -0,0 +1,99 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/10-ctf-tables.sql +-- +-- Beta 5 games: +-- Tables specific to CTF games +-- +-- Copyright(C) 2004-2008, DeepClone Development +-- -------------------------------------------------------- + + +-- +-- Target status +-- +-- Targets are always listed in this table, and the status +-- of the targets is set according to who holds the system. +-- + +CREATE TABLE ctf_target ( + system INT PRIMARY KEY REFERENCES system (id), + held_by INT REFERENCES alliance (id), + held_since INT, + grace_expires INT, + + CHECK( (held_by IS NULL AND held_since IS NULL) + OR (held_by IS NOT NULL AND held_since IS NOT NULL) ), + + CHECK( (held_by IS NULL AND grace_expires IS NULL) OR held_by IS NOT NULL ) +); + +CREATE INDEX ctf_target_alliance ON ctf_target (held_by); + +GRANT SELECT,INSERT,UPDATE ON ctf_target TO legacyworlds; + + +-- +-- Allocated system status +-- +-- This is used in order to clear players out of other +-- teams' zones and to know where each player was spawned. +-- + +CREATE TABLE ctf_alloc ( + system INT PRIMARY KEY REFERENCES system (id), + alliance INT NOT NULL CHECK( alliance > 0 AND alliance < 9 ), + spawn_point BOOLEAN NOT NULL, + player BIGINT REFERENCES player (id), + + CHECK( spawn_point OR (NOT spawn_point AND player IS NULL) ) +); + +GRANT SELECT,INSERT,UPDATE ON ctf_alloc TO legacyworlds; + + +-- +-- Team points +-- +-- This table stores the points for each team +-- + +CREATE TABLE ctf_points ( + team INT NOT NULL PRIMARY KEY REFERENCES alliance(id), + points INT NOT NULL DEFAULT 0 CHECK (points >= 0 AND points <= 100) +); +GRANT SELECT,INSERT,UPDATE ON ctf_points TO legacyworlds; + + +-- +-- Game messages +-- +-- The message type is one of the following: +-- 0 => Player joined the game, inform him [team] +-- 1 => Player joined a team, inform the rest of the team [team] +-- 2 => A player's team is now holding all the targets [team,time_stamp] +-- 3 => Another team is now holding all the targets [team,time_stamp] +-- 4 => A player's team is no longer holding all the targets, but there is a grace period [team,time_stamp] +-- 5 => A player's team is no longer holding all the targets, and there is no grace period [team] +-- 6 => A player's team is no longer holding all the targets, and the grace period has expired [team] +-- 7 => Another team is no longer holding all the targets, but there is a grace period [team,time_stamp] +-- 8 => Another team is no longer holding all the targets, and there is no grace period [team] +-- 9 => Another team is no longer holding all the targets, and the grace period has expired [team] +-- 10 => A player's team is still holding the targets after half the required time [team,time_stamp] +-- 11 => Another team is still holding the targets after half the required time [team,time_stamp] +-- 12 => A player's team has held the targets long enough and the game has been reset [team] +-- 13 => Another team has held the targets long enough and the game has been reset [team] +-- 14 => A player's team has won the match [team] +-- 15 => A player's team has lost the match [team] + +CREATE TABLE msg_ctf ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + msg_type INT NOT NULL CHECK(msg_type >= 0 AND msg_type < 16), + team INT NOT NULL REFERENCES alliance (id), + time_stamp INT +); + +CREATE INDEX msg_ctf_team ON msg_ctf (team); + +GRANT SELECT,INSERT,UPDATE,DELETE ON msg_ctf TO legacyworlds; diff --git a/sql/beta5/structure/10-prot-tables.sql b/sql/beta5/structure/10-prot-tables.sql new file mode 100644 index 0000000..99e971d --- /dev/null +++ b/sql/beta5/structure/10-prot-tables.sql @@ -0,0 +1,89 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/10-prot-tables.sql +-- +-- Beta 5 games: +-- Tables to support protection +-- +-- Copyright(C) 2004-2008, DeepClone Development +-- -------------------------------------------------------- + + +-- +-- Enemies of the Peacekeepers +-- +CREATE TABLE pk_enemy ( + player BIGINT NOT NULL PRIMARY KEY REFERENCES player (id) ON DELETE CASCADE, + until INT NOT NULL +); +CREATE INDEX pk_enemy_until ON pk_enemy (until); +GRANT SELECT,INSERT,DELETE,UPDATE ON pk_enemy TO legacyworlds; + + +-- +-- Offenses against protected systems +-- +CREATE TABLE pk_offenses ( + player BIGINT NOT NULL PRIMARY KEY REFERENCES player (id) ON DELETE CASCADE, + nb_offenses INT NOT NULL +); +CREATE INDEX pk_offenses_nb ON pk_offenses (nb_offenses); +GRANT SELECT,INSERT,DELETE,UPDATE ON pk_offenses TO legacyworlds; + + +-- +-- System status +-- +-- Status is one of: +-- A -> Allied player +-- W -> Warning sent +-- O -> Player is on the offensive +-- E -> Player has been declared an enemy +-- +CREATE TABLE pk_sys_status ( + system INT NOT NULL REFERENCES system (id), + player BIGINT NOT NULL REFERENCES player (id) ON DELETE CASCADE, + status CHAR(1) NOT NULL CHECK(status IN ('A', 'W', 'O', 'E')), + switch_at INT, + PRIMARY KEY (system, player) +); +CREATE INDEX pk_sys_status_system ON pk_sys_status (system); +CREATE INDEX pk_sys_status_player ON pk_sys_status (player); +CREATE INDEX pk_sys_status_status ON pk_sys_status (status); +CREATE INDEX pk_sys_status_switch_at ON pk_sys_status (switch_at); +GRANT SELECT,INSERT,DELETE,UPDATE ON pk_sys_status TO legacyworlds; + + +-- +-- End of protection messages +-- +CREATE TABLE msg_endprotection ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + end_type CHAR(3) NOT NULL CHECK (end_type IN ('BRK', 'ACT', 'EXP')) +); +GRANT INSERT,DELETE,SELECT ON msg_endprotection TO legacyworlds; + +-- +-- Warnings from the Peacekeepers +-- +-- msg_type is one of: +-- 'W' -> warning, player must leave the planets +-- 'D' -> "you will be destroyed" +-- 'E' -> "you have been declared an enemy" +-- +CREATE TABLE msg_pkwarning ( + id BIGINT NOT NULL PRIMARY KEY REFERENCES message (id) ON DELETE CASCADE, + msg_type CHAR(1) NOT NULL CHECK(msg_type IN ('W', 'D', 'E')), + delay INT +); +GRANT SELECT,INSERT,DELETE ON msg_pkwarning TO legacyworlds; + +CREATE TABLE pkwarning_planet ( + id BIGINT NOT NULL REFERENCES msg_pkwarning (id) ON DELETE CASCADE, + planet BIGINT NOT NULL REFERENCES planet (id), + p_name VARCHAR(15) NOT NULL, + PRIMARY KEY (id, planet) +); +CREATE INDEX pkwarning_planet_id ON pkwarning_planet (planet); +GRANT SELECT,INSERT,DELETE ON pkwarning_planet TO legacyworlds; diff --git a/sql/beta5/structure/99-player-fk.sql b/sql/beta5/structure/99-player-fk.sql new file mode 100644 index 0000000..1954089 --- /dev/null +++ b/sql/beta5/structure/99-player-fk.sql @@ -0,0 +1,15 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/99-player-fk.sql +-- +-- Beta 5 games: +-- Foreign keys on the player table +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + +ALTER TABLE player ADD FOREIGN KEY (alliance) REFERENCES alliance (id) ON DELETE SET NULL; +ALTER TABLE player ADD FOREIGN KEY (a_grade) REFERENCES alliance_grade (id) ON DELETE SET NULL; +ALTER TABLE player ADD FOREIGN KEY (a_vote) REFERENCES alliance_candidate (id) ON DELETE SET NULL; +ALTER TABLE player ADD FOREIGN KEY (first_planet) REFERENCES planet (id); diff --git a/sql/beta5/structure/finalise.sql b/sql/beta5/structure/finalise.sql new file mode 100644 index 0000000..8e6e9d9 --- /dev/null +++ b/sql/beta5/structure/finalise.sql @@ -0,0 +1,15 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- beta5/structure/finalise.sql +-- +-- Finalise a Beta 5 game database by revoking extra +-- privileges from the user role and dropping helper +-- functions +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + +-- Revoke user privileges +REVOKE INSERT ON gdata FROM legacyworlds; +