]> de.git.xonotic.org Git - xonotic/xonotic.git/commitdiff
fix lots of CRLFs
authorRudolf Polzer <divverent@alientrap.org>
Sat, 17 Apr 2010 19:00:10 +0000 (21:00 +0200)
committerRudolf Polzer <divverent@alientrap.org>
Sat, 17 Apr 2010 19:00:10 +0000 (21:00 +0200)
71 files changed:
.gitattributes
Docs/basics.html
Docs/compile-settings.txt
Docs/egyptsoc/egyptsoc_readme.txt
Docs/eventlog.txt
Docs/faq.html
Docs/htmlfiles/style.css
Docs/irc.html
Docs/mapdownload.txt
Docs/mapping.txt
Docs/nexuiz.game
Docs/say-esc.html
Docs/scorelog.txt
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderApplication.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderException.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/application/DemoRecorderUtils.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/application/NDRPreferences.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/application/RecorderJobPoolExecutor.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutter.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterException.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutterUtils.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoPacket.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/application/jobs/EncoderJob.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordJob.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordsDoneJob.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPlugin.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/application/plugins/EncoderPluginException.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/main/Driver.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/ui/DemoRecorderUI.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/ui/swinggui/ApplyTemplateDialog.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/ui/swinggui/JobDialog.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/ui/swinggui/NexuizUserDirFilter.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/ui/swinggui/PreferencesDialog.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/ui/swinggui/RecordJobTemplate.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/ui/swinggui/StatusBar.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/ui/swinggui/SwingGUI.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobTemplatesTableModel.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/ui/swinggui/tablemodels/RecordJobsTableModel.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/ShowErrorDialogExceptionHandler.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/SwingGUIUtils.java
misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/ui/swinggui/utils/XProperties.java
misc/tools/NexuizDemoRecorder/main/src/main/resources/about.html
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/DemoRecorderHelp.hs
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/DemoRecorderHelpIndex.xml
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/DemoRecorderHelpTOC.xml
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/JHelpDev Project.xml
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/html/advanced-how-it-works.html
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/html/advanced-prelim-stop.html
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/html/advanced-table-settings.html
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/html/advanced-topics.html
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/html/apply_templates.html
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/html/basic_tutorial.html
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/html/changelog.html
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/html/compat-limitations.html
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/html/credits.html
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/html/faq.html
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/html/introduction.html
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/html/license.html
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/html/open-save.html
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/html/plugin-architecture.html
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/html/plugin-virtualdub.html
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/html/preferences.html
misc/tools/NexuizDemoRecorder/main/src/main/resources/help/html/templates.html
misc/tools/NexuizDemoRecorder/plugins/sample/pom.xml
misc/tools/NexuizDemoRecorder/plugins/sample/src/main/java/com/nexuiz/demorecorder/application/plugins/impl/sample/SamplePlugin.java
misc/tools/NexuizDemoRecorder/plugins/sample/src/main/resources/META-INF/services/com.nexuiz.demorecorder.application.plugins.EncoderPlugin
misc/tools/NexuizDemoRecorder/plugins/virtualdub/src/main/java/com/nexuiz/demorecorder/application/plugins/impl/virtualdub/VirtualDubPlugin.java
misc/tools/NexuizDemoRecorder/plugins/virtualdub/src/main/resources/META-INF/services/com.nexuiz.demorecorder.application.plugins.EncoderPlugin
readme.html
server/readme.txt
server/server.cfg

index edd30fd6d3a6a26ff33e82b9aefc4add51322d70..67420ef0ad13d8f98bbe86dd7002e157eddef660 100644 (file)
@@ -95,8 +95,8 @@ makespr32 crlf=input
 *.mapinfo crlf=input
 *.m crlf=input
 *.md3 -crlf -diff
-*.md5anim crlf=input
-*.md5mesh crlf=input
+*.md5anim -crlf
+*.md5mesh -crlf
 *.mdl -crlf -diff
 *.med crlf=input
 *.mf crlf=input
@@ -124,7 +124,7 @@ PkgInfo crlf=input
 *.pm crlf=input
 *.png -crlf -diff
 POSITIONS -crlf -diff
-*.proj crlf=input
+*.proj -crlf
 *.properties crlf=input
 *.psd -crlf -diff
 *.py crlf=input
index fed325b8a3a71bb5f87f8b64368430a1f01e45a6..fbe2d50ff2fc84aa845c89b827e514a2c99b1bfa 100644 (file)
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\r
-        "http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd">\r
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\r
-<head>\r
-<meta http-equiv="content-type" content="text/html; charset=utf-8" />\r
-<meta name="author" content="Tyler Mulligan of www.detrition.net a.k.a -z- of www.nexuizninjaz.com" />\r
-<meta name="Description" content="Nexuiz is a free open-source fast paced first person shooter (FPS) that runs on Windows, Linux and OSX.  Nexuiz utilizes the darkplaces engine modeled after the Quake series." />\r
-<meta name="Keywords" content="Nexuiz, Nexiuz, first person shooter, darkplaces, open-source game, open source game, free game, linux game, deathmatch, death match, ctf, quake, alientrap, alien trap, ninjaz" />\r
-<title>Nexuiz - A free open-source fast paced first person shooter (FPS) for Windows, Linux and OSX</title>\r
-<link rel="stylesheet" href="htmlfiles/style.css" type="text/css"></link>\r
-<link rel="shortcut icon" href="favicon.ico"></link>\r
-</head>\r
-<body>\r
-<div id="container">\r
-       <div id="left">\r
-               <div id="logo">\r
-\r
-                       <a href="http://www.nexuiz.com" title="Nexuiz - Simple, fast, intense and completely free"><img src="htmlfiles/img/nexuiz_logo.jpg" alt="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="201" height="193" border="0" title="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." /></a>\r
-               </div>\r
-               <!-- Sidebar -->\r
-         <div id="sidebar">\r
-                       <a href="news.php" title="Latest Nexuiz News"><img src="htmlfiles/img/headers/latest_news.gif" alt="Latest News" width="168" height="16" border="0" class="imgHeader" style="margin-top:0;" title="Latest Nexuiz News" /></a>\r
-                       <div id="news">\r
-                         <div class="newsPost">\r
-                                               <h1><a href="http://nexuiz.com/news.php" title="Two Nexuiz 1v1 Tourneys">nexuiz.com/news - for nexuiz news updates</a></h1>\r
-                         </div>\r
-               </div>\r
-                       <!-- Downloads -->\r
-                       <a href="downloads.php" title="Nexuiz Downloads (Game, Maps)"><img src="htmlfiles/img/headers/downloads.gif" alt="Downloads" width="145" height="15" border="0" class="imgHeader" title="Downloads" /></a>\r
-                       <a id="download_nexuiz" href="http://www.sourceforge.net/projects/nexuiz/" title="Download Nexuiz from Source Forge - Left Click">&nbsp;</a>\r
-                       <!--<a id="download_q3_mappack" href="http://downloads.sourceforge.net/nexuiz/nexmappack_r2.zip" title="Download Nexuiz Q3 Mappack from Source\r
-                       Forge - Left Click">&nbsp;</a>-->\r
-                       <h2 class="page"><a href="http://nexuiz.com/downloads.php" title="Nexuiz Download Mirrors">Mirrors -&gt;</a></h2>\r
-                       <!-- Help Wanted -->\r
-\r
-                       <img src="htmlfiles/img/headers/help_wanted.gif" alt="Help Wanted" width="165" height="15" class="imgHeader" title="Help Wanted" />                     \r
-                       <form action="https://www.paypal.com/cgi-bin/webscr" method="post" name="donate_form"> <input name="cmd" value="_s-xclick" type="hidden" />\r
-                       <a id="donate" href="javascript:document.donate_form.submit();" title="Help The Aliens, Please Donate">&nbsp;</a>\r
-                       <noscript>\r
-                               <!-- It not an elegant solution but I don't expect many users to arrive with JS turned off -->\r
-                               <input name="submit" type="image" src="htmlfiles/img/donate_button_noscript.jpg" alt="Help this project - Donate to the aliens" width="166" height="43" /> \r
-                       </noscript>\r
-                       <h2 class="page"><a href="http://nexuiz.com/donators.php" title="Those who have donated to Nexuiz">Donators -&gt;</a></h2>\r
-                       <input name="encrypted" value="-----BEGIN PKCS7-----MIIHBgYJKoZIhvcNAQcEoIIG9zCCBvMCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYBfp40KRLmnxKPY06C4gjvEiWZchbxK6bgD7ZdjhdWO5Vbwo4T4Ro+HE041PVVqIxPlJgO80l3aQpBtfhC66FfM2kIF1BjLs1zzhQM89XoPGViS3e4kbmzxkMnpdiZFmOsR5Fs5NJYiVaMnVGcoQ+K3+KsyOehZGket7GwUeNFMRzELMAkGBSsOAwIaBQAwgYMGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIb1QeJqVIc5SAYOPcI23W84XMGt8mSfHE1Gf0/GZAM7NvqLiHF0BeaecRW1Bc85O0tL9OduZiraGf7WVnAmP5kp1D0irXsA5+N2l15WADxwNQ/GoCAU293l0dAQ7Qy4F3vh6eSii18MaH2KCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA1MDUxMjE5MDQ0OFowIwYJKoZIhvcNAQkEMRYEFC7mlfdaA7Pg2eBhxI5xQTe7ydTtMA0GCSqGSIb3DQEBAQUABIGAfR38tX84huxk9JjvhggcSMxzHbmDxpxInBU6/lbyqAu7iT5KJn7rcJgaH5ZVyKNoNQLGp9IxweBrcMiYUQNVxShm9+hunXhQmj5r7AMGaxNZ0mE8mQRW1ZTaz7TLz1HkDFA+R0Dm8HYyDQA4L505cBiWNEsKC17VwNK1G7CEVvA=-----END PKCS7-----" type="hidden" /></form>\r
-                       \r
-                 <!-- Created By -->\r
-                 <img src="htmlfiles/img/headers/created_by.gif" alt="Created By" width="145" height="15" class="imgHeader" title="Created By" />\r
-\r
-         <a id="alien_trap" href="http://www.alientrap.org" title="Developed by Alien Trap" target="_blank">&nbsp;</a>   </div>\r
-               <!-- end sidebar -->    </div><!-- end left -->\r
-       <div id="right">\r
-               <div id="header">\r
-                       <a href="http://nexuiz.com" title="Nexuiz - Simple, fast, intense and completely free"><img src="htmlfiles/img/nexuiz_header.jpg" alt="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="766" height="107" border="0" title="Nexuiz - Simple, fast, intense and completely free" /></a>\r
-               </div>\r
-               <div id="menu">\r
-                       <ul>\r
-\r
-                               <li class="first"><a href="../readme.html" title="Nexuiz - Latest News">About </a></li>\r
-                               <li><a href="basics.html" title="Nexuiz - Media (Screenshots, videos and graphics)">Nexuiz Basics</a></li>\r
-                               <li><a href="say-esc.html" title="Nexuiz Information  (General, System Requirements)">Say Escapes</a></li>\r
-                               <li><a href="irc.html" title="Nexuiz - Downloads (Game, Maps)">IRC</a></li>\r
-                               <li><a href="faq.html" title="Official Nexuiz FAQ">FAQ</a></li>\r
-                               <li class="first"></li>\r
-                               <li class="first"></li>\r
-                 </ul>         </div>\r
-               <p id="tagline">Nexuiz is a <b>free</b> open-source first person shooter that runs on <b>Windows</b>, <b>Linux</b> and <b>OSX</b>.</p>\r
-         <div id="content">\r
-<div class="body">\r
-\r
-<br><center><h1><a name="AFT-top">Nexuiz Basics explained</a></h1></center>\r
-<center><a class="link" href="http://nexuiz.com">http://nexuiz.com</a>&nbsp;&nbsp;<a class="link" href="http://alientrap.org">http://alientrap.org</a></center>\r
-<hr>\r
-<p class="Body">\r
-This file attempts to cover all of the fundamental principles and techniques for playing Nexuiz in particular, though it will largely apply to any first person shooter.\r
-</p>\r
-<!-- Start SectLevel1 -->\r
-<h2><a name="_Getting Comfortable_"><strong>Getting Comfortable</strong></a></h2>\r
-<p class="Body">\r
-The most important thing when playing is 'to feel good'.  You need to be comfortable with how the game feels, and how it works, in order to play well.  This means being able to concentrate on the game play itself, without being disturbed by in-game distractions or by various sorts of handicaps.  This guide provides a systematic but very brief introduction to Nexuiz, with a view to increasing your effectiveness in-game, straight off the mark.\r
-</p>\r
-<!-- Start SectLevel2 -->\r
-<h3><a name="''Reducing Clutter''"><em>Reducing Clutter</em></a></h3>\r
-<p class="Body">\r
-Especially when playing against others the first thing you need to ensure is that you can hear and see well.  Although music can enhance the feeling of a game by complementing the action, it can also obscure the sounds of other events - such as players shooting at you or picking up items nearby.  For this reason, many people turn off music while playing (set <em>music</em> in the <em>settings/misc</em> menu to <em>off</em>).  You can also improve your ability to hear in-game events by simply using headphones instead of speakers.  Headphones muffle external noises around you, and also make it easier to hear faint noises in the game.  Because of this, you can hear others more easily, and thus react better.\r
-</p>\r
-<p class="Body">\r
-Similarly, eye-candy can make the game very appealing to look at, but can also act to obscure in-game events - the flares and smoke caused by rockets, for example, can make spotting your target much more difficult in the heat of battle.  Eye-candy also incurs performance hits on slower machines, which can make playing difficult.  Bloom might be nice to look at, but it is often just distracting when playing.  So again, many players disable some of the visual effects, so as to make the game easier to play in this regard (things to turn off include <em>Bloom</em>, <em>HDR</em>, <em>Gloss</em>, <em>Coronas</em> and all the Realtime stuff in the <em>settings/effects</em> menu).  Although it may seem as if removing visual (and audio) effects will make the game less immersive by reducing the mood which the game designers were aiming for, in the long run it will make it much more rewarding and fun to play.  Darkness and shadows can create creepiness and realism, but you simply don't notice these things in a fast-paced multi player battle.  On the contrary, they make it harder to spot opponents or items and end up being frustrating.  Better to play the game in campaign mode with full visual effects to see how pretty it can be, and then, once you're over the initial craving for eye-candy, start tuning the game to be more functional in multi player.\r
-</p>\r
-<p class="Body">\r
-In this regard, one of the first things to adjust is the brightness and contrast (in the <em>settings/video</em> menu), so that shadows are not too impenetrable, and areas of alternating light and dark don't create difficulties in tracking opponents.  It's also worthwhile to try increasing you field of view (fov) - the amount you can see to the left and right.  By default, Nexuiz uses a fov of 90 degrees.  Increasing this improves your peripheral vision, but also makes everything seem smaller and thus reduces your ability to aim.  Decreasing it has the opposite effect.  Most gamers find a nice balance somewhere around fov 100-120.  You can find the fov control in the <em>multiplayer/player setup</em> menu.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="''Controlling Your Character''"><em>Controlling Your Character</em></a></h3>\r
-<p class="Body">\r
-Another thing which will greatly improve the game experience is to have comfortable movement controls, and to be able to get to the 'right gun' quickly.  In terms of movement, there's no need to stick to the game defaults for controlling your character.  Some people like to jump with space; some with mouse2.  Some like inverted mouse, and some can't use it.  Some use WASD for forward, left, back, right; some use ESDF or even 8456.  Use whatever you feel good with.\r
-</p>\r
-<p class="Body">\r
-In terms of weapon controls, try to get out of the habit of using the mouse's scroll-wheel to select which weapon to use.  This is a very slow and unreliable way of changing weapons, and in a fast-paced deathmatch your ability to correctly and immediately pick the most appropriate gun for the job is critical.  For example, if you hit someone pretty hard with the rocket launcher, and he has taken a lot of damage, you should switch to the shotgun or machine gun to finish him off, rather than worrying about whether you'll be able to hit him again with the slower and more inaccurate rockets.  Using the mouse wheel to switch may well take too long, and you also have to keep an eye on the weapons as they scroll by (unless your mouse has very good scroll feedback, <em>and</em> you've memorized the order of weapons, <em>and</em> you know which weapons you currently have, <em>and</em> you can figure out on the fly how many scrolls you need to get to the shotgun!).  On the other hand, if you have configured, say, the G key to switch to the shotgun, you can easily switch quickly and reliably, and finish him off in an instant.  For this reason, it's extremely helpful to configure weapon-selection keys around the ones you use for moving.  It's also obviously handy to use a similar layout in all the games you play.  For example, you can have G as the key for 'all shotgun like weapons' in each game.  All 'rocket like' weapons can go on R, and all 'sniper guns' on T, and so on - or whatever suits you!  This way you do not need to think about which key you need for which gun - just what you want to do.  You can change those <em>key bindings</em> in the <em>settings/input</em> menu.\r
-</p>\r
-<p class="Body">\r
-Finally, adjust your mouse sensitivity.  Sensitivity controls how much (or fast) your view turns when moving the mouse.  You can make it high sensitivity, which means you need to move the mouse only a short distance to turn fast or far; or you can make it low sensitivity, so that you turn slowly and have to move the mouse further.  A high sensitivity reduces the need to lift and reposition the mouse, and it will help you when doing lots of turns.  A low sensitivity will help you to aim more easily, and also make your aim more steady if you are nervous and have slightly shaky hands.  For the most part, the level of sensitivity you choose depends on how you aim.  This is a very personal thing and you will have to see what you like - again, select the setting which is most comfortable for you.  However, a good rule of thumb when you're learning to play is to configure your sensitivity a little lower than you're comfortable with, and try to get used to it.  After a few hours you will probably find that you have acclimatized, and are aiming better than before.  Keep reducing the sensitivity below your comfort zone until you are happy with your aim, or until it definitely becomes too low for you.  You'd be surprised the difference this makes.  Also consider that you can either use your wrist or your arm to aim - and you'll probably do one or the other more naturally.  Using the wrist is more exact, and thus high sensitivity is found more often among wrist aimers; using the arm involves more mouse movements, and so low sensitivity is common to arm aimers.  As a rule of thumb your sensitivity should be set so that you can comfortably perform a 180 degree turn with one sweep of your mouse.  The sensitivity setting can be found in the <em>settings/input</em> menu.\r
-</p>\r
-<!--End Section 2-->\r
-<!-- End SectLevel2 -->\r
-<!--End Section 1-->\r
-<h2><a name="_Playing_"><strong>Playing</strong></a></h2>\r
-<!-- Start SectLevel2 -->\r
-<h3><a name="''Identifying Your Opponent''"><em>Identifying Your Opponent</em></a></h3>\r
-<p class="Body">\r
-Depending on the game mode played you either fight for yourself against all others (sometimes there is just one other player to play against) or its your team vs the other team(s).  Pay attention to the welcoming screen it will tell you what gamemode is being played.  You can find an explanation of those at the end of this guide.\r
-If its a team match you might have to choose a team or you could be automatically assigned to a team when you join the match.  If you have to choose yourself its best to just use the <tt>Auto</tt> button!  It will assign you to the smallest team or if the teams have the same size to the one that has less points.  Only use the specific team buttons if you REALLY want to join that team.  You can press F5 to bring up the team selection dialog or instead of pressing F5 and clicking on the <tt>Auto</tt> button you can also press F6, thats a shortcut for the <tt>Auto</tt> button.  If you are unsure about which team you are in look at your gun or at the scoreboard, which by default will be shown when you press <em>TAB</em>.  The weapon color and the color beside your name is your team color.  Try to only hit the OTHER team(s) :).\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="''Hitting Your Opponent''"><em>Hitting Your Opponent</em></a></h3>\r
-<p class="Body">\r
-Once you're comfortable with your controls and your mouse, you should already find it easier to hit your opponents.  And, if you've bound keys to each of the weapons, you can accurately pick which one to use.  But to know which weapon to select, and to get the most out of it, you must understand the advantages and the use of each.\r
-</p>\r
-<p class="Body">\r
-When you use direct-hit (hitscan) weapons like the shotgun, machinegun, campingrifle and the nex, you 'just' have to directly aim with the crosshair and hit fire.  Obviously this can be much harder than it sounds, particularly when both you and your opponent are moving.  Therefore, you should use geometry to your advantage, to reduce the relative movement between you as much as possible, so that you have as much time as possible to aim.  When someone is moving from one side of your view to the other it is harder to aim at him than when is moving towards or away from you.  In the latter case he will hardly change his position relative to yours, and this gives you more time to aim at him.  In the former, his position is changing a lot, and so you have to react to how he moves very quickly and accurately to ensure a good shot.  The same principle applies when your opponent uses a jump pad.  He will prescribe a trajectory in the air, and at its highest point he will stop for a moment.  It is easier to aim at him at this point than when he is accelerating off the jump-pad.\r
-</p>\r
-<p class="Body">\r
-In addition to using geometry to your advantage, when you're moving relative to your opponent it is sometimes easier to <em>not</em> aim with the mouse, but rather to leave it steady at the right height, and use the strafe buttons to move yourself and thus your cross hair onto him.  If you have the advantage of his not seeing you, or being unable to shoot at you, or even if you simply have a health and armor advantage over him, you can even just aim at some point and wait for him to walk into your cross hair.  However, always bear in mind that people will generally move unpredictably so as to lessen their chances of being hit - so unless you're sure of where he's going, don't sacrifice your own movement advantage for a hit that might never come.\r
-</p>\r
-<p class="Body">\r
-Using projectile weapons such as the mortar, electro, hagar or rocket launcher can be both easier and harder than using hitscan weapons.  You must still consider the same geometric factors, but you also have to keep in mind that your projectile will take some time to travel the distance between you and your opponent.  In that time they will continue moving!  So it's absolutely no use to aim directly at them.  It will take some experience with each weapon, and with getting used to how people move, but you should eventually develop a good instinct for where your opponent will be at the time when your projectile will land (and where the projectile itself will land), and thus be able to 'lead' him when you aim.  If your target moves to the right, aim further to the right and press fire.  Just note how off your hit was and try to adjust next time.  It takes a few matches to get the hang of this.  Also consider that almost all projectiles will explode when they hit something hard, and those explosions will also cause damage to players nearby (splash damage).  Now thats the part that makes projectile weapons easier to use.  You can use splash damage to your advantage, so that rather than trying to predict the exact spot at which to aim your projectile (which is very difficult), you can aim at the general area of floor or wall where you expect your opponent to be and let the splash damage do its work.  Along these lines, you'll find that it's much easier to aim at his feet and use the splash damage from the explosion on the floor than it is to hit him directly.\r
-</p>\r
-<p class="Body">\r
-Also bear in mind that people try to evade projectiles; and it is even comparatively easy to do this at distance.  If you fire to their left they will see it coming, and change direction to the right.  You can use this to your advantage by boxing them in with explosions - just fire another one to their right.  If you correctly place both projectiles, they will be unable to evade the splash damage from one of them.  Either they will avoid the first and be hit by the second, or they will backtrack to avoid the second and be hit by the first.  Or, even better, they'll not see the first one at all and be hit directly.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="''Controlling The Map''"><em>Controlling The Map</em></a></h3>\r
-<p class="Body">\r
-To put up a good fight you need to survive an attack, so make sure you pick up health and armor lying around the map.  This will also ensure that your opponents are deprived of these, so it will be easier for you to frag them.  To do this effectively you will have to learn where those items are, and try to get them before someone else does.  Armor and health items reappear 30 seconds after they were taken.  You can use this to your advantage by timing your pickups - look at the scoreboard to see the current map time, and then use this to predict when items will respawn.  This way, you can develop patterns on the map so that you're always around when an item respawns.  However, be careful - patterns make you predictable, and your opponent can also use the timer!  If you keep grabbing the armor or health, he will use this against you by lying an ambush.\r
-</p>\r
-<p class="Body">\r
-The health and armor system in Nexuiz is different to other games in that it tends toward an equilibrium of 100 health and 100 armor.  You can collect as much health and armor as you like, but it will degenerate toward these values.  The more health and armor you have, the faster it will degenerate - but when you pick them up the degeneration stops momentarily.  Because of this, you can't just load up on health and armor and head off into battle - you need to constantly replenish to remain strong.  On the converse, though, when your health is below 100 it will slowly <em>re</em>generate (armor will not).  The lower it is, the faster it will replenish.  This regeneration stops for a few seconds if you are hurt, so just running away after a fight helps staying alive but is usually not enough to restore you to useful battle ready levels again - you have to wait a while to regain your full health, and so picking up health after a fight is still important.\r
-</p>\r
-<p class="Body">\r
-In regard to the previous point of working a map to control items, and also shooting at where your opponent will be, it's important to try to predict where on the map your opponents are even when you can't directly see them.  First, try to listen for them; secondly, learn the maps so you know the most useful routes and the most popular areas to which people tend to gravitate.  It gives you a marked advantage to be able to say, &quot;I saw or heard someone pick up an armor, so they must be at such-and-such location, and from there he can only go to this place or that place.&quot; Just shooting something to the likely places your opponent will be - a rocket or a grenade for example - can often result in either a surprise kill, or in surprise damage which you can then follow up.  Don't underestimate how likely a well-aimed educated guess is to hit someone.  Maybe he'll walk into it - rockets can be hard to see coming, and grenades lying on the floor or spammed into a small space can be really hard to avoid.  Once you've hurt him you'll hear the tink of your damage, and you will also know for sure were he is and can finish him off.  You can pretty much 'lock down' a map by picking up items, and making sure others have a hard time getting around which will also mess with their concentration.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="''Using Weapons Effectively''"><em>Using Weapons Effectively</em></a></h3>\r
-<p class="Body">\r
-Among the items you can pick up are the weapons.  Some of them are very unique to Nexuiz, and to get the most out of them you need to be familiar with every aspect of how they work - including the rate of fire, the damage per hit, the spread and speed of the payload, and so on.\r
-</p>\r
-<p class="Body">\r
-Almost all weapons in Nexuiz have two firing modes, so make sure you know both.  You normally (depending on game-mode) start equipped with two weapons: the Laser and the shotgun.  You can bind these weapons to a key at the <em>settings/input</em> menu or in the console using the command <em>bind key &quot;impulse <strong>n</strong>&quot;</em>, where n is the number of the weapon.\r
-</p>\r
-<table style="border: 0"><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-laser.png" alt="laser"></td><td style="border: 0"><p class="Body">\r
-The <strong>Laser</strong> is not often used as a weapon, as it does not do much damage - but it's a useful tool to move around the map, because it has a significant push.  The 'explosion' from its projectile will push you and other players around a lot.  The trick to using it to move around is to use this push to gain speed or to do huge jumps.  For an easy start, take the Laser, look down at the floor, and press fire.  It will push you high into the air.  If you press jump and fire at the same time, you will get even higher - but it depends on the timing, and, for more advanced jumps, also on the angle you fire at the floor, as well as how fast and in which direction you move.  For example if you just run forwards, look down and press fire it will catapult you forward with great speed.  Play around with it, and watch others, and you will learn a very useful skill in Nexuiz.  The secondary fire mode of the Laser switches back to the last used weapon.  The Laser does not use up any ammo.\r
-</p>\r
-</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-laser.png" alt="laser"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-hookgun.png" alt="hookgun"></td><td style="border: 0"><p class="Body">\r
-The <strong>Grappling Hook</strong> is an other movement tool.  The primary mode attaches itself to all sorts of solid objects and will pull you there to move through the map like spider man.  You can get very fast that way or reach spots you would not get to by other means.  The secondary firemode drops a small gravity bomb that will affect enemy players and also releases a lot of smoke which will make you harder to see and hit.  The gravity bomb uses cell ammo.  There is also a mutator that adds a off-hand hook which can be used all the time, uses no ammo but only does the pulling part.\r
-</p>\r
-</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-hookgun.png" alt="hookgun"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-porto.png" alt="porto"></td><td style="border: 0"><p class="Body">\r
-The <strong>Port-O-Launch</strong> is the perfection of movement.  The grenade it launches creates two portals.  On the first impact it creates the entrance portal, on the second impact the exit portal.  If you enter the first one you (or others players and also projectiles!!) will be teleported to the second portal.  As the first portal will teleport everything it can't be hit but the second portal is quite fragile and, because of the used wormhole technology, will take the other portal along if it lost too much energy.  The wormhole is also the reason why the portals energy decays after some time.  You can replenish the energy by simply teleporting something through it.\r
-</p>\r
-</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-porto.png" alt="porto"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-shotgun.png" alt="shotgun"></td><td style="border: 0"><p class="Body">\r
-The <strong>Shotgun</strong> is very similar to that of other games.  It shoots several bullets with a high spread, so it does very high damage at close range, but becomes very inaccurate at distance.  It is also very useful to knock opponents out after you already hit them hard with a rocket or another projectile, and subsequently closed to a distance because it's hard to miss with the Shotgun at close range.  The primary fire mode fires once with a short reload, while the secondary fire mode shoots three very quick shots, but with a long reload afterwards.  The secondary firemode also has a higher spread so the primary is useful for a longer distance than the secondary firemode.  The Shotgun uses shell ammo which is not shared with any other weapons.\r
-</p>\r
-</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-shotgun.png" alt="shotgun"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-uzi.png" alt="uzi"></td><td style="border: 0"><p class="Body">\r
-The <strong>Machine Gun</strong> is also quite self-explanatory.  It shoots bullets at a high rate of fire, and can do significant damage.  The primary fire mode has a high spread and is thus quite inaccurate - but it fires very fast and deals more damage per second than most other weapons.  It is therefore devastating when used at close quarters.  One of the Machine Gun's unique features is that the first bullet after pressing fire has less spread and does more damage than the rest - keep this in mind when using it.  The secondary fire mode is much slower, but its the same as the primary modes <em>first</em> bullets.  This firemode is therefore very useful as an improvised sniper weapon for mid range kills.  Like the Shotgun, it is an excellent finishing weapon, but its effective use relies heavily on a good, steady aim.  The Machine Gun uses bullet ammo just as the camping rifle.\r
-</p>\r
-</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-uzi.png" alt="uzi"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-hlac.png" alt="hlac"></td><td style="border: 0"><p class="Body">\r
-The <strong>Heavy Laser Assault Cannon</strong> (HLAC) is a similar gun but fires highly accelerated Laser bolts at an insane refire rate.  The bolts explode on impact and for some extra damage the secondary mode fires several of them at once.  This is only gun that has reduced spread when crouching!  Beware of your cell ammo (shared with the Electro, Crylink and (Minsta)Nex), this baby eats it like crazy.\r
-</p>\r
-</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-hlac.png" alt="hlac"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-gl.png" alt="gl"></td><td style="border: 0"><p class="Body">\r
-The <strong>Mortar</strong> is a grenade-launcher on steroids.  Both firing modes shoot a fast-moving projectile grenade which is influenced by gravity, and thus moves in a parabolic trajectory.  The primary fire mode shoots grenades that explode on impact while the secondary fire mode shoots grenades which will bounce off objects, and either explode on contact with an other player, or after a few seconds if they are not triggered.  The grenades' arc makes them harder to aim, but also hard to dodge.  Since they deal quite high damage, with an excellent rate of fire, the Mortar is a solid all-round weapon once you've got the hang of aiming it.  The Mortar uses rocket ammo which is shared with the Rocket Launcher, T.A.G. and the Hagar.\r
-</p>\r
-</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-gl.png" alt="gl"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-electro.png" alt="electro"></td><td style="border: 0"><p class="Body">\r
-The <strong>Electro</strong> is a rather underestimated gun.  Contrary to its name, it does not deal electricity damage, but rather fires plasma - understanding this will help you to use it well.  The primary fire mode shoots a plasma projectile which does quite high damage on impact.  It also explodes rather like a rocket or grenade, and is quite comparable in use to the Quake 2 rocket launcher.  The secondary fire mode spits out balls of contained plasma that will bounce like grenades, and deal similarly high damage.  Those blobs have a short trajectory and so do not get very far - but they're useful for blocking a way, or for spamming around your opponent so that he is hedged in by them.  If you use the primary fire mode to hit these contained plasma blobs, they will explode more violently than without, causing a lot of extra damage.  This combo is very effective in corridors and small rooms.  The Electro uses cell ammo which is shared with the Crylink, HLAC and the (Minsta)Nex.\r
-</p>\r
-</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-electro.png" alt="electro"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-crylink.png" alt="crylink"></td><td style="border: 0"><p class="Body">\r
-The <strong>Crylink</strong> is comparable to the Shotgun but shoots slow plasma projectiles; so it's harder to aim but they also do more damage.  The primary fire mode shoots several bouncing projectiles at a high rate of fire and narrow spread.  Each bounce causes splash damage so try to hit the floor near someone for damage from both the splash and the bullets themselves.  The secondary fire mode has a wider, horizontal spread and lower rate of fire.  This mode does not bounce but does more damage and is sometimes easier to aim.  The Crylink uses cell ammo which is shared with the Electro, HLAC and the (Minsta)Nex.\r
-</p>\r
-</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-crylink.png" alt="crylink"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-nex.png" alt="nex"></td><td style="border: 0"><p class="Body">\r
-The <strong>Nex</strong> is a powerful sniper weapon.  Its primary fire mode fires a hitscan antimatter lance for significant damage, but requires a very precise aim, and has a relatively long reload time.  The secondary fire mode activates zoom.  The Nex uses cell ammo which is shared with the HLAC, Crylink, MinstaNex and the Electro.\r
-</p>\r
-</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-nex.png" alt="nex"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-minstanex.png" alt="minstanex"></td><td style="border: 0"><p class="Body">\r
-The <strong>MinstaNex</strong> is a sniper's wet dream.  Similar to the nex but MUCH more powerful.  One hit, one kill, no kidding!  The secondary firemode is identical to the Laser's primary shot.  Back in the glory days this gun was used solely in Minstagib to train steady aim and fast movement, now this gun can spread fear in every gamemode.  Cell ammo is also used by Nex, Crylink, HLAC and Electro.\r
-</p>\r
-</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-minstanex.png" alt="minstanex"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-campingrifle.png" alt="campingrifle"></td><td style="border: 0"><p class="Body">\r
-The <strong>Rifle</strong> is a kind of mixture of a sniper rifle and an assault gun.  The primary mode is quite similar to the nex but fires a bullet.  The rate of fire is quite moderate.  The secondary mode can be used to empty the whole magazine of bullets in an quick burst.  This firemode is less wall piercing and has some spread to it but is deadly at mid-to-close range.  The magazine is reloaded automatically if you equip the Rifle.  It is also the only gun that can do headshots which cause extra damage, so aim with care, as this gun can out-perform the nex.  The Rifle uses the same bullet ammo as the Machine Gun.\r
-</p>\r
-</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-campingrifle.png" alt="campingrifle"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-hagar.png" alt="hagar"></td><td style="border: 0"><p class="Body">\r
-The <strong>Hagar</strong> can be compared to the plasma gun from Quake3.  It has a very high rate of fire, with moderate damage, and some splash damage.  The primary fire mode shoots small missiles at high speed and an extraordinary rate of fire.  The secondary fire mode shoots the same missiles, but at a slower speed and rate of fire, and without the instant trigger.  This means that they will bounce once when hitting a floor or a wall, giving you the opportunity to fire around corners, or double your chances to hit someone by spamming an area.  The Hagar uses rocket ammo which is shared with the Mortar, T.A.G. and the Rocket Launcher.\r
-</p>\r
-</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-hagar.png" alt="hagar"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-rl.png" alt="rl"></td><td style="border: 0"><p class="Body">\r
-The <strong>Rocket Launcher</strong> is a little different from that of other games, in that the rockets move relatively slowly.  The primary fire mode shoots a slow-moving, high-damage rocket projectile at a medium rate of fire.  The secondary fire mode will explode all your flying rockets at once.  This means that you don't need to hit your opponent directly - just get the rocket <em>near</em> him, and use the secondary fire to cause splash damage which will badly hurt him.  The Rocket Launcher uses rocket ammo which is shared with the Mortar, T.A.G. and the Hagar.\r
-</p>\r
-</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-rl.png" alt="rl"></td></tr></table><p class="Body">\r
-Now that you know all the guns you also have to understand that in Nexuiz each projectile can be set off by explosions.  So its possible to blow up the rocket an opponent is shooting at you.  A good strategy against someone with a rocketlauncher is to spam him with the hagar.  Chances are high you make his own rocket explode shortly after he launched it and hurt him which his own weapon!\r
-Also note that some objects are affected / pushed by explosion like the keys in Keyhunt or the flags in Capture the Flag.\r
-</p>\r
-<p class="Body">\r
-A special feature in Nexuiz is that walls do not block explosions, so you can't hide behind them.  Similarly, if you know that someone is on the floor above you, shoot at the ceiling, and with a little luck the explosion might hurt him.  This <em>can</em> be changed by a server admin, but the default allows for more fun; and it's also nice to see how good people are at guessing where their opponents are.  Furthermore bullets are ballistic, which means they are affected by gravity much like real bullets and can also pierce walls and enemies like real bullets.  That enables you to hit several enemies or enemies behind walls/floors up to a certain thickness.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="''Using Jumping Effectively''"><em>Using Jumping Effectively</em></a></h3>\r
-<p class="Body">\r
-You will need to be able to get around the map quickly and efficiently if you are to hunt down your opponents, and collect items well.  As already mentioned, the Laser is useful for jumping and gaining speed - but you can use all explosive weapons for a similar effect.  Other weapons, however, will cause (much) more damage to your health, and not all offer the same strong push as the Laser.  The rocket launcher will push you twice as far, but you will also take <em>lots</em> more damage.  The mortar has the same push as the Laser, but its explosion is bigger so the timing and the right angle are less of an issue.  For this reason, the mortar is better for a beginner to learn these sorts of jumps.\r
-</p>\r
-<p class="Body">\r
-You should also know that you gain a bit extra speed moving forward simply by jumping.  When you walk, there is friction from the floor which keeps you at a constant speed.  If you jump as you walk, the amount of time you touch the floor is considerably less, and so the friction does not slow you down as much.  The longer or rather more often you jump, the faster you go.  The most simple way to do this is to run forward, jump, and then while you are in the air release and repress/hold the jump button.  Your character will jump again the moment he lands, without you having to worry about timing hitting the jump key.  Just repeat it a few times and you will notice you get faster and faster.  Unlike other games, Nexuiz does not need you to do this with strange key combinations or even with circular movements of the mouse - trying to do these will generally make you slower.  The only time that strafe-jumping (holding one of the strafe-keys while jumping) is useful is for the very first jump you make.  This can increase your jump speed a little - although not by much and its complicated to learn.\r
-</p>\r
-<p class="Body">\r
-Another useful thing to know about movement is that if you are flying/jumping, you can release the forward key and instead press the left or right key, and turn your mouse in the same direction.  Doing so will turn or 'bend' your jump in this direction.  This is a useful trick to get around corners, to become less predictable, and also to avoid having to stop jumping to change direction.  After getting used to this you can get around maps very fast and maintain a high speed.\r
-</p>\r
-<p class="Body">\r
-Both the speed jumping and the turning require a certain amount of practice, but they are easy to learn - especially compared to the trick jumping found in games like Quake 3 CPMA or Enemy Territory.  However, there is another trick in Nexuiz that does not need any practice at all: the ramp jump.  Just walk up a ramp or slope, and jump.  That jump will get you higher and further than if you had jumped on flat ground.  You can reach some interesting spots by doing this, and ramp jumps can be effectively combined with speed jumping to achieve some remarkable acrobatics which can be very useful for taking shortcuts and thus dominating a map effectively.  It takes some time to learn the good spots were these four tricks can be used to the maximum effectiveness - again, experiment yourself, watch others, and ask around in-game or on the Nexuiz forums (<a class="link" href="http://www.alientrap.org/forum">http://www.alientrap.org/forum</a>).\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="''The Console''"><em>The Console</em></a></h3>\r
-<p class="Body">\r
-Games generally have a great many settings which can be changed or tweaked to give you the best advantage.  A lot of these aren't included in the game menus, which are limited by space and intended to give only a quick and compact view of the most important options.  For all the other variables, you have the in-game console.  This is like a command line inside the game.  You can enter commands to connect to a server, vote for options on the server, or to change variables like your name or various graphic effects.  You can access the console by pressing shift-escape, and you can close it again by pressing escape.  Lots of info is available on the official Nexuiz forum regarding the effective use of the console, but probably the most important in multiplayer is using it for voting, which will be briefly covered here:\r
-</p>\r
-<p class="Body">\r
-When you are on a server, open the console and type <em>vhelp</em>.  This will display a short help regarding voting, and also tell you what can be voted on the server.  By default, things you can vote for include the timelimit and the map played.  By entering <em>vcall timelimit -1</em> you call a vote to set the timelimit to -1, which means to end the current map and play the next one.  Or, as another example, <em>vcall chmap aggressor</em> will start a vote to switch to the map 'Aggressor'.  This vote is printed to every player on the server, who can then accept or reject it.  To accept a vote, press F1 (or type in <em>vyes</em> at the console); to reject it, press F2 (or enter <em>vno</em> at the console).  If more then 50% of the players currently on the server accept a vote, the vote is passed and whichever setting has been polled will change.  And in this example the current map will end and the next map will begin.  It does not hurt to tell people to press F1/F2, as some still do not know about it.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="''Game Types''"><em>Game Types</em></a></h3>\r
-<p class="Body">\r
-Nexuiz supports several different game modes and some smaller changes called mutators.  Both are displayed when you connect to a server.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="''Modes''"><em>Modes</em></a></h3>\r
-<ul>\r
-<li>Deathmatch (DM) is the most simple game mode.  It is a free-for-all fragfest with every man for himself.  All items are free to grab, and the player with the most frags will win (either by hitting the frag limit, or when the time limit expires).</li>\r
-</ul>\r
-<ul>\r
-<li>Team Deathmatch (TDM) is similar, but pits teams of players against each other.  Only shoot players with a different color than yourself.  Look at your gun to see your color.  The team with the most frags wins.</li>\r
-</ul>\r
-<ul>\r
-<li>Capture The Flag (CTF) is a team match in which you have two teams, two bases, and two flags.  You have to get into the enemy base, grab their flag, and bring it back to your own flag to score.  Scoring gives the most points, normal fragging and returning the flag gives some points.  The team with the most points wins.  CTF is a surprisingly complicated game mode, so see the comprehensive CTF guide in the Nexuiz forum for more information (<a class="link" href="http://alientrap.org/forum/viewtopic.php?t=849">http://alientrap.org/forum/viewtopic.php?t=849</a>).</li>\r
-</ul>\r
-<ul>\r
-<li>Minstagib is an improved instagib.  You can pick up lives, so you can survive two shots, and you can pick up an item to become almost invisible.  This is were the minstanex got its name from.</li>\r
-</ul>\r
-<ul>\r
-<li>Runematch places five runes into the map in addition to the normal items.  A rune gives you a bonus but also a curse - but you only get points for fragging if you carry a rune, so make sure you have one.  You also get points for fragging a rune carrier, and for just holding a rune (but fragging is the quicker way to gain points).  The player with the most points wins.</li>\r
-</ul>\r
-<ul>\r
-<li>Domination (DOM) is also a team mode.  Throughout the map are domination points.  Touch them and your team will own that point.  You get points for each second you own such a domination point.  Touch points the other team holds to turn it into your own.  The team with the most points wins.</li>\r
-</ul>\r
-<ul>\r
-<li>Last Man Standing (LMS) puts all players into a map without any items.  Each player has several lives, and starts with all weapons when he spawns.  Each time you are fragged you lose a life.  The winner is the last surviving player.</li>\r
-</ul>\r
-<ul>\r
-<li>Arena mode is similar to deathmatch, but it only puts two players into a map, with the others waiting in a queue.  When one of both is fragged the next one in the queue will play against the winner.</li>\r
-</ul>\r
-<ul>\r
-<li>Keyhunt (KH) is a new team mode in Nexuiz 2.3.  Each team has one key and the goal is the collect all keys.  The team with the most points wins.  You get small points for fragging, fragging a keycarrier and picking up a key but you should go for the BIG points you get when <em>one team has all the keys and all the keycarriers meet</em>.  Of course it also works when ONE player gets all the keys for his team but this is <em>much</em> harder.</li>\r
-</ul>\r
-<ul>\r
-<li>Onslaught (ONS) is a new team mode in Nexuiz 2.4.  Each team has a powergenerator and the goal is to destroy the enemies generator.  Each generator and all except one checkpoint in the map are protected by an forcefield which is powered by nearby checkpoint.  Your team has to start at the one unprotected checkpoint and work your way up to the enemies generator.  Find the perfect balance between attacking the enemies checkpoints/generator and protecting your own.</li>\r
-</ul>\r
-<ul>\r
-<li>Assault (AS) is a similar team mode and also new in Nexuiz 2.4.  There are objects in the map, only one is active at a time and you have to complete that one to unlock the next objective.  One round ends if the last objective is completed or timelimit is hit.  After the round the teams swap the attackers/defender roles.</li>\r
-</ul>\r
-<ul>\r
-<li>Race is a new mode for all the quick people in Nexuiz 2.5.  The goal is to race through the level as fast as possible.  A timer is set off at the startline and stopped at the endline.  Checkpoints in between give you an update on your time and compared to the top player.  There are three different race modes:</li>\r
-</ul>\r
-<ul>\r
-<ul>\r
-<li>Qualifying mode saves each player's fastest lap time and the player with the fastest time wins. When you die, you respawn in front of the startline.  Players can't interact in this mode. They walk through each other and can't shoot at the others.</li>\r
-</ul>\r
-</ul>\r
-<ul>\r
-<ul>\r
-<li>Race mode is won by the player who makes a certain count of laps first.  Players can interact and kill each other.  When killed, you respawn in front of the last checkpoint you went through.</li>\r
-</ul>\r
-</ul>\r
-<ul>\r
-<ul>\r
-<li>Mixed mode has the players first compete in qualifying mode for a certain time, or until they are all ready, then the game switches to race mode.  The fastest player in qualifying mode is the first to spawn in race mode and so on. </li>\r
-</ul>\r
-</ul>\r
-<!--End Section 2-->\r
-<h3><a name="''Mutators''"><em>Mutators</em></a></h3>\r
-<ul>\r
-<li>The hook mutator will add an off-hand hook which can be used like the grappling hook but can be used while you carry other guns.  Fire the hook at a ceiling or wall using the key configured through the menu or bound using <em>bind &quot;key&quot; &quot;+hook&quot;</em>.  Hold down this key while the hook is attached to be pulled toward it.</li>\r
-</ul>\r
-<ul>\r
-<li>Laser-guided rockets will give you a Laser dot on the rocket launcher which you can use to guide the most recently fired rocket.</li>\r
-</ul>\r
-<ul>\r
-<li>Vampire will add whatever damage you do to your opponent to your own health.</li>\r
-</ul>\r
-<ul>\r
-<li>Nixnex will remove normal guns from the map and give all players the same gun.  That gun is changed after a while.</li>\r
-</ul>\r
-<ul>\r
-<li>Footsteps will add footstep sounds to your movements.</li>\r
-</ul>\r
-<ul>\r
-<li>Weapon Arena equips everyone with the <em>arena gun</em> chosen by the server admin.</li>\r
-</ul>\r
-<p class="Body">\r
-There are some more of these mutators, but those are the most important ones.\r
-</p>\r
-<p class="Body">\r
-A list of useful links:\r
-<ul>\r
-<li>The official Nexuiz forum: <a class="link" href="http://alientrap.org/forum">http://alientrap.org/forum</a></li>\r
-</ul>\r
-</p>\r
-<ul>\r
-<li>The semiofficial Nexuiz wiki: <a class="link" href="http://alientrap.org/wiki">http://alientrap.org/wiki</a></li>\r
-</ul>\r
-<ul>\r
-<li>The official Nexuiz ladder: <a class="link" href="http://planetnexuiz.com/ladder">http://planetnexuiz.com/ladder</a></li>\r
-</ul>\r
-<ul>\r
-<li>The Official Nexuiz tournaments: <a class="link" href="http://planetnexuiz.com/tourney">http://planetnexuiz.com/tourney</a></li>\r
-</ul>\r
-<ul>\r
-<li>General Nexuiz Tips and Tricks: <a class="link" href="http://alientrap.org/forum/viewtopic.php?t=90">http://alientrap.org/forum/viewtopic.php?t=90</a></li>\r
-</ul>\r
-<ul>\r
-<li>How to get more help via IRC: <a class="link" href="http://alientrap.org/forum/viewtopic.php?t=497">http://alientrap.org/forum/viewtopic.php?t=497</a></li>\r
-</ul>\r
-<ul>\r
-<li>CTF guide and ctf MAPS guide: <a class="link" href="http://alientrap.org/forum/viewtopic.php?t=849">http://alientrap.org/forum/viewtopic.php?t=849</a></li>\r
-</ul>\r
-<ul>\r
-<li>Team communication explained: <a class="link" href="http://alientrap.org/forum/viewtopic.php?t=555">http://alientrap.org/forum/viewtopic.php?t=555</a></li>\r
-</ul>\r
-<ul>\r
-<li>A few useful jumps: <a class="link" href="http://esteel.planetnexuiz.de/nexuiz/demos/jumps.pk3">http://esteel.planetnexuiz.de/nexuiz/demos/jumps.pk3</a>  (copy the file into Nexuiz/data and you should find the demos in the demos menu)</li>\r
-</ul>\r
-<ul>\r
-<li>FPS settings: <a class="link" href="http://alientrap.org/forum/viewtopic.php?t=1628">http://alientrap.org/forum/viewtopic.php?t=1628</a></li>\r
-</ul>\r
-<ul>\r
-<li>A list of all available variables in Nexuiz: <a class="link" href="http://alientrap.org/wiki/pmwiki.php?n=Main.GameTweaks">http://alientrap.org/wiki/pmwiki.php?n=Main.GameTweaks</a></li>\r
-</ul>\r
-<ul>\r
-<li>A list of all available commands in Nexuiz: <a class="link" href="http://alientrap.org/wiki/pmwiki.php?n=Main.ConsoleCommands">http://alientrap.org/wiki/pmwiki.php?n=Main.ConsoleCommands</a></li>\r
-</ul>\r
-<ul>\r
-<li>Some info about generic strategy in games: <a class="link" href="http://alientrap.org/forum/viewtopic.php?t=3496">http://alientrap.org/forum/viewtopic.php?t=3496</a></li>\r
-</ul>\r
-<!--End Section 2-->\r
-<!-- End SectLevel2 -->\r
-<!--End Section 1-->\r
-<!-- End SectLevel1 -->\r
-<hr>\r
-<br>\r
-<p align="right"> <small>This document was generated using <a class="link" href="http://www.maplefish.com/todd/aft.html">AFT v5.096</a></small> </p>\r
-                 <p><br style="clear:left" />\r
-        </p>\r
-         </div>\r
-         <!-- end content -->\r
-               <div id="footer">\r
-                       <p id="ninja"><a href="http://www.detrition.net" title="Tyler Mulligan's Working Portfolio" target="_blank">page created by</a> <a href="http://www.nexuizninjaz.com" title="Nexuiz Ninjaz - Practicing the ninja arts of Nexuiz" target="_blank">a ninja</a></p>\r
-                       <ul>\r
-\r
-                               <li class="first"><a href="http://alientrap.org/nexuiz/news.php" title="Nexuiz - Latest News">News</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/media.php" title="Nexuiz - Media (Screenshots, videos and graphics)">Media</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/information.php" title="Nexuiz Information  (General, System Requirements)">Info</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/team.php" title="The Nexuiz Team">Team</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/links.php" title="Nexuiz Links">Links</a></li>\r
-                               <li><a href="http://alientrap.org/forum/index.php?c=3" title="Official Nexuiz Forums" target="_blank">Forums</a></li>\r
-                               <li><a href="http://planetnexuiz.com/ladder/" title="Nexuiz Ladder" target="_blank">Ladder</a></li>\r
-                               <li><a href="http://planetnexuiz.com/tourney/" title="Nexuiz Tournaments" target="_blank">Tournament</a></li>\r
-                       </ul>           </div><!-- end footer -->\r
-               <p class="subFooter">Come to the Nexuiz IRC channel: <a href="irc://irc.quakenet.org/nexuiz" title="#nexuiz on irc.quakenet.org">#nexuiz on irc.quakenet.org</a> or the team channel: <a href="irc://irc.anynet.org/alientrap" title="#alientrap on irc.anynet.org">#alientrap on irc.anynet.org</a></p>\r
-\r
-               <div id="valid">\r
-                       <a id="valid_css" href="http://jigsaw.w3.org/css-validator/validator?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>\r
-                       <a id="valid_xhtml" href="http://validator.w3.org/check?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>\r
-                       <p>&copy; 2004-2008 Nexuiz.com</p>\r
-               </div>\r
-       </div><!-- end right -->\r
-</div>\r
-</div>\r
-</body>\r
-</html>\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<meta name="author" content="Tyler Mulligan of www.detrition.net a.k.a -z- of www.nexuizninjaz.com" />
+<meta name="Description" content="Nexuiz is a free open-source fast paced first person shooter (FPS) that runs on Windows, Linux and OSX.  Nexuiz utilizes the darkplaces engine modeled after the Quake series." />
+<meta name="Keywords" content="Nexuiz, Nexiuz, first person shooter, darkplaces, open-source game, open source game, free game, linux game, deathmatch, death match, ctf, quake, alientrap, alien trap, ninjaz" />
+<title>Nexuiz - A free open-source fast paced first person shooter (FPS) for Windows, Linux and OSX</title>
+<link rel="stylesheet" href="htmlfiles/style.css" type="text/css"></link>
+<link rel="shortcut icon" href="favicon.ico"></link>
+</head>
+<body>
+<div id="container">
+       <div id="left">
+               <div id="logo">
+
+                       <a href="http://www.nexuiz.com" title="Nexuiz - Simple, fast, intense and completely free"><img src="htmlfiles/img/nexuiz_logo.jpg" alt="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="201" height="193" border="0" title="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." /></a>
+               </div>
+               <!-- Sidebar -->
+         <div id="sidebar">
+                       <a href="news.php" title="Latest Nexuiz News"><img src="htmlfiles/img/headers/latest_news.gif" alt="Latest News" width="168" height="16" border="0" class="imgHeader" style="margin-top:0;" title="Latest Nexuiz News" /></a>
+                       <div id="news">
+                         <div class="newsPost">
+                                               <h1><a href="http://nexuiz.com/news.php" title="Two Nexuiz 1v1 Tourneys">nexuiz.com/news - for nexuiz news updates</a></h1>
+                         </div>
+               </div>
+                       <!-- Downloads -->
+                       <a href="downloads.php" title="Nexuiz Downloads (Game, Maps)"><img src="htmlfiles/img/headers/downloads.gif" alt="Downloads" width="145" height="15" border="0" class="imgHeader" title="Downloads" /></a>
+                       <a id="download_nexuiz" href="http://www.sourceforge.net/projects/nexuiz/" title="Download Nexuiz from Source Forge - Left Click">&nbsp;</a>
+                       <!--<a id="download_q3_mappack" href="http://downloads.sourceforge.net/nexuiz/nexmappack_r2.zip" title="Download Nexuiz Q3 Mappack from Source
+                       Forge - Left Click">&nbsp;</a>-->
+                       <h2 class="page"><a href="http://nexuiz.com/downloads.php" title="Nexuiz Download Mirrors">Mirrors -&gt;</a></h2>
+                       <!-- Help Wanted -->
+
+                       <img src="htmlfiles/img/headers/help_wanted.gif" alt="Help Wanted" width="165" height="15" class="imgHeader" title="Help Wanted" />                     
+                       <form action="https://www.paypal.com/cgi-bin/webscr" method="post" name="donate_form"> <input name="cmd" value="_s-xclick" type="hidden" />
+                       <a id="donate" href="javascript:document.donate_form.submit();" title="Help The Aliens, Please Donate">&nbsp;</a>
+                       <noscript>
+                               <!-- It not an elegant solution but I don't expect many users to arrive with JS turned off -->
+                               <input name="submit" type="image" src="htmlfiles/img/donate_button_noscript.jpg" alt="Help this project - Donate to the aliens" width="166" height="43" /> 
+                       </noscript>
+                       <h2 class="page"><a href="http://nexuiz.com/donators.php" title="Those who have donated to Nexuiz">Donators -&gt;</a></h2>
+                       <input name="encrypted" value="-----BEGIN PKCS7-----MIIHBgYJKoZIhvcNAQcEoIIG9zCCBvMCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYBfp40KRLmnxKPY06C4gjvEiWZchbxK6bgD7ZdjhdWO5Vbwo4T4Ro+HE041PVVqIxPlJgO80l3aQpBtfhC66FfM2kIF1BjLs1zzhQM89XoPGViS3e4kbmzxkMnpdiZFmOsR5Fs5NJYiVaMnVGcoQ+K3+KsyOehZGket7GwUeNFMRzELMAkGBSsOAwIaBQAwgYMGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIb1QeJqVIc5SAYOPcI23W84XMGt8mSfHE1Gf0/GZAM7NvqLiHF0BeaecRW1Bc85O0tL9OduZiraGf7WVnAmP5kp1D0irXsA5+N2l15WADxwNQ/GoCAU293l0dAQ7Qy4F3vh6eSii18MaH2KCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA1MDUxMjE5MDQ0OFowIwYJKoZIhvcNAQkEMRYEFC7mlfdaA7Pg2eBhxI5xQTe7ydTtMA0GCSqGSIb3DQEBAQUABIGAfR38tX84huxk9JjvhggcSMxzHbmDxpxInBU6/lbyqAu7iT5KJn7rcJgaH5ZVyKNoNQLGp9IxweBrcMiYUQNVxShm9+hunXhQmj5r7AMGaxNZ0mE8mQRW1ZTaz7TLz1HkDFA+R0Dm8HYyDQA4L505cBiWNEsKC17VwNK1G7CEVvA=-----END PKCS7-----" type="hidden" /></form>
+                       
+                 <!-- Created By -->
+                 <img src="htmlfiles/img/headers/created_by.gif" alt="Created By" width="145" height="15" class="imgHeader" title="Created By" />
+
+         <a id="alien_trap" href="http://www.alientrap.org" title="Developed by Alien Trap" target="_blank">&nbsp;</a>   </div>
+               <!-- end sidebar -->    </div><!-- end left -->
+       <div id="right">
+               <div id="header">
+                       <a href="http://nexuiz.com" title="Nexuiz - Simple, fast, intense and completely free"><img src="htmlfiles/img/nexuiz_header.jpg" alt="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="766" height="107" border="0" title="Nexuiz - Simple, fast, intense and completely free" /></a>
+               </div>
+               <div id="menu">
+                       <ul>
+
+                               <li class="first"><a href="../readme.html" title="Nexuiz - Latest News">About </a></li>
+                               <li><a href="basics.html" title="Nexuiz - Media (Screenshots, videos and graphics)">Nexuiz Basics</a></li>
+                               <li><a href="say-esc.html" title="Nexuiz Information  (General, System Requirements)">Say Escapes</a></li>
+                               <li><a href="irc.html" title="Nexuiz - Downloads (Game, Maps)">IRC</a></li>
+                               <li><a href="faq.html" title="Official Nexuiz FAQ">FAQ</a></li>
+                               <li class="first"></li>
+                               <li class="first"></li>
+                 </ul>         </div>
+               <p id="tagline">Nexuiz is a <b>free</b> open-source first person shooter that runs on <b>Windows</b>, <b>Linux</b> and <b>OSX</b>.</p>
+         <div id="content">
+<div class="body">
+
+<br><center><h1><a name="AFT-top">Nexuiz Basics explained</a></h1></center>
+<center><a class="link" href="http://nexuiz.com">http://nexuiz.com</a>&nbsp;&nbsp;<a class="link" href="http://alientrap.org">http://alientrap.org</a></center>
+<hr>
+<p class="Body">
+This file attempts to cover all of the fundamental principles and techniques for playing Nexuiz in particular, though it will largely apply to any first person shooter.
+</p>
+<!-- Start SectLevel1 -->
+<h2><a name="_Getting Comfortable_"><strong>Getting Comfortable</strong></a></h2>
+<p class="Body">
+The most important thing when playing is 'to feel good'.  You need to be comfortable with how the game feels, and how it works, in order to play well.  This means being able to concentrate on the game play itself, without being disturbed by in-game distractions or by various sorts of handicaps.  This guide provides a systematic but very brief introduction to Nexuiz, with a view to increasing your effectiveness in-game, straight off the mark.
+</p>
+<!-- Start SectLevel2 -->
+<h3><a name="''Reducing Clutter''"><em>Reducing Clutter</em></a></h3>
+<p class="Body">
+Especially when playing against others the first thing you need to ensure is that you can hear and see well.  Although music can enhance the feeling of a game by complementing the action, it can also obscure the sounds of other events - such as players shooting at you or picking up items nearby.  For this reason, many people turn off music while playing (set <em>music</em> in the <em>settings/misc</em> menu to <em>off</em>).  You can also improve your ability to hear in-game events by simply using headphones instead of speakers.  Headphones muffle external noises around you, and also make it easier to hear faint noises in the game.  Because of this, you can hear others more easily, and thus react better.
+</p>
+<p class="Body">
+Similarly, eye-candy can make the game very appealing to look at, but can also act to obscure in-game events - the flares and smoke caused by rockets, for example, can make spotting your target much more difficult in the heat of battle.  Eye-candy also incurs performance hits on slower machines, which can make playing difficult.  Bloom might be nice to look at, but it is often just distracting when playing.  So again, many players disable some of the visual effects, so as to make the game easier to play in this regard (things to turn off include <em>Bloom</em>, <em>HDR</em>, <em>Gloss</em>, <em>Coronas</em> and all the Realtime stuff in the <em>settings/effects</em> menu).  Although it may seem as if removing visual (and audio) effects will make the game less immersive by reducing the mood which the game designers were aiming for, in the long run it will make it much more rewarding and fun to play.  Darkness and shadows can create creepiness and realism, but you simply don't notice these things in a fast-paced multi player battle.  On the contrary, they make it harder to spot opponents or items and end up being frustrating.  Better to play the game in campaign mode with full visual effects to see how pretty it can be, and then, once you're over the initial craving for eye-candy, start tuning the game to be more functional in multi player.
+</p>
+<p class="Body">
+In this regard, one of the first things to adjust is the brightness and contrast (in the <em>settings/video</em> menu), so that shadows are not too impenetrable, and areas of alternating light and dark don't create difficulties in tracking opponents.  It's also worthwhile to try increasing you field of view (fov) - the amount you can see to the left and right.  By default, Nexuiz uses a fov of 90 degrees.  Increasing this improves your peripheral vision, but also makes everything seem smaller and thus reduces your ability to aim.  Decreasing it has the opposite effect.  Most gamers find a nice balance somewhere around fov 100-120.  You can find the fov control in the <em>multiplayer/player setup</em> menu.
+</p>
+<!--End Section 2-->
+<h3><a name="''Controlling Your Character''"><em>Controlling Your Character</em></a></h3>
+<p class="Body">
+Another thing which will greatly improve the game experience is to have comfortable movement controls, and to be able to get to the 'right gun' quickly.  In terms of movement, there's no need to stick to the game defaults for controlling your character.  Some people like to jump with space; some with mouse2.  Some like inverted mouse, and some can't use it.  Some use WASD for forward, left, back, right; some use ESDF or even 8456.  Use whatever you feel good with.
+</p>
+<p class="Body">
+In terms of weapon controls, try to get out of the habit of using the mouse's scroll-wheel to select which weapon to use.  This is a very slow and unreliable way of changing weapons, and in a fast-paced deathmatch your ability to correctly and immediately pick the most appropriate gun for the job is critical.  For example, if you hit someone pretty hard with the rocket launcher, and he has taken a lot of damage, you should switch to the shotgun or machine gun to finish him off, rather than worrying about whether you'll be able to hit him again with the slower and more inaccurate rockets.  Using the mouse wheel to switch may well take too long, and you also have to keep an eye on the weapons as they scroll by (unless your mouse has very good scroll feedback, <em>and</em> you've memorized the order of weapons, <em>and</em> you know which weapons you currently have, <em>and</em> you can figure out on the fly how many scrolls you need to get to the shotgun!).  On the other hand, if you have configured, say, the G key to switch to the shotgun, you can easily switch quickly and reliably, and finish him off in an instant.  For this reason, it's extremely helpful to configure weapon-selection keys around the ones you use for moving.  It's also obviously handy to use a similar layout in all the games you play.  For example, you can have G as the key for 'all shotgun like weapons' in each game.  All 'rocket like' weapons can go on R, and all 'sniper guns' on T, and so on - or whatever suits you!  This way you do not need to think about which key you need for which gun - just what you want to do.  You can change those <em>key bindings</em> in the <em>settings/input</em> menu.
+</p>
+<p class="Body">
+Finally, adjust your mouse sensitivity.  Sensitivity controls how much (or fast) your view turns when moving the mouse.  You can make it high sensitivity, which means you need to move the mouse only a short distance to turn fast or far; or you can make it low sensitivity, so that you turn slowly and have to move the mouse further.  A high sensitivity reduces the need to lift and reposition the mouse, and it will help you when doing lots of turns.  A low sensitivity will help you to aim more easily, and also make your aim more steady if you are nervous and have slightly shaky hands.  For the most part, the level of sensitivity you choose depends on how you aim.  This is a very personal thing and you will have to see what you like - again, select the setting which is most comfortable for you.  However, a good rule of thumb when you're learning to play is to configure your sensitivity a little lower than you're comfortable with, and try to get used to it.  After a few hours you will probably find that you have acclimatized, and are aiming better than before.  Keep reducing the sensitivity below your comfort zone until you are happy with your aim, or until it definitely becomes too low for you.  You'd be surprised the difference this makes.  Also consider that you can either use your wrist or your arm to aim - and you'll probably do one or the other more naturally.  Using the wrist is more exact, and thus high sensitivity is found more often among wrist aimers; using the arm involves more mouse movements, and so low sensitivity is common to arm aimers.  As a rule of thumb your sensitivity should be set so that you can comfortably perform a 180 degree turn with one sweep of your mouse.  The sensitivity setting can be found in the <em>settings/input</em> menu.
+</p>
+<!--End Section 2-->
+<!-- End SectLevel2 -->
+<!--End Section 1-->
+<h2><a name="_Playing_"><strong>Playing</strong></a></h2>
+<!-- Start SectLevel2 -->
+<h3><a name="''Identifying Your Opponent''"><em>Identifying Your Opponent</em></a></h3>
+<p class="Body">
+Depending on the game mode played you either fight for yourself against all others (sometimes there is just one other player to play against) or its your team vs the other team(s).  Pay attention to the welcoming screen it will tell you what gamemode is being played.  You can find an explanation of those at the end of this guide.
+If its a team match you might have to choose a team or you could be automatically assigned to a team when you join the match.  If you have to choose yourself its best to just use the <tt>Auto</tt> button!  It will assign you to the smallest team or if the teams have the same size to the one that has less points.  Only use the specific team buttons if you REALLY want to join that team.  You can press F5 to bring up the team selection dialog or instead of pressing F5 and clicking on the <tt>Auto</tt> button you can also press F6, thats a shortcut for the <tt>Auto</tt> button.  If you are unsure about which team you are in look at your gun or at the scoreboard, which by default will be shown when you press <em>TAB</em>.  The weapon color and the color beside your name is your team color.  Try to only hit the OTHER team(s) :).
+</p>
+<!--End Section 2-->
+<h3><a name="''Hitting Your Opponent''"><em>Hitting Your Opponent</em></a></h3>
+<p class="Body">
+Once you're comfortable with your controls and your mouse, you should already find it easier to hit your opponents.  And, if you've bound keys to each of the weapons, you can accurately pick which one to use.  But to know which weapon to select, and to get the most out of it, you must understand the advantages and the use of each.
+</p>
+<p class="Body">
+When you use direct-hit (hitscan) weapons like the shotgun, machinegun, campingrifle and the nex, you 'just' have to directly aim with the crosshair and hit fire.  Obviously this can be much harder than it sounds, particularly when both you and your opponent are moving.  Therefore, you should use geometry to your advantage, to reduce the relative movement between you as much as possible, so that you have as much time as possible to aim.  When someone is moving from one side of your view to the other it is harder to aim at him than when is moving towards or away from you.  In the latter case he will hardly change his position relative to yours, and this gives you more time to aim at him.  In the former, his position is changing a lot, and so you have to react to how he moves very quickly and accurately to ensure a good shot.  The same principle applies when your opponent uses a jump pad.  He will prescribe a trajectory in the air, and at its highest point he will stop for a moment.  It is easier to aim at him at this point than when he is accelerating off the jump-pad.
+</p>
+<p class="Body">
+In addition to using geometry to your advantage, when you're moving relative to your opponent it is sometimes easier to <em>not</em> aim with the mouse, but rather to leave it steady at the right height, and use the strafe buttons to move yourself and thus your cross hair onto him.  If you have the advantage of his not seeing you, or being unable to shoot at you, or even if you simply have a health and armor advantage over him, you can even just aim at some point and wait for him to walk into your cross hair.  However, always bear in mind that people will generally move unpredictably so as to lessen their chances of being hit - so unless you're sure of where he's going, don't sacrifice your own movement advantage for a hit that might never come.
+</p>
+<p class="Body">
+Using projectile weapons such as the mortar, electro, hagar or rocket launcher can be both easier and harder than using hitscan weapons.  You must still consider the same geometric factors, but you also have to keep in mind that your projectile will take some time to travel the distance between you and your opponent.  In that time they will continue moving!  So it's absolutely no use to aim directly at them.  It will take some experience with each weapon, and with getting used to how people move, but you should eventually develop a good instinct for where your opponent will be at the time when your projectile will land (and where the projectile itself will land), and thus be able to 'lead' him when you aim.  If your target moves to the right, aim further to the right and press fire.  Just note how off your hit was and try to adjust next time.  It takes a few matches to get the hang of this.  Also consider that almost all projectiles will explode when they hit something hard, and those explosions will also cause damage to players nearby (splash damage).  Now thats the part that makes projectile weapons easier to use.  You can use splash damage to your advantage, so that rather than trying to predict the exact spot at which to aim your projectile (which is very difficult), you can aim at the general area of floor or wall where you expect your opponent to be and let the splash damage do its work.  Along these lines, you'll find that it's much easier to aim at his feet and use the splash damage from the explosion on the floor than it is to hit him directly.
+</p>
+<p class="Body">
+Also bear in mind that people try to evade projectiles; and it is even comparatively easy to do this at distance.  If you fire to their left they will see it coming, and change direction to the right.  You can use this to your advantage by boxing them in with explosions - just fire another one to their right.  If you correctly place both projectiles, they will be unable to evade the splash damage from one of them.  Either they will avoid the first and be hit by the second, or they will backtrack to avoid the second and be hit by the first.  Or, even better, they'll not see the first one at all and be hit directly.
+</p>
+<!--End Section 2-->
+<h3><a name="''Controlling The Map''"><em>Controlling The Map</em></a></h3>
+<p class="Body">
+To put up a good fight you need to survive an attack, so make sure you pick up health and armor lying around the map.  This will also ensure that your opponents are deprived of these, so it will be easier for you to frag them.  To do this effectively you will have to learn where those items are, and try to get them before someone else does.  Armor and health items reappear 30 seconds after they were taken.  You can use this to your advantage by timing your pickups - look at the scoreboard to see the current map time, and then use this to predict when items will respawn.  This way, you can develop patterns on the map so that you're always around when an item respawns.  However, be careful - patterns make you predictable, and your opponent can also use the timer!  If you keep grabbing the armor or health, he will use this against you by lying an ambush.
+</p>
+<p class="Body">
+The health and armor system in Nexuiz is different to other games in that it tends toward an equilibrium of 100 health and 100 armor.  You can collect as much health and armor as you like, but it will degenerate toward these values.  The more health and armor you have, the faster it will degenerate - but when you pick them up the degeneration stops momentarily.  Because of this, you can't just load up on health and armor and head off into battle - you need to constantly replenish to remain strong.  On the converse, though, when your health is below 100 it will slowly <em>re</em>generate (armor will not).  The lower it is, the faster it will replenish.  This regeneration stops for a few seconds if you are hurt, so just running away after a fight helps staying alive but is usually not enough to restore you to useful battle ready levels again - you have to wait a while to regain your full health, and so picking up health after a fight is still important.
+</p>
+<p class="Body">
+In regard to the previous point of working a map to control items, and also shooting at where your opponent will be, it's important to try to predict where on the map your opponents are even when you can't directly see them.  First, try to listen for them; secondly, learn the maps so you know the most useful routes and the most popular areas to which people tend to gravitate.  It gives you a marked advantage to be able to say, &quot;I saw or heard someone pick up an armor, so they must be at such-and-such location, and from there he can only go to this place or that place.&quot; Just shooting something to the likely places your opponent will be - a rocket or a grenade for example - can often result in either a surprise kill, or in surprise damage which you can then follow up.  Don't underestimate how likely a well-aimed educated guess is to hit someone.  Maybe he'll walk into it - rockets can be hard to see coming, and grenades lying on the floor or spammed into a small space can be really hard to avoid.  Once you've hurt him you'll hear the tink of your damage, and you will also know for sure were he is and can finish him off.  You can pretty much 'lock down' a map by picking up items, and making sure others have a hard time getting around which will also mess with their concentration.
+</p>
+<!--End Section 2-->
+<h3><a name="''Using Weapons Effectively''"><em>Using Weapons Effectively</em></a></h3>
+<p class="Body">
+Among the items you can pick up are the weapons.  Some of them are very unique to Nexuiz, and to get the most out of them you need to be familiar with every aspect of how they work - including the rate of fire, the damage per hit, the spread and speed of the payload, and so on.
+</p>
+<p class="Body">
+Almost all weapons in Nexuiz have two firing modes, so make sure you know both.  You normally (depending on game-mode) start equipped with two weapons: the Laser and the shotgun.  You can bind these weapons to a key at the <em>settings/input</em> menu or in the console using the command <em>bind key &quot;impulse <strong>n</strong>&quot;</em>, where n is the number of the weapon.
+</p>
+<table style="border: 0"><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-laser.png" alt="laser"></td><td style="border: 0"><p class="Body">
+The <strong>Laser</strong> is not often used as a weapon, as it does not do much damage - but it's a useful tool to move around the map, because it has a significant push.  The 'explosion' from its projectile will push you and other players around a lot.  The trick to using it to move around is to use this push to gain speed or to do huge jumps.  For an easy start, take the Laser, look down at the floor, and press fire.  It will push you high into the air.  If you press jump and fire at the same time, you will get even higher - but it depends on the timing, and, for more advanced jumps, also on the angle you fire at the floor, as well as how fast and in which direction you move.  For example if you just run forwards, look down and press fire it will catapult you forward with great speed.  Play around with it, and watch others, and you will learn a very useful skill in Nexuiz.  The secondary fire mode of the Laser switches back to the last used weapon.  The Laser does not use up any ammo.
+</p>
+</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-laser.png" alt="laser"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-hookgun.png" alt="hookgun"></td><td style="border: 0"><p class="Body">
+The <strong>Grappling Hook</strong> is an other movement tool.  The primary mode attaches itself to all sorts of solid objects and will pull you there to move through the map like spider man.  You can get very fast that way or reach spots you would not get to by other means.  The secondary firemode drops a small gravity bomb that will affect enemy players and also releases a lot of smoke which will make you harder to see and hit.  The gravity bomb uses cell ammo.  There is also a mutator that adds a off-hand hook which can be used all the time, uses no ammo but only does the pulling part.
+</p>
+</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-hookgun.png" alt="hookgun"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-porto.png" alt="porto"></td><td style="border: 0"><p class="Body">
+The <strong>Port-O-Launch</strong> is the perfection of movement.  The grenade it launches creates two portals.  On the first impact it creates the entrance portal, on the second impact the exit portal.  If you enter the first one you (or others players and also projectiles!!) will be teleported to the second portal.  As the first portal will teleport everything it can't be hit but the second portal is quite fragile and, because of the used wormhole technology, will take the other portal along if it lost too much energy.  The wormhole is also the reason why the portals energy decays after some time.  You can replenish the energy by simply teleporting something through it.
+</p>
+</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-porto.png" alt="porto"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-shotgun.png" alt="shotgun"></td><td style="border: 0"><p class="Body">
+The <strong>Shotgun</strong> is very similar to that of other games.  It shoots several bullets with a high spread, so it does very high damage at close range, but becomes very inaccurate at distance.  It is also very useful to knock opponents out after you already hit them hard with a rocket or another projectile, and subsequently closed to a distance because it's hard to miss with the Shotgun at close range.  The primary fire mode fires once with a short reload, while the secondary fire mode shoots three very quick shots, but with a long reload afterwards.  The secondary firemode also has a higher spread so the primary is useful for a longer distance than the secondary firemode.  The Shotgun uses shell ammo which is not shared with any other weapons.
+</p>
+</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-shotgun.png" alt="shotgun"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-uzi.png" alt="uzi"></td><td style="border: 0"><p class="Body">
+The <strong>Machine Gun</strong> is also quite self-explanatory.  It shoots bullets at a high rate of fire, and can do significant damage.  The primary fire mode has a high spread and is thus quite inaccurate - but it fires very fast and deals more damage per second than most other weapons.  It is therefore devastating when used at close quarters.  One of the Machine Gun's unique features is that the first bullet after pressing fire has less spread and does more damage than the rest - keep this in mind when using it.  The secondary fire mode is much slower, but its the same as the primary modes <em>first</em> bullets.  This firemode is therefore very useful as an improvised sniper weapon for mid range kills.  Like the Shotgun, it is an excellent finishing weapon, but its effective use relies heavily on a good, steady aim.  The Machine Gun uses bullet ammo just as the camping rifle.
+</p>
+</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-uzi.png" alt="uzi"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-hlac.png" alt="hlac"></td><td style="border: 0"><p class="Body">
+The <strong>Heavy Laser Assault Cannon</strong> (HLAC) is a similar gun but fires highly accelerated Laser bolts at an insane refire rate.  The bolts explode on impact and for some extra damage the secondary mode fires several of them at once.  This is only gun that has reduced spread when crouching!  Beware of your cell ammo (shared with the Electro, Crylink and (Minsta)Nex), this baby eats it like crazy.
+</p>
+</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-hlac.png" alt="hlac"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-gl.png" alt="gl"></td><td style="border: 0"><p class="Body">
+The <strong>Mortar</strong> is a grenade-launcher on steroids.  Both firing modes shoot a fast-moving projectile grenade which is influenced by gravity, and thus moves in a parabolic trajectory.  The primary fire mode shoots grenades that explode on impact while the secondary fire mode shoots grenades which will bounce off objects, and either explode on contact with an other player, or after a few seconds if they are not triggered.  The grenades' arc makes them harder to aim, but also hard to dodge.  Since they deal quite high damage, with an excellent rate of fire, the Mortar is a solid all-round weapon once you've got the hang of aiming it.  The Mortar uses rocket ammo which is shared with the Rocket Launcher, T.A.G. and the Hagar.
+</p>
+</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-gl.png" alt="gl"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-electro.png" alt="electro"></td><td style="border: 0"><p class="Body">
+The <strong>Electro</strong> is a rather underestimated gun.  Contrary to its name, it does not deal electricity damage, but rather fires plasma - understanding this will help you to use it well.  The primary fire mode shoots a plasma projectile which does quite high damage on impact.  It also explodes rather like a rocket or grenade, and is quite comparable in use to the Quake 2 rocket launcher.  The secondary fire mode spits out balls of contained plasma that will bounce like grenades, and deal similarly high damage.  Those blobs have a short trajectory and so do not get very far - but they're useful for blocking a way, or for spamming around your opponent so that he is hedged in by them.  If you use the primary fire mode to hit these contained plasma blobs, they will explode more violently than without, causing a lot of extra damage.  This combo is very effective in corridors and small rooms.  The Electro uses cell ammo which is shared with the Crylink, HLAC and the (Minsta)Nex.
+</p>
+</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-electro.png" alt="electro"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-crylink.png" alt="crylink"></td><td style="border: 0"><p class="Body">
+The <strong>Crylink</strong> is comparable to the Shotgun but shoots slow plasma projectiles; so it's harder to aim but they also do more damage.  The primary fire mode shoots several bouncing projectiles at a high rate of fire and narrow spread.  Each bounce causes splash damage so try to hit the floor near someone for damage from both the splash and the bullets themselves.  The secondary fire mode has a wider, horizontal spread and lower rate of fire.  This mode does not bounce but does more damage and is sometimes easier to aim.  The Crylink uses cell ammo which is shared with the Electro, HLAC and the (Minsta)Nex.
+</p>
+</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-crylink.png" alt="crylink"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-nex.png" alt="nex"></td><td style="border: 0"><p class="Body">
+The <strong>Nex</strong> is a powerful sniper weapon.  Its primary fire mode fires a hitscan antimatter lance for significant damage, but requires a very precise aim, and has a relatively long reload time.  The secondary fire mode activates zoom.  The Nex uses cell ammo which is shared with the HLAC, Crylink, MinstaNex and the Electro.
+</p>
+</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-nex.png" alt="nex"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-minstanex.png" alt="minstanex"></td><td style="border: 0"><p class="Body">
+The <strong>MinstaNex</strong> is a sniper's wet dream.  Similar to the nex but MUCH more powerful.  One hit, one kill, no kidding!  The secondary firemode is identical to the Laser's primary shot.  Back in the glory days this gun was used solely in Minstagib to train steady aim and fast movement, now this gun can spread fear in every gamemode.  Cell ammo is also used by Nex, Crylink, HLAC and Electro.
+</p>
+</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-minstanex.png" alt="minstanex"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-campingrifle.png" alt="campingrifle"></td><td style="border: 0"><p class="Body">
+The <strong>Rifle</strong> is a kind of mixture of a sniper rifle and an assault gun.  The primary mode is quite similar to the nex but fires a bullet.  The rate of fire is quite moderate.  The secondary mode can be used to empty the whole magazine of bullets in an quick burst.  This firemode is less wall piercing and has some spread to it but is deadly at mid-to-close range.  The magazine is reloaded automatically if you equip the Rifle.  It is also the only gun that can do headshots which cause extra damage, so aim with care, as this gun can out-perform the nex.  The Rifle uses the same bullet ammo as the Machine Gun.
+</p>
+</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-campingrifle.png" alt="campingrifle"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-hagar.png" alt="hagar"></td><td style="border: 0"><p class="Body">
+The <strong>Hagar</strong> can be compared to the plasma gun from Quake3.  It has a very high rate of fire, with moderate damage, and some splash damage.  The primary fire mode shoots small missiles at high speed and an extraordinary rate of fire.  The secondary fire mode shoots the same missiles, but at a slower speed and rate of fire, and without the instant trigger.  This means that they will bounce once when hitting a floor or a wall, giving you the opportunity to fire around corners, or double your chances to hit someone by spamming an area.  The Hagar uses rocket ammo which is shared with the Mortar, T.A.G. and the Rocket Launcher.
+</p>
+</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-hagar.png" alt="hagar"></td></tr><tr><td style="border: 0; vertical-align: top; text-align: left"><img src="htmlfiles/weaponimg/thirdperson-rl.png" alt="rl"></td><td style="border: 0"><p class="Body">
+The <strong>Rocket Launcher</strong> is a little different from that of other games, in that the rockets move relatively slowly.  The primary fire mode shoots a slow-moving, high-damage rocket projectile at a medium rate of fire.  The secondary fire mode will explode all your flying rockets at once.  This means that you don't need to hit your opponent directly - just get the rocket <em>near</em> him, and use the secondary fire to cause splash damage which will badly hurt him.  The Rocket Launcher uses rocket ammo which is shared with the Mortar, T.A.G. and the Hagar.
+</p>
+</td><td style="border: 0; vertical-align: bottom; text-align: right"><img src="htmlfiles/weaponimg/firstperson-rl.png" alt="rl"></td></tr></table><p class="Body">
+Now that you know all the guns you also have to understand that in Nexuiz each projectile can be set off by explosions.  So its possible to blow up the rocket an opponent is shooting at you.  A good strategy against someone with a rocketlauncher is to spam him with the hagar.  Chances are high you make his own rocket explode shortly after he launched it and hurt him which his own weapon!
+Also note that some objects are affected / pushed by explosion like the keys in Keyhunt or the flags in Capture the Flag.
+</p>
+<p class="Body">
+A special feature in Nexuiz is that walls do not block explosions, so you can't hide behind them.  Similarly, if you know that someone is on the floor above you, shoot at the ceiling, and with a little luck the explosion might hurt him.  This <em>can</em> be changed by a server admin, but the default allows for more fun; and it's also nice to see how good people are at guessing where their opponents are.  Furthermore bullets are ballistic, which means they are affected by gravity much like real bullets and can also pierce walls and enemies like real bullets.  That enables you to hit several enemies or enemies behind walls/floors up to a certain thickness.
+</p>
+<!--End Section 2-->
+<h3><a name="''Using Jumping Effectively''"><em>Using Jumping Effectively</em></a></h3>
+<p class="Body">
+You will need to be able to get around the map quickly and efficiently if you are to hunt down your opponents, and collect items well.  As already mentioned, the Laser is useful for jumping and gaining speed - but you can use all explosive weapons for a similar effect.  Other weapons, however, will cause (much) more damage to your health, and not all offer the same strong push as the Laser.  The rocket launcher will push you twice as far, but you will also take <em>lots</em> more damage.  The mortar has the same push as the Laser, but its explosion is bigger so the timing and the right angle are less of an issue.  For this reason, the mortar is better for a beginner to learn these sorts of jumps.
+</p>
+<p class="Body">
+You should also know that you gain a bit extra speed moving forward simply by jumping.  When you walk, there is friction from the floor which keeps you at a constant speed.  If you jump as you walk, the amount of time you touch the floor is considerably less, and so the friction does not slow you down as much.  The longer or rather more often you jump, the faster you go.  The most simple way to do this is to run forward, jump, and then while you are in the air release and repress/hold the jump button.  Your character will jump again the moment he lands, without you having to worry about timing hitting the jump key.  Just repeat it a few times and you will notice you get faster and faster.  Unlike other games, Nexuiz does not need you to do this with strange key combinations or even with circular movements of the mouse - trying to do these will generally make you slower.  The only time that strafe-jumping (holding one of the strafe-keys while jumping) is useful is for the very first jump you make.  This can increase your jump speed a little - although not by much and its complicated to learn.
+</p>
+<p class="Body">
+Another useful thing to know about movement is that if you are flying/jumping, you can release the forward key and instead press the left or right key, and turn your mouse in the same direction.  Doing so will turn or 'bend' your jump in this direction.  This is a useful trick to get around corners, to become less predictable, and also to avoid having to stop jumping to change direction.  After getting used to this you can get around maps very fast and maintain a high speed.
+</p>
+<p class="Body">
+Both the speed jumping and the turning require a certain amount of practice, but they are easy to learn - especially compared to the trick jumping found in games like Quake 3 CPMA or Enemy Territory.  However, there is another trick in Nexuiz that does not need any practice at all: the ramp jump.  Just walk up a ramp or slope, and jump.  That jump will get you higher and further than if you had jumped on flat ground.  You can reach some interesting spots by doing this, and ramp jumps can be effectively combined with speed jumping to achieve some remarkable acrobatics which can be very useful for taking shortcuts and thus dominating a map effectively.  It takes some time to learn the good spots were these four tricks can be used to the maximum effectiveness - again, experiment yourself, watch others, and ask around in-game or on the Nexuiz forums (<a class="link" href="http://www.alientrap.org/forum">http://www.alientrap.org/forum</a>).
+</p>
+<!--End Section 2-->
+<h3><a name="''The Console''"><em>The Console</em></a></h3>
+<p class="Body">
+Games generally have a great many settings which can be changed or tweaked to give you the best advantage.  A lot of these aren't included in the game menus, which are limited by space and intended to give only a quick and compact view of the most important options.  For all the other variables, you have the in-game console.  This is like a command line inside the game.  You can enter commands to connect to a server, vote for options on the server, or to change variables like your name or various graphic effects.  You can access the console by pressing shift-escape, and you can close it again by pressing escape.  Lots of info is available on the official Nexuiz forum regarding the effective use of the console, but probably the most important in multiplayer is using it for voting, which will be briefly covered here:
+</p>
+<p class="Body">
+When you are on a server, open the console and type <em>vhelp</em>.  This will display a short help regarding voting, and also tell you what can be voted on the server.  By default, things you can vote for include the timelimit and the map played.  By entering <em>vcall timelimit -1</em> you call a vote to set the timelimit to -1, which means to end the current map and play the next one.  Or, as another example, <em>vcall chmap aggressor</em> will start a vote to switch to the map 'Aggressor'.  This vote is printed to every player on the server, who can then accept or reject it.  To accept a vote, press F1 (or type in <em>vyes</em> at the console); to reject it, press F2 (or enter <em>vno</em> at the console).  If more then 50% of the players currently on the server accept a vote, the vote is passed and whichever setting has been polled will change.  And in this example the current map will end and the next map will begin.  It does not hurt to tell people to press F1/F2, as some still do not know about it.
+</p>
+<!--End Section 2-->
+<h3><a name="''Game Types''"><em>Game Types</em></a></h3>
+<p class="Body">
+Nexuiz supports several different game modes and some smaller changes called mutators.  Both are displayed when you connect to a server.
+</p>
+<!--End Section 2-->
+<h3><a name="''Modes''"><em>Modes</em></a></h3>
+<ul>
+<li>Deathmatch (DM) is the most simple game mode.  It is a free-for-all fragfest with every man for himself.  All items are free to grab, and the player with the most frags will win (either by hitting the frag limit, or when the time limit expires).</li>
+</ul>
+<ul>
+<li>Team Deathmatch (TDM) is similar, but pits teams of players against each other.  Only shoot players with a different color than yourself.  Look at your gun to see your color.  The team with the most frags wins.</li>
+</ul>
+<ul>
+<li>Capture The Flag (CTF) is a team match in which you have two teams, two bases, and two flags.  You have to get into the enemy base, grab their flag, and bring it back to your own flag to score.  Scoring gives the most points, normal fragging and returning the flag gives some points.  The team with the most points wins.  CTF is a surprisingly complicated game mode, so see the comprehensive CTF guide in the Nexuiz forum for more information (<a class="link" href="http://alientrap.org/forum/viewtopic.php?t=849">http://alientrap.org/forum/viewtopic.php?t=849</a>).</li>
+</ul>
+<ul>
+<li>Minstagib is an improved instagib.  You can pick up lives, so you can survive two shots, and you can pick up an item to become almost invisible.  This is were the minstanex got its name from.</li>
+</ul>
+<ul>
+<li>Runematch places five runes into the map in addition to the normal items.  A rune gives you a bonus but also a curse - but you only get points for fragging if you carry a rune, so make sure you have one.  You also get points for fragging a rune carrier, and for just holding a rune (but fragging is the quicker way to gain points).  The player with the most points wins.</li>
+</ul>
+<ul>
+<li>Domination (DOM) is also a team mode.  Throughout the map are domination points.  Touch them and your team will own that point.  You get points for each second you own such a domination point.  Touch points the other team holds to turn it into your own.  The team with the most points wins.</li>
+</ul>
+<ul>
+<li>Last Man Standing (LMS) puts all players into a map without any items.  Each player has several lives, and starts with all weapons when he spawns.  Each time you are fragged you lose a life.  The winner is the last surviving player.</li>
+</ul>
+<ul>
+<li>Arena mode is similar to deathmatch, but it only puts two players into a map, with the others waiting in a queue.  When one of both is fragged the next one in the queue will play against the winner.</li>
+</ul>
+<ul>
+<li>Keyhunt (KH) is a new team mode in Nexuiz 2.3.  Each team has one key and the goal is the collect all keys.  The team with the most points wins.  You get small points for fragging, fragging a keycarrier and picking up a key but you should go for the BIG points you get when <em>one team has all the keys and all the keycarriers meet</em>.  Of course it also works when ONE player gets all the keys for his team but this is <em>much</em> harder.</li>
+</ul>
+<ul>
+<li>Onslaught (ONS) is a new team mode in Nexuiz 2.4.  Each team has a powergenerator and the goal is to destroy the enemies generator.  Each generator and all except one checkpoint in the map are protected by an forcefield which is powered by nearby checkpoint.  Your team has to start at the one unprotected checkpoint and work your way up to the enemies generator.  Find the perfect balance between attacking the enemies checkpoints/generator and protecting your own.</li>
+</ul>
+<ul>
+<li>Assault (AS) is a similar team mode and also new in Nexuiz 2.4.  There are objects in the map, only one is active at a time and you have to complete that one to unlock the next objective.  One round ends if the last objective is completed or timelimit is hit.  After the round the teams swap the attackers/defender roles.</li>
+</ul>
+<ul>
+<li>Race is a new mode for all the quick people in Nexuiz 2.5.  The goal is to race through the level as fast as possible.  A timer is set off at the startline and stopped at the endline.  Checkpoints in between give you an update on your time and compared to the top player.  There are three different race modes:</li>
+</ul>
+<ul>
+<ul>
+<li>Qualifying mode saves each player's fastest lap time and the player with the fastest time wins. When you die, you respawn in front of the startline.  Players can't interact in this mode. They walk through each other and can't shoot at the others.</li>
+</ul>
+</ul>
+<ul>
+<ul>
+<li>Race mode is won by the player who makes a certain count of laps first.  Players can interact and kill each other.  When killed, you respawn in front of the last checkpoint you went through.</li>
+</ul>
+</ul>
+<ul>
+<ul>
+<li>Mixed mode has the players first compete in qualifying mode for a certain time, or until they are all ready, then the game switches to race mode.  The fastest player in qualifying mode is the first to spawn in race mode and so on. </li>
+</ul>
+</ul>
+<!--End Section 2-->
+<h3><a name="''Mutators''"><em>Mutators</em></a></h3>
+<ul>
+<li>The hook mutator will add an off-hand hook which can be used like the grappling hook but can be used while you carry other guns.  Fire the hook at a ceiling or wall using the key configured through the menu or bound using <em>bind &quot;key&quot; &quot;+hook&quot;</em>.  Hold down this key while the hook is attached to be pulled toward it.</li>
+</ul>
+<ul>
+<li>Laser-guided rockets will give you a Laser dot on the rocket launcher which you can use to guide the most recently fired rocket.</li>
+</ul>
+<ul>
+<li>Vampire will add whatever damage you do to your opponent to your own health.</li>
+</ul>
+<ul>
+<li>Nixnex will remove normal guns from the map and give all players the same gun.  That gun is changed after a while.</li>
+</ul>
+<ul>
+<li>Footsteps will add footstep sounds to your movements.</li>
+</ul>
+<ul>
+<li>Weapon Arena equips everyone with the <em>arena gun</em> chosen by the server admin.</li>
+</ul>
+<p class="Body">
+There are some more of these mutators, but those are the most important ones.
+</p>
+<p class="Body">
+A list of useful links:
+<ul>
+<li>The official Nexuiz forum: <a class="link" href="http://alientrap.org/forum">http://alientrap.org/forum</a></li>
+</ul>
+</p>
+<ul>
+<li>The semiofficial Nexuiz wiki: <a class="link" href="http://alientrap.org/wiki">http://alientrap.org/wiki</a></li>
+</ul>
+<ul>
+<li>The official Nexuiz ladder: <a class="link" href="http://planetnexuiz.com/ladder">http://planetnexuiz.com/ladder</a></li>
+</ul>
+<ul>
+<li>The Official Nexuiz tournaments: <a class="link" href="http://planetnexuiz.com/tourney">http://planetnexuiz.com/tourney</a></li>
+</ul>
+<ul>
+<li>General Nexuiz Tips and Tricks: <a class="link" href="http://alientrap.org/forum/viewtopic.php?t=90">http://alientrap.org/forum/viewtopic.php?t=90</a></li>
+</ul>
+<ul>
+<li>How to get more help via IRC: <a class="link" href="http://alientrap.org/forum/viewtopic.php?t=497">http://alientrap.org/forum/viewtopic.php?t=497</a></li>
+</ul>
+<ul>
+<li>CTF guide and ctf MAPS guide: <a class="link" href="http://alientrap.org/forum/viewtopic.php?t=849">http://alientrap.org/forum/viewtopic.php?t=849</a></li>
+</ul>
+<ul>
+<li>Team communication explained: <a class="link" href="http://alientrap.org/forum/viewtopic.php?t=555">http://alientrap.org/forum/viewtopic.php?t=555</a></li>
+</ul>
+<ul>
+<li>A few useful jumps: <a class="link" href="http://esteel.planetnexuiz.de/nexuiz/demos/jumps.pk3">http://esteel.planetnexuiz.de/nexuiz/demos/jumps.pk3</a>  (copy the file into Nexuiz/data and you should find the demos in the demos menu)</li>
+</ul>
+<ul>
+<li>FPS settings: <a class="link" href="http://alientrap.org/forum/viewtopic.php?t=1628">http://alientrap.org/forum/viewtopic.php?t=1628</a></li>
+</ul>
+<ul>
+<li>A list of all available variables in Nexuiz: <a class="link" href="http://alientrap.org/wiki/pmwiki.php?n=Main.GameTweaks">http://alientrap.org/wiki/pmwiki.php?n=Main.GameTweaks</a></li>
+</ul>
+<ul>
+<li>A list of all available commands in Nexuiz: <a class="link" href="http://alientrap.org/wiki/pmwiki.php?n=Main.ConsoleCommands">http://alientrap.org/wiki/pmwiki.php?n=Main.ConsoleCommands</a></li>
+</ul>
+<ul>
+<li>Some info about generic strategy in games: <a class="link" href="http://alientrap.org/forum/viewtopic.php?t=3496">http://alientrap.org/forum/viewtopic.php?t=3496</a></li>
+</ul>
+<!--End Section 2-->
+<!-- End SectLevel2 -->
+<!--End Section 1-->
+<!-- End SectLevel1 -->
+<hr>
+<br>
+<p align="right"> <small>This document was generated using <a class="link" href="http://www.maplefish.com/todd/aft.html">AFT v5.096</a></small> </p>
+                 <p><br style="clear:left" />
+        </p>
+         </div>
+         <!-- end content -->
+               <div id="footer">
+                       <p id="ninja"><a href="http://www.detrition.net" title="Tyler Mulligan's Working Portfolio" target="_blank">page created by</a> <a href="http://www.nexuizninjaz.com" title="Nexuiz Ninjaz - Practicing the ninja arts of Nexuiz" target="_blank">a ninja</a></p>
+                       <ul>
+
+                               <li class="first"><a href="http://alientrap.org/nexuiz/news.php" title="Nexuiz - Latest News">News</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/media.php" title="Nexuiz - Media (Screenshots, videos and graphics)">Media</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/information.php" title="Nexuiz Information  (General, System Requirements)">Info</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/team.php" title="The Nexuiz Team">Team</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/links.php" title="Nexuiz Links">Links</a></li>
+                               <li><a href="http://alientrap.org/forum/index.php?c=3" title="Official Nexuiz Forums" target="_blank">Forums</a></li>
+                               <li><a href="http://planetnexuiz.com/ladder/" title="Nexuiz Ladder" target="_blank">Ladder</a></li>
+                               <li><a href="http://planetnexuiz.com/tourney/" title="Nexuiz Tournaments" target="_blank">Tournament</a></li>
+                       </ul>           </div><!-- end footer -->
+               <p class="subFooter">Come to the Nexuiz IRC channel: <a href="irc://irc.quakenet.org/nexuiz" title="#nexuiz on irc.quakenet.org">#nexuiz on irc.quakenet.org</a> or the team channel: <a href="irc://irc.anynet.org/alientrap" title="#alientrap on irc.anynet.org">#alientrap on irc.anynet.org</a></p>
+
+               <div id="valid">
+                       <a id="valid_css" href="http://jigsaw.w3.org/css-validator/validator?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>
+                       <a id="valid_xhtml" href="http://validator.w3.org/check?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>
+                       <p>&copy; 2004-2008 Nexuiz.com</p>
+               </div>
+       </div><!-- end right -->
+</div>
+</div>
+</body>
+</html>
index 851432c55a16299783c2a8cf203bd85022bb48d3..847f7cb997961472a50d8cdc8a2a071786258b52 100644 (file)
@@ -1,50 +1,50 @@
-This file contains the q3map2 compile settings used to compile the maps that are part of Nexuiz.\r
-There are also compile-scripts for some maps (mapname.sh). \r
-They can be found in your Nexuiz/data/maps folder\r
-\r
-------------\r
-Standard\r
-------------\r
-\r
-"...q3map2.exe" -meta "...nexuizmap.map"\r
-"...q3map2.exe" -vis "...nexuizmap.bsp"\r
-"...q3map2.exe" -light -bounce 3 -deluxe -fast -filter -patchshadows -samples 2 "...nexuizmap.bsp"\r
-\r
-------------\r
-Only update the entities.\r
-------------\r
-\r
-"...q3map2.exe" -onlyents "...nexuizmap.map"\r
-\r
-------------\r
-bloodprison.map\r
-------------\r
-\r
-"...q3map2.exe" -meta -samplesize 8 -mv 1000000 -mi 6000000 "...bloodprisonctf.map"\r
-"...q3map2.exe" -scale 1.2 "...bloodprisonctf.bsp"\r
-"...q3map2.exe" -vis "...bloodprisonctf.bsp"\r
-"...q3map2.exe" -light -deluxe -fast -filter -deluxe -patchshadows -samples 3 "...bloodprisonctf.bsp"\r
-\r
-------------\r
-final_rage.map\r
-------------\r
-\r
-"...q3map2.exe" -meta -v -samplesize 8 -skyfix -np 50 "...final_rage.map"\r
-"...q3map2.exe" -vis -v "...final_rage.map"\r
-"...q3map2.exe" -light -deluxe -compensate 1.4 -fast -gamma 1.5 -patchshadows -areascale 1.5 -pointscale 2 -v -bounce 3 -bouncegrid "...final_rage.map"\r
-\r
-------------\r
-reslimed.map\r
-------------\r
-\r
-"...q3map2.exe" -meta -patchmeta -v "...reslimed.map"\r
-"...q3map2.exe" -vis "...reslimed.bsp"\r
-"...q3map2.exe" -light -bounce 3 -deluxe -fast -filter -patchshadows -samples 2 "...reslimed.bsp"\r
-\r
-------------\r
-strength.map\r
-------------\r
-\r
-"...q3map2.exe" -meta "...strength.map"\r
-"...q3map2.exe" -vis "...strength.bsp"\r
-"...q3map2.exe" -light -bounce 3 -deluxe -fast -filter -patchshadows -samples 2 "...strength.bsp"\r
+This file contains the q3map2 compile settings used to compile the maps that are part of Nexuiz.
+There are also compile-scripts for some maps (mapname.sh). 
+They can be found in your Nexuiz/data/maps folder
+
+------------
+Standard
+------------
+
+"...q3map2.exe" -meta "...nexuizmap.map"
+"...q3map2.exe" -vis "...nexuizmap.bsp"
+"...q3map2.exe" -light -bounce 3 -deluxe -fast -filter -patchshadows -samples 2 "...nexuizmap.bsp"
+
+------------
+Only update the entities.
+------------
+
+"...q3map2.exe" -onlyents "...nexuizmap.map"
+
+------------
+bloodprison.map
+------------
+
+"...q3map2.exe" -meta -samplesize 8 -mv 1000000 -mi 6000000 "...bloodprisonctf.map"
+"...q3map2.exe" -scale 1.2 "...bloodprisonctf.bsp"
+"...q3map2.exe" -vis "...bloodprisonctf.bsp"
+"...q3map2.exe" -light -deluxe -fast -filter -deluxe -patchshadows -samples 3 "...bloodprisonctf.bsp"
+
+------------
+final_rage.map
+------------
+
+"...q3map2.exe" -meta -v -samplesize 8 -skyfix -np 50 "...final_rage.map"
+"...q3map2.exe" -vis -v "...final_rage.map"
+"...q3map2.exe" -light -deluxe -compensate 1.4 -fast -gamma 1.5 -patchshadows -areascale 1.5 -pointscale 2 -v -bounce 3 -bouncegrid "...final_rage.map"
+
+------------
+reslimed.map
+------------
+
+"...q3map2.exe" -meta -patchmeta -v "...reslimed.map"
+"...q3map2.exe" -vis "...reslimed.bsp"
+"...q3map2.exe" -light -bounce 3 -deluxe -fast -filter -patchshadows -samples 2 "...reslimed.bsp"
+
+------------
+strength.map
+------------
+
+"...q3map2.exe" -meta "...strength.map"
+"...q3map2.exe" -vis "...strength.bsp"
+"...q3map2.exe" -light -bounce 3 -deluxe -fast -filter -patchshadows -samples 2 "...strength.bsp"
index 0bed9ea60d55706933ddca9417c3d16e2657ae94..a457e02b3f548bb9615f14ac618298a642bdeb2a 100644 (file)
@@ -1,61 +1,61 @@
-============================================================\r
-Date:             13th August 2001.\r
-file:             tp-egyptian.zip\r
-author:           Sock \r
-email:            sock@planetquake.com\r
-URL:              http://www.planetquake.com/simland\r
-New URL:          http://www.simonoc.com/pages/materials/tpegypt/index.htm\r
-Version:          1.5\r
-\r
-============================================================\r
-\r
-COPYRIGHT NOTICES\r
------------------\r
-\r
-If you use any of these Egyptian shader/textures I kindly ask\r
-YOU to give me credit for my work within your README file or\r
-TEXT file distributed with your map/mod.\r
-\r
-============================================================\r
-\r
-Testing of Shaders\r
-------------------\r
-\r
-* All of the shaders included with this texture pack have been \r
-  tested with Q3 point release 1.27g.\r
-\r
-============================================================\r
-\r
-Instructions for using the Egyptian Texture Pack\r
----------------------------------------------------\r
-\r
-** You must have installed the Q3 editor tools first **\r
-\r
-1. Extract the tp-egyptian.zip file in the BASEQ3 directory.\r
-   This will create 6 texture directory under the\r
-   BASEQ3/TEXTURES directory as follows :-\r
-\r
-   BASEQ3/TEXTURES/EGYPTSOC_FLOOR\r
-   BASEQ3/TEXTURES/EGYPTSOC_MAT\r
-   BASEQ3/TEXTURES/EGYPTSOC_SFX\r
-   BASEQ3/TEXTURES/EGYPTSOC_TRIM\r
-   BASEQ3/TEXTURES/EGYPTSOC_TRIMD\r
-   BASEQ3/TEXTURES/EGYPTSOC_WALL\r
-   \r
-2. Goto the SCRIPTS sub-directory under the BASEQ3\r
-   directory and find the following \r
-   file :- SHADERLIST.TXT.\r
-   \r
-3. Open this file up in a text editor and add the\r
-   following line at the bottom of the file.\r
-   \r
-   EGYPTSOC\r
-   \r
-4. Close the file and open Q3Radiant and you should\r
-   find on the texture menu the 1 new subdirectory.\r
-\r
-For a full explaination of each texture/shader please\r
-refer to the website.\r
-\r
-Enjoy\r
-Sock\r
+============================================================
+Date:             13th August 2001.
+file:             tp-egyptian.zip
+author:           Sock 
+email:            sock@planetquake.com
+URL:              http://www.planetquake.com/simland
+New URL:          http://www.simonoc.com/pages/materials/tpegypt/index.htm
+Version:          1.5
+
+============================================================
+
+COPYRIGHT NOTICES
+-----------------
+
+If you use any of these Egyptian shader/textures I kindly ask
+YOU to give me credit for my work within your README file or
+TEXT file distributed with your map/mod.
+
+============================================================
+
+Testing of Shaders
+------------------
+
+* All of the shaders included with this texture pack have been 
+  tested with Q3 point release 1.27g.
+
+============================================================
+
+Instructions for using the Egyptian Texture Pack
+---------------------------------------------------
+
+** You must have installed the Q3 editor tools first **
+
+1. Extract the tp-egyptian.zip file in the BASEQ3 directory.
+   This will create 6 texture directory under the
+   BASEQ3/TEXTURES directory as follows :-
+
+   BASEQ3/TEXTURES/EGYPTSOC_FLOOR
+   BASEQ3/TEXTURES/EGYPTSOC_MAT
+   BASEQ3/TEXTURES/EGYPTSOC_SFX
+   BASEQ3/TEXTURES/EGYPTSOC_TRIM
+   BASEQ3/TEXTURES/EGYPTSOC_TRIMD
+   BASEQ3/TEXTURES/EGYPTSOC_WALL
+   
+2. Goto the SCRIPTS sub-directory under the BASEQ3
+   directory and find the following 
+   file :- SHADERLIST.TXT.
+   
+3. Open this file up in a text editor and add the
+   following line at the bottom of the file.
+   
+   EGYPTSOC
+   
+4. Close the file and open Q3Radiant and you should
+   find on the texture menu the 1 new subdirectory.
+
+For a full explaination of each texture/shader please
+refer to the website.
+
+Enjoy
+Sock
index 030ba52f28a4a30c2794979fb849fa57264b8d3a..1152b4346959b45dcccb472cb42dd9392164f11c 100644 (file)
-cvars:\r
-______________\r
-\r
-   sv_eventlog                    master switch (default: 0)\r
-   sv_eventlog_files              print frags, scores and captures for separate files each match (default: 0)\r
-   sv_eventlog_console            print frags, scores and captures to serverconsole during the match (default: 1)\r
-   sv_logscores_bots              choose whether bot are included in stats or not (default: 0)\r
-                                \r
-   sv_eventlog_files_counter      number of matches logged until now\r
-   sv_eventlog_files_nameprefix   file name prefix to be used (default: nexuiz)\r
-   sv_eventlog_files_namesuffix   file name extension to be used (default: .log)\r
-   sv_eventlog_files_timestamps   prefix log lines in the files with :time events (default: 1)\r
-\r
-log format:\r
-______________\r
-\r
-   :logversion:3\r
-   :gamestart:<gametype>_<mapname>:<matchid>\r
-   :gameinfo:mutators:LIST:mutator1:mutator2:...\r
-\r
-      (note that mutators are listed by their cvar name with g_ removed,\r
-      unless such a cvar is 1 by default - then the mutator is listed with\r
-      a no_ prefix if the cvar is 0)\r
-\r
-   :gameinfo:end\r
-   :join:<ID>:<slot>:<ip>:<nickname>\r
-   :join:<ID>:<slot>:bot:<nickname>\r
-   :name:<ID>:<nickname>\r
-   :part:<ID>\r
-   :team:<ID>:<team>:<jointype>\r
-   :kill:frag:<ID of killer>:<ID of victim>:type=<death type>:items=<itemstring of killer>:victimitems=<itemstring of victim>\r
-   :kill:tk:<ID of killer>:<ID of victim>:type=<death type>:items=<itemstring of killer>:victimitems=<itemstring of victim>\r
-   :kill:suicide:<ID>:<ID>:type=<death type>:items=<itemstring>\r
-   :kill:accident:<ID>:<ID>:type=<death type>:items=<itemstring>\r
-   :ctf:steal:<flagcolor>:<ID of attacker>\r
-   :ctf:dropped:<flagcolor>:<ID of dropper>\r
-   :ctf:pickup:<flagcolor>:<ID of attacker>\r
-   :ctf:capture:<flagcolor>:<ID of attacker>\r
-   :ctf:return:<flagcolor>:<ID of defender>\r
-   :ctf:returned:<flagcolor>\r
-   :dom:taken:<previouscolor>:<ID of player>\r
-   :keyhunt:capture:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>\r
-   :keyhunt:carrierfrag:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>\r
-   :keyhunt:collect:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>\r
-   :keyhunt:destroyed:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>\r
-   :keyhunt:destroyed_holdingkey:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>\r
-   :keyhunt:dropkey:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>\r
-   :keyhunt:losekey:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>\r
-   :keyhunt:push:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>\r
-   :keyhunt:pushed:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>\r
-   :scores:<gametype>_<mapname>:<map runtime>\r
-   :labels:player:<head1><flags>,<head2><flags>,...\r
-   :player:see-labels:<score1>,<score2>,...:<playtime>:<team>:<ID>:<nickname>\r
-   :player:see-labels:<score1>,<score2>,...:<playtime>:spectator:<ID>:<nickname>\r
-   :labels:teamscores:<head1><flags>,<head2><flags>,...\r
-   :teamscores:see-labels:<score1>,<score2>,...:<team>\r
-   :end\r
-   :restart\r
-   :gameover\r
-   :vote:suggested:<mapname>:<playerid>\r
-   :vote:keeptwo:<mapname>:<mapvotes>:<mapname>:<mapvotes>:::<mapname>:<mapvotes>:...:didn't vote:<notvoters>\r
-   :vote:finished:<mapname>:<mapvotes>:::<mapname>:<mapvotes>:<mapname>:<mapvotes>:...:didn't vote:<notvoters>\r
-   :vote:suggestion_accepted:<mapname>\r
-   :vote:vcall:<ID of player>:<vote command display string>\r
-   :vote:vyes:<yescount>:<nocount>:<abstaincount>:<notvoters>:<mincount>\r
-   :vote:vno:<yescount>:<nocount>:<abstaincount>:<notvoters>:<mincount>\r
-   :vote:vtimeout:<yescount>:<nocount>:<abstaincount>:<notvoters>:<mincount>\r
-   :vote:vstop:<ID of stopper>\r
-   :vote:vlogin:<ID of player>\r
-   :vote:vdo:<ID of player>:<do command display string>\r
-   :time:<YYYY-MM-DD HH:MM:SS>\r
-   :recordset:<ID of player>:<time in seconds>\r
-\r
-   Note that only the :join and :player lines ever contain player names. The\r
-   :time event only appears in the log files if sv_eventlog_files_timestamps\r
-   is 1; there is no way to log these time stamps to the console (for console\r
-   timestamps, set timestamps to 1).\r
-\r
-team colors:\r
-    1 = No Team (Domination)\r
-    5 = Red Team\r
-   14 = Blue Team\r
-   13 = Yellow Team\r
-   10 = Pink Team\r
-\r
-join types:\r
-    1 = connect\r
-    2 = auto\r
-    3 = manual\r
-    4 = spectating\r
-    6 = adminmove\r
-\r
-label flags:\r
-   !! = primary sorting key\r
-   <!! = primary sorting key, lower is better\r
-   ! = secondary sorting key\r
-   <! = secondary sorting key, lower is better\r
-   < = lower is better\r
-\r
-itemstring:\r
-   <weaponid><flags>\r
-   or\r
-   <weaponid><flags>|<runes>\r
-\r
-   where flags can contain:\r
-         F = player carries the flag\r
-         S = player has strength\r
-         I = player has the shield\r
-         T = player is typing (console, menu or chat)\r
-   \r
-   and weapon IDs are:\r
-         1 = Laser\r
-                2 = Shotgun\r
-                3 = Uzi\r
-         4 = Mortar\r
-         5 = Electro\r
-         6 = Crylink\r
-         7 = Nex\r
-         8 = Hagar\r
-         9 = Rocket Launcher\r
-        10 = Port-O-Launch\r
-        11 = MinstaNex\r
-        12 = Grappling Hook\r
-        13 = Heavy Laser Assault Cannon\r
-        14 = T.A.G. Seeker\r
-\r
-   runes/curses are stored as a bit mask with the following values:\r
-         1 = Strength\r
-         2 = Defense\r
-         4 = Regeneration\r
-         8 = Speed\r
-        16 = Vampire\r
-      8192 = Weakness\r
-     16384 = Vulnerability\r
-     32768 = Venom\r
-     65536 = Slow\r
-    131072 = Empathy\r
-\r
-death type:\r
-   either a weapon ID ORed with weapon death flags, or one of:\r
-     10000 = fallen to death\r
-     10001 = telefragged\r
-     10002 = drowned\r
-     10003 = killed by a trap / fallen into the void\r
-     10004 = lava\r
-     10005 = slime\r
-     10006 = console kill\r
-     10007 = (MinstaGib) out of ammo\r
-     10008 = swamp\r
-     10009 = team change\r
-     10010 = auto team change\r
-     10011 = camping protection\r
-        10012 = player became too fast (should never happen)\r
-        10013 = health rot\r
-        10014 = mirror damage\r
-        10015 = g_touchexplode\r
-        10100 = turret\r
-       10150 = spiderbot miniguns\r
-       10151 = spiderbot rocket\r
-       10152 = spiderbot, cushed by\r
-       10300 = custom deathmessage\r
-\r
-   weapon death flags are:\r
-       256 = secondary fire\r
-          512 = splash damage\r
-         1024 = bounced projectile\r
-         2048 = head shot (MinstaNex only)\r
-         4096 = unused flag\r
-\r
-There will be a log analyzer parsing this file format soon. Note that weapon\r
-IDs are below 10000.\r
+cvars:
+______________
+
+   sv_eventlog                    master switch (default: 0)
+   sv_eventlog_files              print frags, scores and captures for separate files each match (default: 0)
+   sv_eventlog_console            print frags, scores and captures to serverconsole during the match (default: 1)
+   sv_logscores_bots              choose whether bot are included in stats or not (default: 0)
+                                
+   sv_eventlog_files_counter      number of matches logged until now
+   sv_eventlog_files_nameprefix   file name prefix to be used (default: nexuiz)
+   sv_eventlog_files_namesuffix   file name extension to be used (default: .log)
+   sv_eventlog_files_timestamps   prefix log lines in the files with :time events (default: 1)
+
+log format:
+______________
+
+   :logversion:3
+   :gamestart:<gametype>_<mapname>:<matchid>
+   :gameinfo:mutators:LIST:mutator1:mutator2:...
+
+      (note that mutators are listed by their cvar name with g_ removed,
+      unless such a cvar is 1 by default - then the mutator is listed with
+      a no_ prefix if the cvar is 0)
+
+   :gameinfo:end
+   :join:<ID>:<slot>:<ip>:<nickname>
+   :join:<ID>:<slot>:bot:<nickname>
+   :name:<ID>:<nickname>
+   :part:<ID>
+   :team:<ID>:<team>:<jointype>
+   :kill:frag:<ID of killer>:<ID of victim>:type=<death type>:items=<itemstring of killer>:victimitems=<itemstring of victim>
+   :kill:tk:<ID of killer>:<ID of victim>:type=<death type>:items=<itemstring of killer>:victimitems=<itemstring of victim>
+   :kill:suicide:<ID>:<ID>:type=<death type>:items=<itemstring>
+   :kill:accident:<ID>:<ID>:type=<death type>:items=<itemstring>
+   :ctf:steal:<flagcolor>:<ID of attacker>
+   :ctf:dropped:<flagcolor>:<ID of dropper>
+   :ctf:pickup:<flagcolor>:<ID of attacker>
+   :ctf:capture:<flagcolor>:<ID of attacker>
+   :ctf:return:<flagcolor>:<ID of defender>
+   :ctf:returned:<flagcolor>
+   :dom:taken:<previouscolor>:<ID of player>
+   :keyhunt:capture:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>
+   :keyhunt:carrierfrag:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>
+   :keyhunt:collect:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>
+   :keyhunt:destroyed:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>
+   :keyhunt:destroyed_holdingkey:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>
+   :keyhunt:dropkey:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>
+   :keyhunt:losekey:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>
+   :keyhunt:push:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>
+   :keyhunt:pushed:<ID of player>:<points for player>:<ID of key owner>:<points for key owner>:<name of key>
+   :scores:<gametype>_<mapname>:<map runtime>
+   :labels:player:<head1><flags>,<head2><flags>,...
+   :player:see-labels:<score1>,<score2>,...:<playtime>:<team>:<ID>:<nickname>
+   :player:see-labels:<score1>,<score2>,...:<playtime>:spectator:<ID>:<nickname>
+   :labels:teamscores:<head1><flags>,<head2><flags>,...
+   :teamscores:see-labels:<score1>,<score2>,...:<team>
+   :end
+   :restart
+   :gameover
+   :vote:suggested:<mapname>:<playerid>
+   :vote:keeptwo:<mapname>:<mapvotes>:<mapname>:<mapvotes>:::<mapname>:<mapvotes>:...:didn't vote:<notvoters>
+   :vote:finished:<mapname>:<mapvotes>:::<mapname>:<mapvotes>:<mapname>:<mapvotes>:...:didn't vote:<notvoters>
+   :vote:suggestion_accepted:<mapname>
+   :vote:vcall:<ID of player>:<vote command display string>
+   :vote:vyes:<yescount>:<nocount>:<abstaincount>:<notvoters>:<mincount>
+   :vote:vno:<yescount>:<nocount>:<abstaincount>:<notvoters>:<mincount>
+   :vote:vtimeout:<yescount>:<nocount>:<abstaincount>:<notvoters>:<mincount>
+   :vote:vstop:<ID of stopper>
+   :vote:vlogin:<ID of player>
+   :vote:vdo:<ID of player>:<do command display string>
+   :time:<YYYY-MM-DD HH:MM:SS>
+   :recordset:<ID of player>:<time in seconds>
+
+   Note that only the :join and :player lines ever contain player names. The
+   :time event only appears in the log files if sv_eventlog_files_timestamps
+   is 1; there is no way to log these time stamps to the console (for console
+   timestamps, set timestamps to 1).
+
+team colors:
+    1 = No Team (Domination)
+    5 = Red Team
+   14 = Blue Team
+   13 = Yellow Team
+   10 = Pink Team
+
+join types:
+    1 = connect
+    2 = auto
+    3 = manual
+    4 = spectating
+    6 = adminmove
+
+label flags:
+   !! = primary sorting key
+   <!! = primary sorting key, lower is better
+   ! = secondary sorting key
+   <! = secondary sorting key, lower is better
+   < = lower is better
+
+itemstring:
+   <weaponid><flags>
+   or
+   <weaponid><flags>|<runes>
+
+   where flags can contain:
+         F = player carries the flag
+         S = player has strength
+         I = player has the shield
+         T = player is typing (console, menu or chat)
+   
+   and weapon IDs are:
+         1 = Laser
+                2 = Shotgun
+                3 = Uzi
+         4 = Mortar
+         5 = Electro
+         6 = Crylink
+         7 = Nex
+         8 = Hagar
+         9 = Rocket Launcher
+        10 = Port-O-Launch
+        11 = MinstaNex
+        12 = Grappling Hook
+        13 = Heavy Laser Assault Cannon
+        14 = T.A.G. Seeker
+
+   runes/curses are stored as a bit mask with the following values:
+         1 = Strength
+         2 = Defense
+         4 = Regeneration
+         8 = Speed
+        16 = Vampire
+      8192 = Weakness
+     16384 = Vulnerability
+     32768 = Venom
+     65536 = Slow
+    131072 = Empathy
+
+death type:
+   either a weapon ID ORed with weapon death flags, or one of:
+     10000 = fallen to death
+     10001 = telefragged
+     10002 = drowned
+     10003 = killed by a trap / fallen into the void
+     10004 = lava
+     10005 = slime
+     10006 = console kill
+     10007 = (MinstaGib) out of ammo
+     10008 = swamp
+     10009 = team change
+     10010 = auto team change
+     10011 = camping protection
+        10012 = player became too fast (should never happen)
+        10013 = health rot
+        10014 = mirror damage
+        10015 = g_touchexplode
+        10100 = turret
+       10150 = spiderbot miniguns
+       10151 = spiderbot rocket
+       10152 = spiderbot, cushed by
+       10300 = custom deathmessage
+
+   weapon death flags are:
+       256 = secondary fire
+          512 = splash damage
+         1024 = bounced projectile
+         2048 = head shot (MinstaNex only)
+         4096 = unused flag
+
+There will be a log analyzer parsing this file format soon. Note that weapon
+IDs are below 10000.
index 240dbd7d3ebb0120aa678cf72b6d2a349dd96a41..6c415dad7479f13cd76a795da46b1e9321f67e6a 100644 (file)
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\r
-        "http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd">\r
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\r
-<head>\r
-<meta http-equiv="content-type" content="text/html; charset=utf-8" />\r
-<meta name="author" content="Tyler Mulligan of www.detrition.net a.k.a -z- of www.nexuizninjaz.com" />\r
-<meta name="Description" content="Nexuiz is a free open-source fast paced first person shooter (FPS) that runs on Windows, Linux and OSX.  Nexuiz utilizes the darkplaces engine modeled after the Quake series." />\r
-<meta name="Keywords" content="Nexuiz, Nexiuz, first person shooter, darkplaces, open-source game, open source game, free game, linux game, deathmatch, death match, ctf, quake, alientrap, alien trap, ninjaz" />\r
-<title>Nexuiz - A free open-source fast paced first person shooter (FPS) for Windows, Linux and OSX</title>\r
-<link rel="stylesheet" href="htmlfiles/style.css" type="text/css"></link>\r
-<link rel="shortcut icon" href="favicon.ico"></link>\r
-</head>\r
-<body>\r
-<div id="container">\r
-       <div id="left">\r
-               <div id="logo">\r
-\r
-                       <a href="http://www.nexuiz.com" title="Nexuiz - Simple, fast, intense and completely free"><img src="htmlfiles/img/nexuiz_logo.jpg" alt="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="201" height="193" border="0" title="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." /></a>\r
-               </div>\r
-               <!-- Sidebar -->\r
-         <div id="sidebar">\r
-                       <a href="news.php" title="Latest Nexuiz News"><img src="htmlfiles/img/headers/latest_news.gif" alt="Latest News" width="168" height="16" border="0" class="imgHeader" style="margin-top:0;" title="Latest Nexuiz News" /></a>\r
-                       <div id="news">\r
-                         <div class="newsPost">\r
-                                               <h1><a href="http://nexuiz.com/news.php" title="Two Nexuiz 1v1 Tourneys">nexuiz.com/news - for nexuiz news updates</a></h1>\r
-                         </div>\r
-               </div>\r
-                       <!-- Downloads -->\r
-                       <a href="downloads.php" title="Nexuiz Downloads (Game, Maps)"><img src="htmlfiles/img/headers/downloads.gif" alt="Downloads" width="145" height="15" border="0" class="imgHeader" title="Downloads" /></a>\r
-                       <a id="download_nexuiz" href="http://www.sourceforge.net/projects/nexuiz/" title="Download Nexuiz from Source Forge - Left Click">&nbsp;</a>\r
-                       <!--<a id="download_q3_mappack" href="http://downloads.sourceforge.net/nexuiz/nexmappack_r2.zip" title="Download Nexuiz Q3 Mappack from Source\r
-                       Forge - Left Click">&nbsp;</a>-->\r
-                       <h2 class="page"><a href="http://nexuiz.com/downloads.php" title="Nexuiz Download Mirrors">Mirrors -&gt;</a></h2>\r
-                       <!-- Help Wanted -->\r
-\r
-                       <img src="htmlfiles/img/headers/help_wanted.gif" alt="Help Wanted" width="165" height="15" class="imgHeader" title="Help Wanted" />                     \r
-                       <form action="https://www.paypal.com/cgi-bin/webscr" method="post" name="donate_form"> <input name="cmd" value="_s-xclick" type="hidden" />\r
-                       <a id="donate" href="javascript:document.donate_form.submit();" title="Help The Aliens, Please Donate">&nbsp;</a>\r
-                       <noscript>\r
-                               <!-- It not an elegant solution but I don't expect many users to arrive with JS turned off -->\r
-                               <input name="submit" type="image" src="htmlfiles/img/donate_button_noscript.jpg" alt="Help this project - Donate to the aliens" width="166" height="43" /> \r
-                       </noscript>\r
-                       <h2 class="page"><a href="http://nexuiz.com/donators.php" title="Those who have donated to Nexuiz">Donators -&gt;</a></h2>\r
-                       <input name="encrypted" value="-----BEGIN PKCS7-----MIIHBgYJKoZIhvcNAQcEoIIG9zCCBvMCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYBfp40KRLmnxKPY06C4gjvEiWZchbxK6bgD7ZdjhdWO5Vbwo4T4Ro+HE041PVVqIxPlJgO80l3aQpBtfhC66FfM2kIF1BjLs1zzhQM89XoPGViS3e4kbmzxkMnpdiZFmOsR5Fs5NJYiVaMnVGcoQ+K3+KsyOehZGket7GwUeNFMRzELMAkGBSsOAwIaBQAwgYMGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIb1QeJqVIc5SAYOPcI23W84XMGt8mSfHE1Gf0/GZAM7NvqLiHF0BeaecRW1Bc85O0tL9OduZiraGf7WVnAmP5kp1D0irXsA5+N2l15WADxwNQ/GoCAU293l0dAQ7Qy4F3vh6eSii18MaH2KCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA1MDUxMjE5MDQ0OFowIwYJKoZIhvcNAQkEMRYEFC7mlfdaA7Pg2eBhxI5xQTe7ydTtMA0GCSqGSIb3DQEBAQUABIGAfR38tX84huxk9JjvhggcSMxzHbmDxpxInBU6/lbyqAu7iT5KJn7rcJgaH5ZVyKNoNQLGp9IxweBrcMiYUQNVxShm9+hunXhQmj5r7AMGaxNZ0mE8mQRW1ZTaz7TLz1HkDFA+R0Dm8HYyDQA4L505cBiWNEsKC17VwNK1G7CEVvA=-----END PKCS7-----" type="hidden" /></form>\r
-                       \r
-                 <!-- Created By -->\r
-                 <img src="htmlfiles/img/headers/created_by.gif" alt="Created By" width="145" height="15" class="imgHeader" title="Created By" />\r
-\r
-         <a id="alien_trap" href="http://www.alientrap.org" title="Developed by Alien Trap" target="_blank">&nbsp;</a>   </div>\r
-               <!-- end sidebar -->    </div><!-- end left -->\r
-       <div id="right">\r
-               <div id="header">\r
-                       <a href="http://nexuiz.com" title="Nexuiz - Simple, fast, intense and completely free"><img src="htmlfiles/img/nexuiz_header.jpg" alt="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="766" height="107" border="0" title="Nexuiz - Simple, fast, intense and completely free" /></a>\r
-               </div>\r
-               <div id="menu">\r
-                       <ul>\r
-\r
-                               <li class="first"><a href="../readme.html" title="Nexuiz - Latest News">About </a></li>\r
-                               <li><a href="basics.html" title="Nexuiz - Media (Screenshots, videos and graphics)">Nexuiz Basics</a></li>\r
-                               <li><a href="say-esc.html" title="Nexuiz Information  (General, System Requirements)">Say Escapes</a></li>\r
-                               <li><a href="irc.html" title="Nexuiz - Downloads (Game, Maps)">IRC</a></li>\r
-                               <li><a href="faq.html" title="Official Nexuiz FAQ">FAQ</a></li>\r
-                               <li class="first"></li>\r
-                               <li class="first"></li>\r
-                 </ul>         </div>\r
-               <p id="tagline">Nexuiz is a <b>free</b> open-source first person shooter that runs on <b>Windows</b>, <b>Linux</b> and <b>OSX</b>.</p>\r
-         <div id="content">\r
-<div class="body">\r
-\r
-<br><center><h1><a name="AFT-top">Nexuiz 2.5 FAQ</a></h1></center>\r
-<center><a class="link" href="http://nexuiz.com">http://nexuiz.com</a>&nbsp;&nbsp;<a class="link" href="http://alientrap.org">http://alientrap.org</a></center>\r
-<hr>\r
-<!--  AFT Table of Contents (auto generated) -->\r
-<ul>\r
-<li> <a class="link" href="#Troubleshooting">Troubleshooting</a></li>\r
-<ul>\r
-<li> <a class="link" href="#How do I install Nexuiz?">How do I install Nexuiz?</a></li>\r
-<li> <a class="link" href="#When I start Nexuiz all I see is a black screen or a black screen with some checkered squares">When I start Nexuiz all I see is a black screen or a black screen with some checkered squares</a></li>\r
-<li> <a class="link" href="#Using Linux I only see the map but no players and items">Using Linux I only see the map but no players and items</a></li>\r
-<li> <a class="link" href="#When I start Nexuiz my screen is flickering">When I start Nexuiz my screen is flickering</a></li>\r
-<li> <a class="link" href="#How can I speed up my frame rate?">How can I speed up my frame rate?</a></li>\r
-<li> <a class="link" href="#The sound is broken, it crackles and stutters">The sound is broken, it crackles and stutters</a></li>\r
-<li> <a class="link" href="#Mouse is too slow and sensitivity is at top (on Mac / Linux)">Mouse is too slow and sensitivity is at top (on Mac / Linux)</a></li>\r
-<li> <a class="link" href="#I can't switch to 32 bit color depth (on Windows)">I can't switch to 32 bit color depth (on Windows)</a></li>\r
-<li> <a class="link" href="#When I join a server or after a map change I see nothing but a black screen, but I can still move and shoot">When I join a server or after a map change I see nothing but a black screen, but I can still move and shoot</a></li>\r
-<li> <a class="link" href="#How to report crashes">How to report crashes</a></li>\r
-<li> <a class="link" href="#Where can I get more help?">Where can I get more help?</a></li>\r
-</ul>\r
-<li> <a class="link" href="#General questions">General questions</a></li>\r
-<ul>\r
-<li> <a class="link" href="#How do I install new maps?">How do I install new maps?</a></li>\r
-<li> <a class="link" href="#How can I place a shortcut to Nexuiz on my Linux desktop?">How can I place a shortcut to Nexuiz on my Linux desktop?</a></li>\r
-<li> <a class="link" href="#How do I open the console?">How do I open the console?</a></li>\r
-<li> <a class="link" href="#What console commands/variables are there?">What console commands/variables are there?</a></li>\r
-<li> <a class="link" href="#How can I use colors in my nickname and messages?">How can I use colors in my nickname and messages?</a></li>\r
-<li> <a class="link" href="#How do I watch/record demos?">How do I watch/record demos?</a></li>\r
-</ul>\r
-<li> <a class="link" href="#Server setup">Server setup</a></li>\r
-<ul>\r
-<li> <a class="link" href="#How do I start a server?">How do I start a server?</a></li>\r
-<li> <a class="link" href="#Which ports do I have to open in firewall/forward from my router to run a server?">Which ports do I have to open in firewall/forward from my router to run a server?</a></li>\r
-<li> <a class="link" href="#Is there some kind of rcon?">Is there some kind of rcon?</a></li>\r
-<li> <a class="link" href="#How can I kick people who are using special characters in their names?">How can I kick people who are using special characters in their names?</a></li>\r
-</ul>\r
-<li> <a class="link" href="#Development">Development</a></li>\r
-<ul>\r
-<li> <a class="link" href="#How can I create or edit Nexuiz maps?">How can I create or edit Nexuiz maps?</a></li>\r
-<li> <a class="link" href="#Where can I get latest development versions of the source code?">Where can I get latest development versions of the source code?</a>\r
-\r
-</li>\r
-</ul>\r
-</ul>\r
-<hr>\r
-<!-- Start SectLevel1 -->\r
-<h2><a name="Troubleshooting">Troubleshooting</a></h2>\r
-<!-- Start SectLevel2 -->\r
-<h3><a name="How do I install Nexuiz?">How do I install Nexuiz?</a></h3>\r
-<p class="Body">\r
-Just unzip the <tt>nexuiz-&lt;version&gt;.zip</tt> file.  You can do this with any archive program (like <a class="link" href="http://7zip.org">7zip</a>).  It's very important to keep the directory structure while unpacking (in WinZip this option is called &quot;Use folder names&quot;).  The directory structure should look like this afterwards:\r
-<div class="block"><pre>\r
-Nexuiz/\r
-|-- Docs/\r
-|-- data/\r
-|   |-- common-spog.pk3\r
-|   `-- data*.pk3\r
-|-- havoc/\r
-|   `-- data*.pk3\r
-|-- Nexuiz.app/\r
-|-- Nexuiz-SDL.app/\r
-|-- server/\r
-|   `-- *\r
-|-- sources/\r
-|-- *.exe\r
-|-- *.dll\r
-`-- nexuiz-linux-*\r
-</pre></div>\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="When I start Nexuiz all I see is a black screen or a black screen with some checkered squares">When I start Nexuiz all I see is a black screen or a black screen with some checkered squares</a></h3>\r
-<p class="Body">\r
-This happens when the engine can't load the data*.pk3 file or has trouble to initialize OpenGL.  The reasons could be:\r
-<ol>\r
-<li> you unpacked the zip file without folder names (see <a class="link" href="#How do I install Nexuiz?">How do I install Nexuiz?</a>)</li>\r
-<li> on Linux: the current directory is not your Nexuiz/ folder.  See <a class="link" href="#How can I place a shortcut to Nexuiz on my Linux desktop?">How can I place a shortcut to Nexuiz on my Linux desktop?</a> on how to fix this</li>\r
-<li> on Mac: you tried to extract and move the files from a Nexuiz update and it deleted the old files.  When using the mac GUI please be sure to move only the files not the folders as that will delete the old files or use the mv console command which will not delete the old files</li>\r
-<li> the engine could not initialize OpenGL.  Please install the latest drivers for your graphic card.  You will probably find one for your card there: <a class="link" href="http://intel.com">intel</a> <a class="link" href="http://ati.com">ati</a> <a class="link" href="http://nvidia.com">nvidia</a></li>\r
-<li> your download might be corrupted, please download Nexuiz again</li>\r
-</ol>\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="Using Linux I only see the map but no players and items">Using Linux I only see the map but no players and items</a></h3>\r
-<p class="Body">\r
-This happens when the engine has trouble to initialize OpenGL.  The reasons could be:\r
-<ol>\r
-<li> the engine could not initialize OpenGL.  Please install the latest drivers for your graphic card.  You will probably find one for your card there: <a class="link" href="http://intel.com">intel</a> <a class="link" href="http://ati.com">ati</a> <a class="link" href="http://nvidia.com">nvidia</a></li>\r
-<li> You do not have permissions needed for 3d acceleration.  Usually you need to add yourself to the group <tt>video</tt>, you can do that via console as root like this: <tt>usermod -a -G video YOURUSERNAME</tt>.  You need to logoff/in afterwards.</li>\r
-</ol>\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="When I start Nexuiz my screen is flickering">When I start Nexuiz my screen is flickering</a></h3>\r
-<p class="Body">\r
-This is known to happen on Windows with Intel graphic chips and is a bug in the graphics drivers.  A workaround is to set <tt>Flip-Policiy</tt> to <tt>blit</tt>.  Open the control panel, there should be an icon called <tt>Intel(R) GMA driver</tt> (or something like that), double click it. Click on <tt>3D Settings</tt> to find the screen with those settings.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="How can I speed up my frame rate?">How can I speed up my frame rate?</a></h3>\r
-<p class="Body">\r
-You can choose predefined performance settings in the <tt>Settings / Video</tt> menu.  <tt>Normal</tt> is the default, <tt>Medium</tt> and <tt>Low</tt> run faster while <tt>High</tt>, <tt>Ultra</tt> and <tt>Ultimate</tt> enable more features you might want to test if you have a high end graphic card.  Or you can enable/disable single features.  The greatest performance boost can be achieved by turning off dynamic lights and shadows in the <tt>Settings / Effects</tt> menu.  Bloom is also quite resource intensive.  Other fps boots include disabling <tt>Deluxemapping</tt> and <tt>Coronas</tt>.  On older graphics cards or on-board/notebook chips with little video ram you can try to lower the texture quality in the <tt>Settings / Video</tt> menu.\r
-Some graphic cards (mostly ATI or quite old cards) run A LOT faster if you disable the <tt>Vertex Buffer Objects</tt> in the <tt>Settings / Video</tt> menu.  An other thing that can greatly help on such cards is to disable the <tt>OpenGL 2.0 Shaders</tt>.  Having that option enabled is faster on most cards however that is why both are active by default.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="The sound is broken, it crackles and stutters">The sound is broken, it crackles and stutters</a></h3>\r
-<p class="Body">\r
-Try run <tt>nexuiz-sdl.exe</tt> instead of <tt>nexuiz.exe</tt> (on Windows).  Adding the command line options <tt>-sndspeed 48000</tt> and/or <tt>-sndstereo</tt> can also help on some systems (on Linux, Mac, Windows).\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="Mouse is too slow and sensitivity is at top (on Mac / Linux)">Mouse is too slow and sensitivity is at top (on Mac / Linux)</a></h3>\r
-<p class="Body">\r
-Mac: The default mouse acceleration on Mac is very high and strange.  The Nexuiz defaults work fine with it but some mouse drivers seem to 'correct' the mouse acceleration and conflict with the Nexuiz defaults.  Try to disable the option <tt>Turn off OS mouse acceleration</tt> in the <tt>Settings / Input</tt> menu.  Or the same via console: <tt>apple_mouse_noaccel 0; vid_restart</tt> (<a class="link" href="#How do I open the console?">How do I open the console?</a>)\r
-Linux: A similar problem can arise on Linux.  Its also <tt>Turn off OS mouse acceleration</tt> in the <tt>Settings / Input</tt> menu but the console command is different: <tt>vid_dga 0; vid_restart</tt> (<a class="link" href="#How do I open the console?">How do I open the console?</a>)\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="I can't switch to 32 bit color depth (on Windows)">I can't switch to 32 bit color depth (on Windows)</a></h3>\r
-<p class="Body">\r
-Check if your desktop color depth is set to 32 bits per pixel.  If it's just set to 16, Nexuiz can't switch to 32 bit mode.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="When I join a server or after a map change I see nothing but a black screen, but I can still move and shoot">When I join a server or after a map change I see nothing but a black screen, but I can still move and shoot</a></h3>\r
-<p class="Body">\r
-This is probably because you don't have the map that's running on the server.  As Nexuiz supports map/content download the server might not be be setup (correctly) to support this and you will have to get the map the old fashioned way.  Those servers should have a download URL in their name or welcome message.  There is also a <a class="link" href="http://alientrap.org/wiki/pmwiki.php?n=Main.CustomMaps">wiki page</a> dedicated to new maps.\r
-For Linux users: you need to have libcurl installed, otherwise you won't be able to download any maps.  libcurl should be available in any Linux distribution, just search for &quot;libcurl&quot; and install it in your distribution's package manager.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="How to report crashes">How to report crashes</a></h3>\r
-<p class="Body">\r
-Alientrap wants to ensure everyone has a pleasant time playing Nexuiz, so if you have problems we would like to ask you to report them and thus help solve them.  Especially crash reports can be very helpful.  See below for how to contact Alientrap (<a class="link" href="#Where can i get more help?">Where can i get more help?</a>)\r
-</p>\r
-<p class="Body">\r
-<strong>On Mac</strong>: IS THERE AN EASY WAY? </p>\r
-<p class="Body">\r
-<strong>On Linux</strong>: In a xterm, <tt>cd</tt> into your Nexuiz installation directory, start <tt>catchsegv ./nexuiz-linux-686-sdl -condebug -developer &gt; crash.txt 2&gt;&amp;1</tt> and give the file crash.txt to the developers.  Note that you can also use <tt>./nexuiz-linux-686-glx</tt> or if you have a 64bit system <tt>./nexuiz-linux-x86_64-sdl</tt> and <tt>./nexuiz-linux-x86_64-glx</tt>\r
-</p>\r
-<p class="Body">\r
-<strong>On Windows</strong>: Click Start-&gt;Run, and enter drwtsn32, click Ok in the next window, run Nexuiz and wait for the crash.  Then go to <tt>C:\Documents and Settings\All Users\Application Data\Microsoft\Dr Watson</tt> there should be a file called &quot;drwtsn32.log&quot;, give that file along with the engine's build date to the developers.  You'll see that date when you open the ingame console (<a class="link" href="#How do I open the console?">How do I open the console?</a>).  Note that some folders of that path may be hidden or have a translated name if you're using a non-english windows.\r
-<br />\r
-<strong>This method is not availible in Windows Vista or Windows 7.</strong><br />\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="Where can I get more help?">Where can I get more help?</a></h3>\r
-<p class="Body">\r
-Visit <a class="link" href="http://alientrap.org/forum">the official Nexuiz forum</a> there is a <a class="link" href="http://alientrap.org/forum/viewforum.php?f=3">support and bug report area</a>.  Or get on <a class="link" href="http://en.wikipedia.org/wiki/Internet_Relay_Chat">IRC</a> to chat with other or ask for help.  There is the normal users channel (irc://irc.quakenet.org/nexuiz) and the developers channel (irc://irc.anynet.org/alientrap)\r
-</p>\r
-<!--End Section 2-->\r
-<!-- End SectLevel2 -->\r
-<!--End Section 1-->\r
-<h2><a name="General questions">General questions</a></h2>\r
-<!-- Start SectLevel2 -->\r
-<h3><a name="How do I install new maps?">How do I install new maps?</a></h3>\r
-<p class="Body">\r
-Maps usually ship as *.pk3 file.  All you have to do is to copy this file to the <tt>Nexuiz/data/</tt> (on Linux, Mac, Windows) OR <tt>~/.nexuiz/data/</tt> (on Linux, Mac) directory.\r
-Map packages that were downloaded from a server during playing end up in <tt>Nexuiz/data/dlcache/</tt> or <tt>~/.nexuiz/data/dlcache/</tt> and are only used till you exit Nexuiz. If you want to play them locally or use them to setup a server of your own you can &quot;accept&quot; the packages by moving it one level up - right next to your config.cfg.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="How can I place a shortcut to Nexuiz on my Linux desktop?">How can I place a shortcut to Nexuiz on my Linux desktop?</a></h3>\r
-<p class="Body">\r
-Use the script <tt>nexuiz-linux-sdl.sh</tt> or <tt>nexuiz-linux-glx.sh</tt> instead of the binaries.  The scripts will use the correct <tt>working directory</tt>,choose the right version (32 or 64 bit) and also allow you to start a extra X server.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="How do I open the console?">How do I open the console?</a></h3>\r
-<p class="Body">\r
-Press <tt>[shift]+[escape]</tt>.  To close it press <tt>[escape]</tt>.  While playing ` or ^ will also open the console.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="What console commands/variables are there?">What console commands/variables are there?</a></h3>\r
-<p class="Body">\r
-You can get a list of variables by entering <tt>cvarlist</tt> on the console (<a class="link" href="#How do I open the console?">How do I open the console?</a>).  <tt>cmdlist</tt> will give you a list of available commands.  An annotated version of that output can be found at the <a class="link" href="http://alientrap.org/wiki/pmwiki.php?n=Main.ConsoleCommands">Nexuiz wiki</a>.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="How can I use colors in my nickname and messages?">How can I use colors in my nickname and messages?</a></h3>\r
-<p class="Body">\r
-Colors can be used in nicknames and chat messages via two ways: Either the simple way by typing ^ followed by a number between 0 and 9 or by typing ^x followed by three hexadecimal numbers (0-F) representing red, green and blue components of the color before the text. The second way allows for much more colors. For example if you type ^xF00message the text &quot;message&quot; will be displayed in red color. Simple examples:\r
-</p>\r
-<!-- 3 columns --><center><table cellspacing="0">\r
-<caption>&nbsp;&nbsp;</caption>\r
-<tr><th> code   </th><th> rgb code   </th><th> color&nbsp;&nbsp;&nbsp;&nbsp;  </th></tr>\r
-<tr><td> ^0     </td><td> ^x000      </td><td> black&nbsp;&nbsp;&nbsp;&nbsp;  </td></tr>\r
-<tr><td> ^1     </td><td> ^xF00      </td><td> red&nbsp;&nbsp;&nbsp;&nbsp;    </td></tr>\r
-<tr><td> ^2     </td><td> ^x0F0      </td><td> green&nbsp;&nbsp;&nbsp;&nbsp;  </td></tr>\r
-<tr><td> ^3     </td><td> ^xFF0      </td><td> yellow&nbsp;&nbsp;&nbsp;&nbsp; </td></tr>\r
-<tr><td> ^4     </td><td> ^x00F      </td><td> blue&nbsp;&nbsp;&nbsp;&nbsp;   </td></tr>\r
-<tr><td> ^5     </td><td> ^x0FF      </td><td> cyan&nbsp;&nbsp;&nbsp;&nbsp;   </td></tr>\r
-<tr><td> ^6     </td><td> ^xF0F      </td><td> magenta&nbsp;&nbsp;&nbsp;&nbsp;</td></tr>\r
-<tr><td> ^7     </td><td> ^xFFF      </td><td> white&nbsp;&nbsp;&nbsp;&nbsp;  </td></tr>\r
-<tr><td> ^8     </td><td>&nbsp;&nbsp;    </td><td> half-transparent black </td></tr>\r
-<tr><td> ^9     </td><td> ^x888      </td><td> grey&nbsp;&nbsp;&nbsp;&nbsp;   </td></tr>\r
-<tr><td>&nbsp;&nbsp;</td><td> ^x800      </td><td> dark red&nbsp;&nbsp;</td></tr>\r
-<tr><td>&nbsp;&nbsp;</td><td> ^x080      </td><td> dark green&nbsp;&nbsp;     </td></tr>\r
-<tr><td>&nbsp;&nbsp;</td><td> ^x880      </td><td> dark yellow&nbsp;&nbsp;    </td></tr>\r
-<tr><td>&nbsp;&nbsp;</td><td> ^x008      </td><td> dark blue&nbsp;&nbsp;      </td></tr>\r
-<tr><td>&nbsp;&nbsp;</td><td> ^x088      </td><td> dark cyan&nbsp;&nbsp;      </td></tr>\r
-<tr><td>&nbsp;&nbsp;</td><td> ^x808      </td><td> dark magenta&nbsp;&nbsp;   </td></tr>\r
-</table></center>\r
-<!--End Section 2-->\r
-<h3><a name="How do I watch/record demos?">How do I watch/record demos?</a></h3>\r
-<p class="Body">\r
-Demos are recordings of matches that you have played. To automatically record a demo each time you play enable the option <tt>Record demos while playing</tt> in the <tt>Multiplayer / Demos</tt> menu. Or if you just want to record some matches open the console and type <tt>rec &lt;demos/name&gt;</tt> before playing. That is before starting a game or connecting to a server. The demo file will then be stored in <tt>Nexuiz/data/demos/&lt;name&gt;.dem</tt> (on Windows) or <tt>~/.nexuiz/data/demos/&lt;name&gt;.dem</tt> (on Linux, Mac).\r
-If you downloaded a demo, copy it to <tt>Nexuiz/data/demos/&lt;name&gt;.dem</tt> (on Linux, Mac, Windows) or <tt>~/.nexuiz/data/demos/&lt;name&gt;.dem</tt> (on Linux, Mac).  You might have to create this directory if you have never recorded a demo before.\r
-To watch demos you can choose a demo file in the <tt>Multiplayer / Demos</tt> and click the play button. Also you can watch demos typing <tt>ply &lt;demos/name&gt;</tt> in the console (<a class="link" href="#How do I open the console?">How do I open the console?</a>). You can list all your demo files by typing <tt>dem</tt> on the console.  Some useful key bindings for viewing demos and making videos are listed <a class="link" href="http://www.alientrap.org/forum/viewtopic.php?p=357#357">here</a>.  A simple way to automatically record demos can be found <a class="link" href="http://www.alientrap.org/forum/viewtopic.php?t=90">here</a>.\r
-</p>\r
-<!--End Section 2-->\r
-<!-- End SectLevel2 -->\r
-<!--End Section 1-->\r
-<h2><a name="Server setup">Server setup</a></h2>\r
-<!-- Start SectLevel2 -->\r
-<h3><a name="How do I start a server?">How do I start a server?</a></h3>\r
-<p class="Body">\r
-Use the <tt>Multiplayer / Create</tt> menu to start a <tt>listen server</tt>.  You will always have to play yourself in a <tt>listen server</tt>.\r
-If you want to create a server without being forced to play yourself please take a look at the file <tt>readme.txt</tt> in the <tt>Nexuiz/server/</tt> directory where the <tt>dedicated server</tt> is explained.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="Which ports do I have to open in firewall/forward from my router to run a server?">Which ports do I have to open in firewall/forward from my router to run a server?</a></h3>\r
-<p class="Body">\r
-The default port is 26000 UDP.  You can change that in the <tt>Multiplayer / Create</tt> menu or by starting Nexuiz with the parameter <tt>-port &lt;port&gt;</tt> or having a line <tt>port &lt;port&gt;</tt> in the server config file.\r
-If you follow the tutorial mentioned above you do not need this command line argument as it will be done in the config file created for the server.  To add the command line argument on Windows, create a new shortcut to <tt>nexuiz.exe</tt> or <tt>nexuiz-dedicated.exe</tt> and right click on it.  Select properties and <tt>-port &lt;port&gt;</tt> in the &quot;Target:&quot; line.  Be sure that the &quot;Start in:&quot; line contains the full path to your Nexuiz folder and click &quot;OK&quot;.  The parameter will be used if you start Nexuiz via that new shortcut.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="Is there some kind of rcon?">Is there some kind of rcon?</a></h3>\r
-<p class="Body">\r
-Yes starting with Nexuiz 2.0 there is rcon (QuakeWorld compatible).  To use it you must enter <tt>rcon_password &lt;password&gt;</tt> in the server console or server config file.  The Nexuiz client has to set the same password in the same fashion.  You can then issue commands with <tt>rcon &lt;command&gt;</tt> if you are connected to the server or will have to set <tt>rcon_address &lt;ip/hostname&gt;</tt> or <tt>rcon_address &lt;ip/hostname&gt;:&lt;port&gt;</tt> to point to the server.  There are also external rcon tools but make sure you use a QW compatible rcon tool.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="How can I kick people who are using special characters in their names?">How can I kick people who are using special characters in their names?</a></h3>\r
-<p class="Body">\r
-Enter <tt>status</tt> at the server console.  You will see a list of all players.  In front of their names you will see a number (the player id).  You can kick the player you don't like with <tt>kick # &lt;player id&gt; &lt;reason&gt;</tt> (notice the space after #).\r
-</p>\r
-<!--End Section 2-->\r
-<!-- End SectLevel2 -->\r
-<!--End Section 1-->\r
-<h2><a name="Development">Development</a></h2>\r
-<!-- Start SectLevel2 -->\r
-<h3><a name="How can I create or edit Nexuiz maps?">How can I create or edit Nexuiz maps?</a></h3>\r
-<p class="Body">\r
-You need <a class="link" href="http://icculus.org/netradiant/">NetRadiant</a>, a stabilized Q3 map editor. For convenience, a snapshot configured for Nexuiz is included in <tt>extra/</tt> for Windows and MAC-Intel, but the program is not officially supported by Alientrap.\r
-</p>\r
-<!--End Section 2-->\r
-<h3><a name="Where can I get latest development versions of the source code?">Where can I get latest development versions of the source code?</a></h3>\r
-<p class="Body">\r
-<strong>Engine:</strong> instructions are available at <a class="link" href="http://icculus.org/twilight/darkplaces/download.html">http://icculus.org/twilight/darkplaces/download.html</a> look for <tt>Accessing the SVN server</tt> (you will need a <a class="link" href="http://subversion.tigris.org/project_packages.html">svn client</a>, beta builds are available <a class="link" href="http://icculus.org/twilight/darkplaces/files/">here</a>)\r
-</p>\r
-<p class="Body">\r
-<strong>Game data:</strong> to check out the Nexuiz svn, you'll need a <a class="link" href="http://subversion.tigris.org/project_packages.html">svn client</a>.  To check out the repository with a command line svn client, do\r
-</p>\r
-<div class="block"><pre>\r
-svn co svn://svn.icculus.org/nexuiz/trunk/\r
-</pre></div>\r
-<p class="Body">\r
-To compile the game code you need <a class="link" href="http://sourceforge.net/project/showfiles.php?group_id=116842&amp;package_id=129507">fteqcc</a>.  Just download it and run it 3 times, once in <tt>data/qcsrc/menu</tt>, once in <tt>data/qcsrc/server</tt> and once in <tt>data/qcsrc/client</tt>.  Alternatively a Makefile is available in <tt>data/</tt> to perform directly the operation.\r
-</p>\r
-<!--End Section 2-->\r
-<!-- End SectLevel2 -->\r
-<!--End Section 1-->\r
-<!-- End SectLevel1 -->\r
-<hr>\r
-<br>\r
-<p align="right"> <small>This document was generated using <a class="link" href="http://www.maplefish.com/todd/aft.html">AFT v5.096</a></small> </p>\r
-                 <p><br style="clear:left" />\r
-        </p>\r
-         </div>\r
-         <!-- end content -->\r
-               <div id="footer">\r
-                       <p id="ninja"><a href="http://www.detrition.net" title="Tyler Mulligan's Working Portfolio" target="_blank">page created by</a> <a href="http://www.nexuizninjaz.com" title="Nexuiz Ninjaz - Practicing the ninja arts of Nexuiz" target="_blank">a ninja</a></p>\r
-                       <ul>\r
-\r
-                               <li class="first"><a href="http://alientrap.org/nexuiz/news.php" title="Nexuiz - Latest News">News</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/media.php" title="Nexuiz - Media (Screenshots, videos and graphics)">Media</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/information.php" title="Nexuiz Information  (General, System Requirements)">Info</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/team.php" title="The Nexuiz Team">Team</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/links.php" title="Nexuiz Links">Links</a></li>\r
-                               <li><a href="http://alientrap.org/forum/index.php?c=3" title="Official Nexuiz Forums" target="_blank">Forums</a></li>\r
-                               <li><a href="http://planetnexuiz.com/ladder/" title="Nexuiz Ladder" target="_blank">Ladder</a></li>\r
-                               <li><a href="http://planetnexuiz.com/tourney/" title="Nexuiz Tournaments" target="_blank">Tournament</a></li>\r
-                       </ul>           </div><!-- end footer -->\r
-               <p class="subFooter">Come to the Nexuiz IRC channel: <a href="irc://irc.quakenet.org/nexuiz" title="#nexuiz on irc.quakenet.org">#nexuiz on irc.quakenet.org</a> or the team channel: <a href="irc://irc.anynet.org/alientrap" title="#alientrap on irc.anynet.org">#alientrap on irc.anynet.org</a></p>\r
-\r
-               <div id="valid">\r
-                       <a id="valid_css" href="http://jigsaw.w3.org/css-validator/validator?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>\r
-                       <a id="valid_xhtml" href="http://validator.w3.org/check?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>\r
-                       <p>&copy; 2004-2008 Nexuiz.com</p>\r
-               </div>\r
-       </div><!-- end right -->\r
-</div>\r
-</div>\r
-</body>\r
-</html>\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<meta name="author" content="Tyler Mulligan of www.detrition.net a.k.a -z- of www.nexuizninjaz.com" />
+<meta name="Description" content="Nexuiz is a free open-source fast paced first person shooter (FPS) that runs on Windows, Linux and OSX.  Nexuiz utilizes the darkplaces engine modeled after the Quake series." />
+<meta name="Keywords" content="Nexuiz, Nexiuz, first person shooter, darkplaces, open-source game, open source game, free game, linux game, deathmatch, death match, ctf, quake, alientrap, alien trap, ninjaz" />
+<title>Nexuiz - A free open-source fast paced first person shooter (FPS) for Windows, Linux and OSX</title>
+<link rel="stylesheet" href="htmlfiles/style.css" type="text/css"></link>
+<link rel="shortcut icon" href="favicon.ico"></link>
+</head>
+<body>
+<div id="container">
+       <div id="left">
+               <div id="logo">
+
+                       <a href="http://www.nexuiz.com" title="Nexuiz - Simple, fast, intense and completely free"><img src="htmlfiles/img/nexuiz_logo.jpg" alt="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="201" height="193" border="0" title="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." /></a>
+               </div>
+               <!-- Sidebar -->
+         <div id="sidebar">
+                       <a href="news.php" title="Latest Nexuiz News"><img src="htmlfiles/img/headers/latest_news.gif" alt="Latest News" width="168" height="16" border="0" class="imgHeader" style="margin-top:0;" title="Latest Nexuiz News" /></a>
+                       <div id="news">
+                         <div class="newsPost">
+                                               <h1><a href="http://nexuiz.com/news.php" title="Two Nexuiz 1v1 Tourneys">nexuiz.com/news - for nexuiz news updates</a></h1>
+                         </div>
+               </div>
+                       <!-- Downloads -->
+                       <a href="downloads.php" title="Nexuiz Downloads (Game, Maps)"><img src="htmlfiles/img/headers/downloads.gif" alt="Downloads" width="145" height="15" border="0" class="imgHeader" title="Downloads" /></a>
+                       <a id="download_nexuiz" href="http://www.sourceforge.net/projects/nexuiz/" title="Download Nexuiz from Source Forge - Left Click">&nbsp;</a>
+                       <!--<a id="download_q3_mappack" href="http://downloads.sourceforge.net/nexuiz/nexmappack_r2.zip" title="Download Nexuiz Q3 Mappack from Source
+                       Forge - Left Click">&nbsp;</a>-->
+                       <h2 class="page"><a href="http://nexuiz.com/downloads.php" title="Nexuiz Download Mirrors">Mirrors -&gt;</a></h2>
+                       <!-- Help Wanted -->
+
+                       <img src="htmlfiles/img/headers/help_wanted.gif" alt="Help Wanted" width="165" height="15" class="imgHeader" title="Help Wanted" />                     
+                       <form action="https://www.paypal.com/cgi-bin/webscr" method="post" name="donate_form"> <input name="cmd" value="_s-xclick" type="hidden" />
+                       <a id="donate" href="javascript:document.donate_form.submit();" title="Help The Aliens, Please Donate">&nbsp;</a>
+                       <noscript>
+                               <!-- It not an elegant solution but I don't expect many users to arrive with JS turned off -->
+                               <input name="submit" type="image" src="htmlfiles/img/donate_button_noscript.jpg" alt="Help this project - Donate to the aliens" width="166" height="43" /> 
+                       </noscript>
+                       <h2 class="page"><a href="http://nexuiz.com/donators.php" title="Those who have donated to Nexuiz">Donators -&gt;</a></h2>
+                       <input name="encrypted" value="-----BEGIN PKCS7-----MIIHBgYJKoZIhvcNAQcEoIIG9zCCBvMCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYBfp40KRLmnxKPY06C4gjvEiWZchbxK6bgD7ZdjhdWO5Vbwo4T4Ro+HE041PVVqIxPlJgO80l3aQpBtfhC66FfM2kIF1BjLs1zzhQM89XoPGViS3e4kbmzxkMnpdiZFmOsR5Fs5NJYiVaMnVGcoQ+K3+KsyOehZGket7GwUeNFMRzELMAkGBSsOAwIaBQAwgYMGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIb1QeJqVIc5SAYOPcI23W84XMGt8mSfHE1Gf0/GZAM7NvqLiHF0BeaecRW1Bc85O0tL9OduZiraGf7WVnAmP5kp1D0irXsA5+N2l15WADxwNQ/GoCAU293l0dAQ7Qy4F3vh6eSii18MaH2KCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA1MDUxMjE5MDQ0OFowIwYJKoZIhvcNAQkEMRYEFC7mlfdaA7Pg2eBhxI5xQTe7ydTtMA0GCSqGSIb3DQEBAQUABIGAfR38tX84huxk9JjvhggcSMxzHbmDxpxInBU6/lbyqAu7iT5KJn7rcJgaH5ZVyKNoNQLGp9IxweBrcMiYUQNVxShm9+hunXhQmj5r7AMGaxNZ0mE8mQRW1ZTaz7TLz1HkDFA+R0Dm8HYyDQA4L505cBiWNEsKC17VwNK1G7CEVvA=-----END PKCS7-----" type="hidden" /></form>
+                       
+                 <!-- Created By -->
+                 <img src="htmlfiles/img/headers/created_by.gif" alt="Created By" width="145" height="15" class="imgHeader" title="Created By" />
+
+         <a id="alien_trap" href="http://www.alientrap.org" title="Developed by Alien Trap" target="_blank">&nbsp;</a>   </div>
+               <!-- end sidebar -->    </div><!-- end left -->
+       <div id="right">
+               <div id="header">
+                       <a href="http://nexuiz.com" title="Nexuiz - Simple, fast, intense and completely free"><img src="htmlfiles/img/nexuiz_header.jpg" alt="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="766" height="107" border="0" title="Nexuiz - Simple, fast, intense and completely free" /></a>
+               </div>
+               <div id="menu">
+                       <ul>
+
+                               <li class="first"><a href="../readme.html" title="Nexuiz - Latest News">About </a></li>
+                               <li><a href="basics.html" title="Nexuiz - Media (Screenshots, videos and graphics)">Nexuiz Basics</a></li>
+                               <li><a href="say-esc.html" title="Nexuiz Information  (General, System Requirements)">Say Escapes</a></li>
+                               <li><a href="irc.html" title="Nexuiz - Downloads (Game, Maps)">IRC</a></li>
+                               <li><a href="faq.html" title="Official Nexuiz FAQ">FAQ</a></li>
+                               <li class="first"></li>
+                               <li class="first"></li>
+                 </ul>         </div>
+               <p id="tagline">Nexuiz is a <b>free</b> open-source first person shooter that runs on <b>Windows</b>, <b>Linux</b> and <b>OSX</b>.</p>
+         <div id="content">
+<div class="body">
+
+<br><center><h1><a name="AFT-top">Nexuiz 2.5 FAQ</a></h1></center>
+<center><a class="link" href="http://nexuiz.com">http://nexuiz.com</a>&nbsp;&nbsp;<a class="link" href="http://alientrap.org">http://alientrap.org</a></center>
+<hr>
+<!--  AFT Table of Contents (auto generated) -->
+<ul>
+<li> <a class="link" href="#Troubleshooting">Troubleshooting</a></li>
+<ul>
+<li> <a class="link" href="#How do I install Nexuiz?">How do I install Nexuiz?</a></li>
+<li> <a class="link" href="#When I start Nexuiz all I see is a black screen or a black screen with some checkered squares">When I start Nexuiz all I see is a black screen or a black screen with some checkered squares</a></li>
+<li> <a class="link" href="#Using Linux I only see the map but no players and items">Using Linux I only see the map but no players and items</a></li>
+<li> <a class="link" href="#When I start Nexuiz my screen is flickering">When I start Nexuiz my screen is flickering</a></li>
+<li> <a class="link" href="#How can I speed up my frame rate?">How can I speed up my frame rate?</a></li>
+<li> <a class="link" href="#The sound is broken, it crackles and stutters">The sound is broken, it crackles and stutters</a></li>
+<li> <a class="link" href="#Mouse is too slow and sensitivity is at top (on Mac / Linux)">Mouse is too slow and sensitivity is at top (on Mac / Linux)</a></li>
+<li> <a class="link" href="#I can't switch to 32 bit color depth (on Windows)">I can't switch to 32 bit color depth (on Windows)</a></li>
+<li> <a class="link" href="#When I join a server or after a map change I see nothing but a black screen, but I can still move and shoot">When I join a server or after a map change I see nothing but a black screen, but I can still move and shoot</a></li>
+<li> <a class="link" href="#How to report crashes">How to report crashes</a></li>
+<li> <a class="link" href="#Where can I get more help?">Where can I get more help?</a></li>
+</ul>
+<li> <a class="link" href="#General questions">General questions</a></li>
+<ul>
+<li> <a class="link" href="#How do I install new maps?">How do I install new maps?</a></li>
+<li> <a class="link" href="#How can I place a shortcut to Nexuiz on my Linux desktop?">How can I place a shortcut to Nexuiz on my Linux desktop?</a></li>
+<li> <a class="link" href="#How do I open the console?">How do I open the console?</a></li>
+<li> <a class="link" href="#What console commands/variables are there?">What console commands/variables are there?</a></li>
+<li> <a class="link" href="#How can I use colors in my nickname and messages?">How can I use colors in my nickname and messages?</a></li>
+<li> <a class="link" href="#How do I watch/record demos?">How do I watch/record demos?</a></li>
+</ul>
+<li> <a class="link" href="#Server setup">Server setup</a></li>
+<ul>
+<li> <a class="link" href="#How do I start a server?">How do I start a server?</a></li>
+<li> <a class="link" href="#Which ports do I have to open in firewall/forward from my router to run a server?">Which ports do I have to open in firewall/forward from my router to run a server?</a></li>
+<li> <a class="link" href="#Is there some kind of rcon?">Is there some kind of rcon?</a></li>
+<li> <a class="link" href="#How can I kick people who are using special characters in their names?">How can I kick people who are using special characters in their names?</a></li>
+</ul>
+<li> <a class="link" href="#Development">Development</a></li>
+<ul>
+<li> <a class="link" href="#How can I create or edit Nexuiz maps?">How can I create or edit Nexuiz maps?</a></li>
+<li> <a class="link" href="#Where can I get latest development versions of the source code?">Where can I get latest development versions of the source code?</a>
+
+</li>
+</ul>
+</ul>
+<hr>
+<!-- Start SectLevel1 -->
+<h2><a name="Troubleshooting">Troubleshooting</a></h2>
+<!-- Start SectLevel2 -->
+<h3><a name="How do I install Nexuiz?">How do I install Nexuiz?</a></h3>
+<p class="Body">
+Just unzip the <tt>nexuiz-&lt;version&gt;.zip</tt> file.  You can do this with any archive program (like <a class="link" href="http://7zip.org">7zip</a>).  It's very important to keep the directory structure while unpacking (in WinZip this option is called &quot;Use folder names&quot;).  The directory structure should look like this afterwards:
+<div class="block"><pre>
+Nexuiz/
+|-- Docs/
+|-- data/
+|   |-- common-spog.pk3
+|   `-- data*.pk3
+|-- havoc/
+|   `-- data*.pk3
+|-- Nexuiz.app/
+|-- Nexuiz-SDL.app/
+|-- server/
+|   `-- *
+|-- sources/
+|-- *.exe
+|-- *.dll
+`-- nexuiz-linux-*
+</pre></div>
+</p>
+<!--End Section 2-->
+<h3><a name="When I start Nexuiz all I see is a black screen or a black screen with some checkered squares">When I start Nexuiz all I see is a black screen or a black screen with some checkered squares</a></h3>
+<p class="Body">
+This happens when the engine can't load the data*.pk3 file or has trouble to initialize OpenGL.  The reasons could be:
+<ol>
+<li> you unpacked the zip file without folder names (see <a class="link" href="#How do I install Nexuiz?">How do I install Nexuiz?</a>)</li>
+<li> on Linux: the current directory is not your Nexuiz/ folder.  See <a class="link" href="#How can I place a shortcut to Nexuiz on my Linux desktop?">How can I place a shortcut to Nexuiz on my Linux desktop?</a> on how to fix this</li>
+<li> on Mac: you tried to extract and move the files from a Nexuiz update and it deleted the old files.  When using the mac GUI please be sure to move only the files not the folders as that will delete the old files or use the mv console command which will not delete the old files</li>
+<li> the engine could not initialize OpenGL.  Please install the latest drivers for your graphic card.  You will probably find one for your card there: <a class="link" href="http://intel.com">intel</a> <a class="link" href="http://ati.com">ati</a> <a class="link" href="http://nvidia.com">nvidia</a></li>
+<li> your download might be corrupted, please download Nexuiz again</li>
+</ol>
+</p>
+<!--End Section 2-->
+<h3><a name="Using Linux I only see the map but no players and items">Using Linux I only see the map but no players and items</a></h3>
+<p class="Body">
+This happens when the engine has trouble to initialize OpenGL.  The reasons could be:
+<ol>
+<li> the engine could not initialize OpenGL.  Please install the latest drivers for your graphic card.  You will probably find one for your card there: <a class="link" href="http://intel.com">intel</a> <a class="link" href="http://ati.com">ati</a> <a class="link" href="http://nvidia.com">nvidia</a></li>
+<li> You do not have permissions needed for 3d acceleration.  Usually you need to add yourself to the group <tt>video</tt>, you can do that via console as root like this: <tt>usermod -a -G video YOURUSERNAME</tt>.  You need to logoff/in afterwards.</li>
+</ol>
+</p>
+<!--End Section 2-->
+<h3><a name="When I start Nexuiz my screen is flickering">When I start Nexuiz my screen is flickering</a></h3>
+<p class="Body">
+This is known to happen on Windows with Intel graphic chips and is a bug in the graphics drivers.  A workaround is to set <tt>Flip-Policiy</tt> to <tt>blit</tt>.  Open the control panel, there should be an icon called <tt>Intel(R) GMA driver</tt> (or something like that), double click it. Click on <tt>3D Settings</tt> to find the screen with those settings.
+</p>
+<!--End Section 2-->
+<h3><a name="How can I speed up my frame rate?">How can I speed up my frame rate?</a></h3>
+<p class="Body">
+You can choose predefined performance settings in the <tt>Settings / Video</tt> menu.  <tt>Normal</tt> is the default, <tt>Medium</tt> and <tt>Low</tt> run faster while <tt>High</tt>, <tt>Ultra</tt> and <tt>Ultimate</tt> enable more features you might want to test if you have a high end graphic card.  Or you can enable/disable single features.  The greatest performance boost can be achieved by turning off dynamic lights and shadows in the <tt>Settings / Effects</tt> menu.  Bloom is also quite resource intensive.  Other fps boots include disabling <tt>Deluxemapping</tt> and <tt>Coronas</tt>.  On older graphics cards or on-board/notebook chips with little video ram you can try to lower the texture quality in the <tt>Settings / Video</tt> menu.
+Some graphic cards (mostly ATI or quite old cards) run A LOT faster if you disable the <tt>Vertex Buffer Objects</tt> in the <tt>Settings / Video</tt> menu.  An other thing that can greatly help on such cards is to disable the <tt>OpenGL 2.0 Shaders</tt>.  Having that option enabled is faster on most cards however that is why both are active by default.
+</p>
+<!--End Section 2-->
+<h3><a name="The sound is broken, it crackles and stutters">The sound is broken, it crackles and stutters</a></h3>
+<p class="Body">
+Try run <tt>nexuiz-sdl.exe</tt> instead of <tt>nexuiz.exe</tt> (on Windows).  Adding the command line options <tt>-sndspeed 48000</tt> and/or <tt>-sndstereo</tt> can also help on some systems (on Linux, Mac, Windows).
+</p>
+<!--End Section 2-->
+<h3><a name="Mouse is too slow and sensitivity is at top (on Mac / Linux)">Mouse is too slow and sensitivity is at top (on Mac / Linux)</a></h3>
+<p class="Body">
+Mac: The default mouse acceleration on Mac is very high and strange.  The Nexuiz defaults work fine with it but some mouse drivers seem to 'correct' the mouse acceleration and conflict with the Nexuiz defaults.  Try to disable the option <tt>Turn off OS mouse acceleration</tt> in the <tt>Settings / Input</tt> menu.  Or the same via console: <tt>apple_mouse_noaccel 0; vid_restart</tt> (<a class="link" href="#How do I open the console?">How do I open the console?</a>)
+Linux: A similar problem can arise on Linux.  Its also <tt>Turn off OS mouse acceleration</tt> in the <tt>Settings / Input</tt> menu but the console command is different: <tt>vid_dga 0; vid_restart</tt> (<a class="link" href="#How do I open the console?">How do I open the console?</a>)
+</p>
+<!--End Section 2-->
+<h3><a name="I can't switch to 32 bit color depth (on Windows)">I can't switch to 32 bit color depth (on Windows)</a></h3>
+<p class="Body">
+Check if your desktop color depth is set to 32 bits per pixel.  If it's just set to 16, Nexuiz can't switch to 32 bit mode.
+</p>
+<!--End Section 2-->
+<h3><a name="When I join a server or after a map change I see nothing but a black screen, but I can still move and shoot">When I join a server or after a map change I see nothing but a black screen, but I can still move and shoot</a></h3>
+<p class="Body">
+This is probably because you don't have the map that's running on the server.  As Nexuiz supports map/content download the server might not be be setup (correctly) to support this and you will have to get the map the old fashioned way.  Those servers should have a download URL in their name or welcome message.  There is also a <a class="link" href="http://alientrap.org/wiki/pmwiki.php?n=Main.CustomMaps">wiki page</a> dedicated to new maps.
+For Linux users: you need to have libcurl installed, otherwise you won't be able to download any maps.  libcurl should be available in any Linux distribution, just search for &quot;libcurl&quot; and install it in your distribution's package manager.
+</p>
+<!--End Section 2-->
+<h3><a name="How to report crashes">How to report crashes</a></h3>
+<p class="Body">
+Alientrap wants to ensure everyone has a pleasant time playing Nexuiz, so if you have problems we would like to ask you to report them and thus help solve them.  Especially crash reports can be very helpful.  See below for how to contact Alientrap (<a class="link" href="#Where can i get more help?">Where can i get more help?</a>)
+</p>
+<p class="Body">
+<strong>On Mac</strong>: IS THERE AN EASY WAY? </p>
+<p class="Body">
+<strong>On Linux</strong>: In a xterm, <tt>cd</tt> into your Nexuiz installation directory, start <tt>catchsegv ./nexuiz-linux-686-sdl -condebug -developer &gt; crash.txt 2&gt;&amp;1</tt> and give the file crash.txt to the developers.  Note that you can also use <tt>./nexuiz-linux-686-glx</tt> or if you have a 64bit system <tt>./nexuiz-linux-x86_64-sdl</tt> and <tt>./nexuiz-linux-x86_64-glx</tt>
+</p>
+<p class="Body">
+<strong>On Windows</strong>: Click Start-&gt;Run, and enter drwtsn32, click Ok in the next window, run Nexuiz and wait for the crash.  Then go to <tt>C:\Documents and Settings\All Users\Application Data\Microsoft\Dr Watson</tt> there should be a file called &quot;drwtsn32.log&quot;, give that file along with the engine's build date to the developers.  You'll see that date when you open the ingame console (<a class="link" href="#How do I open the console?">How do I open the console?</a>).  Note that some folders of that path may be hidden or have a translated name if you're using a non-english windows.
+<br />
+<strong>This method is not availible in Windows Vista or Windows 7.</strong><br />
+</p>
+<!--End Section 2-->
+<h3><a name="Where can I get more help?">Where can I get more help?</a></h3>
+<p class="Body">
+Visit <a class="link" href="http://alientrap.org/forum">the official Nexuiz forum</a> there is a <a class="link" href="http://alientrap.org/forum/viewforum.php?f=3">support and bug report area</a>.  Or get on <a class="link" href="http://en.wikipedia.org/wiki/Internet_Relay_Chat">IRC</a> to chat with other or ask for help.  There is the normal users channel (irc://irc.quakenet.org/nexuiz) and the developers channel (irc://irc.anynet.org/alientrap)
+</p>
+<!--End Section 2-->
+<!-- End SectLevel2 -->
+<!--End Section 1-->
+<h2><a name="General questions">General questions</a></h2>
+<!-- Start SectLevel2 -->
+<h3><a name="How do I install new maps?">How do I install new maps?</a></h3>
+<p class="Body">
+Maps usually ship as *.pk3 file.  All you have to do is to copy this file to the <tt>Nexuiz/data/</tt> (on Linux, Mac, Windows) OR <tt>~/.nexuiz/data/</tt> (on Linux, Mac) directory.
+Map packages that were downloaded from a server during playing end up in <tt>Nexuiz/data/dlcache/</tt> or <tt>~/.nexuiz/data/dlcache/</tt> and are only used till you exit Nexuiz. If you want to play them locally or use them to setup a server of your own you can &quot;accept&quot; the packages by moving it one level up - right next to your config.cfg.
+</p>
+<!--End Section 2-->
+<h3><a name="How can I place a shortcut to Nexuiz on my Linux desktop?">How can I place a shortcut to Nexuiz on my Linux desktop?</a></h3>
+<p class="Body">
+Use the script <tt>nexuiz-linux-sdl.sh</tt> or <tt>nexuiz-linux-glx.sh</tt> instead of the binaries.  The scripts will use the correct <tt>working directory</tt>,choose the right version (32 or 64 bit) and also allow you to start a extra X server.
+</p>
+<!--End Section 2-->
+<h3><a name="How do I open the console?">How do I open the console?</a></h3>
+<p class="Body">
+Press <tt>[shift]+[escape]</tt>.  To close it press <tt>[escape]</tt>.  While playing ` or ^ will also open the console.
+</p>
+<!--End Section 2-->
+<h3><a name="What console commands/variables are there?">What console commands/variables are there?</a></h3>
+<p class="Body">
+You can get a list of variables by entering <tt>cvarlist</tt> on the console (<a class="link" href="#How do I open the console?">How do I open the console?</a>).  <tt>cmdlist</tt> will give you a list of available commands.  An annotated version of that output can be found at the <a class="link" href="http://alientrap.org/wiki/pmwiki.php?n=Main.ConsoleCommands">Nexuiz wiki</a>.
+</p>
+<!--End Section 2-->
+<h3><a name="How can I use colors in my nickname and messages?">How can I use colors in my nickname and messages?</a></h3>
+<p class="Body">
+Colors can be used in nicknames and chat messages via two ways: Either the simple way by typing ^ followed by a number between 0 and 9 or by typing ^x followed by three hexadecimal numbers (0-F) representing red, green and blue components of the color before the text. The second way allows for much more colors. For example if you type ^xF00message the text &quot;message&quot; will be displayed in red color. Simple examples:
+</p>
+<!-- 3 columns --><center><table cellspacing="0">
+<caption>&nbsp;&nbsp;</caption>
+<tr><th> code   </th><th> rgb code   </th><th> color&nbsp;&nbsp;&nbsp;&nbsp;  </th></tr>
+<tr><td> ^0     </td><td> ^x000      </td><td> black&nbsp;&nbsp;&nbsp;&nbsp;  </td></tr>
+<tr><td> ^1     </td><td> ^xF00      </td><td> red&nbsp;&nbsp;&nbsp;&nbsp;    </td></tr>
+<tr><td> ^2     </td><td> ^x0F0      </td><td> green&nbsp;&nbsp;&nbsp;&nbsp;  </td></tr>
+<tr><td> ^3     </td><td> ^xFF0      </td><td> yellow&nbsp;&nbsp;&nbsp;&nbsp; </td></tr>
+<tr><td> ^4     </td><td> ^x00F      </td><td> blue&nbsp;&nbsp;&nbsp;&nbsp;   </td></tr>
+<tr><td> ^5     </td><td> ^x0FF      </td><td> cyan&nbsp;&nbsp;&nbsp;&nbsp;   </td></tr>
+<tr><td> ^6     </td><td> ^xF0F      </td><td> magenta&nbsp;&nbsp;&nbsp;&nbsp;</td></tr>
+<tr><td> ^7     </td><td> ^xFFF      </td><td> white&nbsp;&nbsp;&nbsp;&nbsp;  </td></tr>
+<tr><td> ^8     </td><td>&nbsp;&nbsp;    </td><td> half-transparent black </td></tr>
+<tr><td> ^9     </td><td> ^x888      </td><td> grey&nbsp;&nbsp;&nbsp;&nbsp;   </td></tr>
+<tr><td>&nbsp;&nbsp;</td><td> ^x800      </td><td> dark red&nbsp;&nbsp;</td></tr>
+<tr><td>&nbsp;&nbsp;</td><td> ^x080      </td><td> dark green&nbsp;&nbsp;     </td></tr>
+<tr><td>&nbsp;&nbsp;</td><td> ^x880      </td><td> dark yellow&nbsp;&nbsp;    </td></tr>
+<tr><td>&nbsp;&nbsp;</td><td> ^x008      </td><td> dark blue&nbsp;&nbsp;      </td></tr>
+<tr><td>&nbsp;&nbsp;</td><td> ^x088      </td><td> dark cyan&nbsp;&nbsp;      </td></tr>
+<tr><td>&nbsp;&nbsp;</td><td> ^x808      </td><td> dark magenta&nbsp;&nbsp;   </td></tr>
+</table></center>
+<!--End Section 2-->
+<h3><a name="How do I watch/record demos?">How do I watch/record demos?</a></h3>
+<p class="Body">
+Demos are recordings of matches that you have played. To automatically record a demo each time you play enable the option <tt>Record demos while playing</tt> in the <tt>Multiplayer / Demos</tt> menu. Or if you just want to record some matches open the console and type <tt>rec &lt;demos/name&gt;</tt> before playing. That is before starting a game or connecting to a server. The demo file will then be stored in <tt>Nexuiz/data/demos/&lt;name&gt;.dem</tt> (on Windows) or <tt>~/.nexuiz/data/demos/&lt;name&gt;.dem</tt> (on Linux, Mac).
+If you downloaded a demo, copy it to <tt>Nexuiz/data/demos/&lt;name&gt;.dem</tt> (on Linux, Mac, Windows) or <tt>~/.nexuiz/data/demos/&lt;name&gt;.dem</tt> (on Linux, Mac).  You might have to create this directory if you have never recorded a demo before.
+To watch demos you can choose a demo file in the <tt>Multiplayer / Demos</tt> and click the play button. Also you can watch demos typing <tt>ply &lt;demos/name&gt;</tt> in the console (<a class="link" href="#How do I open the console?">How do I open the console?</a>). You can list all your demo files by typing <tt>dem</tt> on the console.  Some useful key bindings for viewing demos and making videos are listed <a class="link" href="http://www.alientrap.org/forum/viewtopic.php?p=357#357">here</a>.  A simple way to automatically record demos can be found <a class="link" href="http://www.alientrap.org/forum/viewtopic.php?t=90">here</a>.
+</p>
+<!--End Section 2-->
+<!-- End SectLevel2 -->
+<!--End Section 1-->
+<h2><a name="Server setup">Server setup</a></h2>
+<!-- Start SectLevel2 -->
+<h3><a name="How do I start a server?">How do I start a server?</a></h3>
+<p class="Body">
+Use the <tt>Multiplayer / Create</tt> menu to start a <tt>listen server</tt>.  You will always have to play yourself in a <tt>listen server</tt>.
+If you want to create a server without being forced to play yourself please take a look at the file <tt>readme.txt</tt> in the <tt>Nexuiz/server/</tt> directory where the <tt>dedicated server</tt> is explained.
+</p>
+<!--End Section 2-->
+<h3><a name="Which ports do I have to open in firewall/forward from my router to run a server?">Which ports do I have to open in firewall/forward from my router to run a server?</a></h3>
+<p class="Body">
+The default port is 26000 UDP.  You can change that in the <tt>Multiplayer / Create</tt> menu or by starting Nexuiz with the parameter <tt>-port &lt;port&gt;</tt> or having a line <tt>port &lt;port&gt;</tt> in the server config file.
+If you follow the tutorial mentioned above you do not need this command line argument as it will be done in the config file created for the server.  To add the command line argument on Windows, create a new shortcut to <tt>nexuiz.exe</tt> or <tt>nexuiz-dedicated.exe</tt> and right click on it.  Select properties and <tt>-port &lt;port&gt;</tt> in the &quot;Target:&quot; line.  Be sure that the &quot;Start in:&quot; line contains the full path to your Nexuiz folder and click &quot;OK&quot;.  The parameter will be used if you start Nexuiz via that new shortcut.
+</p>
+<!--End Section 2-->
+<h3><a name="Is there some kind of rcon?">Is there some kind of rcon?</a></h3>
+<p class="Body">
+Yes starting with Nexuiz 2.0 there is rcon (QuakeWorld compatible).  To use it you must enter <tt>rcon_password &lt;password&gt;</tt> in the server console or server config file.  The Nexuiz client has to set the same password in the same fashion.  You can then issue commands with <tt>rcon &lt;command&gt;</tt> if you are connected to the server or will have to set <tt>rcon_address &lt;ip/hostname&gt;</tt> or <tt>rcon_address &lt;ip/hostname&gt;:&lt;port&gt;</tt> to point to the server.  There are also external rcon tools but make sure you use a QW compatible rcon tool.
+</p>
+<!--End Section 2-->
+<h3><a name="How can I kick people who are using special characters in their names?">How can I kick people who are using special characters in their names?</a></h3>
+<p class="Body">
+Enter <tt>status</tt> at the server console.  You will see a list of all players.  In front of their names you will see a number (the player id).  You can kick the player you don't like with <tt>kick # &lt;player id&gt; &lt;reason&gt;</tt> (notice the space after #).
+</p>
+<!--End Section 2-->
+<!-- End SectLevel2 -->
+<!--End Section 1-->
+<h2><a name="Development">Development</a></h2>
+<!-- Start SectLevel2 -->
+<h3><a name="How can I create or edit Nexuiz maps?">How can I create or edit Nexuiz maps?</a></h3>
+<p class="Body">
+You need <a class="link" href="http://icculus.org/netradiant/">NetRadiant</a>, a stabilized Q3 map editor. For convenience, a snapshot configured for Nexuiz is included in <tt>extra/</tt> for Windows and MAC-Intel, but the program is not officially supported by Alientrap.
+</p>
+<!--End Section 2-->
+<h3><a name="Where can I get latest development versions of the source code?">Where can I get latest development versions of the source code?</a></h3>
+<p class="Body">
+<strong>Engine:</strong> instructions are available at <a class="link" href="http://icculus.org/twilight/darkplaces/download.html">http://icculus.org/twilight/darkplaces/download.html</a> look for <tt>Accessing the SVN server</tt> (you will need a <a class="link" href="http://subversion.tigris.org/project_packages.html">svn client</a>, beta builds are available <a class="link" href="http://icculus.org/twilight/darkplaces/files/">here</a>)
+</p>
+<p class="Body">
+<strong>Game data:</strong> to check out the Nexuiz svn, you'll need a <a class="link" href="http://subversion.tigris.org/project_packages.html">svn client</a>.  To check out the repository with a command line svn client, do
+</p>
+<div class="block"><pre>
+svn co svn://svn.icculus.org/nexuiz/trunk/
+</pre></div>
+<p class="Body">
+To compile the game code you need <a class="link" href="http://sourceforge.net/project/showfiles.php?group_id=116842&amp;package_id=129507">fteqcc</a>.  Just download it and run it 3 times, once in <tt>data/qcsrc/menu</tt>, once in <tt>data/qcsrc/server</tt> and once in <tt>data/qcsrc/client</tt>.  Alternatively a Makefile is available in <tt>data/</tt> to perform directly the operation.
+</p>
+<!--End Section 2-->
+<!-- End SectLevel2 -->
+<!--End Section 1-->
+<!-- End SectLevel1 -->
+<hr>
+<br>
+<p align="right"> <small>This document was generated using <a class="link" href="http://www.maplefish.com/todd/aft.html">AFT v5.096</a></small> </p>
+                 <p><br style="clear:left" />
+        </p>
+         </div>
+         <!-- end content -->
+               <div id="footer">
+                       <p id="ninja"><a href="http://www.detrition.net" title="Tyler Mulligan's Working Portfolio" target="_blank">page created by</a> <a href="http://www.nexuizninjaz.com" title="Nexuiz Ninjaz - Practicing the ninja arts of Nexuiz" target="_blank">a ninja</a></p>
+                       <ul>
+
+                               <li class="first"><a href="http://alientrap.org/nexuiz/news.php" title="Nexuiz - Latest News">News</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/media.php" title="Nexuiz - Media (Screenshots, videos and graphics)">Media</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/information.php" title="Nexuiz Information  (General, System Requirements)">Info</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/team.php" title="The Nexuiz Team">Team</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/links.php" title="Nexuiz Links">Links</a></li>
+                               <li><a href="http://alientrap.org/forum/index.php?c=3" title="Official Nexuiz Forums" target="_blank">Forums</a></li>
+                               <li><a href="http://planetnexuiz.com/ladder/" title="Nexuiz Ladder" target="_blank">Ladder</a></li>
+                               <li><a href="http://planetnexuiz.com/tourney/" title="Nexuiz Tournaments" target="_blank">Tournament</a></li>
+                       </ul>           </div><!-- end footer -->
+               <p class="subFooter">Come to the Nexuiz IRC channel: <a href="irc://irc.quakenet.org/nexuiz" title="#nexuiz on irc.quakenet.org">#nexuiz on irc.quakenet.org</a> or the team channel: <a href="irc://irc.anynet.org/alientrap" title="#alientrap on irc.anynet.org">#alientrap on irc.anynet.org</a></p>
+
+               <div id="valid">
+                       <a id="valid_css" href="http://jigsaw.w3.org/css-validator/validator?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>
+                       <a id="valid_xhtml" href="http://validator.w3.org/check?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>
+                       <p>&copy; 2004-2008 Nexuiz.com</p>
+               </div>
+       </div><!-- end right -->
+</div>
+</div>
+</body>
+</html>
index f34d5e855dc3f307245160d77c44f12ead4c3c22..89b63164ad48e92c49a67e94e7e5885d2e1817b1 100644 (file)
-body {\r
-background:#000 url('img/background.jpg') 0 0 repeat-x;\r
-text-align:center;\r
-margin:0;\r
-padding:0;\r
-font-family:verdana,arial,tahoma, sans serif;\r
-font-size:0;\r
-}\r
-a {\r
-color:#bbb;\r
-}\r
-a:hover {\r
-text-decoration:none;\r
-}\r
-#container {\r
-width:978px;\r
-text-align:left;\r
-margin-left:auto;\r
-margin-right:auto;\r
-margin-top:16px;\r
-}\r
-#left { float:left; width:201px; }\r
-#right { float:right; margin:0 4px 5px 0; }\r
-#logo {\r
-width:201px;\r
-height:193px;\r
-}\r
-#header {\r
-width:766px;\r
-height:114px;\r
-}\r
-#menu {\r
-background:url('img/menu_bg.jpg') 0 0 no-repeat;\r
-width:766px;\r
-height:30px;\r
-font-size:15px;\r
-}\r
-#menu span {\r
-color:#89ABE5;\r
-font-weight:bold;\r
-}\r
-#menu ul {\r
-list-style:none;\r
-margin:0;\r
-padding:5px 0 0 8px;\r
-}\r
-#menu li {\r
-background:url('img/menu_div.jpg') 0 3px no-repeat;\r
-display:inline;\r
-padding:0 1px 0 9px;\r
-margin:0;\r
-}\r
-#menu li.first {\r
-background:none;\r
-padding-left:2px;\r
-}\r
-#menu a, #news a, #ninja a {\r
-color:#fff;\r
-text-decoration:none;\r
-font-weight:bold;\r
-}\r
-#menu a:hover, #news a:hover {\r
-color:#999;\r
-}\r
-#tagline {\r
-padding:10px 0 0 0;\r
-font-size:16px;\r
-margin:0;\r
-color:#848690;\r
-text-align:center;\r
-}\r
-#content {\r
-background:url('img/body_bg.jpg') 0 0 no-repeat;\r
-width:746px;\r
-min-height:520px;\r
-height:auto !important;\r
-height:520px;\r
-padding:12px 10px 10px 10px;\r
-margin-top:10px;\r
-text-align:center;\r
-}\r
-#content h1 {\r
-font-size:18px;\r
-margin:3px 0 3px 0;\r
-padding:0;\r
-color:#B0C7ED;\r
-text-align:left;\r
-}\r
-#content h1 a {\r
-color:#eee;\r
-text-decoration:none;\r
-}\r
-#content h1 a:hover {\r
-color:#bbb;\r
-text-decoration:underline;\r
-}\r
-#content h2 {\r
-font-size:15px;\r
-margin:2px 0 2px 0;\r
-padding:0;\r
-color:#aaa;\r
-text-align:left;\r
-}\r
-#content form h2 {\r
-margin-bottom:-10px;\r
-}\r
-#content h2 a {\r
-color:#ccc;\r
-text-decoration:none;\r
-}\r
-#content h2 a:hover {\r
-color:#aaa;\r
-text-decoration:underline;\r
-}\r
-#content h3 {\r
-text-align:left;\r
-font-size:15px;\r
-}\r
-#content p {\r
-text-align:left;\r
-color:#999;\r
-font-size:15px;\r
-}\r
-#content ul {\r
-margin:0 0 15px 0;\r
-padding:0 0 0 6px;\r
-font-size:12px;\r
-color:#888;\r
-text-align:left;\r
-list-style:none;\r
-}\r
-#content ol {\r
-margin:0 0 15px 0;\r
-padding:0 0 0 6px;\r
-font-size:12px;\r
-color:#888;\r
-text-align:left;\r
-}\r
-#content table {\r
-font-size:13px;\r
-color:#777;\r
-border:1px solid #333;\r
-}\r
-#content table p {\r
-padding:0;\r
-margin:0;\r
-font-size:11px;\r
-}\r
-#content th {\r
-background-color:#444;\r
-color:#fff;\r
-}\r
-#content td {\r
-border:1px solid #222;\r
-text-align:left;\r
-margin:0;\r
-padding:2px;\r
-}\r
-#content input, #content textarea {\r
-background-color:#111;\r
-border:1px solid #666;\r
-color:#ccc;\r
-}\r
-#content pre {\r
-text-align:left;\r
-font-size:12px;\r
-color:#999;\r
-}\r
-#content .copyBox {\r
-background-color:#111;\r
-color:#666;\r
-width:600px;\r
-height:50px;\r
-border:1px solid #444;\r
-overflow:hidden;\r
-margin:3px 3px 20px 3px;\r
-}\r
-#content .description {\r
-padding:0;margin:0 0 10px 0;font-size:12px;color:#555;\r
-}\r
-#content .comment {\r
-padding-bottom:16px;\r
-}\r
-#content .comment a {\r
-color:#ccc;\r
-}\r
-#content .comment a:hover {\r
-color:#fff;\r
-}\r
-#videoContainer {\r
-padding-top:10px;\r
-}\r
-#videoContainer p {\r
-font-size:10px;\r
-text-align:center;\r
-color:#666;\r
-margin:10px 0 0 0;\r
-padding:0;\r
-}\r
-#videoContainer a {\r
-color:#3256AE;\r
-text-decoration:none;\r
-}\r
-#videoContainer a:hover {\r
-text-decoration:underline;\r
-}\r
-#content a#see_more {\r
-       background: url('img/see_more.jpg') 0 0 no-repeat;\r
-       display: block;\r
-       height:120px;\r
-       width:150px;\r
-       float:left;\r
-       margin-top:20px;\r
-       text-decoration:none;\r
-}\r
-#content a:hover#see_more {\r
-       background-position: -150px 0;\r
-}\r
-#content .screenshot {\r
-margin:2px;\r
-}\r
-#content a:hover {background: transparent;}\r
-#content a img {\r
-border: 2px solid #333;\r
-}\r
-#content a:hover img,\r
-#content a:active img {\r
-border: 2px solid #ccc;\r
-} \r
-#sidebar {\r
-background:url('img/sidebar_bg.jpg') 0 0 no-repeat;\r
-width:166px;\r
-height:560px;\r
-margin:10px 0 0 8px;\r
-padding:8px;\r
-text-align:center;\r
-}\r
-/* ie fix */\r
-#sidebar a {\r
-text-decoration:none;\r
-}\r
-.imgHeader {\r
-margin-top:14px;\r
-margin-bottom:5px;\r
-}\r
-.imgButton {\r
-margin-left:-2px;\r
-margin-bottom:3px;\r
-}\r
-#news {\r
-min-height:100px;\r
-height:auto !important;\r
-height:100px;\r
-}\r
-.newsPost {\r
-margin:0 0 12px 0;\r
-}\r
-#news h1 {\r
-font-size:12px;\r
-text-align:left;\r
-padding:0;\r
-margin:0;\r
-}\r
-#news h1 a {\r
-color:#babecc;\r
-}\r
-#news h1 a:hover {\r
-color:#808491;\r
-}\r
-#news p {\r
-color:#abaeb9;\r
-font-size:9px;\r
-text-align:right;\r
-padding:0;\r
-margin:5px 3px 0 0;\r
-}\r
-#sidebar h2.page {\r
-text-align:right;\r
-padding:0;\r
-margin:0;\r
-}\r
-#sidebar h2 a {\r
-color:#667;\r
-font-size:11px;\r
-}\r
-#sidebar h2 a:hover {\r
-color:#556;\r
-}\r
-#footer {\r
-background:url('img/footer_bg.jpg') 0 0 no-repeat;\r
-width:766px;\r
-height:30px;\r
-font-size:10px;\r
-margin-bottom:5px;\r
-}\r
-#footer span {\r
-color:#89ABE5;\r
-font-weight:bold;\r
-}\r
-#footer ul a {\r
-color:#888;\r
-text-decoration:none;\r
-font-weight:bold;\r
-}\r
-#footer a:hover {\r
-color:#ccc;\r
-}\r
-#footer ul {\r
-list-style:none;\r
-padding:8px 0 0 10px;\r
-margin:0;\r
-}\r
-#footer li {\r
-display:inline;\r
-border-left:1px solid #777;\r
-padding:0 2px 0 6px;\r
-}\r
-#footer li.first {\r
-border:none;\r
-}\r
-#ninja {\r
-float:right;\r
-padding:7px 10px 0 0;\r
-margin:0;\r
-color:#888;\r
-font-weight:bold;\r
-}\r
-#ninja a:hover {\r
-color:#777;\r
-}\r
-.subFooter {\r
-color:#555;\r
-font-size:11px;\r
-text-align:center;\r
-margin:0 0 12px 0;\r
-padding:0;\r
-clear:left;\r
-}\r
-.subFooter a {\r
-color:#777;\r
-text-decoration:none;\r
-}\r
-.subFooter a:hover {\r
-text-decoration:underline;\r
-}\r
-form {\r
-padding:0;\r
-margin:0;\r
-}\r
-\r
-#sidebar a#download_nexuiz, #sidebar a#download_q3_mappack {\r
-       display: block;\r
-       margin-left:-2px;\r
-       margin-bottom:3px;\r
-       height:43px;\r
-       width:171px;\r
-}\r
-#sidebar a#download_nexuiz {\r
-       background: url('img/download_nexuiz.jpg') 0 0 no-repeat;\r
-}\r
-#sidebar a#download_q3_mappack {\r
-       background: url('img/download_q3_mappack.jpg') 0 0 no-repeat;\r
-}\r
-#sidebar a:hover#download_nexuiz, #sidebar a:hover#download_q3_mappack {\r
-       background-position: -171px 0;\r
-}\r
-\r
-#sidebar a#donate {\r
-       background: url('img/donate_button.jpg') 0 0 no-repeat;\r
-       display: block;\r
-       height:43px;\r
-       width:166px;\r
-       margin-bottom:3px;\r
-}\r
-#sidebar a:hover#donate {\r
-       background-position: -166px 0;\r
-}\r
-\r
-#sidebar a#alien_trap {\r
-       background: url('img/alien_trap_logo.jpg') 0 0 no-repeat;\r
-       display: block;\r
-       height:184px;\r
-       width:152px;\r
-       margin-left:auto;\r
-       margin-right:auto;\r
-}\r
-#sidebar a:hover#alien_trap {\r
-       background-position: -152px 0;\r
-}\r
-\r
-#valid a#valid_css, #valid a#valid_xhtml {\r
-       display: block;\r
-       height:14px;\r
-       width:80px;\r
-       margin-left:auto;\r
-       margin-right:auto;\r
-}\r
-\r
-#valid {\r
-       position:relative;\r
-       margin:5px 0;\r
-       height:14px;\r
-}\r
-#valid a#valid_css {\r
-       position:absolute;\r
-       left:4px;\r
-       background: url('img/css.png') 0 0 no-repeat;\r
-       text-decoration:none;\r
-}\r
-#valid a#valid_xhtml {\r
-       position:absolute;\r
-       left:86px;\r
-       background: url('img/xhtml10.png') 0 0 no-repeat;\r
-       text-decoration:none;\r
-}\r
-#valid a:hover#valid_css, #valid a:hover#valid_xhtml {\r
-       background-position: -80px 0;\r
-}\r
-#valid p {\r
-text-align:right;\r
-color:#444;\r
-font-size:10px;\r
-margin:-2px 10px 0 0;\r
-padding:0;\r
-}\r
-\r
-#faq h1 { margin-top:10px; }\r
-#faq h1 a { color:#cff; }\r
+body {
+background:#000 url('img/background.jpg') 0 0 repeat-x;
+text-align:center;
+margin:0;
+padding:0;
+font-family:verdana,arial,tahoma, sans serif;
+font-size:0;
+}
+a {
+color:#bbb;
+}
+a:hover {
+text-decoration:none;
+}
+#container {
+width:978px;
+text-align:left;
+margin-left:auto;
+margin-right:auto;
+margin-top:16px;
+}
+#left { float:left; width:201px; }
+#right { float:right; margin:0 4px 5px 0; }
+#logo {
+width:201px;
+height:193px;
+}
+#header {
+width:766px;
+height:114px;
+}
+#menu {
+background:url('img/menu_bg.jpg') 0 0 no-repeat;
+width:766px;
+height:30px;
+font-size:15px;
+}
+#menu span {
+color:#89ABE5;
+font-weight:bold;
+}
+#menu ul {
+list-style:none;
+margin:0;
+padding:5px 0 0 8px;
+}
+#menu li {
+background:url('img/menu_div.jpg') 0 3px no-repeat;
+display:inline;
+padding:0 1px 0 9px;
+margin:0;
+}
+#menu li.first {
+background:none;
+padding-left:2px;
+}
+#menu a, #news a, #ninja a {
+color:#fff;
+text-decoration:none;
+font-weight:bold;
+}
+#menu a:hover, #news a:hover {
+color:#999;
+}
+#tagline {
+padding:10px 0 0 0;
+font-size:16px;
+margin:0;
+color:#848690;
+text-align:center;
+}
+#content {
+background:url('img/body_bg.jpg') 0 0 no-repeat;
+width:746px;
+min-height:520px;
+height:auto !important;
+height:520px;
+padding:12px 10px 10px 10px;
+margin-top:10px;
+text-align:center;
+}
+#content h1 {
+font-size:18px;
+margin:3px 0 3px 0;
+padding:0;
+color:#B0C7ED;
+text-align:left;
+}
+#content h1 a {
+color:#eee;
+text-decoration:none;
+}
+#content h1 a:hover {
+color:#bbb;
+text-decoration:underline;
+}
+#content h2 {
+font-size:15px;
+margin:2px 0 2px 0;
+padding:0;
+color:#aaa;
+text-align:left;
+}
+#content form h2 {
+margin-bottom:-10px;
+}
+#content h2 a {
+color:#ccc;
+text-decoration:none;
+}
+#content h2 a:hover {
+color:#aaa;
+text-decoration:underline;
+}
+#content h3 {
+text-align:left;
+font-size:15px;
+}
+#content p {
+text-align:left;
+color:#999;
+font-size:15px;
+}
+#content ul {
+margin:0 0 15px 0;
+padding:0 0 0 6px;
+font-size:12px;
+color:#888;
+text-align:left;
+list-style:none;
+}
+#content ol {
+margin:0 0 15px 0;
+padding:0 0 0 6px;
+font-size:12px;
+color:#888;
+text-align:left;
+}
+#content table {
+font-size:13px;
+color:#777;
+border:1px solid #333;
+}
+#content table p {
+padding:0;
+margin:0;
+font-size:11px;
+}
+#content th {
+background-color:#444;
+color:#fff;
+}
+#content td {
+border:1px solid #222;
+text-align:left;
+margin:0;
+padding:2px;
+}
+#content input, #content textarea {
+background-color:#111;
+border:1px solid #666;
+color:#ccc;
+}
+#content pre {
+text-align:left;
+font-size:12px;
+color:#999;
+}
+#content .copyBox {
+background-color:#111;
+color:#666;
+width:600px;
+height:50px;
+border:1px solid #444;
+overflow:hidden;
+margin:3px 3px 20px 3px;
+}
+#content .description {
+padding:0;margin:0 0 10px 0;font-size:12px;color:#555;
+}
+#content .comment {
+padding-bottom:16px;
+}
+#content .comment a {
+color:#ccc;
+}
+#content .comment a:hover {
+color:#fff;
+}
+#videoContainer {
+padding-top:10px;
+}
+#videoContainer p {
+font-size:10px;
+text-align:center;
+color:#666;
+margin:10px 0 0 0;
+padding:0;
+}
+#videoContainer a {
+color:#3256AE;
+text-decoration:none;
+}
+#videoContainer a:hover {
+text-decoration:underline;
+}
+#content a#see_more {
+       background: url('img/see_more.jpg') 0 0 no-repeat;
+       display: block;
+       height:120px;
+       width:150px;
+       float:left;
+       margin-top:20px;
+       text-decoration:none;
+}
+#content a:hover#see_more {
+       background-position: -150px 0;
+}
+#content .screenshot {
+margin:2px;
+}
+#content a:hover {background: transparent;}
+#content a img {
+border: 2px solid #333;
+}
+#content a:hover img,
+#content a:active img {
+border: 2px solid #ccc;
+} 
+#sidebar {
+background:url('img/sidebar_bg.jpg') 0 0 no-repeat;
+width:166px;
+height:560px;
+margin:10px 0 0 8px;
+padding:8px;
+text-align:center;
+}
+/* ie fix */
+#sidebar a {
+text-decoration:none;
+}
+.imgHeader {
+margin-top:14px;
+margin-bottom:5px;
+}
+.imgButton {
+margin-left:-2px;
+margin-bottom:3px;
+}
+#news {
+min-height:100px;
+height:auto !important;
+height:100px;
+}
+.newsPost {
+margin:0 0 12px 0;
+}
+#news h1 {
+font-size:12px;
+text-align:left;
+padding:0;
+margin:0;
+}
+#news h1 a {
+color:#babecc;
+}
+#news h1 a:hover {
+color:#808491;
+}
+#news p {
+color:#abaeb9;
+font-size:9px;
+text-align:right;
+padding:0;
+margin:5px 3px 0 0;
+}
+#sidebar h2.page {
+text-align:right;
+padding:0;
+margin:0;
+}
+#sidebar h2 a {
+color:#667;
+font-size:11px;
+}
+#sidebar h2 a:hover {
+color:#556;
+}
+#footer {
+background:url('img/footer_bg.jpg') 0 0 no-repeat;
+width:766px;
+height:30px;
+font-size:10px;
+margin-bottom:5px;
+}
+#footer span {
+color:#89ABE5;
+font-weight:bold;
+}
+#footer ul a {
+color:#888;
+text-decoration:none;
+font-weight:bold;
+}
+#footer a:hover {
+color:#ccc;
+}
+#footer ul {
+list-style:none;
+padding:8px 0 0 10px;
+margin:0;
+}
+#footer li {
+display:inline;
+border-left:1px solid #777;
+padding:0 2px 0 6px;
+}
+#footer li.first {
+border:none;
+}
+#ninja {
+float:right;
+padding:7px 10px 0 0;
+margin:0;
+color:#888;
+font-weight:bold;
+}
+#ninja a:hover {
+color:#777;
+}
+.subFooter {
+color:#555;
+font-size:11px;
+text-align:center;
+margin:0 0 12px 0;
+padding:0;
+clear:left;
+}
+.subFooter a {
+color:#777;
+text-decoration:none;
+}
+.subFooter a:hover {
+text-decoration:underline;
+}
+form {
+padding:0;
+margin:0;
+}
+
+#sidebar a#download_nexuiz, #sidebar a#download_q3_mappack {
+       display: block;
+       margin-left:-2px;
+       margin-bottom:3px;
+       height:43px;
+       width:171px;
+}
+#sidebar a#download_nexuiz {
+       background: url('img/download_nexuiz.jpg') 0 0 no-repeat;
+}
+#sidebar a#download_q3_mappack {
+       background: url('img/download_q3_mappack.jpg') 0 0 no-repeat;
+}
+#sidebar a:hover#download_nexuiz, #sidebar a:hover#download_q3_mappack {
+       background-position: -171px 0;
+}
+
+#sidebar a#donate {
+       background: url('img/donate_button.jpg') 0 0 no-repeat;
+       display: block;
+       height:43px;
+       width:166px;
+       margin-bottom:3px;
+}
+#sidebar a:hover#donate {
+       background-position: -166px 0;
+}
+
+#sidebar a#alien_trap {
+       background: url('img/alien_trap_logo.jpg') 0 0 no-repeat;
+       display: block;
+       height:184px;
+       width:152px;
+       margin-left:auto;
+       margin-right:auto;
+}
+#sidebar a:hover#alien_trap {
+       background-position: -152px 0;
+}
+
+#valid a#valid_css, #valid a#valid_xhtml {
+       display: block;
+       height:14px;
+       width:80px;
+       margin-left:auto;
+       margin-right:auto;
+}
+
+#valid {
+       position:relative;
+       margin:5px 0;
+       height:14px;
+}
+#valid a#valid_css {
+       position:absolute;
+       left:4px;
+       background: url('img/css.png') 0 0 no-repeat;
+       text-decoration:none;
+}
+#valid a#valid_xhtml {
+       position:absolute;
+       left:86px;
+       background: url('img/xhtml10.png') 0 0 no-repeat;
+       text-decoration:none;
+}
+#valid a:hover#valid_css, #valid a:hover#valid_xhtml {
+       background-position: -80px 0;
+}
+#valid p {
+text-align:right;
+color:#444;
+font-size:10px;
+margin:-2px 10px 0 0;
+padding:0;
+}
+
+#faq h1 { margin-top:10px; }
+#faq h1 a { color:#cff; }
index 09d7534232ee22393d5c7d6e63e6649518017d72..cf7e44a3eb05528d4baa6b79d3c019e23e743e17 100644 (file)
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\r
-        "http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd">\r
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\r
-<head>\r
-<meta http-equiv="content-type" content="text/html; charset=utf-8" />\r
-<meta name="author" content="Tyler Mulligan of www.detrition.net a.k.a -z- of www.nexuizninjaz.com" />\r
-<meta name="Description" content="Nexuiz is a free open-source fast paced first person shooter (FPS) that runs on Windows, Linux and OSX.  Nexuiz utilizes the darkplaces engine modeled after the Quake series." />\r
-<meta name="Keywords" content="Nexuiz, Nexiuz, first person shooter, darkplaces, open-source game, open source game, free game, linux game, deathmatch, death match, ctf, quake, alientrap, alien trap, ninjaz" />\r
-<title>Nexuiz - A free open-source fast paced first person shooter (FPS) for Windows, Linux and OSX</title>\r
-<link rel="stylesheet" href="htmlfiles/style.css" type="text/css"></link>\r
-<link rel="shortcut icon" href="favicon.ico"></link>\r
-</head>\r
-<body>\r
-<div id="container">\r
-       <div id="left">\r
-               <div id="logo">\r
-\r
-                       <a href="http://www.nexuiz.com" title="Nexuiz - Simple, fast, intense and completely free"><img src="htmlfiles/img/nexuiz_logo.jpg" alt="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="201" height="193" border="0" title="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." /></a>\r
-               </div>\r
-               <!-- Sidebar -->\r
-         <div id="sidebar">\r
-                       <a href="news.php" title="Latest Nexuiz News"><img src="htmlfiles/img/headers/latest_news.gif" alt="Latest News" width="168" height="16" border="0" class="imgHeader" style="margin-top:0;" title="Latest Nexuiz News" /></a>\r
-                       <div id="news">\r
-                         <div class="newsPost">\r
-                                               <h1><a href="http://nexuiz.com/news.php" title="Two Nexuiz 1v1 Tourneys">nexuiz.com/news - for nexuiz news updates</a></h1>\r
-                         </div>\r
-               </div>\r
-                       <!-- Downloads -->\r
-                       <a href="downloads.php" title="Nexuiz Downloads (Game, Maps)"><img src="htmlfiles/img/headers/downloads.gif" alt="Downloads" width="145" height="15" border="0" class="imgHeader" title="Downloads" /></a>\r
-                       <a id="download_nexuiz" href="http://www.sourceforge.net/projects/nexuiz/" title="Download Nexuiz from Source Forge - Left Click">&nbsp;</a>\r
-                       <!--<a id="download_q3_mappack" href="http://downloads.sourceforge.net/nexuiz/nexmappack_r2.zip" title="Download Nexuiz Q3 Mappack from Source\r
-                       Forge - Left Click">&nbsp;</a>-->\r
-                       <h2 class="page"><a href="http://nexuiz.com/downloads.php" title="Nexuiz Download Mirrors">Mirrors -&gt;</a></h2>\r
-                       <!-- Help Wanted -->\r
-\r
-                       <img src="htmlfiles/img/headers/help_wanted.gif" alt="Help Wanted" width="165" height="15" class="imgHeader" title="Help Wanted" />                     \r
-                       <form action="https://www.paypal.com/cgi-bin/webscr" method="post" name="donate_form"> <input name="cmd" value="_s-xclick" type="hidden" />\r
-                       <a id="donate" href="javascript:document.donate_form.submit();" title="Help The Aliens, Please Donate">&nbsp;</a>\r
-                       <noscript>\r
-                               <!-- It not an elegant solution but I don't expect many users to arrive with JS turned off -->\r
-                               <input name="submit" type="image" src="htmlfiles/img/donate_button_noscript.jpg" alt="Help this project - Donate to the aliens" width="166" height="43" /> \r
-                       </noscript>\r
-                       <h2 class="page"><a href="http://nexuiz.com/donators.php" title="Those who have donated to Nexuiz">Donators -&gt;</a></h2>\r
-                       <input name="encrypted" value="-----BEGIN PKCS7-----MIIHBgYJKoZIhvcNAQcEoIIG9zCCBvMCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYBfp40KRLmnxKPY06C4gjvEiWZchbxK6bgD7ZdjhdWO5Vbwo4T4Ro+HE041PVVqIxPlJgO80l3aQpBtfhC66FfM2kIF1BjLs1zzhQM89XoPGViS3e4kbmzxkMnpdiZFmOsR5Fs5NJYiVaMnVGcoQ+K3+KsyOehZGket7GwUeNFMRzELMAkGBSsOAwIaBQAwgYMGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIb1QeJqVIc5SAYOPcI23W84XMGt8mSfHE1Gf0/GZAM7NvqLiHF0BeaecRW1Bc85O0tL9OduZiraGf7WVnAmP5kp1D0irXsA5+N2l15WADxwNQ/GoCAU293l0dAQ7Qy4F3vh6eSii18MaH2KCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA1MDUxMjE5MDQ0OFowIwYJKoZIhvcNAQkEMRYEFC7mlfdaA7Pg2eBhxI5xQTe7ydTtMA0GCSqGSIb3DQEBAQUABIGAfR38tX84huxk9JjvhggcSMxzHbmDxpxInBU6/lbyqAu7iT5KJn7rcJgaH5ZVyKNoNQLGp9IxweBrcMiYUQNVxShm9+hunXhQmj5r7AMGaxNZ0mE8mQRW1ZTaz7TLz1HkDFA+R0Dm8HYyDQA4L505cBiWNEsKC17VwNK1G7CEVvA=-----END PKCS7-----" type="hidden" /></form>\r
-                       \r
-                 <!-- Created By -->\r
-                 <img src="htmlfiles/img/headers/created_by.gif" alt="Created By" width="145" height="15" class="imgHeader" title="Created By" />\r
-\r
-         <a id="alien_trap" href="http://www.alientrap.org" title="Developed by Alien Trap" target="_blank">&nbsp;</a>   </div>\r
-               <!-- end sidebar -->    </div><!-- end left -->\r
-       <div id="right">\r
-               <div id="header">\r
-                       <a href="http://nexuiz.com" title="Nexuiz - Simple, fast, intense and completely free"><img src="htmlfiles/img/nexuiz_header.jpg" alt="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="766" height="107" border="0" title="Nexuiz - Simple, fast, intense and completely free" /></a>\r
-               </div>\r
-               <div id="menu">\r
-                       <ul>\r
-\r
-                               <li class="first"><a href="../readme.html" title="Nexuiz - Latest News">About </a></li>\r
-                               <li><a href="basics.html" title="Nexuiz - Media (Screenshots, videos and graphics)">Nexuiz Basics</a></li>\r
-                               <li><a href="say-esc.html" title="Nexuiz Information  (General, System Requirements)">Say Escapes</a></li>\r
-                               <li><a href="irc.html" title="Nexuiz - Downloads (Game, Maps)">IRC</a></li>\r
-                               <li><a href="faq.html" title="Official Nexuiz FAQ">FAQ</a></li>\r
-                               <li class="first"></li>\r
-                               <li class="first"></li>\r
-                               <li class="first"></li>\r
-                               <li class="first"></li>\r
-                 </ul>         </div>\r
-               <p id="tagline">Nexuiz is a <b>free</b> open-source first person shooter that runs on <b>Windows</b>, <b>Linux</b> and <b>OSX</b>.</p>\r
-         <div id="content">\r
-           <p><strong>The official Nexuiz irc channel:</strong><br />\r
-             #nexuiz - <a href="irc://irc.quakenet.org/nexuiz">irc://irc.quakenet.org/nexuiz</a> </p>\r
-                 <p><strong>Main nexuiz related channels:</strong><br />\r
-                   #Nexuiz.Admins - <a href="irc://irc.quakenet.org/nexuiz.admins">irc://irc.quakenet.org/nexuiz.admins </a><br />\r
-#Nexuiz.Clans - <a href="irc://irc.quakenet.org/nexuiz.clans">irc://irc.quakenet.org/nexuiz.clans </a><br />\r
-#Nexuiz.Editing - <a href="irc://irc.quakenet.org/nexuiz.editing">irc://irc.quakenet.org/nexuiz.editing </a><br />\r
-#Nexuiz.Pickup - <a href="irc://irc.quakenet.org/nexuiz.pickup">irc://irc.quakenet.org/nexuiz.pickup </a><br />\r
-#Nexuiz.Tourney - <a href="irc://irc.quakenet.org/nexuiz.tourney">irc://irc.quakenet.org/nexuiz.tourney </a></p>\r
-                 <p>Need some more help? Try this <a href="http://alientrap.org/forum/viewtopic.php?t=497">post</a>.  </p>\r
-                 <p><br style="clear:left" />\r
-        </p>\r
-         </div>\r
-         <!-- end content -->\r
-               <div id="footer">\r
-                       <p id="ninja"><a href="http://www.detrition.net" title="Tyler Mulligan's Working Portfolio" target="_blank">page created by</a> <a href="http://www.nexuizninjaz.com" title="Nexuiz Ninjaz - Practicing the ninja arts of Nexuiz" target="_blank">a ninja</a></p>\r
-                       <ul>\r
-\r
-                               <li class="first"><a href="http://alientrap.org/nexuiz/news.php" title="Nexuiz - Latest News">News'</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/media.php" title="Nexuiz - Media (Screenshots, videos and graphics)">Media</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/information.php" title="Nexuiz Information  (General, System Requirements)">Info</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/team.php" title="The Nexuiz Team">Team</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/links.php" title="Nexuiz Links">Links</a></li>\r
-                               <li><a href="http://alientrap.org/forum/index.php?c=3" title="Official Nexuiz Forums" target="_blank">Forums</a></li>\r
-                               <li><a href="http://planetnexuiz.com/ladder/" title="Nexuiz Ladder" target="_blank">Ladder</a></li>\r
-                               <li><a href="http://planetnexuiz.com/tourney/" title="Nexuiz Tournaments" target="_blank">Tournament</a></li>\r
-                               <li class="first"></li>\r
-                 </ul>         </div><p class="subFooter">Come to the Nexuiz IRC channel: <a href="irc://irc.quakenet.org/nexuiz" title="#nexuiz on irc.quakenet.org">#nexuiz on irc.quakenet.org</a> or the team channel: <a href="irc://irc.anynet.org/alientrap" title="#alientrap on irc.anynet.org">#alientrap on irc.anynet.org</a></p>\r
-\r
-               <div id="valid">\r
-                       <a id="valid_css" href="http://jigsaw.w3.org/css-validator/validator?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>\r
-                       <a id="valid_xhtml" href="http://validator.w3.org/check?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>\r
-                       <p>&copy; 2004-2008 Nexuiz.com</p>\r
-               </div>\r
-       </div><!-- end right -->\r
-</div>\r
-</body>\r
-</html>\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<meta name="author" content="Tyler Mulligan of www.detrition.net a.k.a -z- of www.nexuizninjaz.com" />
+<meta name="Description" content="Nexuiz is a free open-source fast paced first person shooter (FPS) that runs on Windows, Linux and OSX.  Nexuiz utilizes the darkplaces engine modeled after the Quake series." />
+<meta name="Keywords" content="Nexuiz, Nexiuz, first person shooter, darkplaces, open-source game, open source game, free game, linux game, deathmatch, death match, ctf, quake, alientrap, alien trap, ninjaz" />
+<title>Nexuiz - A free open-source fast paced first person shooter (FPS) for Windows, Linux and OSX</title>
+<link rel="stylesheet" href="htmlfiles/style.css" type="text/css"></link>
+<link rel="shortcut icon" href="favicon.ico"></link>
+</head>
+<body>
+<div id="container">
+       <div id="left">
+               <div id="logo">
+
+                       <a href="http://www.nexuiz.com" title="Nexuiz - Simple, fast, intense and completely free"><img src="htmlfiles/img/nexuiz_logo.jpg" alt="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="201" height="193" border="0" title="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." /></a>
+               </div>
+               <!-- Sidebar -->
+         <div id="sidebar">
+                       <a href="news.php" title="Latest Nexuiz News"><img src="htmlfiles/img/headers/latest_news.gif" alt="Latest News" width="168" height="16" border="0" class="imgHeader" style="margin-top:0;" title="Latest Nexuiz News" /></a>
+                       <div id="news">
+                         <div class="newsPost">
+                                               <h1><a href="http://nexuiz.com/news.php" title="Two Nexuiz 1v1 Tourneys">nexuiz.com/news - for nexuiz news updates</a></h1>
+                         </div>
+               </div>
+                       <!-- Downloads -->
+                       <a href="downloads.php" title="Nexuiz Downloads (Game, Maps)"><img src="htmlfiles/img/headers/downloads.gif" alt="Downloads" width="145" height="15" border="0" class="imgHeader" title="Downloads" /></a>
+                       <a id="download_nexuiz" href="http://www.sourceforge.net/projects/nexuiz/" title="Download Nexuiz from Source Forge - Left Click">&nbsp;</a>
+                       <!--<a id="download_q3_mappack" href="http://downloads.sourceforge.net/nexuiz/nexmappack_r2.zip" title="Download Nexuiz Q3 Mappack from Source
+                       Forge - Left Click">&nbsp;</a>-->
+                       <h2 class="page"><a href="http://nexuiz.com/downloads.php" title="Nexuiz Download Mirrors">Mirrors -&gt;</a></h2>
+                       <!-- Help Wanted -->
+
+                       <img src="htmlfiles/img/headers/help_wanted.gif" alt="Help Wanted" width="165" height="15" class="imgHeader" title="Help Wanted" />                     
+                       <form action="https://www.paypal.com/cgi-bin/webscr" method="post" name="donate_form"> <input name="cmd" value="_s-xclick" type="hidden" />
+                       <a id="donate" href="javascript:document.donate_form.submit();" title="Help The Aliens, Please Donate">&nbsp;</a>
+                       <noscript>
+                               <!-- It not an elegant solution but I don't expect many users to arrive with JS turned off -->
+                               <input name="submit" type="image" src="htmlfiles/img/donate_button_noscript.jpg" alt="Help this project - Donate to the aliens" width="166" height="43" /> 
+                       </noscript>
+                       <h2 class="page"><a href="http://nexuiz.com/donators.php" title="Those who have donated to Nexuiz">Donators -&gt;</a></h2>
+                       <input name="encrypted" value="-----BEGIN PKCS7-----MIIHBgYJKoZIhvcNAQcEoIIG9zCCBvMCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYBfp40KRLmnxKPY06C4gjvEiWZchbxK6bgD7ZdjhdWO5Vbwo4T4Ro+HE041PVVqIxPlJgO80l3aQpBtfhC66FfM2kIF1BjLs1zzhQM89XoPGViS3e4kbmzxkMnpdiZFmOsR5Fs5NJYiVaMnVGcoQ+K3+KsyOehZGket7GwUeNFMRzELMAkGBSsOAwIaBQAwgYMGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIb1QeJqVIc5SAYOPcI23W84XMGt8mSfHE1Gf0/GZAM7NvqLiHF0BeaecRW1Bc85O0tL9OduZiraGf7WVnAmP5kp1D0irXsA5+N2l15WADxwNQ/GoCAU293l0dAQ7Qy4F3vh6eSii18MaH2KCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA1MDUxMjE5MDQ0OFowIwYJKoZIhvcNAQkEMRYEFC7mlfdaA7Pg2eBhxI5xQTe7ydTtMA0GCSqGSIb3DQEBAQUABIGAfR38tX84huxk9JjvhggcSMxzHbmDxpxInBU6/lbyqAu7iT5KJn7rcJgaH5ZVyKNoNQLGp9IxweBrcMiYUQNVxShm9+hunXhQmj5r7AMGaxNZ0mE8mQRW1ZTaz7TLz1HkDFA+R0Dm8HYyDQA4L505cBiWNEsKC17VwNK1G7CEVvA=-----END PKCS7-----" type="hidden" /></form>
+                       
+                 <!-- Created By -->
+                 <img src="htmlfiles/img/headers/created_by.gif" alt="Created By" width="145" height="15" class="imgHeader" title="Created By" />
+
+         <a id="alien_trap" href="http://www.alientrap.org" title="Developed by Alien Trap" target="_blank">&nbsp;</a>   </div>
+               <!-- end sidebar -->    </div><!-- end left -->
+       <div id="right">
+               <div id="header">
+                       <a href="http://nexuiz.com" title="Nexuiz - Simple, fast, intense and completely free"><img src="htmlfiles/img/nexuiz_header.jpg" alt="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="766" height="107" border="0" title="Nexuiz - Simple, fast, intense and completely free" /></a>
+               </div>
+               <div id="menu">
+                       <ul>
+
+                               <li class="first"><a href="../readme.html" title="Nexuiz - Latest News">About </a></li>
+                               <li><a href="basics.html" title="Nexuiz - Media (Screenshots, videos and graphics)">Nexuiz Basics</a></li>
+                               <li><a href="say-esc.html" title="Nexuiz Information  (General, System Requirements)">Say Escapes</a></li>
+                               <li><a href="irc.html" title="Nexuiz - Downloads (Game, Maps)">IRC</a></li>
+                               <li><a href="faq.html" title="Official Nexuiz FAQ">FAQ</a></li>
+                               <li class="first"></li>
+                               <li class="first"></li>
+                               <li class="first"></li>
+                               <li class="first"></li>
+                 </ul>         </div>
+               <p id="tagline">Nexuiz is a <b>free</b> open-source first person shooter that runs on <b>Windows</b>, <b>Linux</b> and <b>OSX</b>.</p>
+         <div id="content">
+           <p><strong>The official Nexuiz irc channel:</strong><br />
+             #nexuiz - <a href="irc://irc.quakenet.org/nexuiz">irc://irc.quakenet.org/nexuiz</a> </p>
+                 <p><strong>Main nexuiz related channels:</strong><br />
+                   #Nexuiz.Admins - <a href="irc://irc.quakenet.org/nexuiz.admins">irc://irc.quakenet.org/nexuiz.admins </a><br />
+#Nexuiz.Clans - <a href="irc://irc.quakenet.org/nexuiz.clans">irc://irc.quakenet.org/nexuiz.clans </a><br />
+#Nexuiz.Editing - <a href="irc://irc.quakenet.org/nexuiz.editing">irc://irc.quakenet.org/nexuiz.editing </a><br />
+#Nexuiz.Pickup - <a href="irc://irc.quakenet.org/nexuiz.pickup">irc://irc.quakenet.org/nexuiz.pickup </a><br />
+#Nexuiz.Tourney - <a href="irc://irc.quakenet.org/nexuiz.tourney">irc://irc.quakenet.org/nexuiz.tourney </a></p>
+                 <p>Need some more help? Try this <a href="http://alientrap.org/forum/viewtopic.php?t=497">post</a>.  </p>
+                 <p><br style="clear:left" />
+        </p>
+         </div>
+         <!-- end content -->
+               <div id="footer">
+                       <p id="ninja"><a href="http://www.detrition.net" title="Tyler Mulligan's Working Portfolio" target="_blank">page created by</a> <a href="http://www.nexuizninjaz.com" title="Nexuiz Ninjaz - Practicing the ninja arts of Nexuiz" target="_blank">a ninja</a></p>
+                       <ul>
+
+                               <li class="first"><a href="http://alientrap.org/nexuiz/news.php" title="Nexuiz - Latest News">News'</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/media.php" title="Nexuiz - Media (Screenshots, videos and graphics)">Media</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/information.php" title="Nexuiz Information  (General, System Requirements)">Info</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/team.php" title="The Nexuiz Team">Team</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/links.php" title="Nexuiz Links">Links</a></li>
+                               <li><a href="http://alientrap.org/forum/index.php?c=3" title="Official Nexuiz Forums" target="_blank">Forums</a></li>
+                               <li><a href="http://planetnexuiz.com/ladder/" title="Nexuiz Ladder" target="_blank">Ladder</a></li>
+                               <li><a href="http://planetnexuiz.com/tourney/" title="Nexuiz Tournaments" target="_blank">Tournament</a></li>
+                               <li class="first"></li>
+                 </ul>         </div><p class="subFooter">Come to the Nexuiz IRC channel: <a href="irc://irc.quakenet.org/nexuiz" title="#nexuiz on irc.quakenet.org">#nexuiz on irc.quakenet.org</a> or the team channel: <a href="irc://irc.anynet.org/alientrap" title="#alientrap on irc.anynet.org">#alientrap on irc.anynet.org</a></p>
+
+               <div id="valid">
+                       <a id="valid_css" href="http://jigsaw.w3.org/css-validator/validator?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>
+                       <a id="valid_xhtml" href="http://validator.w3.org/check?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>
+                       <p>&copy; 2004-2008 Nexuiz.com</p>
+               </div>
+       </div><!-- end right -->
+</div>
+</body>
+</html>
index 66386b0fe6a756940884ab4122e72c4408cf7afd..d7692000ee90fce5f86bed668a15a427be7fe2a5 100644 (file)
@@ -1,98 +1,98 @@
-map download:\r
-______________\r
-\r
-\r
-CLIENT SIDE:\r
-\r
-Should already work without configuration. You can however use the following\r
-cvars for further tuning:\r
-\r
-   cl_curl_enabled              download support enabled (master switch, default: 1)\r
-   cl_curl_maxdownloads         maximum number of downloads at once (default: 1)\r
-   cl_curl_maxspeed             maximum total speed in KiB/s (default: 100)\r
-\r
-Downloaded packages end up in |Nexuiz/data/dlcache/| or\r
-|~/.nexuiz/data/dlcache/| and are only used till you exit Nexuiz.\r
- If you want to play them localy or use them to setup a server of your\r
-own you can "accept" the packages by moving it one level up - right\r
-next to your config.cfg.\r
-\r
-You should regularily clean up your cache to save space and make the maps\r
-you really want available from the menu.\r
-\r
-\r
-SERVER SIDE:\r
-\r
-First of all, you need a HTTP or FTP server to host your PK3s. You can either\r
-use some web space provider, or set up your own. For this, use any FTP or HTTP\r
-server software you want (HTTP: lighttpd, Apache, thttpd; FTP: Filezilla,\r
-vsftpd). HTTP is to be preferred because it works better for firewalled\r
-players.\r
-\r
-On the server, you need to set up where to download the PK3s of the maps you\r
-are running. You can either use the cvar\r
-\r
-   sv_curl_defaulturl           default download URL\r
-\r
-to set it to some site, or put a file named "curl_urls.txt" in the data\r
-directory of the following format:\r
-\r
-   pattern        url\r
-   pattern        url\r
-   pattern        url\r
-   ...\r
-\r
-where always the first wildcard pattern match is taken.\r
-\r
-   data*          -\r
-   strale*        http://stralemaps.invalid/\r
-   *              http://all.the.other.stuff.invalid/id/here.php?pak=\r
-   foo*           http://wont.get.here.invalid/\r
-\r
-The pk3 name will be appended to the URL by DarkPlaces. Note that you NEED to\r
-append a trailing slash if you refer to a directory. If you specify a "-" as\r
-URL, the package will not be offered for download.\r
-\r
-\r
-INFORMATION FOR MIRROR/MAP SERVER ADMINS:\r
-\r
-The Referer is always set to dp://serverhost:serverport/, the User-Agent\r
-always starts with "Nexuiz". Look at this sample log line:\r
-\r
-141.2.16.3 - - [06/Jun/2006:19:43:14 +0000] "GET /~polzer/temp/nexmaps.php?filename=o-fun.pk3 HTTP/1.1" 302 - "dp://141.2.16.3:26000/" "Nexuiz Linux 21:26:17 Jun  6 2006"\r
-\r
-\r
-If you want to set up a redirection service, here is a sample PHP code for you\r
-to start from:\r
-\r
-<?\r
-\r
-function findmap($filename)\r
-{\r
-    # insert your database query or whatever you want here\r
-    if($filename == "foo.pk3")\r
-        return "http://barserver.invalid/foo.pk3";\r
-    return FALSE;\r
-}\r
-\r
-function bailout($code, $title, $message)\r
-{\r
-    header("HTTP/1.1 $code $title");\r
-    echo "<html><title>$title</title><h1>$title</h1>$message</html>";\r
-    exit(0);\r
-}   \r
-\r
-$filename = $_GET['filename'];\r
-\r
-$useragent = getenv("HTTP_USER_AGENT");\r
-if(strpos($useragent, "Nexuiz ") !== 0)\r
-    bailout(403, "Forbidden", "You're not a Nexuiz client.");\r
-    \r
-$url = findmap($filename);\r
-if(!$url)\r
-    bailout(404, "Not Found", "Well... try another file name?");\r
-    \r
-header("HTTP/1.1 302 Moved Temporarily");\r
-header("Location: $url");\r
-\r
-?>\r
+map download:
+______________
+
+
+CLIENT SIDE:
+
+Should already work without configuration. You can however use the following
+cvars for further tuning:
+
+   cl_curl_enabled              download support enabled (master switch, default: 1)
+   cl_curl_maxdownloads         maximum number of downloads at once (default: 1)
+   cl_curl_maxspeed             maximum total speed in KiB/s (default: 100)
+
+Downloaded packages end up in |Nexuiz/data/dlcache/| or
+|~/.nexuiz/data/dlcache/| and are only used till you exit Nexuiz.
+ If you want to play them localy or use them to setup a server of your
+own you can "accept" the packages by moving it one level up - right
+next to your config.cfg.
+
+You should regularily clean up your cache to save space and make the maps
+you really want available from the menu.
+
+
+SERVER SIDE:
+
+First of all, you need a HTTP or FTP server to host your PK3s. You can either
+use some web space provider, or set up your own. For this, use any FTP or HTTP
+server software you want (HTTP: lighttpd, Apache, thttpd; FTP: Filezilla,
+vsftpd). HTTP is to be preferred because it works better for firewalled
+players.
+
+On the server, you need to set up where to download the PK3s of the maps you
+are running. You can either use the cvar
+
+   sv_curl_defaulturl           default download URL
+
+to set it to some site, or put a file named "curl_urls.txt" in the data
+directory of the following format:
+
+   pattern        url
+   pattern        url
+   pattern        url
+   ...
+
+where always the first wildcard pattern match is taken.
+
+   data*          -
+   strale*        http://stralemaps.invalid/
+   *              http://all.the.other.stuff.invalid/id/here.php?pak=
+   foo*           http://wont.get.here.invalid/
+
+The pk3 name will be appended to the URL by DarkPlaces. Note that you NEED to
+append a trailing slash if you refer to a directory. If you specify a "-" as
+URL, the package will not be offered for download.
+
+
+INFORMATION FOR MIRROR/MAP SERVER ADMINS:
+
+The Referer is always set to dp://serverhost:serverport/, the User-Agent
+always starts with "Nexuiz". Look at this sample log line:
+
+141.2.16.3 - - [06/Jun/2006:19:43:14 +0000] "GET /~polzer/temp/nexmaps.php?filename=o-fun.pk3 HTTP/1.1" 302 - "dp://141.2.16.3:26000/" "Nexuiz Linux 21:26:17 Jun  6 2006"
+
+
+If you want to set up a redirection service, here is a sample PHP code for you
+to start from:
+
+<?
+
+function findmap($filename)
+{
+    # insert your database query or whatever you want here
+    if($filename == "foo.pk3")
+        return "http://barserver.invalid/foo.pk3";
+    return FALSE;
+}
+
+function bailout($code, $title, $message)
+{
+    header("HTTP/1.1 $code $title");
+    echo "<html><title>$title</title><h1>$title</h1>$message</html>";
+    exit(0);
+}   
+
+$filename = $_GET['filename'];
+
+$useragent = getenv("HTTP_USER_AGENT");
+if(strpos($useragent, "Nexuiz ") !== 0)
+    bailout(403, "Forbidden", "You're not a Nexuiz client.");
+    
+$url = findmap($filename);
+if(!$url)
+    bailout(404, "Not Found", "Well... try another file name?");
+    
+header("HTTP/1.1 302 Moved Temporarily");
+header("Location: $url");
+
+?>
index 027452551db7a18262cd7e46afe7909ea2f377a8..abbf6469a98d1b6215b9c382f4e75d6ff8b56843 100644 (file)
-==============\r
-=Mapping Help=\r
-==============\r
-\r
-While I can't help you make a map, I can help you get it listed in the menu and\r
-working in the maplist. :p  The central object is the .mapinfo file, but\r
-there's much more available.\r
-\r
-Table of Contents\r
------------------\r
-I        Map Lists & Scripts\r
-         (get your map listed and working)\r
-  i.     mapinfo\r
-  ii.    mapname.cfg\r
-\r
-II       Map Image\r
-\r
-III      Domination\r
-\r
-IV       CTF\r
-\r
-V        Rune Match\r
-\r
-VI       Race/CTS\r
-\r
-VII      Nexball\r
-\r
-Appendix A - Advanced mapinfo\r
-\r
-Appendix B - Helpful extras\r
-  i.     Team Colors\r
-  ii.    Text Colors\r
-\r
-Appendix C - Advanced Darkplaces shaders\r
-\r
-========================\r
-=I. Map Lists & Scripts=\r
-========================\r
-\r
-There now is just a single script/cfg file available to you, containing all map\r
-specific settings:\r
-\r
-mapname.mapinfo\r
-\r
-The mapinfo is actually required - however, the game is so nice that it\r
-automatically generates a draft of it for you.\r
-\r
-------------------\r
--The mapinfo file-\r
-------------------\r
-\r
-*cue even more scary music*\r
-\r
-The mapinfo file is basically what gets your map listed in the menu, and sets\r
-up the options needed to change to it. If no mapinfo file exists, the menu\r
-will automatically generate a rough draft for you on game startup. It will get\r
-stored into data/data/mapname.mapinfo.\r
-\r
-As an example, let's say I make a map called "wazat1.bsp". As long as I'm\r
-fantasizing, I might as well say this map is so well done I'm actually willing\r
-to release it for friends, neighbors, fellow forum visitors and other people\r
-who like me to judge me by. Let's also assume that I want domination,\r
-deathmatch/team deathmatch and runematch to all be playable on my map.\r
-\r
-This is very easy. First, I start the game and exit it again so the game writes\r
-data/data/wazat1.mapinfo for me. The file may look like:\r
-\r
-    title Wazat's Great Map\r
-    description Bleh.\r
-    author Unknown\r
-    _diameter 1966.839355\r
-    _spawnpoints 5\r
-    has weapons\r
-       cdtrack 5\r
-    type dm 30 20\r
-    type dom 200 20\r
-    type rune 200 20\r
-    type lms 9 20\r
-    type arena 10 20\r
-\r
-As I see, the menu autodetected that my map may be suitable for deathmatch,\r
-domination, runematch, last man standing and arena. But I want the map to be\r
-played in domination, deathmatch, team deathmatch, runematch only, and I also\r
-want different timelimits/fraglimits, so I will change the "type" lines to:\r
-\r
-    type dom 100 15\r
-    type rune 1000 25\r
-    type dm 40 15\r
-    type tdm 50 0 2\r
-\r
-Also, while I am at it, I should fix the placeholders in the map description lines:\r
-\r
-    title Wibble\r
-    description A large multi level arena map\r
-    author Wazat\r
-\r
-Although not entirely necessary, I will now move the .mapinfo file from\r
-data/data/maps/wazat1.mapinfo to data/maps/wazat1.mapinfo, so it is at the same\r
-place as my map. Next time I start the game, my map will be shown supporting\r
-the right game modes and with the right description in the menu.\r
-\r
-Also, note the "has weapons" line. If this line is not there (which happens if\r
-the map contains no weapon entities other than the Nex), the map will run in\r
-MinstaGib only and not be shown in the menu normally. So if you look for your\r
-map and don't find it, add "has weapons" to the mapinfo file, and make sure you\r
-have weapon entities placed.\r
-\r
-There's much more power in a mapinfo file. See Appendix A for more details.\r
-\r
-Gametype        | Syntax                                                                              | Notes\r
-----------------+-------------------------------------------------------------------------------------+-------------------------------------------------------\r
-Deathmatch      | type dm    <fraglimit>  <timelimit>                                                 |\r
-Team Deathmatch | type tdm   <fraglimit>  <timelimit> <teams>                                         | 2, 3, or 4 teams\r
-Domination      | type dom   <pointlimit> <timelimit>                                                 | teams are set by the map's entities\r
-CTF             | type ctf   <pointlimit> <timelimit> <caplimit>                                      | pointlimit if g_ctf_win_mode is 2, otherwise caplimit\r
-Runematch       | type rune  <pointlimit> <timelimit>                                                 |\r
-LMS             | type lms   <lives>      <timelimit>                                                 |\r
-Arena           | type arena <fraglimit>  <timelimit>                                                 |\r
-Key Hunt        | type kh    <pointlimit> <timelimit> <teams>                                         | 2, 3, or 4 teams\r
-Assault         | type as                 <timelimit>                                                 | never uses points\r
-Onslaught       | type ons                <timelimit>                                                 | never uses points\r
-Race            | type rc                 <timelimit> <qualifyingtimelimit> <laplimit> <teamlaplimit> | g_race_teams: teamlaplimit is used instead of laplimit\r
-CTS             | type cts                <timelimit> <skill>                                         | never uses points\r
-\r
-\r
-===============\r
-=II. Map Image=\r
-===============\r
-\r
-So, you've got your map listed in the menu and it plays properly, but the menu\r
-isn't showing your picture! Or the picture is scaled badly! What manner of man\r
-would create such an accursed abomination?!\r
-\r
-Well... That sounds like something I'd do. :D\r
-\r
-And it is really easy: just make a screenshot of the map in action (preferably\r
-with crosshair and HUD switched off), and place it next to the map as\r
-mapname.jpg. For best rendering and file size, make the image in 4:3 aspect\r
-ratio, but scale it to the resolution 256x256 or possibly 512x512. It will look\r
-skewed in your image editing app, but the menu will show it right, and your\r
-graphics card LOVES images of such dimension.\r
-\r
-\r
-=================\r
-=III. Domination=\r
-=================\r
-\r
-In order to get Domination working well in your map, you need to place dom_team\r
-and dom_controlpoint entites. You *must* have at least 3 dom_team entities - 2\r
-minimum teams and one blank one (empty netname and no team). You can have up to\r
-4 teams (5 dom_team entities), and remember: if you set 3 teams, the third team\r
-must be the yellow one, according to the team order.\r
-\r
-Dom Team\r
---------\r
-classname  dom_team\r
-netname    name of team (Red Team). Set to "" or don't define for the required\r
-           blank team.\r
-cnt        color of the team. See the "Helpful Extras" section for info.\r
-model      When this team captures control points, the points turn to this\r
-           model. If this is the neutral team, points start out as this model.\r
-noise      Sound to be played on the control point when it's captured. Only\r
-           players nearby will hear it.\r
-noise1     Sound to be played to all players when the control point is\r
-           captured. Also good for an annoncer voice ("Red Team has captured a\r
-           control point")\r
-\r
-Control Points\r
---------------\r
-classname  dom_controlpoint\r
-message    message to be displayed to all players when this point is captured,\r
-           preceded by the team's name. This defaults to " has captured a control point".\r
-           You can specify different names for each point, for example " has captured the\r
-           Lava Room".\r
-origin     where in the map this point is\r
-wait       How often this point gives its controlling team frags.\r
-frags      How many frags this point gives each wait cycle.\r
-\r
-Here is an example entry in a .ent file that includes colored text and 3 teams:\r
-\r
-{\r
-"classname" "dom_team"\r
-"netname" ""\r
-"model" "models/domination/dom_unclaimed.md3"\r
-}\r
-{\r
-"classname" "dom_team"\r
-"netname" "^4Blue Team"\r
-"cnt" "13"\r
-"noise" ""\r
-"noise1" "domination/claim.wav"\r
-"model" "models/domination/dom_blue.md3"\r
-}\r
-{\r
-"classname" "dom_team"\r
-"netname" "^1Red Team"\r
-"cnt" "4"\r
-"noise" ""\r
-"noise1" "domination/claim.wav"\r
-"model" "models/domination/dom_red.md3"\r
-}\r
-{\r
-"netname" "^3Yellow Team"\r
-"cnt" "12"\r
-"noise" ""\r
-"noise1" "domination/claim.wav"\r
-"model" "models/domination/dom_yellow.md3"\r
-}\r
-{\r
-"netname" "^6Pink Team"\r
-"cnt" "9"\r
-"noise" ""\r
-"noise1" "domination/claim.wav"\r
-"model" "models/domination/dom_pink.md3"\r
-}\r
-{\r
-"classname" "dom_controlpoint"\r
-"message" " ^3has captured the ^1Hallways"\r
-"origin" "-206.0 -488.8 -150.0"\r
-"frags" "3"\r
-"wait" "5"\r
-"scale" "1.3"\r
-}\r
-{\r
-"classname" "dom_controlpoint"\r
-"message" " ^3has captured the ^1Lavaroom"\r
-"origin" "1457.1  19.9 -110.0"\r
-"frags" "1"\r
-"wait" "5"\r
-}\r
-{\r
-"classname" "dom_controlpoint"\r
-"message" " ^3controls the ^1Nex & Strength"\r
-"origin" "-259.8 299.3  5"\r
-"frags" "1"\r
-"wait" "5"\r
-}\r
-{\r
-"classname" "dom_controlpoint"\r
-"message" " ^3has captured the ^1Upper Platform"\r
-"origin" "539.7 1206.0 182.0"\r
-"frags" "1"\r
-"wait" "5"\r
-}\r
-{\r
-"classname" "dom_controlpoint"\r
-"message" " ^3has captured the ^1Teleport Room"\r
-"origin" "-1000.0 636.2 -16.0"\r
-"frags" "1"\r
-"wait" "5"\r
-}\r
-\r
-\r
-As you can see in the example, there are 5 dom_team ents: one blank, Red, Blue,\r
-Yellow and Pink. Each control point has a different message (giving it a\r
-special name), and the one in the hallways gives 3 frags every 5 seconds\r
-instead of just one, making it more valuable.\r
-\r
-If your map contains the required entities for Domination, the menu will\r
-automatically detect it for supporting Domination. To force the map to get\r
-re-detected after you add such entities, delete the data/data/mapname.mapinfo\r
-file - or simply edit it to add the "type dom" line.\r
-\r
-\r
-=========\r
-=IV. CTF=\r
-=========\r
-\r
-Capture the flag needs at least 1 CTF flag per team, and can also make use of\r
-team spawnpoints.\r
-\r
-CTF Flags\r
----------\r
-classname  item_flag_team1 or item_flag_team2\r
-angle      direction the flag will point\r
-model      model of the flag (default: models/ctf/flag_red.md3 or\r
-           models/ctf/flag_blue.md3)\r
-noise      sound played when flag is stolen (default: "ctf/take.wav")\r
-noise1     sound played when flag is returned by a teammate (default:\r
-           "ctf/return.wav")\r
-noise2     sound played when flag is captured (default: "ctf/capture.wav")\r
-noise3     sound played when flag returns itself (default: "ctf/respawn.wav")\r
-\r
-Team Spawnpoints\r
-----------------\r
-classname  info_player_team1 or info_player_team2\r
-*note: These function just like info_player_deathmatch, but for one team only.\r
-If you don't make team spawnpoints, info_player_deathmatch is used instead.\r
-\r
-If your map contains the required entities for CTF, the menu will automatically\r
-detect it for supporting CTF. To force the map to get re-detected after you add\r
-such entities, delete the data/data/mapname.mapinfo file - or simply edit it to\r
-add the "type ctf" line.\r
-\r
-==============\r
-=V. Runematch=\r
-==============\r
-\r
-Runematch needs only one type of entity to work: rune spawn points. You will\r
-need at least one for each rune (5 minimum at the time of this writing), though\r
-you should probably have more than that in the map. Just give the points a\r
-classname and origin.\r
-\r
-Rune Spawnpoints\r
-----------------\r
-classname  runematch_spawn_point\r
-\r
-If your map contains the required entities for Runematch, the menu will automatically\r
-detect it for supporting Runematch. To force the map to get re-detected after you add\r
-such entities, delete the data/data/mapname.mapinfo file - or simply edit it to\r
-add the "type rune" line.\r
-\r
-==============\r
-=VI. Race/CTS=\r
-==============\r
-\r
-Making a race map is not hard: you need some special spawnpoints, and some checkpoints.\r
-\r
-Spawnpoints\r
------------\r
-classname  info_player_race\r
-target     targetname of the checkpoint\r
-race_place for finish line checkpoints, the place of the point, or -1 to make it qualifying/CTS-only, or unset to let all the other players spawn\r
-\r
-Checkpoints\r
------------\r
-classname  trigger_race_checkpoint\r
-targetname some name to target the checkpoint with\r
-cnt        number of the checkpoint (or 0 for finish line)\r
-\r
-Note that checkpoints are brush entities, and they should be somewhat thick and\r
-cover the full volume the player could use to get past them.\r
-\r
-Example of entity placement:\r
-\r
-                         ###\r
-    ---------------------###---\r
-   /    9999  7  5  3  1>###   \\r
-  /     9999 8  6  4  2 >###    \\r
- |     ------------------###     |\r
-%%%%%%%%                 ###|    |\r
-%%%%%%%%                 $$$|    |\r
- | ^ ^ ------------------$$$     |\r
- \                       $$$ <  /\r
-  \                      $$$ < /\r
-   ----------------------$$$---\r
-                         $$$\r
-\r
-###:  classname = trigger_race_checkpoint, cnt = 0, targetname = finish\r
-$$$:  classname = trigger_race_checkpoint, cnt = 1, targetname = cp1\r
-%%%:  classname = trigger_race_checkpoint, cnt = 2, targetname = cp2\r
->:    classname = info_player_race,                 target = finish, angle = 0\r
-1:    classname = info_player_race,                 target = finish, angle = 0, race_place = 1\r
-2:    classname = info_player_race,                 target = finish, angle = 0, race_place = 2\r
-....\r
-8:    classname = info_player_race,                 target = finish, angle = 0, race_place = 8\r
-9:    classname = info_player_race,                 target = finish, angle = 0, race_place = 9\r
-<:    classname = info_player_race,                 target = cp1,    angle = 180\r
-^:    classname = info_player_race,                 target = cp2,    angle = 90\r
-\r
-If your map contains the required entities for Race, the menu will automatically\r
-detect it for supporting Race. To force the map to get re-detected after you add\r
-such entities, delete the data/data/mapname.mapinfo file - or simply edit it to\r
-add the "type rc" line.\r
-\r
-CTS maps do not use checkpoints with race_place set, so you can leave them out\r
-for CTS maps.\r
-\r
-The skill parameter in the mapinfo entry for CTS shall be in the range from 0 (easy) to 10 (impossible).\r
-\r
-=============\r
-=VI. Nexball=\r
-=============\r
-\r
-There are three required entities: nexball_redgoal, nexball_bluegoal, and one of nexball_basketball\r
-or nexball_football. There are also optional nexball_yellowgoal and nexball_pinkgoal entities (don't\r
-add a pink goal when there is no yellow goal on the map, it will crash)\r
-\r
-Goals are made just like any other regular triggers. You can use multiple brushes for one trigger,\r
-but avoid this if possible.\r
-\r
-There are also two other goal-like entities, nexball_fault and nexball_bound, the first taking a point\r
-from the team that hits the trigger with the ball, the second simply returning it. You can spawn the\r
-ball inside a goal-like trigger, this can be useful for basketball maps with separate teams and a\r
-common ball spawn.\r
-The different keys for the entities are documented in entities.def.\r
-\r
-The ball is affected by trigger_impulse, but not by trigger_push or teleporters.\r
-\r
-You should better avoid patches on the field, as collisions can sometimes get buggy on these.\r
-\r
-\r
-===============================\r
-=Appendix A - Advanced mapinfo=\r
-===============================\r
-\r
-You now know how to make a basic, bare-bones mapinfo to set up a couple options\r
-and load your map. However, there's much more you can do!  Consider these\r
-senarios:\r
-\r
-1. The laser has too high of a force for laser jumps and ruins CTF\r
-2. I don't want players to start out with the shotgun, but with the machinegun\r
-   instead\r
-3. The map takes so much server CPU performance that the anti-wallhack can't be\r
-   made active\r
-\r
-Each of these situations can be resolved with ease with a little work in the\r
-mapinfo file.\r
-\r
-To do this, I can add the following lines to my mapinfo file:\r
-\r
-    settemp_for_type ctf g_balance_laser_primary_force 200\r
-    settemp_for_type all g_start_weapon_shotgun 0\r
-    settemp_for_type all g_start_weapon_uzi 1\r
-    settemp_for_type all sv_cullentities_trace 0\r
-\r
-These "settemp" settings are automatically removed when the map is left and\r
-another is loaded. As you can see, it is possible to make per-mode temporary\r
-settings, and global ones.\r
-\r
-Similar settings are also possible for the client:\r
-\r
-    clientsettemp_for_type all r_shadow_glossexponent 96\r
-\r
-Another possibility is to specify fog settings in the mapinfo, for convenience\r
-in case you set sv_foginterval by it too (to force the fog on the clients):\r
-\r
-    fog 0.2 0.25 0.3 0.3 1 1500\r
-    settemp_for_type all sv_foginterval 5\r
-\r
-\r
-=============================\r
-=Appendix B - Helpful Extras=\r
-=============================\r
-\r
-----------------\r
--i. Team Colors-\r
-----------------\r
-When you need to set an entity's color or team, use these values:\r
-\r
-Red\r
----\r
-Team:      5\r
-Color:     4\r
-\r
-Blue\r
-----\r
-Team:      14\r
-Color:     13\r
-\r
-Yellow\r
-------\r
-Team:      13\r
-Color:     12\r
-\r
-Pink\r
------\r
-Team:      10\r
-Color:     9\r
-\r
-\r
-----------------\r
--i. Text Colors-\r
-----------------\r
-Occasionally you may want to print text in color, such as team names. Here are your options:\r
-\r
-1  Red\r
-2  Green\r
-3  Yellow\r
-4  Blue\r
-5  Cyan\r
-6  Magenta\r
-7  White\r
-8  Grey (transparent)\r
-9  Grey (solid)\r
-0  Black\r
-\r
-==========================================\r
-=Appendix C - Advanced Darkplaces shaders=\r
-==========================================\r
-\r
-Shader parameters for DP's own features:\r
-- dp_reflect <distort> <r> <g> <b> <a>\r
-  Makes surfaces of this shader reflective with r_water. The reflection is\r
-  alpha blended on the texture with the given alpha, and modulated by the given\r
-  color. distort is used in conjunction with the normalmap to simulate a\r
-  nonplanar water surface.\r
-- dp_refract <distort> <r> <g> <b>\r
-  Makes surfaces of this shader refractive with r_water. The refraction\r
-  replaces the transparency of the texture. distort is used in conjunction with\r
-  the normalmap to simulate a nonplanar water surface.\r
-- dp_water <reflectmin> <reflectmax> <refractdistort> <reflectdistort> <refractr> <refractg> <refractb> <reflectr> <reflectg> <reflectb> <alpha>\r
-  This combines the effects of dp_reflect and dp_refract to simulate a water\r
-  surface. However, the refraction and the reflection are mixed using a Fresnel\r
-  equation that makes the amount of reflection slide from reflectmin when\r
-  looking parallel to the water to reflectmax when looking directly into the\r
-  water. The result of this reflection/refraction mix is then layered BELOW the\r
-  texture of the shader, so basically, it "fills up" the alpha values of the\r
-  water. The alpha value is a multiplicator for the alpha value on the texture\r
-  - set this to a small value like 0.1 to emphasize the reflection and make\r
-  the water transparent; but if r_water is 0, alpha isn't used, so the water can\r
-  be very visible then too.\r
-\r
+==============
+=Mapping Help=
+==============
+
+While I can't help you make a map, I can help you get it listed in the menu and
+working in the maplist. :p  The central object is the .mapinfo file, but
+there's much more available.
+
+Table of Contents
+-----------------
+I        Map Lists & Scripts
+         (get your map listed and working)
+  i.     mapinfo
+  ii.    mapname.cfg
+
+II       Map Image
+
+III      Domination
+
+IV       CTF
+
+V        Rune Match
+
+VI       Race/CTS
+
+VII      Nexball
+
+Appendix A - Advanced mapinfo
+
+Appendix B - Helpful extras
+  i.     Team Colors
+  ii.    Text Colors
+
+Appendix C - Advanced Darkplaces shaders
+
+========================
+=I. Map Lists & Scripts=
+========================
+
+There now is just a single script/cfg file available to you, containing all map
+specific settings:
+
+mapname.mapinfo
+
+The mapinfo is actually required - however, the game is so nice that it
+automatically generates a draft of it for you.
+
+------------------
+-The mapinfo file-
+------------------
+
+*cue even more scary music*
+
+The mapinfo file is basically what gets your map listed in the menu, and sets
+up the options needed to change to it. If no mapinfo file exists, the menu
+will automatically generate a rough draft for you on game startup. It will get
+stored into data/data/mapname.mapinfo.
+
+As an example, let's say I make a map called "wazat1.bsp". As long as I'm
+fantasizing, I might as well say this map is so well done I'm actually willing
+to release it for friends, neighbors, fellow forum visitors and other people
+who like me to judge me by. Let's also assume that I want domination,
+deathmatch/team deathmatch and runematch to all be playable on my map.
+
+This is very easy. First, I start the game and exit it again so the game writes
+data/data/wazat1.mapinfo for me. The file may look like:
+
+    title Wazat's Great Map
+    description Bleh.
+    author Unknown
+    _diameter 1966.839355
+    _spawnpoints 5
+    has weapons
+       cdtrack 5
+    type dm 30 20
+    type dom 200 20
+    type rune 200 20
+    type lms 9 20
+    type arena 10 20
+
+As I see, the menu autodetected that my map may be suitable for deathmatch,
+domination, runematch, last man standing and arena. But I want the map to be
+played in domination, deathmatch, team deathmatch, runematch only, and I also
+want different timelimits/fraglimits, so I will change the "type" lines to:
+
+    type dom 100 15
+    type rune 1000 25
+    type dm 40 15
+    type tdm 50 0 2
+
+Also, while I am at it, I should fix the placeholders in the map description lines:
+
+    title Wibble
+    description A large multi level arena map
+    author Wazat
+
+Although not entirely necessary, I will now move the .mapinfo file from
+data/data/maps/wazat1.mapinfo to data/maps/wazat1.mapinfo, so it is at the same
+place as my map. Next time I start the game, my map will be shown supporting
+the right game modes and with the right description in the menu.
+
+Also, note the "has weapons" line. If this line is not there (which happens if
+the map contains no weapon entities other than the Nex), the map will run in
+MinstaGib only and not be shown in the menu normally. So if you look for your
+map and don't find it, add "has weapons" to the mapinfo file, and make sure you
+have weapon entities placed.
+
+There's much more power in a mapinfo file. See Appendix A for more details.
+
+Gametype        | Syntax                                                                              | Notes
+----------------+-------------------------------------------------------------------------------------+-------------------------------------------------------
+Deathmatch      | type dm    <fraglimit>  <timelimit>                                                 |
+Team Deathmatch | type tdm   <fraglimit>  <timelimit> <teams>                                         | 2, 3, or 4 teams
+Domination      | type dom   <pointlimit> <timelimit>                                                 | teams are set by the map's entities
+CTF             | type ctf   <pointlimit> <timelimit> <caplimit>                                      | pointlimit if g_ctf_win_mode is 2, otherwise caplimit
+Runematch       | type rune  <pointlimit> <timelimit>                                                 |
+LMS             | type lms   <lives>      <timelimit>                                                 |
+Arena           | type arena <fraglimit>  <timelimit>                                                 |
+Key Hunt        | type kh    <pointlimit> <timelimit> <teams>                                         | 2, 3, or 4 teams
+Assault         | type as                 <timelimit>                                                 | never uses points
+Onslaught       | type ons                <timelimit>                                                 | never uses points
+Race            | type rc                 <timelimit> <qualifyingtimelimit> <laplimit> <teamlaplimit> | g_race_teams: teamlaplimit is used instead of laplimit
+CTS             | type cts                <timelimit> <skill>                                         | never uses points
+
+
+===============
+=II. Map Image=
+===============
+
+So, you've got your map listed in the menu and it plays properly, but the menu
+isn't showing your picture! Or the picture is scaled badly! What manner of man
+would create such an accursed abomination?!
+
+Well... That sounds like something I'd do. :D
+
+And it is really easy: just make a screenshot of the map in action (preferably
+with crosshair and HUD switched off), and place it next to the map as
+mapname.jpg. For best rendering and file size, make the image in 4:3 aspect
+ratio, but scale it to the resolution 256x256 or possibly 512x512. It will look
+skewed in your image editing app, but the menu will show it right, and your
+graphics card LOVES images of such dimension.
+
+
+=================
+=III. Domination=
+=================
+
+In order to get Domination working well in your map, you need to place dom_team
+and dom_controlpoint entites. You *must* have at least 3 dom_team entities - 2
+minimum teams and one blank one (empty netname and no team). You can have up to
+4 teams (5 dom_team entities), and remember: if you set 3 teams, the third team
+must be the yellow one, according to the team order.
+
+Dom Team
+--------
+classname  dom_team
+netname    name of team (Red Team). Set to "" or don't define for the required
+           blank team.
+cnt        color of the team. See the "Helpful Extras" section for info.
+model      When this team captures control points, the points turn to this
+           model. If this is the neutral team, points start out as this model.
+noise      Sound to be played on the control point when it's captured. Only
+           players nearby will hear it.
+noise1     Sound to be played to all players when the control point is
+           captured. Also good for an annoncer voice ("Red Team has captured a
+           control point")
+
+Control Points
+--------------
+classname  dom_controlpoint
+message    message to be displayed to all players when this point is captured,
+           preceded by the team's name. This defaults to " has captured a control point".
+           You can specify different names for each point, for example " has captured the
+           Lava Room".
+origin     where in the map this point is
+wait       How often this point gives its controlling team frags.
+frags      How many frags this point gives each wait cycle.
+
+Here is an example entry in a .ent file that includes colored text and 3 teams:
+
+{
+"classname" "dom_team"
+"netname" ""
+"model" "models/domination/dom_unclaimed.md3"
+}
+{
+"classname" "dom_team"
+"netname" "^4Blue Team"
+"cnt" "13"
+"noise" ""
+"noise1" "domination/claim.wav"
+"model" "models/domination/dom_blue.md3"
+}
+{
+"classname" "dom_team"
+"netname" "^1Red Team"
+"cnt" "4"
+"noise" ""
+"noise1" "domination/claim.wav"
+"model" "models/domination/dom_red.md3"
+}
+{
+"netname" "^3Yellow Team"
+"cnt" "12"
+"noise" ""
+"noise1" "domination/claim.wav"
+"model" "models/domination/dom_yellow.md3"
+}
+{
+"netname" "^6Pink Team"
+"cnt" "9"
+"noise" ""
+"noise1" "domination/claim.wav"
+"model" "models/domination/dom_pink.md3"
+}
+{
+"classname" "dom_controlpoint"
+"message" " ^3has captured the ^1Hallways"
+"origin" "-206.0 -488.8 -150.0"
+"frags" "3"
+"wait" "5"
+"scale" "1.3"
+}
+{
+"classname" "dom_controlpoint"
+"message" " ^3has captured the ^1Lavaroom"
+"origin" "1457.1  19.9 -110.0"
+"frags" "1"
+"wait" "5"
+}
+{
+"classname" "dom_controlpoint"
+"message" " ^3controls the ^1Nex & Strength"
+"origin" "-259.8 299.3  5"
+"frags" "1"
+"wait" "5"
+}
+{
+"classname" "dom_controlpoint"
+"message" " ^3has captured the ^1Upper Platform"
+"origin" "539.7 1206.0 182.0"
+"frags" "1"
+"wait" "5"
+}
+{
+"classname" "dom_controlpoint"
+"message" " ^3has captured the ^1Teleport Room"
+"origin" "-1000.0 636.2 -16.0"
+"frags" "1"
+"wait" "5"
+}
+
+
+As you can see in the example, there are 5 dom_team ents: one blank, Red, Blue,
+Yellow and Pink. Each control point has a different message (giving it a
+special name), and the one in the hallways gives 3 frags every 5 seconds
+instead of just one, making it more valuable.
+
+If your map contains the required entities for Domination, the menu will
+automatically detect it for supporting Domination. To force the map to get
+re-detected after you add such entities, delete the data/data/mapname.mapinfo
+file - or simply edit it to add the "type dom" line.
+
+
+=========
+=IV. CTF=
+=========
+
+Capture the flag needs at least 1 CTF flag per team, and can also make use of
+team spawnpoints.
+
+CTF Flags
+---------
+classname  item_flag_team1 or item_flag_team2
+angle      direction the flag will point
+model      model of the flag (default: models/ctf/flag_red.md3 or
+           models/ctf/flag_blue.md3)
+noise      sound played when flag is stolen (default: "ctf/take.wav")
+noise1     sound played when flag is returned by a teammate (default:
+           "ctf/return.wav")
+noise2     sound played when flag is captured (default: "ctf/capture.wav")
+noise3     sound played when flag returns itself (default: "ctf/respawn.wav")
+
+Team Spawnpoints
+----------------
+classname  info_player_team1 or info_player_team2
+*note: These function just like info_player_deathmatch, but for one team only.
+If you don't make team spawnpoints, info_player_deathmatch is used instead.
+
+If your map contains the required entities for CTF, the menu will automatically
+detect it for supporting CTF. To force the map to get re-detected after you add
+such entities, delete the data/data/mapname.mapinfo file - or simply edit it to
+add the "type ctf" line.
+
+==============
+=V. Runematch=
+==============
+
+Runematch needs only one type of entity to work: rune spawn points. You will
+need at least one for each rune (5 minimum at the time of this writing), though
+you should probably have more than that in the map. Just give the points a
+classname and origin.
+
+Rune Spawnpoints
+----------------
+classname  runematch_spawn_point
+
+If your map contains the required entities for Runematch, the menu will automatically
+detect it for supporting Runematch. To force the map to get re-detected after you add
+such entities, delete the data/data/mapname.mapinfo file - or simply edit it to
+add the "type rune" line.
+
+==============
+=VI. Race/CTS=
+==============
+
+Making a race map is not hard: you need some special spawnpoints, and some checkpoints.
+
+Spawnpoints
+-----------
+classname  info_player_race
+target     targetname of the checkpoint
+race_place for finish line checkpoints, the place of the point, or -1 to make it qualifying/CTS-only, or unset to let all the other players spawn
+
+Checkpoints
+-----------
+classname  trigger_race_checkpoint
+targetname some name to target the checkpoint with
+cnt        number of the checkpoint (or 0 for finish line)
+
+Note that checkpoints are brush entities, and they should be somewhat thick and
+cover the full volume the player could use to get past them.
+
+Example of entity placement:
+
+                         ###
+    ---------------------###---
+   /    9999  7  5  3  1>###   \
+  /     9999 8  6  4  2 >###    \
+ |     ------------------###     |
+%%%%%%%%                 ###|    |
+%%%%%%%%                 $$$|    |
+ | ^ ^ ------------------$$$     |
+ \                       $$$ <  /
+  \                      $$$ < /
+   ----------------------$$$---
+                         $$$
+
+###:  classname = trigger_race_checkpoint, cnt = 0, targetname = finish
+$$$:  classname = trigger_race_checkpoint, cnt = 1, targetname = cp1
+%%%:  classname = trigger_race_checkpoint, cnt = 2, targetname = cp2
+>:    classname = info_player_race,                 target = finish, angle = 0
+1:    classname = info_player_race,                 target = finish, angle = 0, race_place = 1
+2:    classname = info_player_race,                 target = finish, angle = 0, race_place = 2
+....
+8:    classname = info_player_race,                 target = finish, angle = 0, race_place = 8
+9:    classname = info_player_race,                 target = finish, angle = 0, race_place = 9
+<:    classname = info_player_race,                 target = cp1,    angle = 180
+^:    classname = info_player_race,                 target = cp2,    angle = 90
+
+If your map contains the required entities for Race, the menu will automatically
+detect it for supporting Race. To force the map to get re-detected after you add
+such entities, delete the data/data/mapname.mapinfo file - or simply edit it to
+add the "type rc" line.
+
+CTS maps do not use checkpoints with race_place set, so you can leave them out
+for CTS maps.
+
+The skill parameter in the mapinfo entry for CTS shall be in the range from 0 (easy) to 10 (impossible).
+
+=============
+=VI. Nexball=
+=============
+
+There are three required entities: nexball_redgoal, nexball_bluegoal, and one of nexball_basketball
+or nexball_football. There are also optional nexball_yellowgoal and nexball_pinkgoal entities (don't
+add a pink goal when there is no yellow goal on the map, it will crash)
+
+Goals are made just like any other regular triggers. You can use multiple brushes for one trigger,
+but avoid this if possible.
+
+There are also two other goal-like entities, nexball_fault and nexball_bound, the first taking a point
+from the team that hits the trigger with the ball, the second simply returning it. You can spawn the
+ball inside a goal-like trigger, this can be useful for basketball maps with separate teams and a
+common ball spawn.
+The different keys for the entities are documented in entities.def.
+
+The ball is affected by trigger_impulse, but not by trigger_push or teleporters.
+
+You should better avoid patches on the field, as collisions can sometimes get buggy on these.
+
+
+===============================
+=Appendix A - Advanced mapinfo=
+===============================
+
+You now know how to make a basic, bare-bones mapinfo to set up a couple options
+and load your map. However, there's much more you can do!  Consider these
+senarios:
+
+1. The laser has too high of a force for laser jumps and ruins CTF
+2. I don't want players to start out with the shotgun, but with the machinegun
+   instead
+3. The map takes so much server CPU performance that the anti-wallhack can't be
+   made active
+
+Each of these situations can be resolved with ease with a little work in the
+mapinfo file.
+
+To do this, I can add the following lines to my mapinfo file:
+
+    settemp_for_type ctf g_balance_laser_primary_force 200
+    settemp_for_type all g_start_weapon_shotgun 0
+    settemp_for_type all g_start_weapon_uzi 1
+    settemp_for_type all sv_cullentities_trace 0
+
+These "settemp" settings are automatically removed when the map is left and
+another is loaded. As you can see, it is possible to make per-mode temporary
+settings, and global ones.
+
+Similar settings are also possible for the client:
+
+    clientsettemp_for_type all r_shadow_glossexponent 96
+
+Another possibility is to specify fog settings in the mapinfo, for convenience
+in case you set sv_foginterval by it too (to force the fog on the clients):
+
+    fog 0.2 0.25 0.3 0.3 1 1500
+    settemp_for_type all sv_foginterval 5
+
+
+=============================
+=Appendix B - Helpful Extras=
+=============================
+
+----------------
+-i. Team Colors-
+----------------
+When you need to set an entity's color or team, use these values:
+
+Red
+---
+Team:      5
+Color:     4
+
+Blue
+----
+Team:      14
+Color:     13
+
+Yellow
+------
+Team:      13
+Color:     12
+
+Pink
+-----
+Team:      10
+Color:     9
+
+
+----------------
+-i. Text Colors-
+----------------
+Occasionally you may want to print text in color, such as team names. Here are your options:
+
+1  Red
+2  Green
+3  Yellow
+4  Blue
+5  Cyan
+6  Magenta
+7  White
+8  Grey (transparent)
+9  Grey (solid)
+0  Black
+
+==========================================
+=Appendix C - Advanced Darkplaces shaders=
+==========================================
+
+Shader parameters for DP's own features:
+- dp_reflect <distort> <r> <g> <b> <a>
+  Makes surfaces of this shader reflective with r_water. The reflection is
+  alpha blended on the texture with the given alpha, and modulated by the given
+  color. distort is used in conjunction with the normalmap to simulate a
+  nonplanar water surface.
+- dp_refract <distort> <r> <g> <b>
+  Makes surfaces of this shader refractive with r_water. The refraction
+  replaces the transparency of the texture. distort is used in conjunction with
+  the normalmap to simulate a nonplanar water surface.
+- dp_water <reflectmin> <reflectmax> <refractdistort> <reflectdistort> <refractr> <refractg> <refractb> <reflectr> <reflectg> <reflectb> <alpha>
+  This combines the effects of dp_reflect and dp_refract to simulate a water
+  surface. However, the refraction and the reflection are mixed using a Fresnel
+  equation that makes the amount of reflection slide from reflectmin when
+  looking parallel to the water to reflectmax when looking directly into the
+  water. The result of this reflection/refraction mix is then layered BELOW the
+  texture of the shader, so basically, it "fills up" the alpha values of the
+  water. The alpha value is a multiplicator for the alpha value on the texture
+  - set this to a small value like 0.1 to emphasize the reflection and make
+  the water transparent; but if r_water is 0, alpha isn't used, so the water can
+  be very visible then too.
+
index 9346e8d5d8b869e81f93e561555a9f3cb2235eb2..65ae444a5a6c00efa75ce6498e1a49a8221ae96e 100644 (file)
@@ -1,12 +1,12 @@
-<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>\r
-<game\r
-  name="Nexuiz"\r
-  basegame="data"\r
-  enginepath="/home/havoc/nexuizsvn/trunk"\r
-  engine="nexuiz-linux-sdl"\r
-  gametools="/home/havoc/ZeroRadiant/install/games"\r
-  gamename="nexuiz"\r
-  prefix=".nexuiz"\r
-  shaderpath="scripts/"\r
-  default_scale="0.5"\r
-/>\r
+<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>
+<game
+  name="Nexuiz"
+  basegame="data"
+  enginepath="/home/havoc/nexuizsvn/trunk"
+  engine="nexuiz-linux-sdl"
+  gametools="/home/havoc/ZeroRadiant/install/games"
+  gamename="nexuiz"
+  prefix=".nexuiz"
+  shaderpath="scripts/"
+  default_scale="0.5"
+/>
index 64f15afe73bcdddd64e5d58a27482fb0afb50da0..47d5d508eec4e993373372b2d778e4f76cc253a2 100644 (file)
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\r
-        "http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd">\r
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\r
-<head>\r
-<meta http-equiv="content-type" content="text/html; charset=utf-8" />\r
-<meta name="author" content="Tyler Mulligan of www.detrition.net a.k.a -z- of www.nexuizninjaz.com" />\r
-<meta name="Description" content="Nexuiz is a free open-source fast paced first person shooter (FPS) that runs on Windows, Linux and OSX.  Nexuiz utilizes the darkplaces engine modeled after the Quake series." />\r
-<meta name="Keywords" content="Nexuiz, Nexiuz, first person shooter, darkplaces, open-source game, open source game, free game, linux game, deathmatch, death match, ctf, quake, alientrap, alien trap, ninjaz" />\r
-<title>Nexuiz - A free open-source fast paced first person shooter (FPS) for Windows, Linux and OSX</title>\r
-<link rel="stylesheet" href="htmlfiles/style.css" type="text/css"></link>\r
-<link rel="shortcut icon" href="favicon.ico"></link>\r
-</head>\r
-<body>\r
-<div id="container">\r
-       <div id="left">\r
-               <div id="logo">\r
-\r
-                       <a href="http://www.nexuiz.com" title="Nexuiz - Simple, fast, intense and completely free"><img src="htmlfiles/img/nexuiz_logo.jpg" alt="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="201" height="193" border="0" title="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." /></a>\r
-               </div>\r
-               <!-- Sidebar -->\r
-         <div id="sidebar">\r
-                       <a href="news.php" title="Latest Nexuiz News"><img src="htmlfiles/img/headers/latest_news.gif" alt="Latest News" width="168" height="16" border="0" class="imgHeader" style="margin-top:0;" title="Latest Nexuiz News" /></a>\r
-                       <div id="news">\r
-                         <div class="newsPost">\r
-                                               <h1><a href="http://nexuiz.com/news.php" title="Two Nexuiz 1v1 Tourneys">nexuiz.com/news - for nexuiz news updates</a></h1>\r
-                         </div>\r
-               </div>\r
-                       <!-- Downloads -->\r
-                       <a href="downloads.php" title="Nexuiz Downloads (Game, Maps)"><img src="htmlfiles/img/headers/downloads.gif" alt="Downloads" width="145" height="15" border="0" class="imgHeader" title="Downloads" /></a>\r
-                       <a id="download_nexuiz" href="http://www.sourceforge.net/projects/nexuiz/" title="Download Nexuiz from Source Forge - Left Click">&nbsp;</a>\r
-                       <!--<a id="download_q3_mappack" href="http://downloads.sourceforge.net/nexuiz/nexmappack_r2.zip" title="Download Nexuiz Q3 Mappack from Source\r
-                       Forge - Left Click">&nbsp;</a>-->\r
-                       <h2 class="page"><a href="http://nexuiz.comdownloads.php" title="Nexuiz Download Mirrors">Mirrors -&gt;</a></h2>\r
-                       <!-- Help Wanted -->\r
-\r
-                       <img src="htmlfiles/img/headers/help_wanted.gif" alt="Help Wanted" width="165" height="15" class="imgHeader" title="Help Wanted" />                     \r
-                       <form action="https://www.paypal.com/cgi-bin/webscr" method="post" name="donate_form"> <input name="cmd" value="_s-xclick" type="hidden" />\r
-                       <a id="donate" href="javascript:document.donate_form.submit();" title="Help The Aliens, Please Donate">&nbsp;</a>\r
-                       <noscript>\r
-                               <!-- It not an elegant solution but I don't expect many users to arrive with JS turned off -->\r
-                               <input name="submit" type="image" src="htmlfiles/img/donate_button_noscript.jpg" alt="Help this project - Donate to the aliens" width="166" height="43" /> \r
-                       </noscript>\r
-                       <h2 class="page"><a href="http://nexuiz.com/donators.php" title="Those who have donated to Nexuiz">Donators -&gt;</a></h2>\r
-                       <input name="encrypted" value="-----BEGIN PKCS7-----MIIHBgYJKoZIhvcNAQcEoIIG9zCCBvMCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYBfp40KRLmnxKPY06C4gjvEiWZchbxK6bgD7ZdjhdWO5Vbwo4T4Ro+HE041PVVqIxPlJgO80l3aQpBtfhC66FfM2kIF1BjLs1zzhQM89XoPGViS3e4kbmzxkMnpdiZFmOsR5Fs5NJYiVaMnVGcoQ+K3+KsyOehZGket7GwUeNFMRzELMAkGBSsOAwIaBQAwgYMGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIb1QeJqVIc5SAYOPcI23W84XMGt8mSfHE1Gf0/GZAM7NvqLiHF0BeaecRW1Bc85O0tL9OduZiraGf7WVnAmP5kp1D0irXsA5+N2l15WADxwNQ/GoCAU293l0dAQ7Qy4F3vh6eSii18MaH2KCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA1MDUxMjE5MDQ0OFowIwYJKoZIhvcNAQkEMRYEFC7mlfdaA7Pg2eBhxI5xQTe7ydTtMA0GCSqGSIb3DQEBAQUABIGAfR38tX84huxk9JjvhggcSMxzHbmDxpxInBU6/lbyqAu7iT5KJn7rcJgaH5ZVyKNoNQLGp9IxweBrcMiYUQNVxShm9+hunXhQmj5r7AMGaxNZ0mE8mQRW1ZTaz7TLz1HkDFA+R0Dm8HYyDQA4L505cBiWNEsKC17VwNK1G7CEVvA=-----END PKCS7-----" type="hidden" /></form>\r
-                       \r
-                 <!-- Created By -->\r
-                 <img src="htmlfiles/img/headers/created_by.gif" alt="Created By" width="145" height="15" class="imgHeader" title="Created By" />\r
-\r
-         <a id="alien_trap" href="http://www.alientrap.org" title="Developed by Alien Trap" target="_blank">&nbsp;</a>   </div>\r
-               <!-- end sidebar -->    </div><!-- end left -->\r
-       <div id="right">\r
-               <div id="header">\r
-                       <a href="http://nexuiz.com" title="Nexuiz - Simple, fast, intense and completely free"><img src="htmlfiles/img/nexuiz_header.jpg" alt="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="766" height="107" border="0" title="Nexuiz - Simple, fast, intense and completely free" /></a>\r
-               </div>\r
-               <div id="menu">\r
-                       <ul>\r
-\r
-                               <li class="first"><a href="../readme.html" title="Nexuiz - Latest News">About </a></li>\r
-                               <li><a href="basics.html" title="Nexuiz - Media (Screenshots, videos and graphics)">Nexuiz Basics</a></li>\r
-                               <li><a href="say-esc.html" title="Nexuiz Information  (General, System Requirements)">Say Escapes</a></li>\r
-                               <li><a href="irc.html" title="Nexuiz - Downloads (Game, Maps)">IRC</a></li>\r
-                               <li><a href="faq.html" title="Official Nexuiz FAQ">FAQ</a></li>\r
-                               <li class="first"></li>\r
-                               <li class="first"></li>\r
-                               <li class="first"></li>\r
-                               <li class="first"></li>\r
-                 </ul>         </div>\r
-               <p id="tagline">Nexuiz is a <b>free</b> open-source first person shooter that runs on <b>Windows</b>, <b>Linux</b> and <b>OSX</b>.</p>\r
-         <div id="content">\r
-                 <p>In a message you &quot;say&quot;, you can use the following escapes: </p>\r
-                 <ul>\r
-                   <li>%% literal percent sign</li>\r
-           <li>%a current armor value </li>\r
-           <li>%h current health </li>\r
-           <li>%w current weapon </li>\r
-           <li>%W current ammo type </li>\r
-           <li>%s current speed on XY axes</li>\r
-           <li>%S current speed on XYZ axes</li>\r
-           <li>%v accuracy stats of currently held weapon</li>\r
-           <li>%x name of entity pointed at </li>\r
-           <li>%p name of player you look at </li>\r
-           <li>%l current location</li>\r
-           <li>%y location pointed at </li>\r
-           <li>%d location last died at </li>\r
-           </ul>\r
-                 <p>The latter three need location data or use nearby weapons instead. Location data are entities of type target_location with a netname that replaces the escape. </p>\r
-                 <p>The location entity for a point is chosen by the following algorithm: </p>\r
-                 <p>&nbsp; </p>\r
-                 <ul>\r
-                   <li>- the closest four entities are found </li>\r
-           <li>- of these, the first one which is actually VISIBLE to the point is chosen </li>\r
-           <li>- if there is no line of sight from the point to any of the four location </li>\r
-           <li>entities, the closest one is chosen </li>\r
-           <li> </li>\r
-           </ul>\r
-                 <p>Of course you can use these escapes in key bindings and aliases; examples:</p>\r
-                 <ul>\r
-                   <li>alias asay_ctf_flagcarrier &quot;say_team flag carrier at %y&quot; </li>\r
-           <li>alias asay_ctf_haveflag &quot;say_team (%l) have the flag&quot; </li>\r
-           <li>alias asay_willgo &quot;say_team will go to %y&quot; </li>\r
-           <li>alias asay_support &quot;say_team (%l) need help, %h%%&quot; </li>\r
-           <li>alias asay_killed &quot;say_team got killed at %d&quot; </li>\r
-           <li>alias asay_noammo &quot;say_team (%l) need %W for %w&quot; </li>\r
-           <li>alias asay_drop &quot;say_team (%l) dropped %w ; impulse 17&quot; </li>\r
-           </ul>\r
-                 <p>&nbsp; </p>\r
-                 <p><br style="clear:left" />\r
-        </p>\r
-         </div>\r
-         <!-- end content -->\r
-               <div id="footer">\r
-                       <p id="ninja"><a href="http://www.detrition.net" title="Tyler Mulligan's Working Portfolio" target="_blank">page created by</a> <a href="http://www.nexuizninjaz.com" title="Nexuiz Ninjaz - Practicing the ninja arts of Nexuiz" target="_blank">a ninja</a></p>\r
-                       <ul>\r
-\r
-                               <li class="first"><a href="http://alientrap.org/nexuiz/news.php" title="Nexuiz - Latest News">News</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/media.php" title="Nexuiz - Media (Screenshots, videos and graphics)">Media</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/information.php" title="Nexuiz Information  (General, System Requirements)">Info</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/team.php" title="The Nexuiz Team">Team</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/links.php" title="Nexuiz Links">Links</a></li>\r
-                               <li><a href="http://alientrap.org/forum/index.php?c=3" title="Official Nexuiz Forums" target="_blank">Forums</a></li>\r
-                               <li><a href="http://planetnexuiz.com/ladder/" title="Nexuiz Ladder" target="_blank">Ladder</a></li>\r
-                               <li><a href="http://planetnexuiz.com/tourney/" title="Nexuiz Tournaments" target="_blank">Tournament</a></li>\r
-                               <li class="first"></li>\r
-                 </ul>         </div><p class="subFooter">Come to the Nexuiz IRC channel: <a href="irc://irc.quakenet.org/nexuiz" title="#nexuiz on irc.quakenet.org">#nexuiz on irc.quakenet.org</a> or the team channel: <a href="irc://irc.anynet.org/alientrap" title="#alientrap on irc.anynet.org">#alientrap on irc.anynet.org</a></p>\r
-\r
-               <div id="valid">\r
-                       <a id="valid_css" href="http://jigsaw.w3.org/css-validator/validator?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>\r
-                       <a id="valid_xhtml" href="http://validator.w3.org/check?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>\r
-                       <p>&copy; 2004-2008 Nexuiz.com</p>\r
-               </div>\r
-       </div><!-- end right -->\r
-</div>\r
-</body>\r
-</html>\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<meta name="author" content="Tyler Mulligan of www.detrition.net a.k.a -z- of www.nexuizninjaz.com" />
+<meta name="Description" content="Nexuiz is a free open-source fast paced first person shooter (FPS) that runs on Windows, Linux and OSX.  Nexuiz utilizes the darkplaces engine modeled after the Quake series." />
+<meta name="Keywords" content="Nexuiz, Nexiuz, first person shooter, darkplaces, open-source game, open source game, free game, linux game, deathmatch, death match, ctf, quake, alientrap, alien trap, ninjaz" />
+<title>Nexuiz - A free open-source fast paced first person shooter (FPS) for Windows, Linux and OSX</title>
+<link rel="stylesheet" href="htmlfiles/style.css" type="text/css"></link>
+<link rel="shortcut icon" href="favicon.ico"></link>
+</head>
+<body>
+<div id="container">
+       <div id="left">
+               <div id="logo">
+
+                       <a href="http://www.nexuiz.com" title="Nexuiz - Simple, fast, intense and completely free"><img src="htmlfiles/img/nexuiz_logo.jpg" alt="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="201" height="193" border="0" title="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." /></a>
+               </div>
+               <!-- Sidebar -->
+         <div id="sidebar">
+                       <a href="news.php" title="Latest Nexuiz News"><img src="htmlfiles/img/headers/latest_news.gif" alt="Latest News" width="168" height="16" border="0" class="imgHeader" style="margin-top:0;" title="Latest Nexuiz News" /></a>
+                       <div id="news">
+                         <div class="newsPost">
+                                               <h1><a href="http://nexuiz.com/news.php" title="Two Nexuiz 1v1 Tourneys">nexuiz.com/news - for nexuiz news updates</a></h1>
+                         </div>
+               </div>
+                       <!-- Downloads -->
+                       <a href="downloads.php" title="Nexuiz Downloads (Game, Maps)"><img src="htmlfiles/img/headers/downloads.gif" alt="Downloads" width="145" height="15" border="0" class="imgHeader" title="Downloads" /></a>
+                       <a id="download_nexuiz" href="http://www.sourceforge.net/projects/nexuiz/" title="Download Nexuiz from Source Forge - Left Click">&nbsp;</a>
+                       <!--<a id="download_q3_mappack" href="http://downloads.sourceforge.net/nexuiz/nexmappack_r2.zip" title="Download Nexuiz Q3 Mappack from Source
+                       Forge - Left Click">&nbsp;</a>-->
+                       <h2 class="page"><a href="http://nexuiz.comdownloads.php" title="Nexuiz Download Mirrors">Mirrors -&gt;</a></h2>
+                       <!-- Help Wanted -->
+
+                       <img src="htmlfiles/img/headers/help_wanted.gif" alt="Help Wanted" width="165" height="15" class="imgHeader" title="Help Wanted" />                     
+                       <form action="https://www.paypal.com/cgi-bin/webscr" method="post" name="donate_form"> <input name="cmd" value="_s-xclick" type="hidden" />
+                       <a id="donate" href="javascript:document.donate_form.submit();" title="Help The Aliens, Please Donate">&nbsp;</a>
+                       <noscript>
+                               <!-- It not an elegant solution but I don't expect many users to arrive with JS turned off -->
+                               <input name="submit" type="image" src="htmlfiles/img/donate_button_noscript.jpg" alt="Help this project - Donate to the aliens" width="166" height="43" /> 
+                       </noscript>
+                       <h2 class="page"><a href="http://nexuiz.com/donators.php" title="Those who have donated to Nexuiz">Donators -&gt;</a></h2>
+                       <input name="encrypted" value="-----BEGIN PKCS7-----MIIHBgYJKoZIhvcNAQcEoIIG9zCCBvMCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYBfp40KRLmnxKPY06C4gjvEiWZchbxK6bgD7ZdjhdWO5Vbwo4T4Ro+HE041PVVqIxPlJgO80l3aQpBtfhC66FfM2kIF1BjLs1zzhQM89XoPGViS3e4kbmzxkMnpdiZFmOsR5Fs5NJYiVaMnVGcoQ+K3+KsyOehZGket7GwUeNFMRzELMAkGBSsOAwIaBQAwgYMGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIb1QeJqVIc5SAYOPcI23W84XMGt8mSfHE1Gf0/GZAM7NvqLiHF0BeaecRW1Bc85O0tL9OduZiraGf7WVnAmP5kp1D0irXsA5+N2l15WADxwNQ/GoCAU293l0dAQ7Qy4F3vh6eSii18MaH2KCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA1MDUxMjE5MDQ0OFowIwYJKoZIhvcNAQkEMRYEFC7mlfdaA7Pg2eBhxI5xQTe7ydTtMA0GCSqGSIb3DQEBAQUABIGAfR38tX84huxk9JjvhggcSMxzHbmDxpxInBU6/lbyqAu7iT5KJn7rcJgaH5ZVyKNoNQLGp9IxweBrcMiYUQNVxShm9+hunXhQmj5r7AMGaxNZ0mE8mQRW1ZTaz7TLz1HkDFA+R0Dm8HYyDQA4L505cBiWNEsKC17VwNK1G7CEVvA=-----END PKCS7-----" type="hidden" /></form>
+                       
+                 <!-- Created By -->
+                 <img src="htmlfiles/img/headers/created_by.gif" alt="Created By" width="145" height="15" class="imgHeader" title="Created By" />
+
+         <a id="alien_trap" href="http://www.alientrap.org" title="Developed by Alien Trap" target="_blank">&nbsp;</a>   </div>
+               <!-- end sidebar -->    </div><!-- end left -->
+       <div id="right">
+               <div id="header">
+                       <a href="http://nexuiz.com" title="Nexuiz - Simple, fast, intense and completely free"><img src="htmlfiles/img/nexuiz_header.jpg" alt="Nexuiz is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="766" height="107" border="0" title="Nexuiz - Simple, fast, intense and completely free" /></a>
+               </div>
+               <div id="menu">
+                       <ul>
+
+                               <li class="first"><a href="../readme.html" title="Nexuiz - Latest News">About </a></li>
+                               <li><a href="basics.html" title="Nexuiz - Media (Screenshots, videos and graphics)">Nexuiz Basics</a></li>
+                               <li><a href="say-esc.html" title="Nexuiz Information  (General, System Requirements)">Say Escapes</a></li>
+                               <li><a href="irc.html" title="Nexuiz - Downloads (Game, Maps)">IRC</a></li>
+                               <li><a href="faq.html" title="Official Nexuiz FAQ">FAQ</a></li>
+                               <li class="first"></li>
+                               <li class="first"></li>
+                               <li class="first"></li>
+                               <li class="first"></li>
+                 </ul>         </div>
+               <p id="tagline">Nexuiz is a <b>free</b> open-source first person shooter that runs on <b>Windows</b>, <b>Linux</b> and <b>OSX</b>.</p>
+         <div id="content">
+                 <p>In a message you &quot;say&quot;, you can use the following escapes: </p>
+                 <ul>
+                   <li>%% literal percent sign</li>
+           <li>%a current armor value </li>
+           <li>%h current health </li>
+           <li>%w current weapon </li>
+           <li>%W current ammo type </li>
+           <li>%s current speed on XY axes</li>
+           <li>%S current speed on XYZ axes</li>
+           <li>%v accuracy stats of currently held weapon</li>
+           <li>%x name of entity pointed at </li>
+           <li>%p name of player you look at </li>
+           <li>%l current location</li>
+           <li>%y location pointed at </li>
+           <li>%d location last died at </li>
+           </ul>
+                 <p>The latter three need location data or use nearby weapons instead. Location data are entities of type target_location with a netname that replaces the escape. </p>
+                 <p>The location entity for a point is chosen by the following algorithm: </p>
+                 <p>&nbsp; </p>
+                 <ul>
+                   <li>- the closest four entities are found </li>
+           <li>- of these, the first one which is actually VISIBLE to the point is chosen </li>
+           <li>- if there is no line of sight from the point to any of the four location </li>
+           <li>entities, the closest one is chosen </li>
+           <li> </li>
+           </ul>
+                 <p>Of course you can use these escapes in key bindings and aliases; examples:</p>
+                 <ul>
+                   <li>alias asay_ctf_flagcarrier &quot;say_team flag carrier at %y&quot; </li>
+           <li>alias asay_ctf_haveflag &quot;say_team (%l) have the flag&quot; </li>
+           <li>alias asay_willgo &quot;say_team will go to %y&quot; </li>
+           <li>alias asay_support &quot;say_team (%l) need help, %h%%&quot; </li>
+           <li>alias asay_killed &quot;say_team got killed at %d&quot; </li>
+           <li>alias asay_noammo &quot;say_team (%l) need %W for %w&quot; </li>
+           <li>alias asay_drop &quot;say_team (%l) dropped %w ; impulse 17&quot; </li>
+           </ul>
+                 <p>&nbsp; </p>
+                 <p><br style="clear:left" />
+        </p>
+         </div>
+         <!-- end content -->
+               <div id="footer">
+                       <p id="ninja"><a href="http://www.detrition.net" title="Tyler Mulligan's Working Portfolio" target="_blank">page created by</a> <a href="http://www.nexuizninjaz.com" title="Nexuiz Ninjaz - Practicing the ninja arts of Nexuiz" target="_blank">a ninja</a></p>
+                       <ul>
+
+                               <li class="first"><a href="http://alientrap.org/nexuiz/news.php" title="Nexuiz - Latest News">News</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/media.php" title="Nexuiz - Media (Screenshots, videos and graphics)">Media</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/information.php" title="Nexuiz Information  (General, System Requirements)">Info</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/team.php" title="The Nexuiz Team">Team</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/links.php" title="Nexuiz Links">Links</a></li>
+                               <li><a href="http://alientrap.org/forum/index.php?c=3" title="Official Nexuiz Forums" target="_blank">Forums</a></li>
+                               <li><a href="http://planetnexuiz.com/ladder/" title="Nexuiz Ladder" target="_blank">Ladder</a></li>
+                               <li><a href="http://planetnexuiz.com/tourney/" title="Nexuiz Tournaments" target="_blank">Tournament</a></li>
+                               <li class="first"></li>
+                 </ul>         </div><p class="subFooter">Come to the Nexuiz IRC channel: <a href="irc://irc.quakenet.org/nexuiz" title="#nexuiz on irc.quakenet.org">#nexuiz on irc.quakenet.org</a> or the team channel: <a href="irc://irc.anynet.org/alientrap" title="#alientrap on irc.anynet.org">#alientrap on irc.anynet.org</a></p>
+
+               <div id="valid">
+                       <a id="valid_css" href="http://jigsaw.w3.org/css-validator/validator?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>
+                       <a id="valid_xhtml" href="http://validator.w3.org/check?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>
+                       <p>&copy; 2004-2008 Nexuiz.com</p>
+               </div>
+       </div><!-- end right -->
+</div>
+</body>
+</html>
index b894bd6961b231f04400466d615b4ce8b789d67a..a1e4a0735fa9beb9a1f8f189bdb8c07813736d7b 100644 (file)
@@ -1,44 +1,44 @@
-cvars:\r
-______________\r
-\r
-   sv_logscores_console                print scores to serverconsole after each match (default: 0, set to 1 to enable)\r
-   sv_logscores_file           print scores to a file after each match (default: 0)\r
-   sv_logscores_filename       filename of the output file if sv_logscores_file is enabled (default: "scores.log",\r
-                                 the file will be stored in Nexuiz/data/data or ~/.nexuiz/data/data)\r
-   sv_logscores_bots           choose whether bot are included in stats or not (default: 0)\r
-\r
-\r
-commands:\r
-______________\r
-\r
-   printstats                  print current scores to file/console (requires sv_logscores_console and/or \r
-                                 sv_logscores_file to be enabled)\r
-\r
-\r
-log format:\r
-______________\r
-\r
-example:\r
-   \r
-   :scores:dm_nexdm01:131\r
-   :player:1:7:129:1:GrooveMachine\r
-   :player:1:4:129:1:DanceWithMe\r
-   :player:10:1:130:3:Player\r
-   :end\r
-   \r
-   start of a new section:     :scores:<gametype>_<mapname>:<map runtime>              (if the dump was triggered\r
-                                  by "printstats", the line starts with :status:)\r
-   player entry:               :player:<frags>:<deaths>:<playtime>:<team>:<nickname>   (playername might contain ":",\r
-                                  so be sure your parser doesn't split them, playtime is messured in seconds)\r
-   section end:                        :end\r
-\r
-team colors:\r
-   Red Team    =  5\r
-   Blue Team   = 14\r
-   Yellow Team = 13\r
-   Pink Team   = 10\r
-\r
-   other team numbers may appear in free for all games\r
-\r
-   for an example parser (written in php, thanks to tChr) have a look at:\r
-      http://excalibur.nvg.ntnu.no/nexuiz/statsparser\r
+cvars:
+______________
+
+   sv_logscores_console                print scores to serverconsole after each match (default: 0, set to 1 to enable)
+   sv_logscores_file           print scores to a file after each match (default: 0)
+   sv_logscores_filename       filename of the output file if sv_logscores_file is enabled (default: "scores.log",
+                                 the file will be stored in Nexuiz/data/data or ~/.nexuiz/data/data)
+   sv_logscores_bots           choose whether bot are included in stats or not (default: 0)
+
+
+commands:
+______________
+
+   printstats                  print current scores to file/console (requires sv_logscores_console and/or 
+                                 sv_logscores_file to be enabled)
+
+
+log format:
+______________
+
+example:
+   
+   :scores:dm_nexdm01:131
+   :player:1:7:129:1:GrooveMachine
+   :player:1:4:129:1:DanceWithMe
+   :player:10:1:130:3:Player
+   :end
+   
+   start of a new section:     :scores:<gametype>_<mapname>:<map runtime>              (if the dump was triggered
+                                  by "printstats", the line starts with :status:)
+   player entry:               :player:<frags>:<deaths>:<playtime>:<team>:<nickname>   (playername might contain ":",
+                                  so be sure your parser doesn't split them, playtime is messured in seconds)
+   section end:                        :end
+
+team colors:
+   Red Team    =  5
+   Blue Team   = 14
+   Yellow Team = 13
+   Pink Team   = 10
+
+   other team numbers may appear in free for all games
+
+   for an example parser (written in php, thanks to tChr) have a look at:
+      http://excalibur.nvg.ntnu.no/nexuiz/statsparser
index d6717c6e5163a67b1569f04ccda95818a3c3539c..de5da57b7ff403ddb0b38a41e62ab8123370e405 100644 (file)
-package com.nexuiz.demorecorder.application;\r
-\r
-import java.io.File;\r
-import java.io.FileInputStream;\r
-import java.io.FileNotFoundException;\r
-import java.io.FileOutputStream;\r
-import java.io.IOException;\r
-import java.io.ObjectInputStream;\r
-import java.io.ObjectOutputStream;\r
-import java.net.MalformedURLException;\r
-import java.net.URL;\r
-import java.net.URLClassLoader;\r
-import java.util.ArrayList;\r
-import java.util.List;\r
-import java.util.Properties;\r
-import java.util.ServiceLoader;\r
-import java.util.concurrent.CopyOnWriteArrayList;\r
-\r
-import com.nexuiz.demorecorder.application.jobs.EncoderJob;\r
-import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
-import com.nexuiz.demorecorder.application.jobs.RecordsDoneJob;\r
-import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;\r
-import com.nexuiz.demorecorder.ui.DemoRecorderUI;\r
-\r
-public class DemoRecorderApplication {\r
-       \r
-       public static class Preferences {\r
-               public static final String OVERWRITE_VIDEO_FILE = "Overwrite final video destination file if it exists";\r
-               public static final String DISABLE_RENDERING = "Disable rendering while fast-forwarding";\r
-               public static final String DISABLE_SOUND = "Disable sound while fast-forwarding";\r
-               public static final String FFW_SPEED_FIRST_STAGE = "Fast-forward speed (first stage)";\r
-               public static final String FFW_SPEED_SECOND_STAGE = "Fast-forward speed (second stage)";\r
-               public static final String DO_NOT_DELETE_CUT_DEMOS = "Do not delete cut demos";\r
-               public static final String JOB_NAME_APPEND_DUPLICATE = "Append this suffix to job-name when duplicating jobs";\r
-               \r
-               public static final String[] PREFERENCES_ORDER = {\r
-                       OVERWRITE_VIDEO_FILE,\r
-                       DISABLE_RENDERING,\r
-                       DISABLE_SOUND,\r
-                       FFW_SPEED_FIRST_STAGE,\r
-                       FFW_SPEED_SECOND_STAGE,\r
-                       DO_NOT_DELETE_CUT_DEMOS,\r
-                       JOB_NAME_APPEND_DUPLICATE\r
-               };\r
-       }\r
-       \r
-       public static final String PREFERENCES_DIRNAME = "settings";\r
-       public static final String LOGS_DIRNAME = "logs";\r
-       public static final String PLUGINS_DIRNAME = "plugins";\r
-       public static final String APP_PREFERENCES_FILENAME = "app_preferences.xml";\r
-       public static final String JOBQUEUE_FILENAME = "jobs.dat";\r
-       \r
-       public static final int STATE_WORKING = 0;\r
-       public static final int STATE_IDLE = 1;\r
-       \r
-       private RecorderJobPoolExecutor poolExecutor;\r
-       private List<RecordJob> jobs;\r
-       private NDRPreferences preferences = null;\r
-       private List<DemoRecorderUI> registeredUserInterfaces;\r
-       private List<EncoderPlugin> encoderPlugins;\r
-       private int state = STATE_IDLE;\r
-       \r
-       public DemoRecorderApplication() {\r
-               poolExecutor = new RecorderJobPoolExecutor();\r
-               jobs = new CopyOnWriteArrayList<RecordJob>();\r
-               this.registeredUserInterfaces = new ArrayList<DemoRecorderUI>();\r
-               this.encoderPlugins = new ArrayList<EncoderPlugin>();\r
-               this.getPreferences();\r
-               this.loadPlugins();\r
-               this.configurePlugins();\r
-               this.loadJobQueue();\r
-       }\r
-       \r
-       public void setPreference(String category, String preference, boolean value) {\r
-               this.preferences.setProperty(category, preference, String.valueOf(value));\r
-       }\r
-       \r
-       public void setPreference(String category, String preference, int value) {\r
-               this.preferences.setProperty(category, preference, String.valueOf(value));\r
-       }\r
-       \r
-       public void setPreference(String category, String preference, String value) {\r
-               this.preferences.setProperty(category, preference, value);\r
-       }\r
-       \r
-       public NDRPreferences getPreferences() {\r
-               if (this.preferences == null) {\r
-                       this.preferences = new NDRPreferences();\r
-                       this.createPreferenceDefaultValues();\r
-                       File preferencesFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, APP_PREFERENCES_FILENAME);\r
-                       if (preferencesFile.exists()) {\r
-                               FileInputStream fis = null;\r
-                               try {\r
-                                       fis = new FileInputStream(preferencesFile);\r
-                                       this.preferences.loadFromXML(fis);\r
-                               } catch (Exception e) {\r
-                                       DemoRecorderUtils.showNonCriticalErrorDialog("Could not load the application preferences file!", e, true);\r
-                               }\r
-                       }\r
-               }\r
-               \r
-               return this.preferences;\r
-       }\r
-       \r
-       private void createPreferenceDefaultValues() {\r
-               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.OVERWRITE_VIDEO_FILE, "false");\r
-               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_RENDERING, "true");\r
-               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_SOUND, "true");\r
-               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_FIRST_STAGE, "100");\r
-               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_SECOND_STAGE, "10");\r
-               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DO_NOT_DELETE_CUT_DEMOS, "false");\r
-               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.JOB_NAME_APPEND_DUPLICATE, " duplicate");\r
-       }\r
-       \r
-       public void savePreferences() {\r
-               File preferencesFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, APP_PREFERENCES_FILENAME);\r
-               if (!preferencesFile.exists()) {\r
-                       try {\r
-                               preferencesFile.createNewFile();\r
-                       } catch (IOException e) {\r
-                               File parentDir = preferencesFile.getParentFile();\r
-                               if (!parentDir.exists()) {\r
-                                       try {\r
-                                               if (parentDir.mkdirs() == true) {\r
-                                                       try {\r
-                                                               preferencesFile.createNewFile();\r
-                                                       } catch (Exception ex) {}\r
-                                               }\r
-                                       } catch (Exception ex) {}\r
-                               }\r
-                       }\r
-               }\r
-               \r
-               if (!preferencesFile.exists()) {\r
-                       DemoRecorderException ex = new DemoRecorderException("Could not create the preferences file " + preferencesFile.getAbsolutePath());\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog(ex);\r
-                       return;\r
-               }\r
-               \r
-               FileOutputStream fos;\r
-               try {\r
-                       fos = new FileOutputStream(preferencesFile);\r
-               } catch (FileNotFoundException e) {\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog("Could not create the preferences file " + preferencesFile.getAbsolutePath() + ". Unsufficient rights?", e, true);\r
-                       return;\r
-               }\r
-               try {\r
-                       this.preferences.storeToXML(fos, null);\r
-               } catch (IOException e) {\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog("Could not create the preferences file " + preferencesFile.getAbsolutePath(), e, true);\r
-               }\r
-       }\r
-       \r
-       public List<RecordJob> getRecordJobs() {\r
-               return new ArrayList<RecordJob>(this.jobs);\r
-       }\r
-       \r
-       public void startRecording() {\r
-               if (this.state != STATE_WORKING) {\r
-                       this.state = STATE_WORKING;\r
-                       \r
-                       for (RecordJob currentJob : this.jobs) {\r
-                               if (currentJob.getState() == RecordJob.State.WAITING) {\r
-                                       this.poolExecutor.runJob(currentJob);\r
-                               }\r
-                       }\r
-                       \r
-                       //notify ourself when job is done\r
-                       this.poolExecutor.runJob(new RecordsDoneJob(this));\r
-               }\r
-       }\r
-       \r
-       public void recordSelectedJobs(List<RecordJob> jobList) {\r
-               if (this.state == STATE_IDLE) {\r
-                       this.state = STATE_WORKING;\r
-                       for (RecordJob currentJob : jobList) {\r
-                               if (currentJob.getState() == RecordJob.State.WAITING) {\r
-                                       this.poolExecutor.runJob(currentJob);\r
-                               }\r
-                       }\r
-                       \r
-                       //notify ourself when job is done\r
-                       this.poolExecutor.runJob(new RecordsDoneJob(this));\r
-               }\r
-       }\r
-       \r
-       public void executePluginForSelectedJobs(EncoderPlugin plugin, List<RecordJob> jobList) {\r
-               if (this.state == STATE_IDLE) {\r
-                       this.state = STATE_WORKING;\r
-                       for (RecordJob currentJob : jobList) {\r
-                               if (currentJob.getState() == RecordJob.State.DONE) {\r
-                                       this.poolExecutor.runJob(new EncoderJob(currentJob, plugin));\r
-                               }\r
-                       }\r
-                       \r
-                       //notify ourself when job is done\r
-                       this.poolExecutor.runJob(new RecordsDoneJob(this));\r
-               }\r
-       }\r
-       \r
-       public void notifyAllJobsDone() {\r
-               this.state = STATE_IDLE;\r
-               \r
-               //notify all UIs\r
-               for (DemoRecorderUI currentUI : this.registeredUserInterfaces) {\r
-                       currentUI.recordingFinished();\r
-               }\r
-       }\r
-       \r
-       public synchronized void stopRecording() {\r
-               if (this.state == STATE_WORKING) {\r
-                       //clear the queue of the threadpoolexecutor and add the GUI/applayer notify job again\r
-                       this.poolExecutor.clearUnfinishedJobs();\r
-                       this.poolExecutor.runJob(new RecordsDoneJob(this));\r
-               }\r
-       }\r
-       \r
-       public RecordJob createRecordJob(\r
-               String name,\r
-               File enginePath,\r
-               String engineParameters,\r
-               File demoFile,\r
-               String relativeDemoPath,\r
-               File dpVideoPath,\r
-               File videoDestination,\r
-               String executeBeforeCap,\r
-               String executeAfterCap,\r
-               float startSecond,\r
-               float endSecond\r
-       ) {\r
-               int jobIndex = -1;\r
-               if (name == null || name.equals("")) {\r
-                       //we don't have a name, so use a generic one \r
-                       jobIndex = this.getNewJobIndex();\r
-                       name = "Job " + jobIndex;\r
-               } else {\r
-                       //just use the name and keep jobIndex at -1. Jobs with real names don't need an index\r
-               }\r
-               \r
-               \r
-               \r
-               RecordJob newJob = new RecordJob(\r
-                       this,\r
-                       name,\r
-                       jobIndex,\r
-                       enginePath,\r
-                       engineParameters,\r
-                       demoFile,\r
-                       relativeDemoPath,\r
-                       dpVideoPath,\r
-                       videoDestination,\r
-                       executeBeforeCap,\r
-                       executeAfterCap,\r
-                       startSecond,\r
-                       endSecond\r
-               );\r
-               this.jobs.add(newJob);\r
-               this.fireUserInterfaceUpdate(newJob);\r
-               \r
-               return newJob;\r
-       }\r
-       \r
-       public synchronized boolean deleteRecordJob(RecordJob job) {\r
-               if (!this.jobs.contains(job)) {\r
-                       return false;\r
-               }\r
-               \r
-               //don't delete jobs that are scheduled for execution\r
-               if (this.poolExecutor.getJobList().contains(job)) {\r
-                       return false;\r
-               }\r
-               \r
-               this.jobs.remove(job);\r
-               return true;\r
-       }\r
-       \r
-       public void addUserInterfaceListener(DemoRecorderUI ui) {\r
-               this.registeredUserInterfaces.add(ui);\r
-       }\r
-       \r
-       /**\r
-        * Makes sure that all registered user interfaces can update their view/display.\r
-        * @param job either a job that's new to the UI, or one the UI already knows but of which details changed\r
-        */\r
-       public void fireUserInterfaceUpdate(RecordJob job) {\r
-               for (DemoRecorderUI ui : this.registeredUserInterfaces) {\r
-                       ui.RecordJobPropertiesChange(job);\r
-               }\r
-       }\r
-       \r
-       public int getNewJobIndex() {\r
-               int jobIndex;\r
-               if (this.jobs.size() == 0) {\r
-                       jobIndex = 1;\r
-               } else {\r
-                       int greatestIndex = -1;\r
-                       for (RecordJob j : this.jobs) {\r
-                               if (j.getJobIndex() > greatestIndex) {\r
-                                       greatestIndex = j.getJobIndex();\r
-                               }\r
-                       }\r
-                       if (greatestIndex == -1) {\r
-                               jobIndex = 1;\r
-                       } else {\r
-                               jobIndex = greatestIndex + 1;\r
-                       }\r
-               }\r
-               \r
-               return jobIndex;\r
-       }\r
-       \r
-       private void loadJobQueue() {\r
-               File defaultFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, JOBQUEUE_FILENAME);\r
-               this.loadJobQueue(defaultFile, true);\r
-       }\r
-       \r
-       /**\r
-        * Loads the jobs from the given file path. If override is enabled, the previous\r
-        * job list will be overwritten with the newly loaded list. Otherwise the loaded jobs\r
-        * are added to the already existing list.\r
-        * @param path\r
-        * @param override\r
-        * @return the number of jobs loaded from the file\r
-        */\r
-       @SuppressWarnings("unchecked")\r
-       public int loadJobQueue(File path, boolean override) {\r
-               if (!path.exists()) {\r
-                       return 0;\r
-               }\r
-               \r
-               try {\r
-                       FileInputStream fin = new FileInputStream(path);\r
-                       ObjectInputStream ois = new ObjectInputStream(fin);\r
-                       List<RecordJob> newList = (List<RecordJob>) ois.readObject();\r
-                       for (RecordJob currentJob : newList) {\r
-                               currentJob.setAppLayer(this);\r
-                       }\r
-                       if (override) {\r
-                               this.jobs = newList;\r
-                       } else {\r
-                               this.jobs.addAll(newList);\r
-                       }\r
-                       return newList.size();\r
-               } catch (Exception e) {\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog("Could not load the job queue file " + path.getAbsolutePath(), e, true);\r
-                       return 0;\r
-               }\r
-       }\r
-       \r
-       public void saveJobQueue() {\r
-               File defaultFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, JOBQUEUE_FILENAME);\r
-               this.saveJobQueue(defaultFile);\r
-       }\r
-       \r
-       public void saveJobQueue(File path) {\r
-               if (!path.exists()) {\r
-                       try {\r
-                               path.createNewFile();\r
-                       } catch (IOException e) {\r
-                               File parentDir = path.getParentFile();\r
-                               if (!parentDir.exists()) {\r
-                                       try {\r
-                                               if (parentDir.mkdirs() == true) {\r
-                                                       try {\r
-                                                               path.createNewFile();\r
-                                                       } catch (Exception ex) {}\r
-                                               }\r
-                                       } catch (Exception ex) {}\r
-                               }\r
-                       }\r
-               }\r
-               \r
-               String exceptionMessage = "Could not save the job queue file " + path.getAbsolutePath();\r
-               \r
-               if (!path.exists()) {\r
-                       DemoRecorderException ex = new DemoRecorderException(exceptionMessage);\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog(ex);\r
-                       return;\r
-               }\r
-               \r
-               //make sure that for the next start of the program the state is set to waiting again\r
-               for (RecordJob job : this.jobs) {\r
-                       if (job.getState() == RecordJob.State.PROCESSING) {\r
-                               job.setState(RecordJob.State.WAITING);\r
-                       }\r
-                       job.setAppLayer(null); //we don't want to serialize the app layer!\r
-               }\r
-               \r
-               try {\r
-                       FileOutputStream fout = new FileOutputStream(path);\r
-                       ObjectOutputStream oos = new ObjectOutputStream(fout);\r
-                       oos.writeObject(this.jobs);\r
-                       oos.close();\r
-               } catch (Exception e) {\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);\r
-               }\r
-               \r
-               //we sometimes also save the jobqueue and don't exit the program, so restore the applayer again\r
-               for (RecordJob job : this.jobs) {\r
-                       job.setAppLayer(this);\r
-               }\r
-       }\r
-       \r
-       public void shutDown() {\r
-               this.poolExecutor.shutDown();\r
-               this.savePreferences();\r
-               this.saveJobQueue();\r
-       }\r
-       \r
-       public int getState() {\r
-               return this.state;\r
-       }\r
-       \r
-       private void loadPlugins() {\r
-               File pluginDir = DemoRecorderUtils.computeLocalFile(PLUGINS_DIRNAME, "");\r
-\r
-               if (!pluginDir.exists()) {\r
-                       pluginDir.mkdir();\r
-               }\r
-\r
-               File[] jarFiles = pluginDir.listFiles();\r
-\r
-               List<URL> urlList = new ArrayList<URL>();\r
-               for (File f : jarFiles) {\r
-                       try {\r
-                               urlList.add(f.toURI().toURL());\r
-                       } catch (MalformedURLException ex) {}\r
-               }\r
-               ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();\r
-               URL[] urls = new URL[urlList.size()];\r
-               urls = urlList.toArray(urls);\r
-               URLClassLoader classLoader = new URLClassLoader(urls, parentLoader);\r
-               \r
-               ServiceLoader<EncoderPlugin> loader = ServiceLoader.load(EncoderPlugin.class, classLoader);\r
-               for (EncoderPlugin implementation : loader) {\r
-                       this.encoderPlugins.add(implementation);\r
-               }\r
-       }\r
-       \r
-       private void configurePlugins() {\r
-               for (EncoderPlugin plugin : this.encoderPlugins) {\r
-                       plugin.setApplicationLayer(this);\r
-                       Properties pluginPreferences = plugin.getGlobalPreferences();\r
-                       for (Object preference : pluginPreferences.keySet()) {\r
-                               String preferenceString = (String) preference;\r
-                               \r
-                               if (this.preferences.getProperty(plugin.getName(), preferenceString) == null) {\r
-                                       String defaultValue = pluginPreferences.getProperty(preferenceString);\r
-                                       this.preferences.setProperty(plugin.getName(), preferenceString, defaultValue);\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-\r
-       public List<EncoderPlugin> getEncoderPlugins() {\r
-               return encoderPlugins;\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.application;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.ServiceLoader;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.nexuiz.demorecorder.application.jobs.EncoderJob;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+import com.nexuiz.demorecorder.application.jobs.RecordsDoneJob;
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+import com.nexuiz.demorecorder.ui.DemoRecorderUI;
+
+public class DemoRecorderApplication {
+       
+       public static class Preferences {
+               public static final String OVERWRITE_VIDEO_FILE = "Overwrite final video destination file if it exists";
+               public static final String DISABLE_RENDERING = "Disable rendering while fast-forwarding";
+               public static final String DISABLE_SOUND = "Disable sound while fast-forwarding";
+               public static final String FFW_SPEED_FIRST_STAGE = "Fast-forward speed (first stage)";
+               public static final String FFW_SPEED_SECOND_STAGE = "Fast-forward speed (second stage)";
+               public static final String DO_NOT_DELETE_CUT_DEMOS = "Do not delete cut demos";
+               public static final String JOB_NAME_APPEND_DUPLICATE = "Append this suffix to job-name when duplicating jobs";
+               
+               public static final String[] PREFERENCES_ORDER = {
+                       OVERWRITE_VIDEO_FILE,
+                       DISABLE_RENDERING,
+                       DISABLE_SOUND,
+                       FFW_SPEED_FIRST_STAGE,
+                       FFW_SPEED_SECOND_STAGE,
+                       DO_NOT_DELETE_CUT_DEMOS,
+                       JOB_NAME_APPEND_DUPLICATE
+               };
+       }
+       
+       public static final String PREFERENCES_DIRNAME = "settings";
+       public static final String LOGS_DIRNAME = "logs";
+       public static final String PLUGINS_DIRNAME = "plugins";
+       public static final String APP_PREFERENCES_FILENAME = "app_preferences.xml";
+       public static final String JOBQUEUE_FILENAME = "jobs.dat";
+       
+       public static final int STATE_WORKING = 0;
+       public static final int STATE_IDLE = 1;
+       
+       private RecorderJobPoolExecutor poolExecutor;
+       private List<RecordJob> jobs;
+       private NDRPreferences preferences = null;
+       private List<DemoRecorderUI> registeredUserInterfaces;
+       private List<EncoderPlugin> encoderPlugins;
+       private int state = STATE_IDLE;
+       
+       public DemoRecorderApplication() {
+               poolExecutor = new RecorderJobPoolExecutor();
+               jobs = new CopyOnWriteArrayList<RecordJob>();
+               this.registeredUserInterfaces = new ArrayList<DemoRecorderUI>();
+               this.encoderPlugins = new ArrayList<EncoderPlugin>();
+               this.getPreferences();
+               this.loadPlugins();
+               this.configurePlugins();
+               this.loadJobQueue();
+       }
+       
+       public void setPreference(String category, String preference, boolean value) {
+               this.preferences.setProperty(category, preference, String.valueOf(value));
+       }
+       
+       public void setPreference(String category, String preference, int value) {
+               this.preferences.setProperty(category, preference, String.valueOf(value));
+       }
+       
+       public void setPreference(String category, String preference, String value) {
+               this.preferences.setProperty(category, preference, value);
+       }
+       
+       public NDRPreferences getPreferences() {
+               if (this.preferences == null) {
+                       this.preferences = new NDRPreferences();
+                       this.createPreferenceDefaultValues();
+                       File preferencesFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, APP_PREFERENCES_FILENAME);
+                       if (preferencesFile.exists()) {
+                               FileInputStream fis = null;
+                               try {
+                                       fis = new FileInputStream(preferencesFile);
+                                       this.preferences.loadFromXML(fis);
+                               } catch (Exception e) {
+                                       DemoRecorderUtils.showNonCriticalErrorDialog("Could not load the application preferences file!", e, true);
+                               }
+                       }
+               }
+               
+               return this.preferences;
+       }
+       
+       private void createPreferenceDefaultValues() {
+               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.OVERWRITE_VIDEO_FILE, "false");
+               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_RENDERING, "true");
+               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_SOUND, "true");
+               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_FIRST_STAGE, "100");
+               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_SECOND_STAGE, "10");
+               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DO_NOT_DELETE_CUT_DEMOS, "false");
+               this.preferences.setProperty(NDRPreferences.MAIN_APPLICATION, Preferences.JOB_NAME_APPEND_DUPLICATE, " duplicate");
+       }
+       
+       public void savePreferences() {
+               File preferencesFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, APP_PREFERENCES_FILENAME);
+               if (!preferencesFile.exists()) {
+                       try {
+                               preferencesFile.createNewFile();
+                       } catch (IOException e) {
+                               File parentDir = preferencesFile.getParentFile();
+                               if (!parentDir.exists()) {
+                                       try {
+                                               if (parentDir.mkdirs() == true) {
+                                                       try {
+                                                               preferencesFile.createNewFile();
+                                                       } catch (Exception ex) {}
+                                               }
+                                       } catch (Exception ex) {}
+                               }
+                       }
+               }
+               
+               if (!preferencesFile.exists()) {
+                       DemoRecorderException ex = new DemoRecorderException("Could not create the preferences file " + preferencesFile.getAbsolutePath());
+                       DemoRecorderUtils.showNonCriticalErrorDialog(ex);
+                       return;
+               }
+               
+               FileOutputStream fos;
+               try {
+                       fos = new FileOutputStream(preferencesFile);
+               } catch (FileNotFoundException e) {
+                       DemoRecorderUtils.showNonCriticalErrorDialog("Could not create the preferences file " + preferencesFile.getAbsolutePath() + ". Unsufficient rights?", e, true);
+                       return;
+               }
+               try {
+                       this.preferences.storeToXML(fos, null);
+               } catch (IOException e) {
+                       DemoRecorderUtils.showNonCriticalErrorDialog("Could not create the preferences file " + preferencesFile.getAbsolutePath(), e, true);
+               }
+       }
+       
+       public List<RecordJob> getRecordJobs() {
+               return new ArrayList<RecordJob>(this.jobs);
+       }
+       
+       public void startRecording() {
+               if (this.state != STATE_WORKING) {
+                       this.state = STATE_WORKING;
+                       
+                       for (RecordJob currentJob : this.jobs) {
+                               if (currentJob.getState() == RecordJob.State.WAITING) {
+                                       this.poolExecutor.runJob(currentJob);
+                               }
+                       }
+                       
+                       //notify ourself when job is done
+                       this.poolExecutor.runJob(new RecordsDoneJob(this));
+               }
+       }
+       
+       public void recordSelectedJobs(List<RecordJob> jobList) {
+               if (this.state == STATE_IDLE) {
+                       this.state = STATE_WORKING;
+                       for (RecordJob currentJob : jobList) {
+                               if (currentJob.getState() == RecordJob.State.WAITING) {
+                                       this.poolExecutor.runJob(currentJob);
+                               }
+                       }
+                       
+                       //notify ourself when job is done
+                       this.poolExecutor.runJob(new RecordsDoneJob(this));
+               }
+       }
+       
+       public void executePluginForSelectedJobs(EncoderPlugin plugin, List<RecordJob> jobList) {
+               if (this.state == STATE_IDLE) {
+                       this.state = STATE_WORKING;
+                       for (RecordJob currentJob : jobList) {
+                               if (currentJob.getState() == RecordJob.State.DONE) {
+                                       this.poolExecutor.runJob(new EncoderJob(currentJob, plugin));
+                               }
+                       }
+                       
+                       //notify ourself when job is done
+                       this.poolExecutor.runJob(new RecordsDoneJob(this));
+               }
+       }
+       
+       public void notifyAllJobsDone() {
+               this.state = STATE_IDLE;
+               
+               //notify all UIs
+               for (DemoRecorderUI currentUI : this.registeredUserInterfaces) {
+                       currentUI.recordingFinished();
+               }
+       }
+       
+       public synchronized void stopRecording() {
+               if (this.state == STATE_WORKING) {
+                       //clear the queue of the threadpoolexecutor and add the GUI/applayer notify job again
+                       this.poolExecutor.clearUnfinishedJobs();
+                       this.poolExecutor.runJob(new RecordsDoneJob(this));
+               }
+       }
+       
+       public RecordJob createRecordJob(
+               String name,
+               File enginePath,
+               String engineParameters,
+               File demoFile,
+               String relativeDemoPath,
+               File dpVideoPath,
+               File videoDestination,
+               String executeBeforeCap,
+               String executeAfterCap,
+               float startSecond,
+               float endSecond
+       ) {
+               int jobIndex = -1;
+               if (name == null || name.equals("")) {
+                       //we don't have a name, so use a generic one 
+                       jobIndex = this.getNewJobIndex();
+                       name = "Job " + jobIndex;
+               } else {
+                       //just use the name and keep jobIndex at -1. Jobs with real names don't need an index
+               }
+               
+               
+               
+               RecordJob newJob = new RecordJob(
+                       this,
+                       name,
+                       jobIndex,
+                       enginePath,
+                       engineParameters,
+                       demoFile,
+                       relativeDemoPath,
+                       dpVideoPath,
+                       videoDestination,
+                       executeBeforeCap,
+                       executeAfterCap,
+                       startSecond,
+                       endSecond
+               );
+               this.jobs.add(newJob);
+               this.fireUserInterfaceUpdate(newJob);
+               
+               return newJob;
+       }
+       
+       public synchronized boolean deleteRecordJob(RecordJob job) {
+               if (!this.jobs.contains(job)) {
+                       return false;
+               }
+               
+               //don't delete jobs that are scheduled for execution
+               if (this.poolExecutor.getJobList().contains(job)) {
+                       return false;
+               }
+               
+               this.jobs.remove(job);
+               return true;
+       }
+       
+       public void addUserInterfaceListener(DemoRecorderUI ui) {
+               this.registeredUserInterfaces.add(ui);
+       }
+       
+       /**
+        * Makes sure that all registered user interfaces can update their view/display.
+        * @param job either a job that's new to the UI, or one the UI already knows but of which details changed
+        */
+       public void fireUserInterfaceUpdate(RecordJob job) {
+               for (DemoRecorderUI ui : this.registeredUserInterfaces) {
+                       ui.RecordJobPropertiesChange(job);
+               }
+       }
+       
+       public int getNewJobIndex() {
+               int jobIndex;
+               if (this.jobs.size() == 0) {
+                       jobIndex = 1;
+               } else {
+                       int greatestIndex = -1;
+                       for (RecordJob j : this.jobs) {
+                               if (j.getJobIndex() > greatestIndex) {
+                                       greatestIndex = j.getJobIndex();
+                               }
+                       }
+                       if (greatestIndex == -1) {
+                               jobIndex = 1;
+                       } else {
+                               jobIndex = greatestIndex + 1;
+                       }
+               }
+               
+               return jobIndex;
+       }
+       
+       private void loadJobQueue() {
+               File defaultFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, JOBQUEUE_FILENAME);
+               this.loadJobQueue(defaultFile, true);
+       }
+       
+       /**
+        * Loads the jobs from the given file path. If override is enabled, the previous
+        * job list will be overwritten with the newly loaded list. Otherwise the loaded jobs
+        * are added to the already existing list.
+        * @param path
+        * @param override
+        * @return the number of jobs loaded from the file
+        */
+       @SuppressWarnings("unchecked")
+       public int loadJobQueue(File path, boolean override) {
+               if (!path.exists()) {
+                       return 0;
+               }
+               
+               try {
+                       FileInputStream fin = new FileInputStream(path);
+                       ObjectInputStream ois = new ObjectInputStream(fin);
+                       List<RecordJob> newList = (List<RecordJob>) ois.readObject();
+                       for (RecordJob currentJob : newList) {
+                               currentJob.setAppLayer(this);
+                       }
+                       if (override) {
+                               this.jobs = newList;
+                       } else {
+                               this.jobs.addAll(newList);
+                       }
+                       return newList.size();
+               } catch (Exception e) {
+                       DemoRecorderUtils.showNonCriticalErrorDialog("Could not load the job queue file " + path.getAbsolutePath(), e, true);
+                       return 0;
+               }
+       }
+       
+       public void saveJobQueue() {
+               File defaultFile = DemoRecorderUtils.computeLocalFile(PREFERENCES_DIRNAME, JOBQUEUE_FILENAME);
+               this.saveJobQueue(defaultFile);
+       }
+       
+       public void saveJobQueue(File path) {
+               if (!path.exists()) {
+                       try {
+                               path.createNewFile();
+                       } catch (IOException e) {
+                               File parentDir = path.getParentFile();
+                               if (!parentDir.exists()) {
+                                       try {
+                                               if (parentDir.mkdirs() == true) {
+                                                       try {
+                                                               path.createNewFile();
+                                                       } catch (Exception ex) {}
+                                               }
+                                       } catch (Exception ex) {}
+                               }
+                       }
+               }
+               
+               String exceptionMessage = "Could not save the job queue file " + path.getAbsolutePath();
+               
+               if (!path.exists()) {
+                       DemoRecorderException ex = new DemoRecorderException(exceptionMessage);
+                       DemoRecorderUtils.showNonCriticalErrorDialog(ex);
+                       return;
+               }
+               
+               //make sure that for the next start of the program the state is set to waiting again
+               for (RecordJob job : this.jobs) {
+                       if (job.getState() == RecordJob.State.PROCESSING) {
+                               job.setState(RecordJob.State.WAITING);
+                       }
+                       job.setAppLayer(null); //we don't want to serialize the app layer!
+               }
+               
+               try {
+                       FileOutputStream fout = new FileOutputStream(path);
+                       ObjectOutputStream oos = new ObjectOutputStream(fout);
+                       oos.writeObject(this.jobs);
+                       oos.close();
+               } catch (Exception e) {
+                       DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);
+               }
+               
+               //we sometimes also save the jobqueue and don't exit the program, so restore the applayer again
+               for (RecordJob job : this.jobs) {
+                       job.setAppLayer(this);
+               }
+       }
+       
+       public void shutDown() {
+               this.poolExecutor.shutDown();
+               this.savePreferences();
+               this.saveJobQueue();
+       }
+       
+       public int getState() {
+               return this.state;
+       }
+       
+       private void loadPlugins() {
+               File pluginDir = DemoRecorderUtils.computeLocalFile(PLUGINS_DIRNAME, "");
+
+               if (!pluginDir.exists()) {
+                       pluginDir.mkdir();
+               }
+
+               File[] jarFiles = pluginDir.listFiles();
+
+               List<URL> urlList = new ArrayList<URL>();
+               for (File f : jarFiles) {
+                       try {
+                               urlList.add(f.toURI().toURL());
+                       } catch (MalformedURLException ex) {}
+               }
+               ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
+               URL[] urls = new URL[urlList.size()];
+               urls = urlList.toArray(urls);
+               URLClassLoader classLoader = new URLClassLoader(urls, parentLoader);
+               
+               ServiceLoader<EncoderPlugin> loader = ServiceLoader.load(EncoderPlugin.class, classLoader);
+               for (EncoderPlugin implementation : loader) {
+                       this.encoderPlugins.add(implementation);
+               }
+       }
+       
+       private void configurePlugins() {
+               for (EncoderPlugin plugin : this.encoderPlugins) {
+                       plugin.setApplicationLayer(this);
+                       Properties pluginPreferences = plugin.getGlobalPreferences();
+                       for (Object preference : pluginPreferences.keySet()) {
+                               String preferenceString = (String) preference;
+                               
+                               if (this.preferences.getProperty(plugin.getName(), preferenceString) == null) {
+                                       String defaultValue = pluginPreferences.getProperty(preferenceString);
+                                       this.preferences.setProperty(plugin.getName(), preferenceString, defaultValue);
+                               }
+                       }
+               }
+       }
+
+       public List<EncoderPlugin> getEncoderPlugins() {
+               return encoderPlugins;
+       }
+}
index f9318f456fec8b69ef3920ae3c01bdf67906b06e..c12bf171f1cec52fddc04b22def2af34d53cde11 100644 (file)
@@ -1,13 +1,13 @@
-package com.nexuiz.demorecorder.application;\r
-\r
-public class DemoRecorderException extends RuntimeException {\r
-       \r
-       private static final long serialVersionUID = 965053013957793155L;\r
-       public DemoRecorderException(String message) {\r
-               super(message);\r
-       }\r
-       public DemoRecorderException(String message, Throwable cause) {\r
-               super(message, cause);\r
-       }\r
-\r
-}\r
+package com.nexuiz.demorecorder.application;
+
+public class DemoRecorderException extends RuntimeException {
+       
+       private static final long serialVersionUID = 965053013957793155L;
+       public DemoRecorderException(String message) {
+               super(message);
+       }
+       public DemoRecorderException(String message, Throwable cause) {
+               super(message, cause);
+       }
+
+}
index e46e7edeca0dcb90574f06381a36735ac9118619..1da4f1d7c01ecad78eaaf98274518ea0fe3465e3 100644 (file)
@@ -1,95 +1,95 @@
-package com.nexuiz.demorecorder.application;\r
-\r
-import java.io.File;\r
-import java.io.IOException;\r
-\r
-import org.jdesktop.swingx.JXErrorPane;\r
-import org.jdesktop.swingx.error.ErrorInfo;\r
-\r
-public class DemoRecorderUtils {\r
-       \r
-       public static void showNonCriticalErrorDialog(Throwable e) {\r
-               if (!(e instanceof DemoRecorderException)) {\r
-                       e = new DemoRecorderException("Internal error", e);\r
-               }\r
-               ErrorInfo info = new ErrorInfo("Error occurred", e.getMessage(), null, null, e, null, null);\r
-               JXErrorPane.showDialog(null, info);\r
-       }\r
-       \r
-       /**\r
-        * Shows an error dialog that contains the stack trace, catching the exception so that the program flow\r
-        * won't be interrupted.\r
-        * This method will maybe wrap e in a DemoRecorderException with the given message.\r
-        * @param customMessage\r
-        * @param e\r
-        * @param wrapException set to true if Exception should be wrapped into a DemoRecorderException\r
-        */\r
-       public static void showNonCriticalErrorDialog(String customMessage, Throwable e, boolean wrapException) {\r
-               Throwable ex = e;\r
-               if (wrapException && !(e instanceof DemoRecorderException)) {\r
-                       ex = new DemoRecorderException(customMessage, e);\r
-               }\r
-               \r
-               ErrorInfo info = new ErrorInfo("Error occurred", ex.getMessage(), null, null, ex, null, null);\r
-               JXErrorPane.showDialog(null, info);\r
-       }\r
-       \r
-       public static File computeLocalFile(String subDir, String fileName) {\r
-               String path = System.getProperty("user.dir");\r
-               if (subDir != null && !subDir.equals("")) {\r
-                       path += File.separator + subDir;\r
-               }\r
-               path += File.separator + fileName;\r
-               return new File(path);\r
-       }\r
-       \r
-       /**\r
-        * Returns just the name of the file for a given File. E.g. if the File points to\r
-        * /home/someuser/somedir/somefile.end the function will return "somefile.end"\r
-        * @param file\r
-        * @return just the name of the file\r
-        */\r
-       public static String getJustFileNameOfPath(File file) {\r
-               String fileString = file.getAbsolutePath();\r
-               int lastIndex = fileString.lastIndexOf(File.separator);\r
-               String newString = fileString.substring(lastIndex+1, fileString.length());\r
-               return newString;\r
-       }\r
-       \r
-       /**\r
-        * Attempts to create an empty file (unless it already exists), including the creation\r
-        * of parent directories. If it succeeds to do so (or if the file already existed), true\r
-        * will be returned. Otherwise false will be returned\r
-        * @param file the file to be created\r
-        * @return true if file already existed or could successfully created, false otherwise\r
-        */\r
-       public static boolean attemptFileCreation(File file) {\r
-               if (!file.exists()) {\r
-                       try {\r
-                               file.createNewFile();\r
-                               return true;\r
-                       } catch (IOException e) {\r
-                               File parentDir = file.getParentFile();\r
-                               if (!parentDir.exists()) {\r
-                                       try {\r
-                                               if (parentDir.mkdirs() == true) {\r
-                                                       try {\r
-                                                               file.createNewFile();\r
-                                                               return true;\r
-                                                       } catch (Exception ex) {}\r
-                                               }\r
-                                       } catch (Exception ex) {}\r
-                               }\r
-                               return false;\r
-                       }\r
-               } else {\r
-                       return true;\r
-               }\r
-       }\r
-       \r
-       public static final String getFileExtension(File file) {\r
-               String fileName = file.getAbsolutePath();\r
-               String ext = (fileName.lastIndexOf(".") == -1) ? "" : fileName.substring(fileName.lastIndexOf(".") + 1,fileName.length());\r
-               return ext;\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.application;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.jdesktop.swingx.JXErrorPane;
+import org.jdesktop.swingx.error.ErrorInfo;
+
+public class DemoRecorderUtils {
+       
+       public static void showNonCriticalErrorDialog(Throwable e) {
+               if (!(e instanceof DemoRecorderException)) {
+                       e = new DemoRecorderException("Internal error", e);
+               }
+               ErrorInfo info = new ErrorInfo("Error occurred", e.getMessage(), null, null, e, null, null);
+               JXErrorPane.showDialog(null, info);
+       }
+       
+       /**
+        * Shows an error dialog that contains the stack trace, catching the exception so that the program flow
+        * won't be interrupted.
+        * This method will maybe wrap e in a DemoRecorderException with the given message.
+        * @param customMessage
+        * @param e
+        * @param wrapException set to true if Exception should be wrapped into a DemoRecorderException
+        */
+       public static void showNonCriticalErrorDialog(String customMessage, Throwable e, boolean wrapException) {
+               Throwable ex = e;
+               if (wrapException && !(e instanceof DemoRecorderException)) {
+                       ex = new DemoRecorderException(customMessage, e);
+               }
+               
+               ErrorInfo info = new ErrorInfo("Error occurred", ex.getMessage(), null, null, ex, null, null);
+               JXErrorPane.showDialog(null, info);
+       }
+       
+       public static File computeLocalFile(String subDir, String fileName) {
+               String path = System.getProperty("user.dir");
+               if (subDir != null && !subDir.equals("")) {
+                       path += File.separator + subDir;
+               }
+               path += File.separator + fileName;
+               return new File(path);
+       }
+       
+       /**
+        * Returns just the name of the file for a given File. E.g. if the File points to
+        * /home/someuser/somedir/somefile.end the function will return "somefile.end"
+        * @param file
+        * @return just the name of the file
+        */
+       public static String getJustFileNameOfPath(File file) {
+               String fileString = file.getAbsolutePath();
+               int lastIndex = fileString.lastIndexOf(File.separator);
+               String newString = fileString.substring(lastIndex+1, fileString.length());
+               return newString;
+       }
+       
+       /**
+        * Attempts to create an empty file (unless it already exists), including the creation
+        * of parent directories. If it succeeds to do so (or if the file already existed), true
+        * will be returned. Otherwise false will be returned
+        * @param file the file to be created
+        * @return true if file already existed or could successfully created, false otherwise
+        */
+       public static boolean attemptFileCreation(File file) {
+               if (!file.exists()) {
+                       try {
+                               file.createNewFile();
+                               return true;
+                       } catch (IOException e) {
+                               File parentDir = file.getParentFile();
+                               if (!parentDir.exists()) {
+                                       try {
+                                               if (parentDir.mkdirs() == true) {
+                                                       try {
+                                                               file.createNewFile();
+                                                               return true;
+                                                       } catch (Exception ex) {}
+                                               }
+                                       } catch (Exception ex) {}
+                               }
+                               return false;
+                       }
+               } else {
+                       return true;
+               }
+       }
+       
+       public static final String getFileExtension(File file) {
+               String fileName = file.getAbsolutePath();
+               String ext = (fileName.lastIndexOf(".") == -1) ? "" : fileName.substring(fileName.lastIndexOf(".") + 1,fileName.length());
+               return ext;
+       }
+}
index b599e4aea6923a88d9fc7d43b31cb9b37bb31d23..84042f744266599f9d5a396169f2546b93c08c5d 100644 (file)
@@ -1,66 +1,66 @@
-package com.nexuiz.demorecorder.application;\r
-\r
-import java.util.Properties;\r
-\r
-/**\r
- * Class that stores the application and global plug-in preferences of the Nexuiz\r
- * Demo Recorder application. Set and Get property methods have been modified to \r
- * now supply a category.\r
- */\r
-public class NDRPreferences extends Properties {\r
-       \r
-       private static final long serialVersionUID = 4363913054294979418L;\r
-       private static final String CONCATENATOR = ".";\r
-       /**\r
-        * Category that defines a setting to be a setting of the NDR application itself\r
-        * (and not of one of the plugins).\r
-        */\r
-       public static final String MAIN_APPLICATION = "NDR";\r
-\r
-       /**\r
-     * Searches for the property with the specified key in this property list.\r
-     * If the key is not found in this property list, the default property list,\r
-     * and its defaults, recursively, are then checked. The method returns\r
-     * <code>null</code> if the property is not found.\r
-     *\r
-     * @param   category the category of the setting\r
-     * @param   key   the property key.\r
-     * @return  the value in this property list with the specified category+key value.\r
-     */\r
-       public String getProperty(String category, String key) {\r
-               return getProperty(getConcatenatedKey(category, key));\r
-       }\r
-       \r
-       /**\r
-     * Calls the <tt>Hashtable</tt> method <code>put</code>. Provided for\r
-     * parallelism with the <tt>getProperty</tt> method. Enforces use of\r
-     * strings for property keys and values. The value returned is the\r
-     * result of the <tt>Hashtable</tt> call to <code>put</code>.\r
-     *\r
-     * @param category the category of the setting\r
-     * @param key the key to be placed into this property list.\r
-     * @param value the value corresponding to <tt>key</tt>.\r
-     * @return     the previous value of the specified key in this property\r
-     *             list, or <code>null</code> if it did not have one.\r
-     */\r
-       public void setProperty(String category, String key, String value) {\r
-               setProperty(getConcatenatedKey(category, key), value);\r
-       }\r
-       \r
-       /**\r
-        * Returns only the category of a key that is a concatenated string of category and key.\r
-        * @param concatenatedString\r
-        * @return\r
-        */\r
-       public static String getCategory(String concatenatedString) {\r
-               return concatenatedString.substring(0, concatenatedString.indexOf(CONCATENATOR));\r
-       }\r
-       \r
-       public static String getKey(String concatenatedString) {\r
-               return concatenatedString.substring(concatenatedString.indexOf(CONCATENATOR) + 1, concatenatedString.length());\r
-       }\r
-       \r
-       public static String getConcatenatedKey(String category, String key) {\r
-               return category + CONCATENATOR + key;\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.application;
+
+import java.util.Properties;
+
+/**
+ * Class that stores the application and global plug-in preferences of the Nexuiz
+ * Demo Recorder application. Set and Get property methods have been modified to 
+ * now supply a category.
+ */
+public class NDRPreferences extends Properties {
+       
+       private static final long serialVersionUID = 4363913054294979418L;
+       private static final String CONCATENATOR = ".";
+       /**
+        * Category that defines a setting to be a setting of the NDR application itself
+        * (and not of one of the plugins).
+        */
+       public static final String MAIN_APPLICATION = "NDR";
+
+       /**
+     * Searches for the property with the specified key in this property list.
+     * If the key is not found in this property list, the default property list,
+     * and its defaults, recursively, are then checked. The method returns
+     * <code>null</code> if the property is not found.
+     *
+     * @param   category the category of the setting
+     * @param   key   the property key.
+     * @return  the value in this property list with the specified category+key value.
+     */
+       public String getProperty(String category, String key) {
+               return getProperty(getConcatenatedKey(category, key));
+       }
+       
+       /**
+     * Calls the <tt>Hashtable</tt> method <code>put</code>. Provided for
+     * parallelism with the <tt>getProperty</tt> method. Enforces use of
+     * strings for property keys and values. The value returned is the
+     * result of the <tt>Hashtable</tt> call to <code>put</code>.
+     *
+     * @param category the category of the setting
+     * @param key the key to be placed into this property list.
+     * @param value the value corresponding to <tt>key</tt>.
+     * @return     the previous value of the specified key in this property
+     *             list, or <code>null</code> if it did not have one.
+     */
+       public void setProperty(String category, String key, String value) {
+               setProperty(getConcatenatedKey(category, key), value);
+       }
+       
+       /**
+        * Returns only the category of a key that is a concatenated string of category and key.
+        * @param concatenatedString
+        * @return
+        */
+       public static String getCategory(String concatenatedString) {
+               return concatenatedString.substring(0, concatenatedString.indexOf(CONCATENATOR));
+       }
+       
+       public static String getKey(String concatenatedString) {
+               return concatenatedString.substring(concatenatedString.indexOf(CONCATENATOR) + 1, concatenatedString.length());
+       }
+       
+       public static String getConcatenatedKey(String category, String key) {
+               return category + CONCATENATOR + key;
+       }
+}
index 7f4181f68aa6acb8af94593c29efccebb5d41f97..949eef5cd58a4c8774a952b573fe1f73e34136fd 100644 (file)
@@ -1,48 +1,48 @@
-package com.nexuiz.demorecorder.application;\r
-\r
-\r
-import java.util.ArrayList;\r
-import java.util.List;\r
-import java.util.concurrent.ArrayBlockingQueue;\r
-import java.util.concurrent.ThreadPoolExecutor;\r
-import java.util.concurrent.TimeUnit;\r
-\r
-import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
-\r
-public class RecorderJobPoolExecutor {\r
-       \r
-       private int poolSize = 1;\r
-       private int maxPoolSize = 1;\r
-       private long keepAliveTime = 10;\r
-       private ThreadPoolExecutor threadPool = null;\r
-       private ArrayBlockingQueue<Runnable> queue = null;\r
-\r
-       public RecorderJobPoolExecutor() {\r
-               queue = new ArrayBlockingQueue<Runnable>(99999);\r
-               threadPool = new ThreadPoolExecutor(poolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS, queue);\r
-       }\r
-\r
-       public void runJob(Runnable task) {\r
-               threadPool.execute(task);\r
-       }\r
-       \r
-       public void clearUnfinishedJobs() {\r
-               threadPool.getQueue().clear();\r
-       }\r
-\r
-       public void shutDown() {\r
-               threadPool.shutdownNow();\r
-       }\r
-       \r
-       public synchronized List<RecordJob> getJobList() {\r
-               List<RecordJob> list = new ArrayList<RecordJob>();\r
-               for (Runnable job : this.queue) {\r
-                       try {\r
-                               RecordJob j = (RecordJob)job;\r
-                               list.add(j);\r
-                       } catch (ClassCastException e) {}\r
-               }\r
-               \r
-               return list;\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.application;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+
+public class RecorderJobPoolExecutor {
+       
+       private int poolSize = 1;
+       private int maxPoolSize = 1;
+       private long keepAliveTime = 10;
+       private ThreadPoolExecutor threadPool = null;
+       private ArrayBlockingQueue<Runnable> queue = null;
+
+       public RecorderJobPoolExecutor() {
+               queue = new ArrayBlockingQueue<Runnable>(99999);
+               threadPool = new ThreadPoolExecutor(poolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS, queue);
+       }
+
+       public void runJob(Runnable task) {
+               threadPool.execute(task);
+       }
+       
+       public void clearUnfinishedJobs() {
+               threadPool.getQueue().clear();
+       }
+
+       public void shutDown() {
+               threadPool.shutdownNow();
+       }
+       
+       public synchronized List<RecordJob> getJobList() {
+               List<RecordJob> list = new ArrayList<RecordJob>();
+               for (Runnable job : this.queue) {
+                       try {
+                               RecordJob j = (RecordJob)job;
+                               list.add(j);
+                       } catch (ClassCastException e) {}
+               }
+               
+               return list;
+       }
+}
index 0aff553fd855705653b1f655599f9e95fbcffa24..a1174e66fee17d0ff6e099f7d8982fb2bc3cd767 100644 (file)
-package com.nexuiz.demorecorder.application.democutter;\r
-import java.io.DataInputStream;\r
-import java.io.DataOutputStream;\r
-import java.io.EOFException;\r
-import java.io.File;\r
-import java.io.FileInputStream;\r
-import java.io.FileNotFoundException;\r
-import java.io.FileOutputStream;\r
-import java.io.IOException;\r
-import java.io.UnsupportedEncodingException;\r
-\r
-public class DemoCutter {\r
-\r
-       private static final byte CDTRACK_SEPARATOR = 0x0A;\r
-\r
-       private DataInputStream inStream;\r
-       private DataOutputStream outStream;\r
-       private File inFile;\r
-       private File outFile;\r
-\r
-       /**\r
-        * Calls the cutDemo method with reasonable default values for the second and first fast-forward stage.\r
-        * @param inFile @see other cutDemo method\r
-        * @param outFile @see other cutDemo method\r
-        * @param startTime @see other cutDemo method\r
-        * @param endTime @see other cutDemo method\r
-        * @param injectAtStart @see other cutDemo method\r
-        * @param injectBeforeCap @see other cutDemo method\r
-        * @param injectAfterCap @see other cutDemo method\r
-        */\r
-       public void cutDemo(File inFile, File outFile, float startTime, float endTime, String injectAtStart, String injectBeforeCap, String injectAfterCap) {\r
-               this.cutDemo(inFile, outFile, startTime, endTime, injectAtStart, injectBeforeCap, injectAfterCap, 100, 10);\r
-       }\r
-       \r
-       /**\r
-        * Cuts the demo by injecting a 2-phase fast forward command until startTime is reached, then injects the cl_capturevideo 1 command\r
-        * and once endTime is reached the cl_capturevideo 0 command is injected.\r
-        * @param inFile the original demo file\r
-        * @param outFile the new cut demo file\r
-        * @param startTime when to start capturing (use the gametime in seconds)\r
-        * @param endTime when to stop capturing\r
-        * @param injectAtStart a String that will be injected right at the beginning of the demo\r
-        *                                              can be anything that would make sense and can be parsed by DP's console\r
-        * @param injectBeforeCap a String that will be injected 5 seconds before capturing starts\r
-        * @param injectAfterCap a String that will be injected shortly after capturing ended\r
-        * @param ffwSpeedFirstStage fast-forward speed at first stage, when the startTime is still about a minute away (use high values, e.g. 100)\r
-        * @param ffwSpeedSecondStage fast-forward speed when coming a few seconds close to startTime, use lower values e.g. 5 or 10\r
-        */\r
-       public void cutDemo(File inFile, File outFile, float startTime, float endTime, String injectAtStart, String injectBeforeCap, String injectAfterCap, int ffwSpeedFirstStage, int ffwSpeedSecondStage) {\r
-               this.inFile = inFile;\r
-               this.outFile = outFile;\r
-               this.prepareStreams();\r
-               this.readCDTrack();\r
-               injectAfterCap = this.checkInjectString(injectAfterCap);\r
-               injectAtStart = this.checkInjectString(injectAtStart);\r
-               injectBeforeCap = this.checkInjectString(injectBeforeCap);\r
-\r
-               byte[] data;\r
-               float svctime = -1;\r
-               boolean firstLoop = true;\r
-               String injectBuffer = "";\r
-               int demoStarted = 0;\r
-               boolean endIsReached = false;\r
-               boolean finalInjectionDone = false;\r
-               boolean disconnectIssued = false;\r
-               int svcLoops = 0;\r
-               float firstSvcTime = -1;\r
-               float lastSvcTime = -1;\r
-               \r
-               try {\r
-                       while (true) {\r
-                               DemoPacket demoPacket = new DemoPacket(this.inStream);\r
-                               if (demoPacket.isEndOfFile()) {\r
-                                       break;\r
-                               }\r
-                               \r
-                               if (demoPacket.isClientToServerPacket()) {\r
-                                       try {\r
-                                               this.outStream.write(demoPacket.getOriginalLengthAsByte());\r
-                                               this.outStream.write(demoPacket.getAngles());\r
-                                               this.outStream.write(demoPacket.getOriginalData());\r
-                                       } catch (IOException e) {\r
-                                               throw new DemoCutterException("Unexpected I/O Exception occurred when writing to the cut demo", e);\r
-                                       }\r
-                                       \r
-                                       continue;\r
-                               }\r
-\r
-                               if (demoPacket.getSvcTime() != -1) {\r
-                                       svctime = demoPacket.getSvcTime();\r
-                               }\r
-\r
-                               if (svctime != -1) {\r
-                                       if (firstSvcTime == -1) {\r
-                                               firstSvcTime = svctime;\r
-                                       }\r
-                                       lastSvcTime = svctime;\r
-                                       \r
-                                       if (firstLoop) {\r
-                                               injectBuffer = "\011\n" + injectAtStart + ";slowmo " + ffwSpeedFirstStage + "\n\000";\r
-                                               firstLoop = false;\r
-                                       }\r
-                                       if (demoStarted < 1 && svctime > (startTime - 50)) {\r
-                                               if (svcLoops == 0) {\r
-                                                       //make sure that for short demos (duration less than 50 sec)\r
-                                                       //the injectAtStart is still honored\r
-                                                       injectBuffer = "\011\n" + injectAtStart + ";slowmo " + ffwSpeedSecondStage + "\n\000";\r
-                                               } else {\r
-                                                       injectBuffer = "\011\nslowmo " + ffwSpeedSecondStage + "\n\000";\r
-                                               }\r
-                                               \r
-                                               demoStarted = 1;\r
-                                       }\r
-                                       if (demoStarted < 2 && svctime > (startTime - 5)) {\r
-                                               injectBuffer = "\011\nslowmo 1;" + injectBeforeCap +"\n\000";\r
-                                               demoStarted = 2;\r
-                                       }\r
-                                       if (demoStarted < 3 && svctime > startTime) {\r
-                                               injectBuffer = "\011\ncl_capturevideo 1\n\000";\r
-                                               demoStarted = 3;\r
-                                       }\r
-                                       if (!endIsReached && svctime > endTime) {\r
-                                               injectBuffer = "\011\ncl_capturevideo 0\n\000";\r
-                                               endIsReached = true;\r
-                                       }\r
-                                       if (endIsReached && !finalInjectionDone && svctime > (endTime + 1)) {\r
-                                               injectBuffer = "\011\n" + injectAfterCap + "\n\000";\r
-                                               finalInjectionDone = true;\r
-                                       }\r
-                                       if (finalInjectionDone && !disconnectIssued && svctime > (endTime + 2)) {\r
-                                               injectBuffer = "\011\ndisconnect\n\000";\r
-                                               disconnectIssued = true;\r
-                                       }\r
-                                       svcLoops++;\r
-                               }\r
-\r
-                               byte[] injectBufferAsBytes = null;\r
-                               try {\r
-                                       injectBufferAsBytes = injectBuffer.getBytes("US-ASCII");\r
-                               } catch (UnsupportedEncodingException e) {\r
-                                       throw new DemoCutterException("Could not convert String to bytes using US-ASCII charset!", e);\r
-                               }\r
-\r
-                               data = demoPacket.getOriginalData();\r
-                               if ((injectBufferAsBytes.length + data.length) < 65536) {\r
-                                       data = DemoCutterUtils.mergeByteArrays(injectBufferAsBytes, data);\r
-                                       injectBuffer = "";\r
-                               }\r
-                               \r
-                               byte[] newLengthLittleEndian = DemoCutterUtils.convertLittleEndian(data.length);\r
-                               try {\r
-                                       this.outStream.write(newLengthLittleEndian);\r
-                                       this.outStream.write(demoPacket.getAngles());\r
-                                       this.outStream.write(data);\r
-                               } catch (IOException e) {\r
-                                       throw new DemoCutterException("Unexpected I/O Exception occurred when writing to the cut demo", e);\r
-                               }\r
-\r
-                       }\r
-                       \r
-                       if (startTime < firstSvcTime) {\r
-                               throw new DemoCutterException("Start time for the demo is " + startTime + ", but demo doesn't start before " + firstSvcTime);\r
-                       }\r
-                       if (endTime > lastSvcTime) {\r
-                               throw new DemoCutterException("End time for the demo is " + endTime + ", but demo already stops at " + lastSvcTime);\r
-                       }\r
-               } catch (DemoCutterException e) {\r
-                       throw e;\r
-               } catch (Throwable e) {\r
-                       throw new DemoCutterException("Internal error in demo cutter sub-route (invalid demo file?)", e);\r
-               } finally {\r
-                       try {\r
-                               this.outStream.close();\r
-                               this.inStream.close();\r
-                       } catch (IOException e) {}\r
-               }\r
-       }\r
-\r
-       \r
-\r
-       /**\r
-        * Seeks forward in the inStream until CDTRACK_SEPARATOR byte was reached.\r
-        * All the content is copied to the outStream.\r
-        */\r
-       private void readCDTrack() {\r
-               byte lastByte;\r
-               try {\r
-                       while ((lastByte = inStream.readByte()) != CDTRACK_SEPARATOR) {\r
-                               this.outStream.write(lastByte);\r
-                       }\r
-                       this.outStream.write(CDTRACK_SEPARATOR);\r
-               } catch (EOFException e) {\r
-                       throw new DemoCutterException("Unexpected EOF occurred when reading CD track of demo " + inFile.getPath(), e);\r
-               }\r
-               catch (IOException e) {\r
-                       throw new DemoCutterException("Unexpected I/O Exception occurred when reading CD track of demo " + inFile.getPath(), e);\r
-               }\r
-       }\r
-\r
-       private void prepareStreams() {\r
-               try {\r
-                       this.inStream = new DataInputStream(new FileInputStream(this.inFile));\r
-               } catch (FileNotFoundException e) {\r
-                       throw new DemoCutterException("Could not open demo file " + inFile.getPath(), e);\r
-               }\r
-               \r
-               try {\r
-                       this.outStream = new DataOutputStream(new FileOutputStream(this.outFile));\r
-               } catch (FileNotFoundException e) {\r
-                       throw new DemoCutterException("Could not open demo file " + outFile.getPath(), e);\r
-               }\r
-       }\r
-       \r
-       private String checkInjectString(String injectionString) {\r
-               while (injectionString.endsWith(";") || injectionString.endsWith("\n")) {\r
-                       injectionString = injectionString.substring(0, injectionString.length()-1);\r
-               }\r
-               return injectionString;\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.application.democutter;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+public class DemoCutter {
+
+       private static final byte CDTRACK_SEPARATOR = 0x0A;
+
+       private DataInputStream inStream;
+       private DataOutputStream outStream;
+       private File inFile;
+       private File outFile;
+
+       /**
+        * Calls the cutDemo method with reasonable default values for the second and first fast-forward stage.
+        * @param inFile @see other cutDemo method
+        * @param outFile @see other cutDemo method
+        * @param startTime @see other cutDemo method
+        * @param endTime @see other cutDemo method
+        * @param injectAtStart @see other cutDemo method
+        * @param injectBeforeCap @see other cutDemo method
+        * @param injectAfterCap @see other cutDemo method
+        */
+       public void cutDemo(File inFile, File outFile, float startTime, float endTime, String injectAtStart, String injectBeforeCap, String injectAfterCap) {
+               this.cutDemo(inFile, outFile, startTime, endTime, injectAtStart, injectBeforeCap, injectAfterCap, 100, 10);
+       }
+       
+       /**
+        * Cuts the demo by injecting a 2-phase fast forward command until startTime is reached, then injects the cl_capturevideo 1 command
+        * and once endTime is reached the cl_capturevideo 0 command is injected.
+        * @param inFile the original demo file
+        * @param outFile the new cut demo file
+        * @param startTime when to start capturing (use the gametime in seconds)
+        * @param endTime when to stop capturing
+        * @param injectAtStart a String that will be injected right at the beginning of the demo
+        *                                              can be anything that would make sense and can be parsed by DP's console
+        * @param injectBeforeCap a String that will be injected 5 seconds before capturing starts
+        * @param injectAfterCap a String that will be injected shortly after capturing ended
+        * @param ffwSpeedFirstStage fast-forward speed at first stage, when the startTime is still about a minute away (use high values, e.g. 100)
+        * @param ffwSpeedSecondStage fast-forward speed when coming a few seconds close to startTime, use lower values e.g. 5 or 10
+        */
+       public void cutDemo(File inFile, File outFile, float startTime, float endTime, String injectAtStart, String injectBeforeCap, String injectAfterCap, int ffwSpeedFirstStage, int ffwSpeedSecondStage) {
+               this.inFile = inFile;
+               this.outFile = outFile;
+               this.prepareStreams();
+               this.readCDTrack();
+               injectAfterCap = this.checkInjectString(injectAfterCap);
+               injectAtStart = this.checkInjectString(injectAtStart);
+               injectBeforeCap = this.checkInjectString(injectBeforeCap);
+
+               byte[] data;
+               float svctime = -1;
+               boolean firstLoop = true;
+               String injectBuffer = "";
+               int demoStarted = 0;
+               boolean endIsReached = false;
+               boolean finalInjectionDone = false;
+               boolean disconnectIssued = false;
+               int svcLoops = 0;
+               float firstSvcTime = -1;
+               float lastSvcTime = -1;
+               
+               try {
+                       while (true) {
+                               DemoPacket demoPacket = new DemoPacket(this.inStream);
+                               if (demoPacket.isEndOfFile()) {
+                                       break;
+                               }
+                               
+                               if (demoPacket.isClientToServerPacket()) {
+                                       try {
+                                               this.outStream.write(demoPacket.getOriginalLengthAsByte());
+                                               this.outStream.write(demoPacket.getAngles());
+                                               this.outStream.write(demoPacket.getOriginalData());
+                                       } catch (IOException e) {
+                                               throw new DemoCutterException("Unexpected I/O Exception occurred when writing to the cut demo", e);
+                                       }
+                                       
+                                       continue;
+                               }
+
+                               if (demoPacket.getSvcTime() != -1) {
+                                       svctime = demoPacket.getSvcTime();
+                               }
+
+                               if (svctime != -1) {
+                                       if (firstSvcTime == -1) {
+                                               firstSvcTime = svctime;
+                                       }
+                                       lastSvcTime = svctime;
+                                       
+                                       if (firstLoop) {
+                                               injectBuffer = "\011\n" + injectAtStart + ";slowmo " + ffwSpeedFirstStage + "\n\000";
+                                               firstLoop = false;
+                                       }
+                                       if (demoStarted < 1 && svctime > (startTime - 50)) {
+                                               if (svcLoops == 0) {
+                                                       //make sure that for short demos (duration less than 50 sec)
+                                                       //the injectAtStart is still honored
+                                                       injectBuffer = "\011\n" + injectAtStart + ";slowmo " + ffwSpeedSecondStage + "\n\000";
+                                               } else {
+                                                       injectBuffer = "\011\nslowmo " + ffwSpeedSecondStage + "\n\000";
+                                               }
+                                               
+                                               demoStarted = 1;
+                                       }
+                                       if (demoStarted < 2 && svctime > (startTime - 5)) {
+                                               injectBuffer = "\011\nslowmo 1;" + injectBeforeCap +"\n\000";
+                                               demoStarted = 2;
+                                       }
+                                       if (demoStarted < 3 && svctime > startTime) {
+                                               injectBuffer = "\011\ncl_capturevideo 1\n\000";
+                                               demoStarted = 3;
+                                       }
+                                       if (!endIsReached && svctime > endTime) {
+                                               injectBuffer = "\011\ncl_capturevideo 0\n\000";
+                                               endIsReached = true;
+                                       }
+                                       if (endIsReached && !finalInjectionDone && svctime > (endTime + 1)) {
+                                               injectBuffer = "\011\n" + injectAfterCap + "\n\000";
+                                               finalInjectionDone = true;
+                                       }
+                                       if (finalInjectionDone && !disconnectIssued && svctime > (endTime + 2)) {
+                                               injectBuffer = "\011\ndisconnect\n\000";
+                                               disconnectIssued = true;
+                                       }
+                                       svcLoops++;
+                               }
+
+                               byte[] injectBufferAsBytes = null;
+                               try {
+                                       injectBufferAsBytes = injectBuffer.getBytes("US-ASCII");
+                               } catch (UnsupportedEncodingException e) {
+                                       throw new DemoCutterException("Could not convert String to bytes using US-ASCII charset!", e);
+                               }
+
+                               data = demoPacket.getOriginalData();
+                               if ((injectBufferAsBytes.length + data.length) < 65536) {
+                                       data = DemoCutterUtils.mergeByteArrays(injectBufferAsBytes, data);
+                                       injectBuffer = "";
+                               }
+                               
+                               byte[] newLengthLittleEndian = DemoCutterUtils.convertLittleEndian(data.length);
+                               try {
+                                       this.outStream.write(newLengthLittleEndian);
+                                       this.outStream.write(demoPacket.getAngles());
+                                       this.outStream.write(data);
+                               } catch (IOException e) {
+                                       throw new DemoCutterException("Unexpected I/O Exception occurred when writing to the cut demo", e);
+                               }
+
+                       }
+                       
+                       if (startTime < firstSvcTime) {
+                               throw new DemoCutterException("Start time for the demo is " + startTime + ", but demo doesn't start before " + firstSvcTime);
+                       }
+                       if (endTime > lastSvcTime) {
+                               throw new DemoCutterException("End time for the demo is " + endTime + ", but demo already stops at " + lastSvcTime);
+                       }
+               } catch (DemoCutterException e) {
+                       throw e;
+               } catch (Throwable e) {
+                       throw new DemoCutterException("Internal error in demo cutter sub-route (invalid demo file?)", e);
+               } finally {
+                       try {
+                               this.outStream.close();
+                               this.inStream.close();
+                       } catch (IOException e) {}
+               }
+       }
+
+       
+
+       /**
+        * Seeks forward in the inStream until CDTRACK_SEPARATOR byte was reached.
+        * All the content is copied to the outStream.
+        */
+       private void readCDTrack() {
+               byte lastByte;
+               try {
+                       while ((lastByte = inStream.readByte()) != CDTRACK_SEPARATOR) {
+                               this.outStream.write(lastByte);
+                       }
+                       this.outStream.write(CDTRACK_SEPARATOR);
+               } catch (EOFException e) {
+                       throw new DemoCutterException("Unexpected EOF occurred when reading CD track of demo " + inFile.getPath(), e);
+               }
+               catch (IOException e) {
+                       throw new DemoCutterException("Unexpected I/O Exception occurred when reading CD track of demo " + inFile.getPath(), e);
+               }
+       }
+
+       private void prepareStreams() {
+               try {
+                       this.inStream = new DataInputStream(new FileInputStream(this.inFile));
+               } catch (FileNotFoundException e) {
+                       throw new DemoCutterException("Could not open demo file " + inFile.getPath(), e);
+               }
+               
+               try {
+                       this.outStream = new DataOutputStream(new FileOutputStream(this.outFile));
+               } catch (FileNotFoundException e) {
+                       throw new DemoCutterException("Could not open demo file " + outFile.getPath(), e);
+               }
+       }
+       
+       private String checkInjectString(String injectionString) {
+               while (injectionString.endsWith(";") || injectionString.endsWith("\n")) {
+                       injectionString = injectionString.substring(0, injectionString.length()-1);
+               }
+               return injectionString;
+       }
+}
index 6b6666b5153f6156900e33006fde8139ad5bb32c..ef13059be1f9c47f35fdf8035b64a0bb78d37aa9 100644 (file)
@@ -1,14 +1,14 @@
-package com.nexuiz.demorecorder.application.democutter;\r
-\r
-public class DemoCutterException extends RuntimeException {\r
-       \r
-       private static final long serialVersionUID = -1419472153834762285L;\r
-       \r
-       public DemoCutterException(String message) {\r
-               super(message);\r
-       }\r
-       public DemoCutterException(String message, Throwable cause) {\r
-               super(message, cause);\r
-       }\r
-\r
-}\r
+package com.nexuiz.demorecorder.application.democutter;
+
+public class DemoCutterException extends RuntimeException {
+       
+       private static final long serialVersionUID = -1419472153834762285L;
+       
+       public DemoCutterException(String message) {
+               super(message);
+       }
+       public DemoCutterException(String message, Throwable cause) {
+               super(message, cause);
+       }
+
+}
index dd8f9ca45f58f84d27e89b3a88b7568ab80a1361..0de518aef576dbfd169de1a9be0a469b58d47182 100644 (file)
@@ -1,40 +1,40 @@
-package com.nexuiz.demorecorder.application.democutter;\r
-import java.nio.ByteBuffer;\r
-import java.nio.ByteOrder;\r
-\r
-\r
-public class DemoCutterUtils {\r
-\r
-       public static float byteArrayToFloat(byte[] array) {\r
-               byte[] tmp = new byte[4];\r
-               System.arraycopy(array, 0, tmp, 0, 4);\r
-               int accum = 0;\r
-               int i = 0;\r
-               for (int shiftBy = 0; shiftBy < 32; shiftBy += 8) {\r
-                       accum |= ((long) (tmp[i++] & 0xff)) << shiftBy;\r
-               }\r
-               return Float.intBitsToFloat(accum);\r
-       }\r
-\r
-       public static byte[] convertLittleEndian(int i) {\r
-               ByteBuffer bb = ByteBuffer.allocate(4);\r
-               bb.order(ByteOrder.LITTLE_ENDIAN);\r
-               bb.putInt(i);\r
-               return bb.array();\r
-       }\r
-\r
-       public static byte[] mergeByteArrays(byte[] array1, byte[] array2) {\r
-               ByteBuffer bb = ByteBuffer.allocate(array1.length + array2.length);\r
-               bb.put(array1);\r
-               bb.put(array2);\r
-               return bb.array();\r
-       }\r
-\r
-       public static int convertLittleEndian(byte[] b) {\r
-               ByteBuffer bb = ByteBuffer.allocate(4);\r
-               bb.order(ByteOrder.LITTLE_ENDIAN);\r
-               bb.put(b);\r
-               bb.position(0);\r
-               return bb.getInt();\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.application.democutter;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+
+public class DemoCutterUtils {
+
+       public static float byteArrayToFloat(byte[] array) {
+               byte[] tmp = new byte[4];
+               System.arraycopy(array, 0, tmp, 0, 4);
+               int accum = 0;
+               int i = 0;
+               for (int shiftBy = 0; shiftBy < 32; shiftBy += 8) {
+                       accum |= ((long) (tmp[i++] & 0xff)) << shiftBy;
+               }
+               return Float.intBitsToFloat(accum);
+       }
+
+       public static byte[] convertLittleEndian(int i) {
+               ByteBuffer bb = ByteBuffer.allocate(4);
+               bb.order(ByteOrder.LITTLE_ENDIAN);
+               bb.putInt(i);
+               return bb.array();
+       }
+
+       public static byte[] mergeByteArrays(byte[] array1, byte[] array2) {
+               ByteBuffer bb = ByteBuffer.allocate(array1.length + array2.length);
+               bb.put(array1);
+               bb.put(array2);
+               return bb.array();
+       }
+
+       public static int convertLittleEndian(byte[] b) {
+               ByteBuffer bb = ByteBuffer.allocate(4);
+               bb.order(ByteOrder.LITTLE_ENDIAN);
+               bb.put(b);
+               bb.position(0);
+               return bb.getInt();
+       }
+}
index dcca8219b66190c41afe6c4404f0e9a9862bfcfb..b60d402e4977abc17dd639e86a5e1c20ddf9ae9a 100644 (file)
-package com.nexuiz.demorecorder.application.democutter;\r
-import java.io.DataInputStream;\r
-import java.io.EOFException;\r
-import java.io.IOException;\r
-import java.nio.ByteBuffer;\r
-\r
-\r
-public class DemoPacket {\r
-       \r
-       private static final int DEMOMSG_CLIENT_TO_SERVER = 0x80000000;\r
-       \r
-       private DataInputStream inStream = null;\r
-       private boolean isEndOfFile = false;\r
-       private byte[] buffer = new byte[4]; //contains packet length\r
-       private byte[] angles = new byte[12];\r
-       private byte[] data;\r
-       private int packetLength;\r
-       private boolean isClientToServer = false;\r
-       private float svcTime = -1;\r
-\r
-       public DemoPacket(DataInputStream inStream) {\r
-               this.inStream = inStream;\r
-               \r
-               try {\r
-                       inStream.readFully(buffer);\r
-               } catch (EOFException e) {\r
-                       this.isEndOfFile = true;\r
-                       return;\r
-               } catch (IOException e) {\r
-                       throw new DemoCutterException("Unexpected I/O Exception occurred when processing demo");\r
-               }\r
-               \r
-               packetLength = DemoCutterUtils.convertLittleEndian(buffer);\r
-               if ((packetLength & DEMOMSG_CLIENT_TO_SERVER) != 0) {\r
-                       packetLength = packetLength & ~DEMOMSG_CLIENT_TO_SERVER;\r
-\r
-                       this.isClientToServer = true;\r
-                       this.readAnglesAndData();\r
-                       return;\r
-               }\r
-               \r
-               this.readAnglesAndData();\r
-               \r
-               // extract svc_time\r
-               this.readSvcTime();\r
-               \r
-       }\r
-       \r
-       public boolean isEndOfFile() {\r
-               return this.isEndOfFile;\r
-       }\r
-       \r
-       public boolean isClientToServerPacket() {\r
-               return this.isClientToServer;\r
-       }\r
-       \r
-       public byte[] getOriginalLengthAsByte() {\r
-               return this.buffer;\r
-       }\r
-       \r
-       public byte[] getAngles() {\r
-               return this.angles;\r
-       }\r
-       \r
-       public byte[] getOriginalData() {\r
-               return this.data;\r
-       }\r
-       \r
-       public float getSvcTime() {\r
-               return this.svcTime;\r
-       }\r
-       \r
-       private void readAnglesAndData() {\r
-               // read angles\r
-               try {\r
-                       inStream.readFully(angles);\r
-               } catch (EOFException e) {\r
-                       throw new DemoCutterException("Invalid Demo Packet");\r
-               } catch (IOException e) {\r
-                       throw new DemoCutterException("Unexpected I/O Exception occurred when processing demo");\r
-               }\r
-\r
-               // read data\r
-               data = new byte[packetLength];\r
-               try {\r
-                       inStream.readFully(data);\r
-               } catch (EOFException e) {\r
-                       throw new DemoCutterException("Invalid Demo Packet");\r
-               } catch (IOException e) {\r
-                       throw new DemoCutterException("Unexpected I/O Exception occurred when processing demo");\r
-               }\r
-       }\r
-       \r
-       private void readSvcTime() {\r
-               if (data[0] == 0x07) {\r
-                       ByteBuffer bb = ByteBuffer.allocate(4);\r
-                       bb.put(data, 1, 4);\r
-                       byte[] array = bb.array();\r
-                       this.svcTime = DemoCutterUtils.byteArrayToFloat(array);\r
-               }\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.application.democutter;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+
+public class DemoPacket {
+       
+       private static final int DEMOMSG_CLIENT_TO_SERVER = 0x80000000;
+       
+       private DataInputStream inStream = null;
+       private boolean isEndOfFile = false;
+       private byte[] buffer = new byte[4]; //contains packet length
+       private byte[] angles = new byte[12];
+       private byte[] data;
+       private int packetLength;
+       private boolean isClientToServer = false;
+       private float svcTime = -1;
+
+       public DemoPacket(DataInputStream inStream) {
+               this.inStream = inStream;
+               
+               try {
+                       inStream.readFully(buffer);
+               } catch (EOFException e) {
+                       this.isEndOfFile = true;
+                       return;
+               } catch (IOException e) {
+                       throw new DemoCutterException("Unexpected I/O Exception occurred when processing demo");
+               }
+               
+               packetLength = DemoCutterUtils.convertLittleEndian(buffer);
+               if ((packetLength & DEMOMSG_CLIENT_TO_SERVER) != 0) {
+                       packetLength = packetLength & ~DEMOMSG_CLIENT_TO_SERVER;
+
+                       this.isClientToServer = true;
+                       this.readAnglesAndData();
+                       return;
+               }
+               
+               this.readAnglesAndData();
+               
+               // extract svc_time
+               this.readSvcTime();
+               
+       }
+       
+       public boolean isEndOfFile() {
+               return this.isEndOfFile;
+       }
+       
+       public boolean isClientToServerPacket() {
+               return this.isClientToServer;
+       }
+       
+       public byte[] getOriginalLengthAsByte() {
+               return this.buffer;
+       }
+       
+       public byte[] getAngles() {
+               return this.angles;
+       }
+       
+       public byte[] getOriginalData() {
+               return this.data;
+       }
+       
+       public float getSvcTime() {
+               return this.svcTime;
+       }
+       
+       private void readAnglesAndData() {
+               // read angles
+               try {
+                       inStream.readFully(angles);
+               } catch (EOFException e) {
+                       throw new DemoCutterException("Invalid Demo Packet");
+               } catch (IOException e) {
+                       throw new DemoCutterException("Unexpected I/O Exception occurred when processing demo");
+               }
+
+               // read data
+               data = new byte[packetLength];
+               try {
+                       inStream.readFully(data);
+               } catch (EOFException e) {
+                       throw new DemoCutterException("Invalid Demo Packet");
+               } catch (IOException e) {
+                       throw new DemoCutterException("Unexpected I/O Exception occurred when processing demo");
+               }
+       }
+       
+       private void readSvcTime() {
+               if (data[0] == 0x07) {
+                       ByteBuffer bb = ByteBuffer.allocate(4);
+                       bb.put(data, 1, 4);
+                       byte[] array = bb.array();
+                       this.svcTime = DemoCutterUtils.byteArrayToFloat(array);
+               }
+       }
+}
index 430c98890510e22220d114087f0e4b07925b94e8..03f3bbdaabd0293c53a06188c8b11f5acff1bdc6 100644 (file)
@@ -1,24 +1,24 @@
-package com.nexuiz.demorecorder.application.jobs;\r
-\r
-import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;\r
-\r
-/**\r
- * Job for the ThreadPoolExecutor that will just call the encoder-plugin's execute\r
- * method.\r
- */\r
-public class EncoderJob implements Runnable {\r
-       \r
-       private RecordJob job;\r
-       private EncoderPlugin plugin;\r
-       \r
-       public EncoderJob(RecordJob job, EncoderPlugin plugin) {\r
-               this.job = job;\r
-               this.plugin = plugin;\r
-       }\r
-\r
-       @Override\r
-       public void run() {\r
-               this.job.executePlugin(this.plugin);\r
-       }\r
-\r
-}\r
+package com.nexuiz.demorecorder.application.jobs;
+
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+
+/**
+ * Job for the ThreadPoolExecutor that will just call the encoder-plugin's execute
+ * method.
+ */
+public class EncoderJob implements Runnable {
+       
+       private RecordJob job;
+       private EncoderPlugin plugin;
+       
+       public EncoderJob(RecordJob job, EncoderPlugin plugin) {
+               this.job = job;
+               this.plugin = plugin;
+       }
+
+       @Override
+       public void run() {
+               this.job.executePlugin(this.plugin);
+       }
+
+}
index 32f663170b6c891c498bbb21b511b8672cf63b39..c56a7e44e57af8a6415b0975e9cbb2f8d942b6e2 100644 (file)
-package com.nexuiz.demorecorder.application.jobs;\r
-\r
-import java.io.BufferedReader;\r
-import java.io.File;\r
-import java.io.IOException;\r
-import java.io.InputStream;\r
-import java.io.InputStreamReader;\r
-import java.io.Serializable;\r
-import java.util.ArrayList;\r
-import java.util.HashMap;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Properties;\r
-\r
-import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
-import com.nexuiz.demorecorder.application.DemoRecorderException;\r
-import com.nexuiz.demorecorder.application.DemoRecorderUtils;\r
-import com.nexuiz.demorecorder.application.NDRPreferences;\r
-import com.nexuiz.demorecorder.application.DemoRecorderApplication.Preferences;\r
-import com.nexuiz.demorecorder.application.democutter.DemoCutter;\r
-import com.nexuiz.demorecorder.application.democutter.DemoCutterException;\r
-import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;\r
-import com.nexuiz.demorecorder.application.plugins.EncoderPluginException;\r
-\r
-public class RecordJob implements Runnable, Serializable {\r
-       \r
-       private static final long serialVersionUID = -4585637490345587912L;\r
-\r
-       public enum State {\r
-               WAITING, PROCESSING, ERROR, ERROR_PLUGIN, DONE\r
-       }\r
-       \r
-       public static final String CUT_DEMO_FILE_SUFFIX = "_autocut";\r
-       public static final String CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE = "autocap";\r
-       public static final String CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE = "1234567";\r
-       protected static final String[] VIDEO_FILE_ENDINGS = {"avi", "ogv"};\r
-       \r
-       private DemoRecorderApplication appLayer;\r
-       protected String jobName;\r
-       private int jobIndex;\r
-       protected File enginePath;\r
-       protected String engineParameters;\r
-       protected File demoFile;\r
-       protected String relativeDemoPath;\r
-       protected File dpVideoPath;\r
-       protected File videoDestination;\r
-       protected String executeBeforeCap;\r
-       protected String executeAfterCap;\r
-       protected float startSecond;\r
-       protected float endSecond;\r
-       protected State state = State.WAITING;\r
-       protected DemoRecorderException lastException = null;\r
-       \r
-       /**\r
-        * Points to the actual final file, including possible suffixes, e.g. _copy1, and the actualy ending\r
-        */\r
-       protected File actualVideoDestination = null;\r
-       /**\r
-        * Map that identifies the plug-in by its name (String) and maps to the plug-in's job-specific settings\r
-        */\r
-       protected Map<String, Properties> encoderPluginSettings = new HashMap<String, Properties>();\r
-       \r
-       private List<File> cleanUpFiles = null;\r
-       \r
-       public RecordJob(\r
-               DemoRecorderApplication appLayer,\r
-               String jobName,\r
-               int jobIndex,\r
-               File enginePath,\r
-               String engineParameters,\r
-               File demoFile,\r
-               String relativeDemoPath,\r
-               File dpVideoPath,\r
-               File videoDestination,\r
-               String executeBeforeCap,\r
-               String executeAfterCap,\r
-               float startSecond,\r
-               float endSecond\r
-       ) {\r
-               this.appLayer = appLayer;\r
-               this.jobName = jobName;\r
-               this.jobIndex = jobIndex;\r
-               \r
-               this.setEnginePath(enginePath);\r
-               this.setEngineParameters(engineParameters);\r
-               this.setDemoFile(demoFile);\r
-               this.setRelativeDemoPath(relativeDemoPath);\r
-               this.setDpVideoPath(dpVideoPath);\r
-               this.setVideoDestination(videoDestination);\r
-               this.setExecuteBeforeCap(executeBeforeCap);\r
-               this.setExecuteAfterCap(executeAfterCap);\r
-               this.setStartSecond(startSecond);\r
-               this.setEndSecond(endSecond);\r
-       }\r
-       \r
-       public RecordJob(){}\r
-       \r
-       /**\r
-        * Constructor that can be used by other classes such as job templates. Won't throw exceptions\r
-        * as it won't check the input for validity.\r
-        */\r
-       protected RecordJob(\r
-               File enginePath,\r
-               String engineParameters,\r
-               File demoFile,\r
-               String relativeDemoPath,\r
-               File dpVideoPath,\r
-               File videoDestination,\r
-               String executeBeforeCap,\r
-               String executeAfterCap,\r
-               float startSecond,\r
-               float endSecond\r
-               ) {\r
-               this.jobIndex = -1;\r
-               this.enginePath = enginePath;\r
-               this.engineParameters = engineParameters;\r
-               this.demoFile = demoFile;\r
-               this.relativeDemoPath = relativeDemoPath;\r
-               this.dpVideoPath = dpVideoPath;\r
-               this.videoDestination = videoDestination;\r
-               this.executeBeforeCap = executeBeforeCap;\r
-               this.executeAfterCap = executeAfterCap;\r
-               this.startSecond = startSecond;\r
-               this.endSecond = endSecond;\r
-       }\r
-       \r
-       public void execute() {\r
-               if (this.state == State.PROCESSING) {\r
-                       return;\r
-               }\r
-               boolean errorOccurred = false;\r
-               this.setState(State.PROCESSING);\r
-               this.appLayer.fireUserInterfaceUpdate(this);\r
-               cleanUpFiles = new ArrayList<File>();\r
-               \r
-               File cutDemo = computeCutDemoFile();\r
-               cutDemo.delete(); //delete possibly old cutDemoFile\r
-               \r
-               EncoderPlugin recentEncoder = null;\r
-               \r
-               try {\r
-                       this.cutDemo(cutDemo);\r
-                       this.removeOldAutocaps();\r
-                       this.recordClip(cutDemo);\r
-                       this.moveRecordedClip();\r
-                       for (EncoderPlugin plugin : this.appLayer.getEncoderPlugins()) {\r
-                               recentEncoder = plugin;\r
-                               plugin.executeEncoder(this);\r
-                       }\r
-               } catch (DemoRecorderException e) {\r
-                       errorOccurred = true;\r
-                       this.lastException = e;\r
-                       this.setState(State.ERROR);\r
-               } catch (EncoderPluginException e) {\r
-                       errorOccurred = true;\r
-                       this.lastException = new DemoRecorderException("Encoder plug-in " + recentEncoder.getName() + " failed: "\r
-                                       + e.getMessage(), e);\r
-                       this.setState(State.ERROR_PLUGIN);\r
-               } catch (Exception e) {\r
-                       errorOccurred = true;\r
-                       this.lastException = new DemoRecorderException("Executing job failed, click on details for more info", e);\r
-               } finally {\r
-                       NDRPreferences preferences = this.appLayer.getPreferences();\r
-                       if (!Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DO_NOT_DELETE_CUT_DEMOS))) {\r
-                               cleanUpFiles.add(cutDemo);\r
-                       }\r
-                       if (!errorOccurred) {\r
-                               this.setState(State.DONE);\r
-                       }\r
-                       this.cleanUpFiles();\r
-                       this.appLayer.fireUserInterfaceUpdate(this);\r
-                       this.appLayer.saveJobQueue();\r
-               }\r
-       }\r
-       \r
-       /**\r
-        * Will execute just the specified encoder plug-in on an already "done" job.\r
-        * @param pluginName\r
-        */\r
-       public void executePlugin(EncoderPlugin plugin) {\r
-               if (this.getState() != State.DONE) {\r
-                       return;\r
-               }\r
-               this.setState(State.PROCESSING);\r
-               this.appLayer.fireUserInterfaceUpdate(this);\r
-               \r
-               try {\r
-                       plugin.executeEncoder(this);\r
-                       this.setState(State.DONE);\r
-               } catch (EncoderPluginException e) {\r
-                       this.lastException = new DemoRecorderException("Encoder plug-in " + plugin.getName() + " failed: "\r
-                                       + e.getMessage(), e);\r
-                       this.setState(State.ERROR_PLUGIN);\r
-               }\r
-               \r
-               this.appLayer.fireUserInterfaceUpdate(this);\r
-       }\r
-       \r
-       private void cleanUpFiles() {\r
-               try {\r
-                       for (File f : this.cleanUpFiles) {\r
-                               f.delete();\r
-                       }\r
-               } catch (Exception e) {}\r
-               \r
-       }\r
-       \r
-       private void moveRecordedClip() {\r
-               //1. Figure out whether the file is .avi or .ogv\r
-               File sourceFile = null;\r
-               for (String videoExtension : VIDEO_FILE_ENDINGS) {\r
-                       String fileString = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE\r
-                       + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + "." + videoExtension;\r
-                       File videoFile = new File(fileString);\r
-                       if (videoFile.exists()) {\r
-                               sourceFile = videoFile;\r
-                               break;\r
-                       }\r
-               }\r
-               \r
-               if (sourceFile == null) {\r
-                       String p = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE\r
-                       + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE;\r
-                       throw new DemoRecorderException("Could not locate the expected video file being generated by Nexuiz (should have been at "\r
-                                       + p + ".avi/.ogv");\r
-               }\r
-               cleanUpFiles.add(sourceFile);\r
-               \r
-               File destinationFile = null;\r
-               NDRPreferences preferences = this.appLayer.getPreferences();\r
-               String sourceFileExtension = DemoRecorderUtils.getFileExtension(sourceFile);\r
-               String destinationFilePath = this.videoDestination + "." + sourceFileExtension;\r
-               destinationFile = new File(destinationFilePath);\r
-               if (destinationFile.exists()) {\r
-                       if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.OVERWRITE_VIDEO_FILE))) {\r
-                               if (!destinationFile.delete()) {\r
-                                       throw new DemoRecorderException("Could not delete the existing video destinatin file " + destinationFile.getAbsolutePath()\r
-                                                       + " (application setting to overwrite existing video files is enabled!)");\r
-                               }\r
-                       } else {\r
-                               destinationFilePath = this.videoDestination + "_copy" + this.getVideoDestinationCopyNr(sourceFileExtension) + "." + sourceFileExtension;\r
-                               destinationFile = new File(destinationFilePath);\r
-                       }\r
-               }\r
-               \r
-               //finally move the file\r
-               if (!sourceFile.renameTo(destinationFile)) {\r
-                       cleanUpFiles.add(destinationFile);\r
-                       throw new DemoRecorderException("Could not move the video file from " + sourceFile.getAbsolutePath()\r
-                                       + " to " + destinationFile.getAbsolutePath());\r
-               }\r
-               \r
-               this.actualVideoDestination = destinationFile;\r
-       }\r
-       \r
-       /**\r
-        * As destination video files, e.g. "test"[.avi] can already exist, we have to save the\r
-        * the video file to a file name such as test_copy1 or test_copy2.\r
-        * This function will figure out what the number (1, 2....) is.\r
-        * @return\r
-        */\r
-       private int getVideoDestinationCopyNr(String sourceFileExtension) {\r
-               int i = 1;\r
-               File lastFile;\r
-               while (true) {\r
-                       lastFile = new File(this.videoDestination + "_copy" + i + "." + sourceFileExtension);\r
-                       if (!lastFile.exists()) {\r
-                               break;\r
-                       }\r
-                       \r
-                       i++;\r
-               }\r
-               return i;\r
-       }\r
-\r
-       private File computeCutDemoFile() {\r
-               String origFileString = this.demoFile.getAbsolutePath();\r
-               int lastIndex = origFileString.lastIndexOf(File.separator);\r
-               String autoDemoFileName = origFileString.substring(lastIndex+1, origFileString.length());\r
-               //strip .dem ending\r
-               autoDemoFileName = autoDemoFileName.substring(0, autoDemoFileName.length()-4);\r
-               autoDemoFileName = autoDemoFileName + CUT_DEMO_FILE_SUFFIX + ".dem";\r
-               String finalString = origFileString.substring(0, lastIndex) + File.separator + autoDemoFileName;\r
-               File f = new File(finalString);\r
-               \r
-               return f;\r
-       }\r
-       \r
-       private void cutDemo(File cutDemo) {\r
-               String injectAtStart = "";\r
-               String injectBeforeCap = "";\r
-               String injectAfterCap = "";\r
-               \r
-               NDRPreferences preferences = this.appLayer.getPreferences();\r
-               if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_RENDERING))) {\r
-                       injectAtStart += "r_render 0;";\r
-                       injectBeforeCap += "r_render 1;";\r
-               }\r
-               if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_SOUND))) {\r
-                       injectAtStart += "set _volume $volume;volume 0;";\r
-                       injectBeforeCap += "set volume $_volume;";\r
-               }\r
-               injectBeforeCap += this.executeBeforeCap + "\n";\r
-               injectBeforeCap += "set _cl_capturevideo_nameformat $cl_capturevideo_nameformat;set _cl_capturevideo_number $cl_capturevideo_number;";\r
-               injectBeforeCap += "cl_capturevideo_nameformat " + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE + ";";\r
-               injectBeforeCap += "cl_capturevideo_number " + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + ";";\r
-               \r
-               injectAfterCap += this.executeAfterCap + "\n";\r
-               injectAfterCap += "cl_capturevideo_nameformat $_cl_capturevideo_nameformat;cl_capturevideo_number $_cl_capturevideo_number;";\r
-               \r
-               \r
-               DemoCutter cutter = new DemoCutter();\r
-               int fwdSpeedFirstStage, fwdSpeedSecondStage;\r
-               try {\r
-                       fwdSpeedFirstStage = Integer.parseInt(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_FIRST_STAGE));\r
-                       fwdSpeedSecondStage = Integer.parseInt(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_SECOND_STAGE));\r
-               } catch (NumberFormatException e) {\r
-                       throw new DemoRecorderException("Make sure that you specified valid numbers for the settings "\r
-                                       + Preferences.FFW_SPEED_FIRST_STAGE + " and " + Preferences.FFW_SPEED_SECOND_STAGE, e);\r
-               }\r
-               \r
-               try {\r
-                       cutter.cutDemo(\r
-                               this.demoFile,\r
-                               cutDemo,\r
-                               this.startSecond,\r
-                               this.endSecond,\r
-                               injectAtStart,\r
-                               injectBeforeCap,\r
-                               injectAfterCap,\r
-                               fwdSpeedFirstStage,\r
-                               fwdSpeedSecondStage\r
-                       );\r
-               } catch (DemoCutterException e) {\r
-                       throw new DemoRecorderException("Error occurred while trying to cut the demo: " + e.getMessage(), e);\r
-               }\r
-               \r
-       }\r
-       \r
-       private void removeOldAutocaps() {\r
-               for (String videoExtension : VIDEO_FILE_ENDINGS) {\r
-                       String fileString = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE\r
-                       + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + "." + videoExtension;\r
-                       File videoFile = new File(fileString);\r
-                       cleanUpFiles.add(videoFile);\r
-                       if (videoFile.exists()) {\r
-                               if (!videoFile.delete()) {\r
-                                       throw new DemoRecorderException("Could not delete old obsolete video file " + fileString);\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-       \r
-       private void recordClip(File cutDemo) {\r
-               Process nexProc;\r
-               String demoFileName = DemoRecorderUtils.getJustFileNameOfPath(cutDemo);\r
-               String execPath = this.enginePath.getAbsolutePath() + " " + this.engineParameters + " -demo "\r
-                                               + this.relativeDemoPath + "/" + demoFileName;\r
-               File engineDir = this.enginePath.getParentFile();\r
-               try {\r
-                       nexProc = Runtime.getRuntime().exec(execPath, null, engineDir);\r
-                       nexProc.getErrorStream();\r
-                       nexProc.getOutputStream();\r
-                       InputStream is = nexProc.getInputStream();\r
-                       InputStreamReader isr = new InputStreamReader(is);\r
-                       BufferedReader br = new BufferedReader(isr);\r
-                       while (br.readLine() != null) {\r
-                               //System.out.println(line);\r
-                       }\r
-               } catch (IOException e) {\r
-                       throw new DemoRecorderException("I/O Exception occurred when trying to execute the Nexuiz binary", e);\r
-               }\r
-       }\r
-\r
-       public void run() {\r
-               this.execute();\r
-       }\r
-       \r
-       public void setAppLayer(DemoRecorderApplication appLayer) {\r
-               this.appLayer = appLayer;\r
-       }\r
-\r
-       public int getJobIndex() {\r
-               return jobIndex;\r
-       }\r
-\r
-       public File getEnginePath() {\r
-               return enginePath;\r
-       }\r
-\r
-       public void setEnginePath(File enginePath) {\r
-               this.checkForProcessingState();\r
-               if (enginePath == null || !enginePath.exists()) {\r
-                       throw new DemoRecorderException("Could not locate engine binary!");\r
-               }\r
-               if (!enginePath.canExecute()) {\r
-                       throw new DemoRecorderException("The file you specified is not executable!");\r
-               }\r
-               this.enginePath = enginePath.getAbsoluteFile();\r
-       }\r
-\r
-       public String getEngineParameters() {\r
-               return engineParameters;\r
-       }\r
-\r
-       public void setEngineParameters(String engineParameters) {\r
-               this.checkForProcessingState();\r
-               if (engineParameters == null) {\r
-                       engineParameters = "";\r
-               }\r
-               this.engineParameters = engineParameters.trim();\r
-       }\r
-\r
-       public File getDemoFile() {\r
-               return demoFile;\r
-       }\r
-\r
-       public void setDemoFile(File demoFile) {\r
-               this.checkForProcessingState();\r
-               if (demoFile == null) {\r
-                       throw new DemoRecorderException("Could not locate demo file!");\r
-               }\r
-               if (!demoFile.exists()) {\r
-                       throw new DemoRecorderException("Could not locate demo file!: " + demoFile.getAbsolutePath());\r
-               }\r
-               if (!doReadWriteTest(demoFile.getParentFile())) {\r
-                       throw new DemoRecorderException("The directory you specified for the demo to be recorded is not writable!");\r
-               }\r
-               if (!demoFile.getAbsolutePath().endsWith(".dem")) {\r
-                       throw new DemoRecorderException("The demo file you specified must have the ending .dem");\r
-               }\r
-               \r
-               this.demoFile = demoFile.getAbsoluteFile();\r
-       }\r
-\r
-       public String getRelativeDemoPath() {\r
-               return relativeDemoPath;\r
-       }\r
-\r
-       public void setRelativeDemoPath(String relativeDemoPath) {\r
-               this.checkForProcessingState();\r
-               if (relativeDemoPath == null) {\r
-                       relativeDemoPath = "";\r
-               }\r
-               \r
-               //get rid of possible slashes\r
-               while (relativeDemoPath.startsWith("/") || relativeDemoPath.startsWith("\\")) {\r
-                       relativeDemoPath = relativeDemoPath.substring(1, relativeDemoPath.length());\r
-               }\r
-               while (relativeDemoPath.endsWith("/") || relativeDemoPath.endsWith("\\")) {\r
-                       relativeDemoPath = relativeDemoPath.substring(0, relativeDemoPath.length() - 1);\r
-               }\r
-               \r
-               this.relativeDemoPath = relativeDemoPath.trim();\r
-       }\r
-\r
-       public File getDpVideoPath() {\r
-               return dpVideoPath;\r
-       }\r
-\r
-       public void setDpVideoPath(File dpVideoPath) {\r
-               this.checkForProcessingState();\r
-               if (dpVideoPath == null || !dpVideoPath.isDirectory()) {\r
-                       throw new DemoRecorderException("Could not locate the specified DPVideo directory!");\r
-               }\r
-               \r
-               if (!this.doReadWriteTest(dpVideoPath)) {\r
-                       throw new DemoRecorderException("The DPVideo directory is not writable! It needs to be writable so that the file can be moved to its new location");\r
-               }\r
-               this.dpVideoPath = dpVideoPath.getAbsoluteFile();\r
-       }\r
-\r
-       public File getVideoDestination() {\r
-               return videoDestination;\r
-       }\r
-\r
-       public void setVideoDestination(File videoDestination) {\r
-               this.checkForProcessingState();\r
-               //keep in mind, the parameter videoDestination points to the final avi/ogg file w/o extension!\r
-               if (videoDestination == null || !videoDestination.getParentFile().isDirectory()) {\r
-                       throw new DemoRecorderException("Could not locate the specified video destination");\r
-               }\r
-               \r
-               if (!this.doReadWriteTest(videoDestination.getParentFile())) {\r
-                       throw new DemoRecorderException("The video destination directory is not writable! It needs to be writable so that the file can be moved to its new location");\r
-               }\r
-               \r
-               this.videoDestination = videoDestination.getAbsoluteFile();\r
-       }\r
-\r
-       public String getExecuteBeforeCap() {\r
-               return executeBeforeCap;\r
-       }\r
-\r
-       public void setExecuteBeforeCap(String executeBeforeCap) {\r
-               this.checkForProcessingState();\r
-               if (executeBeforeCap == null) {\r
-                       executeBeforeCap = "";\r
-               }\r
-               executeBeforeCap = executeBeforeCap.trim();\r
-               while (executeBeforeCap.endsWith(";")) {\r
-                       executeBeforeCap = executeBeforeCap.substring(0, executeBeforeCap.length()-1);\r
-               }\r
-               this.executeBeforeCap = executeBeforeCap;\r
-       }\r
-\r
-       public String getExecuteAfterCap() {\r
-               return executeAfterCap;\r
-       }\r
-\r
-       public void setExecuteAfterCap(String executeAfterCap) {\r
-               this.checkForProcessingState();\r
-               if (executeAfterCap == null) {\r
-                       executeAfterCap = "";\r
-               }\r
-               executeAfterCap = executeAfterCap.trim();\r
-               while (executeAfterCap.endsWith(";")) {\r
-                       executeAfterCap = executeAfterCap.substring(0, executeAfterCap.length()-1);\r
-               }\r
-               if (executeAfterCap.contains("cl_capturevideo_number") || executeAfterCap.contains("cl_capturevideo_nameformat")) {\r
-                       throw new DemoRecorderException("Execute after String cannot contain cl_capturevideo_number or _nameformat changes!");\r
-               }\r
-               this.executeAfterCap = executeAfterCap;\r
-       }\r
-\r
-       public float getStartSecond() {\r
-               return startSecond;\r
-       }\r
-\r
-       public void setStartSecond(float startSecond) {\r
-               this.checkForProcessingState();\r
-               if (startSecond < 0) {\r
-                       throw new DemoRecorderException("Start second cannot be < 0");\r
-               }\r
-               this.startSecond = startSecond;\r
-       }\r
-\r
-       public float getEndSecond() {\r
-               return endSecond;\r
-       }\r
-\r
-       public void setEndSecond(float endSecond) {\r
-               this.checkForProcessingState();\r
-               if (endSecond < this.startSecond) {\r
-                       throw new DemoRecorderException("End second cannot be < start second");\r
-               }\r
-               this.endSecond = endSecond;\r
-       }\r
-\r
-       public State getState() {\r
-               return state;\r
-       }\r
-\r
-       public void setState(State state) {\r
-               this.state = state;\r
-               this.appLayer.fireUserInterfaceUpdate(this);\r
-       }\r
-\r
-       public String getJobName() {\r
-               if (this.jobName == null || this.jobName.equals("")) {\r
-                       return "Job " + this.jobIndex;\r
-               }\r
-               return this.jobName;\r
-       }\r
-       \r
-       public void setJobName(String jobName) {\r
-               if (jobName == null || jobName.equals("")) {\r
-                       this.jobIndex = appLayer.getNewJobIndex();\r
-                       this.jobName = "Job " + this.jobIndex;\r
-               } else {\r
-                       this.jobName = jobName;\r
-               }\r
-       }\r
-\r
-       public DemoRecorderException getLastException() {\r
-               return lastException;\r
-       }\r
-       \r
-       /**\r
-        * Tests whether the given directory is writable by creating a file in there and deleting\r
-        * it again.\r
-        * @param directory\r
-        * @return true if directory is writable\r
-        */\r
-       protected boolean doReadWriteTest(File directory) {\r
-               boolean writable = false;\r
-               String fileName = "tmp." + Math.random()*10000 + ".dat";\r
-               File tempFile = new File(directory, fileName);\r
-               try {\r
-                       writable = tempFile.createNewFile();\r
-                       if (writable) {\r
-                               tempFile.delete();\r
-                       }\r
-               } catch (IOException e) {\r
-                       writable = false;\r
-               }\r
-               return writable;\r
-       }\r
-       \r
-       private void checkForProcessingState() {\r
-               if (this.state == State.PROCESSING) {\r
-                       throw new DemoRecorderException("Cannot modify this job while it is processing!");\r
-               }\r
-       }\r
-\r
-       public Properties getEncoderPluginSettings(EncoderPlugin plugin) {\r
-               if (this.encoderPluginSettings.containsKey(plugin.getName())) {\r
-                       return this.encoderPluginSettings.get(plugin.getName());\r
-               } else {\r
-                       return new Properties();\r
-               }\r
-       }\r
-\r
-       public void setEncoderPluginSetting(String pluginName, String pluginSettingKey, String value) {\r
-               Properties p = this.encoderPluginSettings.get(pluginName);\r
-               if (p == null) {\r
-                       p = new Properties();\r
-                       this.encoderPluginSettings.put(pluginName, p);\r
-               }\r
-               \r
-               p.put(pluginSettingKey, value);\r
-       }\r
-\r
-       public Map<String, Properties> getEncoderPluginSettings() {\r
-               return encoderPluginSettings;\r
-       }\r
-\r
-       public void setEncoderPluginSettings(Map<String, Properties> encoderPluginSettings) {\r
-               this.encoderPluginSettings = encoderPluginSettings;\r
-       }\r
-\r
-       public File getActualVideoDestination() {\r
-               return actualVideoDestination;\r
-       }\r
-       \r
-       public void setActualVideoDestination(File actualVideoDestination) {\r
-               this.actualVideoDestination = actualVideoDestination;\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.application.jobs;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.DemoRecorderException;
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+import com.nexuiz.demorecorder.application.NDRPreferences;
+import com.nexuiz.demorecorder.application.DemoRecorderApplication.Preferences;
+import com.nexuiz.demorecorder.application.democutter.DemoCutter;
+import com.nexuiz.demorecorder.application.democutter.DemoCutterException;
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+import com.nexuiz.demorecorder.application.plugins.EncoderPluginException;
+
+public class RecordJob implements Runnable, Serializable {
+       
+       private static final long serialVersionUID = -4585637490345587912L;
+
+       public enum State {
+               WAITING, PROCESSING, ERROR, ERROR_PLUGIN, DONE
+       }
+       
+       public static final String CUT_DEMO_FILE_SUFFIX = "_autocut";
+       public static final String CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE = "autocap";
+       public static final String CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE = "1234567";
+       protected static final String[] VIDEO_FILE_ENDINGS = {"avi", "ogv"};
+       
+       private DemoRecorderApplication appLayer;
+       protected String jobName;
+       private int jobIndex;
+       protected File enginePath;
+       protected String engineParameters;
+       protected File demoFile;
+       protected String relativeDemoPath;
+       protected File dpVideoPath;
+       protected File videoDestination;
+       protected String executeBeforeCap;
+       protected String executeAfterCap;
+       protected float startSecond;
+       protected float endSecond;
+       protected State state = State.WAITING;
+       protected DemoRecorderException lastException = null;
+       
+       /**
+        * Points to the actual final file, including possible suffixes, e.g. _copy1, and the actualy ending
+        */
+       protected File actualVideoDestination = null;
+       /**
+        * Map that identifies the plug-in by its name (String) and maps to the plug-in's job-specific settings
+        */
+       protected Map<String, Properties> encoderPluginSettings = new HashMap<String, Properties>();
+       
+       private List<File> cleanUpFiles = null;
+       
+       public RecordJob(
+               DemoRecorderApplication appLayer,
+               String jobName,
+               int jobIndex,
+               File enginePath,
+               String engineParameters,
+               File demoFile,
+               String relativeDemoPath,
+               File dpVideoPath,
+               File videoDestination,
+               String executeBeforeCap,
+               String executeAfterCap,
+               float startSecond,
+               float endSecond
+       ) {
+               this.appLayer = appLayer;
+               this.jobName = jobName;
+               this.jobIndex = jobIndex;
+               
+               this.setEnginePath(enginePath);
+               this.setEngineParameters(engineParameters);
+               this.setDemoFile(demoFile);
+               this.setRelativeDemoPath(relativeDemoPath);
+               this.setDpVideoPath(dpVideoPath);
+               this.setVideoDestination(videoDestination);
+               this.setExecuteBeforeCap(executeBeforeCap);
+               this.setExecuteAfterCap(executeAfterCap);
+               this.setStartSecond(startSecond);
+               this.setEndSecond(endSecond);
+       }
+       
+       public RecordJob(){}
+       
+       /**
+        * Constructor that can be used by other classes such as job templates. Won't throw exceptions
+        * as it won't check the input for validity.
+        */
+       protected RecordJob(
+               File enginePath,
+               String engineParameters,
+               File demoFile,
+               String relativeDemoPath,
+               File dpVideoPath,
+               File videoDestination,
+               String executeBeforeCap,
+               String executeAfterCap,
+               float startSecond,
+               float endSecond
+               ) {
+               this.jobIndex = -1;
+               this.enginePath = enginePath;
+               this.engineParameters = engineParameters;
+               this.demoFile = demoFile;
+               this.relativeDemoPath = relativeDemoPath;
+               this.dpVideoPath = dpVideoPath;
+               this.videoDestination = videoDestination;
+               this.executeBeforeCap = executeBeforeCap;
+               this.executeAfterCap = executeAfterCap;
+               this.startSecond = startSecond;
+               this.endSecond = endSecond;
+       }
+       
+       public void execute() {
+               if (this.state == State.PROCESSING) {
+                       return;
+               }
+               boolean errorOccurred = false;
+               this.setState(State.PROCESSING);
+               this.appLayer.fireUserInterfaceUpdate(this);
+               cleanUpFiles = new ArrayList<File>();
+               
+               File cutDemo = computeCutDemoFile();
+               cutDemo.delete(); //delete possibly old cutDemoFile
+               
+               EncoderPlugin recentEncoder = null;
+               
+               try {
+                       this.cutDemo(cutDemo);
+                       this.removeOldAutocaps();
+                       this.recordClip(cutDemo);
+                       this.moveRecordedClip();
+                       for (EncoderPlugin plugin : this.appLayer.getEncoderPlugins()) {
+                               recentEncoder = plugin;
+                               plugin.executeEncoder(this);
+                       }
+               } catch (DemoRecorderException e) {
+                       errorOccurred = true;
+                       this.lastException = e;
+                       this.setState(State.ERROR);
+               } catch (EncoderPluginException e) {
+                       errorOccurred = true;
+                       this.lastException = new DemoRecorderException("Encoder plug-in " + recentEncoder.getName() + " failed: "
+                                       + e.getMessage(), e);
+                       this.setState(State.ERROR_PLUGIN);
+               } catch (Exception e) {
+                       errorOccurred = true;
+                       this.lastException = new DemoRecorderException("Executing job failed, click on details for more info", e);
+               } finally {
+                       NDRPreferences preferences = this.appLayer.getPreferences();
+                       if (!Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DO_NOT_DELETE_CUT_DEMOS))) {
+                               cleanUpFiles.add(cutDemo);
+                       }
+                       if (!errorOccurred) {
+                               this.setState(State.DONE);
+                       }
+                       this.cleanUpFiles();
+                       this.appLayer.fireUserInterfaceUpdate(this);
+                       this.appLayer.saveJobQueue();
+               }
+       }
+       
+       /**
+        * Will execute just the specified encoder plug-in on an already "done" job.
+        * @param pluginName
+        */
+       public void executePlugin(EncoderPlugin plugin) {
+               if (this.getState() != State.DONE) {
+                       return;
+               }
+               this.setState(State.PROCESSING);
+               this.appLayer.fireUserInterfaceUpdate(this);
+               
+               try {
+                       plugin.executeEncoder(this);
+                       this.setState(State.DONE);
+               } catch (EncoderPluginException e) {
+                       this.lastException = new DemoRecorderException("Encoder plug-in " + plugin.getName() + " failed: "
+                                       + e.getMessage(), e);
+                       this.setState(State.ERROR_PLUGIN);
+               }
+               
+               this.appLayer.fireUserInterfaceUpdate(this);
+       }
+       
+       private void cleanUpFiles() {
+               try {
+                       for (File f : this.cleanUpFiles) {
+                               f.delete();
+                       }
+               } catch (Exception e) {}
+               
+       }
+       
+       private void moveRecordedClip() {
+               //1. Figure out whether the file is .avi or .ogv
+               File sourceFile = null;
+               for (String videoExtension : VIDEO_FILE_ENDINGS) {
+                       String fileString = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE
+                       + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + "." + videoExtension;
+                       File videoFile = new File(fileString);
+                       if (videoFile.exists()) {
+                               sourceFile = videoFile;
+                               break;
+                       }
+               }
+               
+               if (sourceFile == null) {
+                       String p = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE
+                       + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE;
+                       throw new DemoRecorderException("Could not locate the expected video file being generated by Nexuiz (should have been at "
+                                       + p + ".avi/.ogv");
+               }
+               cleanUpFiles.add(sourceFile);
+               
+               File destinationFile = null;
+               NDRPreferences preferences = this.appLayer.getPreferences();
+               String sourceFileExtension = DemoRecorderUtils.getFileExtension(sourceFile);
+               String destinationFilePath = this.videoDestination + "." + sourceFileExtension;
+               destinationFile = new File(destinationFilePath);
+               if (destinationFile.exists()) {
+                       if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.OVERWRITE_VIDEO_FILE))) {
+                               if (!destinationFile.delete()) {
+                                       throw new DemoRecorderException("Could not delete the existing video destinatin file " + destinationFile.getAbsolutePath()
+                                                       + " (application setting to overwrite existing video files is enabled!)");
+                               }
+                       } else {
+                               destinationFilePath = this.videoDestination + "_copy" + this.getVideoDestinationCopyNr(sourceFileExtension) + "." + sourceFileExtension;
+                               destinationFile = new File(destinationFilePath);
+                       }
+               }
+               
+               //finally move the file
+               if (!sourceFile.renameTo(destinationFile)) {
+                       cleanUpFiles.add(destinationFile);
+                       throw new DemoRecorderException("Could not move the video file from " + sourceFile.getAbsolutePath()
+                                       + " to " + destinationFile.getAbsolutePath());
+               }
+               
+               this.actualVideoDestination = destinationFile;
+       }
+       
+       /**
+        * As destination video files, e.g. "test"[.avi] can already exist, we have to save the
+        * the video file to a file name such as test_copy1 or test_copy2.
+        * This function will figure out what the number (1, 2....) is.
+        * @return
+        */
+       private int getVideoDestinationCopyNr(String sourceFileExtension) {
+               int i = 1;
+               File lastFile;
+               while (true) {
+                       lastFile = new File(this.videoDestination + "_copy" + i + "." + sourceFileExtension);
+                       if (!lastFile.exists()) {
+                               break;
+                       }
+                       
+                       i++;
+               }
+               return i;
+       }
+
+       private File computeCutDemoFile() {
+               String origFileString = this.demoFile.getAbsolutePath();
+               int lastIndex = origFileString.lastIndexOf(File.separator);
+               String autoDemoFileName = origFileString.substring(lastIndex+1, origFileString.length());
+               //strip .dem ending
+               autoDemoFileName = autoDemoFileName.substring(0, autoDemoFileName.length()-4);
+               autoDemoFileName = autoDemoFileName + CUT_DEMO_FILE_SUFFIX + ".dem";
+               String finalString = origFileString.substring(0, lastIndex) + File.separator + autoDemoFileName;
+               File f = new File(finalString);
+               
+               return f;
+       }
+       
+       private void cutDemo(File cutDemo) {
+               String injectAtStart = "";
+               String injectBeforeCap = "";
+               String injectAfterCap = "";
+               
+               NDRPreferences preferences = this.appLayer.getPreferences();
+               if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_RENDERING))) {
+                       injectAtStart += "r_render 0;";
+                       injectBeforeCap += "r_render 1;";
+               }
+               if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_SOUND))) {
+                       injectAtStart += "set _volume $volume;volume 0;";
+                       injectBeforeCap += "set volume $_volume;";
+               }
+               injectBeforeCap += this.executeBeforeCap + "\n";
+               injectBeforeCap += "set _cl_capturevideo_nameformat $cl_capturevideo_nameformat;set _cl_capturevideo_number $cl_capturevideo_number;";
+               injectBeforeCap += "cl_capturevideo_nameformat " + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE + ";";
+               injectBeforeCap += "cl_capturevideo_number " + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + ";";
+               
+               injectAfterCap += this.executeAfterCap + "\n";
+               injectAfterCap += "cl_capturevideo_nameformat $_cl_capturevideo_nameformat;cl_capturevideo_number $_cl_capturevideo_number;";
+               
+               
+               DemoCutter cutter = new DemoCutter();
+               int fwdSpeedFirstStage, fwdSpeedSecondStage;
+               try {
+                       fwdSpeedFirstStage = Integer.parseInt(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_FIRST_STAGE));
+                       fwdSpeedSecondStage = Integer.parseInt(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_SECOND_STAGE));
+               } catch (NumberFormatException e) {
+                       throw new DemoRecorderException("Make sure that you specified valid numbers for the settings "
+                                       + Preferences.FFW_SPEED_FIRST_STAGE + " and " + Preferences.FFW_SPEED_SECOND_STAGE, e);
+               }
+               
+               try {
+                       cutter.cutDemo(
+                               this.demoFile,
+                               cutDemo,
+                               this.startSecond,
+                               this.endSecond,
+                               injectAtStart,
+                               injectBeforeCap,
+                               injectAfterCap,
+                               fwdSpeedFirstStage,
+                               fwdSpeedSecondStage
+                       );
+               } catch (DemoCutterException e) {
+                       throw new DemoRecorderException("Error occurred while trying to cut the demo: " + e.getMessage(), e);
+               }
+               
+       }
+       
+       private void removeOldAutocaps() {
+               for (String videoExtension : VIDEO_FILE_ENDINGS) {
+                       String fileString = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE
+                       + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + "." + videoExtension;
+                       File videoFile = new File(fileString);
+                       cleanUpFiles.add(videoFile);
+                       if (videoFile.exists()) {
+                               if (!videoFile.delete()) {
+                                       throw new DemoRecorderException("Could not delete old obsolete video file " + fileString);
+                               }
+                       }
+               }
+       }
+       
+       private void recordClip(File cutDemo) {
+               Process nexProc;
+               String demoFileName = DemoRecorderUtils.getJustFileNameOfPath(cutDemo);
+               String execPath = this.enginePath.getAbsolutePath() + " " + this.engineParameters + " -demo "
+                                               + this.relativeDemoPath + "/" + demoFileName;
+               File engineDir = this.enginePath.getParentFile();
+               try {
+                       nexProc = Runtime.getRuntime().exec(execPath, null, engineDir);
+                       nexProc.getErrorStream();
+                       nexProc.getOutputStream();
+                       InputStream is = nexProc.getInputStream();
+                       InputStreamReader isr = new InputStreamReader(is);
+                       BufferedReader br = new BufferedReader(isr);
+                       while (br.readLine() != null) {
+                               //System.out.println(line);
+                       }
+               } catch (IOException e) {
+                       throw new DemoRecorderException("I/O Exception occurred when trying to execute the Nexuiz binary", e);
+               }
+       }
+
+       public void run() {
+               this.execute();
+       }
+       
+       public void setAppLayer(DemoRecorderApplication appLayer) {
+               this.appLayer = appLayer;
+       }
+
+       public int getJobIndex() {
+               return jobIndex;
+       }
+
+       public File getEnginePath() {
+               return enginePath;
+       }
+
+       public void setEnginePath(File enginePath) {
+               this.checkForProcessingState();
+               if (enginePath == null || !enginePath.exists()) {
+                       throw new DemoRecorderException("Could not locate engine binary!");
+               }
+               if (!enginePath.canExecute()) {
+                       throw new DemoRecorderException("The file you specified is not executable!");
+               }
+               this.enginePath = enginePath.getAbsoluteFile();
+       }
+
+       public String getEngineParameters() {
+               return engineParameters;
+       }
+
+       public void setEngineParameters(String engineParameters) {
+               this.checkForProcessingState();
+               if (engineParameters == null) {
+                       engineParameters = "";
+               }
+               this.engineParameters = engineParameters.trim();
+       }
+
+       public File getDemoFile() {
+               return demoFile;
+       }
+
+       public void setDemoFile(File demoFile) {
+               this.checkForProcessingState();
+               if (demoFile == null) {
+                       throw new DemoRecorderException("Could not locate demo file!");
+               }
+               if (!demoFile.exists()) {
+                       throw new DemoRecorderException("Could not locate demo file!: " + demoFile.getAbsolutePath());
+               }
+               if (!doReadWriteTest(demoFile.getParentFile())) {
+                       throw new DemoRecorderException("The directory you specified for the demo to be recorded is not writable!");
+               }
+               if (!demoFile.getAbsolutePath().endsWith(".dem")) {
+                       throw new DemoRecorderException("The demo file you specified must have the ending .dem");
+               }
+               
+               this.demoFile = demoFile.getAbsoluteFile();
+       }
+
+       public String getRelativeDemoPath() {
+               return relativeDemoPath;
+       }
+
+       public void setRelativeDemoPath(String relativeDemoPath) {
+               this.checkForProcessingState();
+               if (relativeDemoPath == null) {
+                       relativeDemoPath = "";
+               }
+               
+               //get rid of possible slashes
+               while (relativeDemoPath.startsWith("/") || relativeDemoPath.startsWith("\\")) {
+                       relativeDemoPath = relativeDemoPath.substring(1, relativeDemoPath.length());
+               }
+               while (relativeDemoPath.endsWith("/") || relativeDemoPath.endsWith("\\")) {
+                       relativeDemoPath = relativeDemoPath.substring(0, relativeDemoPath.length() - 1);
+               }
+               
+               this.relativeDemoPath = relativeDemoPath.trim();
+       }
+
+       public File getDpVideoPath() {
+               return dpVideoPath;
+       }
+
+       public void setDpVideoPath(File dpVideoPath) {
+               this.checkForProcessingState();
+               if (dpVideoPath == null || !dpVideoPath.isDirectory()) {
+                       throw new DemoRecorderException("Could not locate the specified DPVideo directory!");
+               }
+               
+               if (!this.doReadWriteTest(dpVideoPath)) {
+                       throw new DemoRecorderException("The DPVideo directory is not writable! It needs to be writable so that the file can be moved to its new location");
+               }
+               this.dpVideoPath = dpVideoPath.getAbsoluteFile();
+       }
+
+       public File getVideoDestination() {
+               return videoDestination;
+       }
+
+       public void setVideoDestination(File videoDestination) {
+               this.checkForProcessingState();
+               //keep in mind, the parameter videoDestination points to the final avi/ogg file w/o extension!
+               if (videoDestination == null || !videoDestination.getParentFile().isDirectory()) {
+                       throw new DemoRecorderException("Could not locate the specified video destination");
+               }
+               
+               if (!this.doReadWriteTest(videoDestination.getParentFile())) {
+                       throw new DemoRecorderException("The video destination directory is not writable! It needs to be writable so that the file can be moved to its new location");
+               }
+               
+               this.videoDestination = videoDestination.getAbsoluteFile();
+       }
+
+       public String getExecuteBeforeCap() {
+               return executeBeforeCap;
+       }
+
+       public void setExecuteBeforeCap(String executeBeforeCap) {
+               this.checkForProcessingState();
+               if (executeBeforeCap == null) {
+                       executeBeforeCap = "";
+               }
+               executeBeforeCap = executeBeforeCap.trim();
+               while (executeBeforeCap.endsWith(";")) {
+                       executeBeforeCap = executeBeforeCap.substring(0, executeBeforeCap.length()-1);
+               }
+               this.executeBeforeCap = executeBeforeCap;
+       }
+
+       public String getExecuteAfterCap() {
+               return executeAfterCap;
+       }
+
+       public void setExecuteAfterCap(String executeAfterCap) {
+               this.checkForProcessingState();
+               if (executeAfterCap == null) {
+                       executeAfterCap = "";
+               }
+               executeAfterCap = executeAfterCap.trim();
+               while (executeAfterCap.endsWith(";")) {
+                       executeAfterCap = executeAfterCap.substring(0, executeAfterCap.length()-1);
+               }
+               if (executeAfterCap.contains("cl_capturevideo_number") || executeAfterCap.contains("cl_capturevideo_nameformat")) {
+                       throw new DemoRecorderException("Execute after String cannot contain cl_capturevideo_number or _nameformat changes!");
+               }
+               this.executeAfterCap = executeAfterCap;
+       }
+
+       public float getStartSecond() {
+               return startSecond;
+       }
+
+       public void setStartSecond(float startSecond) {
+               this.checkForProcessingState();
+               if (startSecond < 0) {
+                       throw new DemoRecorderException("Start second cannot be < 0");
+               }
+               this.startSecond = startSecond;
+       }
+
+       public float getEndSecond() {
+               return endSecond;
+       }
+
+       public void setEndSecond(float endSecond) {
+               this.checkForProcessingState();
+               if (endSecond < this.startSecond) {
+                       throw new DemoRecorderException("End second cannot be < start second");
+               }
+               this.endSecond = endSecond;
+       }
+
+       public State getState() {
+               return state;
+       }
+
+       public void setState(State state) {
+               this.state = state;
+               this.appLayer.fireUserInterfaceUpdate(this);
+       }
+
+       public String getJobName() {
+               if (this.jobName == null || this.jobName.equals("")) {
+                       return "Job " + this.jobIndex;
+               }
+               return this.jobName;
+       }
+       
+       public void setJobName(String jobName) {
+               if (jobName == null || jobName.equals("")) {
+                       this.jobIndex = appLayer.getNewJobIndex();
+                       this.jobName = "Job " + this.jobIndex;
+               } else {
+                       this.jobName = jobName;
+               }
+       }
+
+       public DemoRecorderException getLastException() {
+               return lastException;
+       }
+       
+       /**
+        * Tests whether the given directory is writable by creating a file in there and deleting
+        * it again.
+        * @param directory
+        * @return true if directory is writable
+        */
+       protected boolean doReadWriteTest(File directory) {
+               boolean writable = false;
+               String fileName = "tmp." + Math.random()*10000 + ".dat";
+               File tempFile = new File(directory, fileName);
+               try {
+                       writable = tempFile.createNewFile();
+                       if (writable) {
+                               tempFile.delete();
+                       }
+               } catch (IOException e) {
+                       writable = false;
+               }
+               return writable;
+       }
+       
+       private void checkForProcessingState() {
+               if (this.state == State.PROCESSING) {
+                       throw new DemoRecorderException("Cannot modify this job while it is processing!");
+               }
+       }
+
+       public Properties getEncoderPluginSettings(EncoderPlugin plugin) {
+               if (this.encoderPluginSettings.containsKey(plugin.getName())) {
+                       return this.encoderPluginSettings.get(plugin.getName());
+               } else {
+                       return new Properties();
+               }
+       }
+
+       public void setEncoderPluginSetting(String pluginName, String pluginSettingKey, String value) {
+               Properties p = this.encoderPluginSettings.get(pluginName);
+               if (p == null) {
+                       p = new Properties();
+                       this.encoderPluginSettings.put(pluginName, p);
+               }
+               
+               p.put(pluginSettingKey, value);
+       }
+
+       public Map<String, Properties> getEncoderPluginSettings() {
+               return encoderPluginSettings;
+       }
+
+       public void setEncoderPluginSettings(Map<String, Properties> encoderPluginSettings) {
+               this.encoderPluginSettings = encoderPluginSettings;
+       }
+
+       public File getActualVideoDestination() {
+               return actualVideoDestination;
+       }
+       
+       public void setActualVideoDestination(File actualVideoDestination) {
+               this.actualVideoDestination = actualVideoDestination;
+       }
+}
index d1bcd67a100fbb98ab496b430212060c3b96397a..5bd738b411aca708fb612e9f19a13cb8a501c09d 100644 (file)
@@ -1,17 +1,17 @@
-package com.nexuiz.demorecorder.application.jobs;\r
-\r
-import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
-\r
-public class RecordsDoneJob implements Runnable {\r
-       \r
-       private DemoRecorderApplication appLayer;\r
-       \r
-       public RecordsDoneJob(DemoRecorderApplication appLayer) {\r
-               this.appLayer = appLayer;\r
-       }\r
-\r
-       public void run() {\r
-               this.appLayer.notifyAllJobsDone();\r
-       }\r
-\r
-}\r
+package com.nexuiz.demorecorder.application.jobs;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+
+public class RecordsDoneJob implements Runnable {
+       
+       private DemoRecorderApplication appLayer;
+       
+       public RecordsDoneJob(DemoRecorderApplication appLayer) {
+               this.appLayer = appLayer;
+       }
+
+       public void run() {
+               this.appLayer.notifyAllJobsDone();
+       }
+
+}
index b3c7de1271bceb223893ed994825cf51f065625d..338b895feed14dc03fd8be9aeecacd2e6530fde2 100644 (file)
@@ -1,69 +1,69 @@
-package com.nexuiz.demorecorder.application.plugins;\r
-\r
-import java.util.Properties;\r
-\r
-import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
-import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
-\r
-public interface EncoderPlugin {\r
-       \r
-       /**\r
-        * Makes the application layer known to the plug-in, which is required so that the plug-in\r
-        * can access the preferences of the application. Call this method first before using any\r
-        * of the others.\r
-        */\r
-       public void setApplicationLayer(DemoRecorderApplication appLayer);\r
-\r
-       /**\r
-        * Returns the name of the plug-in. Must not contain a "."\r
-        */\r
-       public String getName();\r
-       \r
-       /**\r
-        * Returns true if the plug-in is enabled (checked from the preferences of the app layer)\r
-        * @return true if the plug-in is enabled\r
-        */\r
-       public boolean isEnabled();\r
-       \r
-       /**\r
-        * Global preferences are preferences of a plug-in that are application-wide and not job-\r
-        * specific. They should be shown in a global preferences dialog.\r
-        * Use this method in order to tell the application layer and GUI which global settings your\r
-        * encoder plug-in offers, and set a reasonable default. Note that for the default-values being\r
-        * set you can either set to "true" or "false", any String (can be empty), or "filechooser" if\r
-        * you want the user to select a file. \r
-        * @return\r
-        */\r
-       public Properties getGlobalPreferences();\r
-       \r
-       /**\r
-        * In order to influence the order of settings being displayed to the user in a UI, return an array\r
-        * of all keys used in the Properties object returned in getGlobalPreferences(), with your desired\r
-        * order.\r
-        * @return\r
-        */\r
-       public String[] getGlobalPreferencesOrder();\r
-       \r
-       /**\r
-        * Here you can return a Properties object that contains keys for values that can be specific to each\r
-        * individual RecordJob. \r
-        * @return\r
-        */\r
-       public Properties getJobSpecificPreferences();\r
-       \r
-       /**\r
-        * In order to influence the order of job-specific settings being displayed to the user in a UI,\r
-        * return an array of all keys used in the Properties object returned in getJobSpecificPreferences(), with\r
-        * your desired order.\r
-        * @return\r
-        */\r
-       public String[] getJobSpecificPreferencesOrder();\r
-       \r
-       /**\r
-        * Will be called by the application layer when a job has been successfully recorded and moved to its\r
-        * final destination. This method has to perform the specific tasks your plug-in is supposed to do.\r
-        * @param job\r
-        * @throws EncoderPluginException\r
-        */\r
-       public void executeEncoder(RecordJob job) throws EncoderPluginException;\r
-}\r
+package com.nexuiz.demorecorder.application.plugins;
+
+import java.util.Properties;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+
+public interface EncoderPlugin {
+       
+       /**
+        * Makes the application layer known to the plug-in, which is required so that the plug-in
+        * can access the preferences of the application. Call this method first before using any
+        * of the others.
+        */
+       public void setApplicationLayer(DemoRecorderApplication appLayer);
+
+       /**
+        * Returns the name of the plug-in. Must not contain a "."
+        */
+       public String getName();
+       
+       /**
+        * Returns true if the plug-in is enabled (checked from the preferences of the app layer)
+        * @return true if the plug-in is enabled
+        */
+       public boolean isEnabled();
+       
+       /**
+        * Global preferences are preferences of a plug-in that are application-wide and not job-
+        * specific. They should be shown in a global preferences dialog.
+        * Use this method in order to tell the application layer and GUI which global settings your
+        * encoder plug-in offers, and set a reasonable default. Note that for the default-values being
+        * set you can either set to "true" or "false", any String (can be empty), or "filechooser" if
+        * you want the user to select a file. 
+        * @return
+        */
+       public Properties getGlobalPreferences();
+       
+       /**
+        * In order to influence the order of settings being displayed to the user in a UI, return an array
+        * of all keys used in the Properties object returned in getGlobalPreferences(), with your desired
+        * order.
+        * @return
+        */
+       public String[] getGlobalPreferencesOrder();
+       
+       /**
+        * Here you can return a Properties object that contains keys for values that can be specific to each
+        * individual RecordJob. 
+        * @return
+        */
+       public Properties getJobSpecificPreferences();
+       
+       /**
+        * In order to influence the order of job-specific settings being displayed to the user in a UI,
+        * return an array of all keys used in the Properties object returned in getJobSpecificPreferences(), with
+        * your desired order.
+        * @return
+        */
+       public String[] getJobSpecificPreferencesOrder();
+       
+       /**
+        * Will be called by the application layer when a job has been successfully recorded and moved to its
+        * final destination. This method has to perform the specific tasks your plug-in is supposed to do.
+        * @param job
+        * @throws EncoderPluginException
+        */
+       public void executeEncoder(RecordJob job) throws EncoderPluginException;
+}
index 70a98b30ff7ea5d342eec1ae6432681e68ee4711..4fb73d9b47032477596f75f7a73fd49887603249 100644 (file)
@@ -1,14 +1,14 @@
-package com.nexuiz.demorecorder.application.plugins;\r
-\r
-public class EncoderPluginException extends Exception {\r
-\r
-       private static final long serialVersionUID = 2200737027476726978L;\r
-\r
-       public EncoderPluginException(String message) {\r
-               super(message);\r
-       }\r
-       \r
-       public EncoderPluginException(String message, Throwable t) {\r
-               super(message, t);\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.application.plugins;
+
+public class EncoderPluginException extends Exception {
+
+       private static final long serialVersionUID = 2200737027476726978L;
+
+       public EncoderPluginException(String message) {
+               super(message);
+       }
+       
+       public EncoderPluginException(String message, Throwable t) {
+               super(message, t);
+       }
+}
index d7188b65b5b818059ef46129ecbf68fe6c0256fb..f98ffc90b3c9aac52e8da1ba962e67ce9e6ec312 100644 (file)
@@ -1,18 +1,18 @@
-package com.nexuiz.demorecorder.main;\r
-\r
-import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
-import com.nexuiz.demorecorder.ui.swinggui.SwingGUI;\r
-import com.nexuiz.demorecorder.ui.swinggui.utils.ShowErrorDialogExceptionHandler;\r
-\r
-public class Driver {\r
-       \r
-       public static void main(String[] args) {\r
-               SwingGUI.setSystemLAF();\r
-               Thread.setDefaultUncaughtExceptionHandler(new ShowErrorDialogExceptionHandler());\r
-               DemoRecorderApplication appLayer = new DemoRecorderApplication();\r
-               \r
-               SwingGUI gui = new SwingGUI(appLayer);\r
-               appLayer.addUserInterfaceListener(gui);\r
-               \r
-       }\r
-}\r
+package com.nexuiz.demorecorder.main;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.ui.swinggui.SwingGUI;
+import com.nexuiz.demorecorder.ui.swinggui.utils.ShowErrorDialogExceptionHandler;
+
+public class Driver {
+       
+       public static void main(String[] args) {
+               SwingGUI.setSystemLAF();
+               Thread.setDefaultUncaughtExceptionHandler(new ShowErrorDialogExceptionHandler());
+               DemoRecorderApplication appLayer = new DemoRecorderApplication();
+               
+               SwingGUI gui = new SwingGUI(appLayer);
+               appLayer.addUserInterfaceListener(gui);
+               
+       }
+}
index c8dc3555d2a581cb52733cb26f8d95987cae6943..1f99e978ac9c34c9867076a77f1f3f180a4b8158 100644 (file)
@@ -1,20 +1,20 @@
-package com.nexuiz.demorecorder.ui;\r
-\r
-import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
-\r
-public interface DemoRecorderUI {\r
-\r
-       /**\r
-        * Called by the application layer to inform the GUI about the fact that\r
-        * one or more properties of the given job changed (most likely the status).\r
-        * The given job might also be new to the GUI.\r
-        * @param job the affected job\r
-        */\r
-       public void RecordJobPropertiesChange(RecordJob job);\r
-       \r
-       /**\r
-        * Called by the application layer to inform the GUI that it finished\r
-        * recording all assigned jobs.\r
-        */\r
-       public void recordingFinished();\r
-}\r
+package com.nexuiz.demorecorder.ui;
+
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+
+public interface DemoRecorderUI {
+
+       /**
+        * Called by the application layer to inform the GUI about the fact that
+        * one or more properties of the given job changed (most likely the status).
+        * The given job might also be new to the GUI.
+        * @param job the affected job
+        */
+       public void RecordJobPropertiesChange(RecordJob job);
+       
+       /**
+        * Called by the application layer to inform the GUI that it finished
+        * recording all assigned jobs.
+        */
+       public void recordingFinished();
+}
index 84b2c292069a8e086bbb4cfd9b3d8d195904c0ba..d216b204fba98debb7a4a8a6d32fa56c8bfb6dd3 100644 (file)
-package com.nexuiz.demorecorder.ui.swinggui;\r
-\r
-import java.awt.Frame;\r
-import java.awt.event.ActionEvent;\r
-import java.awt.event.ActionListener;\r
-import java.awt.event.ItemEvent;\r
-import java.awt.event.ItemListener;\r
-import java.io.File;\r
-import java.util.List;\r
-\r
-import javax.swing.JButton;\r
-import javax.swing.JCheckBox;\r
-import javax.swing.JDialog;\r
-import javax.swing.JLabel;\r
-import javax.swing.JOptionPane;\r
-\r
-import net.miginfocom.swing.MigLayout;\r
-\r
-import com.nexuiz.demorecorder.application.DemoRecorderUtils;\r
-import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
-\r
-public class ApplyTemplateDialog extends JDialog implements ActionListener, ItemListener {\r
-\r
-       private static final long serialVersionUID = 4807155579295688578L;\r
-       private Frame parentFrame;\r
-       private RecordJobTemplate template;\r
-       private List<RecordJob> jobs;\r
-       \r
-       private JCheckBox engineCB = new JCheckBox("Engine", true);\r
-       private JCheckBox engineParametersCB = new JCheckBox("Engine parameters", true);\r
-       private JCheckBox dpVideoDirCB = new JCheckBox("DPVideo directory", true);\r
-       private JCheckBox relativeDemoPathCB = new JCheckBox("Relative demo path", true);\r
-       private JCheckBox jobNameCB = new JCheckBox("Job name", true);\r
-       private JCheckBox demoDirectoryCB = new JCheckBox("Demo directory", true);\r
-       private JCheckBox execBeforeCapCB = new JCheckBox("Exec before capture", true);\r
-       private JCheckBox execAfterCB = new JCheckBox("Exec after capture", true);\r
-       private JCheckBox videoDestination = new JCheckBox("Video destination", true);\r
-       private JCheckBox pluginSettingsCB = new JCheckBox("Plug-in settings", true);\r
-       private JCheckBox selectAllCB = new JCheckBox("Select/deselect all", true);\r
-       \r
-       private JButton applyButton = new JButton("Apply");\r
-       private JButton cancelButton = new JButton("Cancel");\r
-       \r
-       public ApplyTemplateDialog(Frame owner, RecordJobTemplate template, List<RecordJob> jobs) {\r
-               super(owner, true);\r
-               this.parentFrame = owner;\r
-               this.template = template;\r
-               this.jobs = jobs;\r
-               \r
-               setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r
-               setTitle("Apply template");\r
-               this.setupLayout();\r
-       }\r
-       \r
-       public void showDialog() {\r
-               this.pack();\r
-               this.setLocationRelativeTo(this.parentFrame);\r
-               this.setVisible(true);\r
-       }\r
-\r
-       private void setupLayout() {\r
-               setLayout(new MigLayout());\r
-               getContentPane().add(new JLabel("Select which properties you want to apply to the selected jobs"), "wrap");\r
-               \r
-               this.setupCheckBoxes();\r
-               \r
-               applyButton.addActionListener(this);\r
-               cancelButton.addActionListener(this);\r
-               getContentPane().add(applyButton);\r
-               getContentPane().add(cancelButton);\r
-       }\r
-       \r
-       private void setupCheckBoxes() {\r
-               getContentPane().add(engineCB, "wrap");\r
-               getContentPane().add(engineParametersCB, "wrap");\r
-               getContentPane().add(dpVideoDirCB, "wrap");\r
-               getContentPane().add(relativeDemoPathCB, "wrap");\r
-               getContentPane().add(jobNameCB, "wrap");\r
-               getContentPane().add(demoDirectoryCB, "wrap");\r
-               getContentPane().add(execBeforeCapCB, "wrap");\r
-               getContentPane().add(execAfterCB, "wrap");\r
-               getContentPane().add(videoDestination, "wrap");\r
-               getContentPane().add(pluginSettingsCB, "wrap");\r
-               getContentPane().add(selectAllCB, "wrap");\r
-               \r
-               selectAllCB.addItemListener(this);\r
-       }\r
-\r
-       @Override\r
-       public void actionPerformed(ActionEvent e) {\r
-               if (e.getSource() == applyButton) {\r
-                       this.applyTemplates();\r
-                       dispose();\r
-               } else if (e.getSource() == cancelButton) {\r
-                       dispose();\r
-               }\r
-       }\r
-       \r
-       private void applyTemplates() {\r
-               String errors = "";\r
-               for (RecordJob job : this.jobs) {\r
-                       try {\r
-                               this.applyTemplate(job);\r
-                       } catch (Throwable e) {\r
-                               errors += "Job <B>" + job.getJobName() + "</B>: " + e.getMessage() + "<BR>";\r
-                       }\r
-               }\r
-               \r
-               if (!errors.equals("")) {\r
-                       //error occurred!\r
-                       String errorMsg = "<HTML><BODY>Error occurred while trying to apply templates:<BR><BR>" + errors + "</BODY></HTML>";\r
-                       JOptionPane.showMessageDialog(this.parentFrame, errorMsg, "Error(s) while applying template", JOptionPane.INFORMATION_MESSAGE);\r
-               }\r
-       }\r
-       \r
-       private void applyTemplate(RecordJob job) {\r
-               if (engineCB.isSelected()) {\r
-                       job.setEnginePath(template.getEnginePath());\r
-               }\r
-               if (engineParametersCB.isSelected()) {\r
-                       job.setEngineParameters(template.getEngineParameters());\r
-               }\r
-               if (dpVideoDirCB.isSelected()) {\r
-                       job.setDpVideoPath(template.getDpVideoPath());\r
-               }\r
-               if (relativeDemoPathCB.isSelected()) {\r
-                       job.setRelativeDemoPath(template.getRelativeDemoPath());\r
-               }\r
-               if (jobNameCB.isSelected()) {\r
-                       job.setJobName(template.getJobName());\r
-               }\r
-               if (demoDirectoryCB.isSelected()) {\r
-                       File demoDir = template.getDemoFile();\r
-                       String demoFileName = DemoRecorderUtils.getJustFileNameOfPath(job.getDemoFile());\r
-                       String newDemoPath = demoDir.getAbsolutePath() + File.separator + demoFileName;\r
-                       job.setDemoFile(new File(newDemoPath));\r
-               }\r
-               if (execBeforeCapCB.isEnabled()) {\r
-                       job.setExecuteBeforeCap(template.getExecuteBeforeCap());\r
-               }\r
-               if (execAfterCB.isSelected()) {\r
-                       job.setExecuteAfterCap(template.getExecuteAfterCap());\r
-               }\r
-               if (videoDestination.isSelected()) {\r
-                       File videoDestinatinDir = template.getVideoDestination();\r
-                       String videoFileName = DemoRecorderUtils.getJustFileNameOfPath(job.getVideoDestination());\r
-                       String newVideoPath = videoDestinatinDir.getAbsolutePath() + File.separator + videoFileName;\r
-                       job.setVideoDestination(new File(newVideoPath));\r
-               }\r
-               if (pluginSettingsCB.isSelected()) {\r
-                       job.setEncoderPluginSettings(template.getEncoderPluginSettings());\r
-               }\r
-       }\r
-\r
-       @Override\r
-       public void itemStateChanged(ItemEvent e) {\r
-               if (e.getSource() == selectAllCB) {\r
-                       boolean selected = false;\r
-                       if (e.getStateChange() == ItemEvent.SELECTED) {\r
-                               selected = true;\r
-                       }\r
-                       \r
-                       engineCB.setSelected(selected);\r
-                       engineParametersCB.setSelected(selected);\r
-                       dpVideoDirCB.setSelected(selected);\r
-                       relativeDemoPathCB.setSelected(selected);\r
-                       jobNameCB.setSelected(selected);\r
-                       demoDirectoryCB.setSelected(selected);\r
-                       execBeforeCapCB.setSelected(selected);\r
-                       execAfterCB.setSelected(selected);\r
-                       videoDestination.setSelected(selected);\r
-                       pluginSettingsCB.setSelected(selected);\r
-               }\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.io.File;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+
+import net.miginfocom.swing.MigLayout;
+
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+
+public class ApplyTemplateDialog extends JDialog implements ActionListener, ItemListener {
+
+       private static final long serialVersionUID = 4807155579295688578L;
+       private Frame parentFrame;
+       private RecordJobTemplate template;
+       private List<RecordJob> jobs;
+       
+       private JCheckBox engineCB = new JCheckBox("Engine", true);
+       private JCheckBox engineParametersCB = new JCheckBox("Engine parameters", true);
+       private JCheckBox dpVideoDirCB = new JCheckBox("DPVideo directory", true);
+       private JCheckBox relativeDemoPathCB = new JCheckBox("Relative demo path", true);
+       private JCheckBox jobNameCB = new JCheckBox("Job name", true);
+       private JCheckBox demoDirectoryCB = new JCheckBox("Demo directory", true);
+       private JCheckBox execBeforeCapCB = new JCheckBox("Exec before capture", true);
+       private JCheckBox execAfterCB = new JCheckBox("Exec after capture", true);
+       private JCheckBox videoDestination = new JCheckBox("Video destination", true);
+       private JCheckBox pluginSettingsCB = new JCheckBox("Plug-in settings", true);
+       private JCheckBox selectAllCB = new JCheckBox("Select/deselect all", true);
+       
+       private JButton applyButton = new JButton("Apply");
+       private JButton cancelButton = new JButton("Cancel");
+       
+       public ApplyTemplateDialog(Frame owner, RecordJobTemplate template, List<RecordJob> jobs) {
+               super(owner, true);
+               this.parentFrame = owner;
+               this.template = template;
+               this.jobs = jobs;
+               
+               setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+               setTitle("Apply template");
+               this.setupLayout();
+       }
+       
+       public void showDialog() {
+               this.pack();
+               this.setLocationRelativeTo(this.parentFrame);
+               this.setVisible(true);
+       }
+
+       private void setupLayout() {
+               setLayout(new MigLayout());
+               getContentPane().add(new JLabel("Select which properties you want to apply to the selected jobs"), "wrap");
+               
+               this.setupCheckBoxes();
+               
+               applyButton.addActionListener(this);
+               cancelButton.addActionListener(this);
+               getContentPane().add(applyButton);
+               getContentPane().add(cancelButton);
+       }
+       
+       private void setupCheckBoxes() {
+               getContentPane().add(engineCB, "wrap");
+               getContentPane().add(engineParametersCB, "wrap");
+               getContentPane().add(dpVideoDirCB, "wrap");
+               getContentPane().add(relativeDemoPathCB, "wrap");
+               getContentPane().add(jobNameCB, "wrap");
+               getContentPane().add(demoDirectoryCB, "wrap");
+               getContentPane().add(execBeforeCapCB, "wrap");
+               getContentPane().add(execAfterCB, "wrap");
+               getContentPane().add(videoDestination, "wrap");
+               getContentPane().add(pluginSettingsCB, "wrap");
+               getContentPane().add(selectAllCB, "wrap");
+               
+               selectAllCB.addItemListener(this);
+       }
+
+       @Override
+       public void actionPerformed(ActionEvent e) {
+               if (e.getSource() == applyButton) {
+                       this.applyTemplates();
+                       dispose();
+               } else if (e.getSource() == cancelButton) {
+                       dispose();
+               }
+       }
+       
+       private void applyTemplates() {
+               String errors = "";
+               for (RecordJob job : this.jobs) {
+                       try {
+                               this.applyTemplate(job);
+                       } catch (Throwable e) {
+                               errors += "Job <B>" + job.getJobName() + "</B>: " + e.getMessage() + "<BR>";
+                       }
+               }
+               
+               if (!errors.equals("")) {
+                       //error occurred!
+                       String errorMsg = "<HTML><BODY>Error occurred while trying to apply templates:<BR><BR>" + errors + "</BODY></HTML>";
+                       JOptionPane.showMessageDialog(this.parentFrame, errorMsg, "Error(s) while applying template", JOptionPane.INFORMATION_MESSAGE);
+               }
+       }
+       
+       private void applyTemplate(RecordJob job) {
+               if (engineCB.isSelected()) {
+                       job.setEnginePath(template.getEnginePath());
+               }
+               if (engineParametersCB.isSelected()) {
+                       job.setEngineParameters(template.getEngineParameters());
+               }
+               if (dpVideoDirCB.isSelected()) {
+                       job.setDpVideoPath(template.getDpVideoPath());
+               }
+               if (relativeDemoPathCB.isSelected()) {
+                       job.setRelativeDemoPath(template.getRelativeDemoPath());
+               }
+               if (jobNameCB.isSelected()) {
+                       job.setJobName(template.getJobName());
+               }
+               if (demoDirectoryCB.isSelected()) {
+                       File demoDir = template.getDemoFile();
+                       String demoFileName = DemoRecorderUtils.getJustFileNameOfPath(job.getDemoFile());
+                       String newDemoPath = demoDir.getAbsolutePath() + File.separator + demoFileName;
+                       job.setDemoFile(new File(newDemoPath));
+               }
+               if (execBeforeCapCB.isEnabled()) {
+                       job.setExecuteBeforeCap(template.getExecuteBeforeCap());
+               }
+               if (execAfterCB.isSelected()) {
+                       job.setExecuteAfterCap(template.getExecuteAfterCap());
+               }
+               if (videoDestination.isSelected()) {
+                       File videoDestinatinDir = template.getVideoDestination();
+                       String videoFileName = DemoRecorderUtils.getJustFileNameOfPath(job.getVideoDestination());
+                       String newVideoPath = videoDestinatinDir.getAbsolutePath() + File.separator + videoFileName;
+                       job.setVideoDestination(new File(newVideoPath));
+               }
+               if (pluginSettingsCB.isSelected()) {
+                       job.setEncoderPluginSettings(template.getEncoderPluginSettings());
+               }
+       }
+
+       @Override
+       public void itemStateChanged(ItemEvent e) {
+               if (e.getSource() == selectAllCB) {
+                       boolean selected = false;
+                       if (e.getStateChange() == ItemEvent.SELECTED) {
+                               selected = true;
+                       }
+                       
+                       engineCB.setSelected(selected);
+                       engineParametersCB.setSelected(selected);
+                       dpVideoDirCB.setSelected(selected);
+                       relativeDemoPathCB.setSelected(selected);
+                       jobNameCB.setSelected(selected);
+                       demoDirectoryCB.setSelected(selected);
+                       execBeforeCapCB.setSelected(selected);
+                       execAfterCB.setSelected(selected);
+                       videoDestination.setSelected(selected);
+                       pluginSettingsCB.setSelected(selected);
+               }
+       }
+}
index adb0f99dcd35c74fa2149c9f56e6d391b1cf8d4a..980695129b44e87a949efda7484b6fabb4d4c96e 100644 (file)
-package com.nexuiz.demorecorder.ui.swinggui;\r
-\r
-import java.awt.Dimension;\r
-import java.awt.Frame;\r
-import java.awt.Toolkit;\r
-import java.awt.event.ActionEvent;\r
-import java.awt.event.ActionListener;\r
-import java.io.File;\r
-import java.util.HashMap;\r
-import java.util.Map;\r
-import java.util.Properties;\r
-import java.util.Set;\r
-\r
-import javax.swing.JButton;\r
-import javax.swing.JCheckBox;\r
-import javax.swing.JComponent;\r
-import javax.swing.JDialog;\r
-import javax.swing.JFileChooser;\r
-import javax.swing.JLabel;\r
-import javax.swing.JPanel;\r
-import javax.swing.JScrollPane;\r
-import javax.swing.JTextArea;\r
-import javax.swing.JTextField;\r
-import javax.swing.ScrollPaneConstants;\r
-import javax.swing.border.EmptyBorder;\r
-import javax.swing.filechooser.FileFilter;\r
-\r
-import net.miginfocom.swing.MigLayout;\r
-\r
-import org.jdesktop.swingx.JXTable;\r
-import org.jdesktop.swingx.JXTitledSeparator;\r
-\r
-import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
-import com.nexuiz.demorecorder.application.DemoRecorderUtils;\r
-import com.nexuiz.demorecorder.application.NDRPreferences;\r
-import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
-import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;\r
-import com.nexuiz.demorecorder.ui.swinggui.tablemodels.RecordJobTemplatesTableModel;\r
-import com.nexuiz.demorecorder.ui.swinggui.utils.SwingGUIUtils;\r
-\r
-/**\r
- * Shows the dialog that allows to create a new job, create one from a template\r
- * or edit an existing job.\r
- */\r
-\r
-public class JobDialog extends JDialog implements ActionListener {\r
-       private static final long serialVersionUID = 6926246716804560522L;\r
-       public static final int CREATE_NEW_JOB = 0;\r
-       public static final int EDIT_JOB = 1;\r
-       public static final int CREATE_NEW_TEMPLATE = 2;\r
-       public static final int EDIT_TEMPLATE = 3;\r
-       public static final int CREATE_JOB_FROM_TEMPLATE = 4;\r
-\r
-       private DemoRecorderApplication appLayer;\r
-       private RecordJobTemplatesTableModel tableModel;\r
-//     private JXTable templatesTable;\r
-       private Frame parentFrame;\r
-       private int dialogType;\r
-       private RecordJob job = null;\r
-       private JPanel inputPanel;\r
-       private JPanel buttonPanel;\r
-\r
-       private JTextField templateNameField;\r
-       private JTextField templateSummaryField;\r
-       private JTextField enginePathField;\r
-       private JButton enginePathChooserButton;\r
-       private JTextField engineParameterField;\r
-       private JTextField dpVideoDirField;\r
-       private JButton dpVideoDirChooserButton;\r
-       private JTextField relativeDemoPathField;\r
-       private JTextField jobNameField;\r
-       private JTextField demoFileField;\r
-       private JButton demoFileChooserButton;\r
-       private JTextField startSecondField;\r
-       private JTextField endSecondField;\r
-       private JTextArea execBeforeField;\r
-       private JTextArea execAfterField;\r
-       private JTextField videoDestinationField;\r
-       private JButton videoDestinationChooserButton;\r
-       \r
-       private JButton createButton;\r
-       private JButton cancelButton;\r
-       \r
-       //file choosers\r
-       private JFileChooser enginePathFC;\r
-       private JFileChooser dpVideoDirFC;\r
-       private JFileChooser demoFileFC;\r
-       private JFileChooser videoDestinationFC;\r
-       \r
-       private FileFilter userDirFilter = new NexuizUserDirFilter();\r
-       \r
-       private Map<String, JComponent> pluginDialogSettings = new HashMap<String, JComponent>();\r
-\r
-       /**\r
-        * Constructor to create a dialog when creating a new job.\r
-        * @param owner\r
-        * @param appLayer\r
-        */\r
-       public JobDialog(Frame owner, DemoRecorderApplication appLayer) {\r
-               super(owner, true);\r
-               this.parentFrame = owner;\r
-               this.dialogType = CREATE_NEW_JOB;\r
-               this.appLayer = appLayer;\r
-               setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r
-\r
-               setTitle("Create new job");\r
-\r
-               this.setupLayout();\r
-       }\r
-       \r
-       /**\r
-        * Constructor to create a dialog when creating a new template.\r
-        * @param owner\r
-        * @param dialogType\r
-        * @param appLayer\r
-        */\r
-       public JobDialog(Frame owner, RecordJobTemplatesTableModel tableModel, JXTable templatesTable, DemoRecorderApplication appLayer) {\r
-               super(owner, true);\r
-               this.parentFrame = owner;\r
-               this.dialogType = CREATE_NEW_TEMPLATE;\r
-               this.tableModel = tableModel;\r
-               this.appLayer = appLayer;\r
-//             this.templatesTable = templatesTable; seems we don't need it\r
-               setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r
-               setTitle("Create new template");\r
-\r
-               this.setupLayout();\r
-       }\r
-       \r
-       /**\r
-        * Constructor to use when creating a new job from a template, or when editing a template.\r
-        * @param owner\r
-        * @param template\r
-        * @param type either CREATE_JOB_FROM_TEMPLATE or EDIT_TEMPLATE\r
-        */\r
-       public JobDialog(Frame owner, RecordJobTemplate template, DemoRecorderApplication appLayer, int type) {\r
-               super(owner, true);\r
-               this.parentFrame = owner;\r
-               \r
-               this.job = template;\r
-               this.appLayer = appLayer;\r
-               setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r
-               \r
-               if (type != CREATE_JOB_FROM_TEMPLATE && type != EDIT_TEMPLATE) {\r
-                       throw new RuntimeException("Illegal paraameter \"type\"");\r
-               }\r
-               \r
-               this.dialogType = type;\r
-               if (type == CREATE_JOB_FROM_TEMPLATE) {\r
-                       setTitle("Create job from template");\r
-               } else {\r
-                       setTitle("Edit template");\r
-               }\r
-\r
-               this.setupLayout();\r
-       }\r
-       \r
-       /**\r
-        * Constructor to create a dialog to be used when editing an existing job.\r
-        * @param owner\r
-        * @param job\r
-        */\r
-       public JobDialog(Frame owner, RecordJob job, DemoRecorderApplication appLayer) {\r
-               super(owner, true);\r
-               this.parentFrame = owner;\r
-               this.dialogType = EDIT_JOB;\r
-               this.appLayer = appLayer;\r
-               setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r
-\r
-               setTitle("Edit job");\r
-               this.job = job;\r
-\r
-               this.setupLayout();\r
-       }\r
-       \r
-       \r
-       \r
-       public void showDialog() {\r
-               this.pack();\r
-               Toolkit t = Toolkit.getDefaultToolkit();\r
-               Dimension screenSize = t.getScreenSize();\r
-               if (getHeight() > screenSize.height) {\r
-                       Dimension newPreferredSize = getPreferredSize();\r
-                       newPreferredSize.height = screenSize.height - 100;\r
-                       setPreferredSize(newPreferredSize);\r
-                       this.pack();\r
-               }\r
-               this.setLocationRelativeTo(this.parentFrame);\r
-               this.setVisible(true);\r
-       }\r
-\r
-       private void setupLayout() {\r
-//             setLayout(new MigLayout("wrap 1", "[grow,fill]", "[]20[]"));\r
-               setLayout(new MigLayout("wrap 1", "[grow,fill]", "[][]"));\r
-               this.setupInputMask();\r
-               this.setupButtonPart();\r
-\r
-       }\r
-\r
-       private void setupInputMask() {\r
-               inputPanel = new JPanel(new MigLayout("insets 0,wrap 3", "[][250::,grow,fill][30::]"));\r
-               JScrollPane inputScrollPane = new JScrollPane(inputPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);\r
-               inputScrollPane.setBorder(new EmptyBorder(0,0,0,0));\r
-               \r
-               JXTitledSeparator environmentHeading = new JXTitledSeparator("Environment settings");\r
-               inputPanel.add(environmentHeading, "span 3,grow");\r
-\r
-               this.setupTemplateNameAndSummary();\r
-               this.setupEnginePath();\r
-               this.setupEngineParameters();\r
-               this.setupDPVideoDir();\r
-               this.setupRelativeDemoPath();\r
-\r
-               JXTitledSeparator jobSettingsHeading = new JXTitledSeparator("Job settings");\r
-               inputPanel.add(jobSettingsHeading, "span 3,grow");\r
-\r
-               this.setupJobName();\r
-               this.setupDemoFile();\r
-               this.setupStartSecond();\r
-               this.setupEndSecond();\r
-               this.setupExecBefore();\r
-               this.setupExecAfter();\r
-               this.setupVideoDestination();\r
-               \r
-               this.setupPluginPreferences();\r
-\r
-               getContentPane().add(inputScrollPane);\r
-       }\r
-       \r
-       private void setupTemplateNameAndSummary() {\r
-               if (this.dialogType != CREATE_NEW_TEMPLATE && this.dialogType != EDIT_TEMPLATE) {\r
-                       return;\r
-               }\r
-               \r
-               //layout stuff\r
-               inputPanel.add(new JLabel("Template name:"));\r
-               templateNameField = new JTextField();\r
-               inputPanel.add(templateNameField, "wrap");\r
-               \r
-               inputPanel.add(new JLabel("Summary:"));\r
-               templateSummaryField = new JTextField();\r
-               inputPanel.add(templateSummaryField, "wrap");\r
-               \r
-               //UI logic stuff\r
-               if (this.dialogType == EDIT_TEMPLATE) {\r
-                       RecordJobTemplate template = (RecordJobTemplate) this.job;\r
-                       templateNameField.setText(template.getName());\r
-                       templateSummaryField.setText(template.getSummary());\r
-               }\r
-       }\r
-       \r
-       private void setupEnginePath() {\r
-               //layout stuff\r
-               inputPanel.add(new JLabel("Engine:"));\r
-               enginePathField = new JTextField();\r
-               enginePathField.setEditable(false);\r
-               inputPanel.add(enginePathField);\r
-               enginePathChooserButton = new FileChooserButton();\r
-               inputPanel.add(enginePathChooserButton);\r
-               \r
-               //UI logic stuff\r
-               this.enginePathFC = createConfiguredFileChooser();\r
-               enginePathChooserButton.addActionListener(this);\r
-               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
-                       this.enginePathFC.setSelectedFile(this.job.getEnginePath());\r
-                       this.enginePathField.setText(this.job.getEnginePath().getAbsolutePath());\r
-               }\r
-       }\r
-       \r
-       private void setupEngineParameters() {\r
-               //layout stuff\r
-               inputPanel.add(new JLabel("Engine parameters:"));\r
-               engineParameterField = new JTextField();\r
-               inputPanel.add(engineParameterField, "wrap");\r
-               \r
-               //UI logic stuff\r
-               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
-                       engineParameterField.setText(this.job.getEngineParameters());\r
-               }\r
-       }\r
-       \r
-       private void setupDPVideoDir() {\r
-               //layout stuff\r
-               inputPanel.add(new JLabel("DPVideo directory:"));\r
-               dpVideoDirField = new JTextField();\r
-               dpVideoDirField.setEditable(false);\r
-               inputPanel.add(dpVideoDirField);\r
-               dpVideoDirChooserButton = new FileChooserButton();\r
-               inputPanel.add(dpVideoDirChooserButton);\r
-               \r
-               //UI logic stuff\r
-               dpVideoDirChooserButton.addActionListener(this);\r
-               this.dpVideoDirFC = createConfiguredFileChooser();\r
-               this.dpVideoDirFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);\r
-               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
-                       this.dpVideoDirFC.setSelectedFile(this.job.getDpVideoPath());\r
-                       this.dpVideoDirField.setText(this.job.getDpVideoPath().getAbsolutePath());\r
-               }\r
-       }\r
-       \r
-       private void setupRelativeDemoPath() {\r
-               //layout stuff\r
-               inputPanel.add(new JLabel("Relative demo path:"));\r
-               relativeDemoPathField = new JTextField();\r
-               inputPanel.add(relativeDemoPathField, "wrap 20");\r
-               \r
-               //UI logic stuff\r
-               if (this.dialogType == CREATE_NEW_JOB || this.dialogType == CREATE_NEW_TEMPLATE) {\r
-                       relativeDemoPathField.setText("demos");\r
-               }\r
-               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
-                       relativeDemoPathField.setText(this.job.getRelativeDemoPath());\r
-               }\r
-       }\r
-       \r
-       private void setupJobName() {\r
-               inputPanel.add(new JLabel("Job name:"));\r
-               \r
-               jobNameField = new JTextField();\r
-               inputPanel.add(jobNameField, "wrap");\r
-               \r
-               //UI logic stuff\r
-               if (this.dialogType != CREATE_NEW_TEMPLATE && this.dialogType != CREATE_NEW_JOB) {\r
-                       jobNameField.setText(this.job.getJobName());\r
-               }\r
-       }\r
-       \r
-       private void setupDemoFile() {\r
-               String label;\r
-               if (this.dialogType == CREATE_NEW_JOB || this.dialogType == EDIT_JOB || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
-                       label = "Demo file:";\r
-               } else {\r
-                       label = "Demo directory:";\r
-               }\r
-               \r
-               //layout stuff\r
-               inputPanel.add(new JLabel(label));\r
-               demoFileField = new JTextField();\r
-               demoFileField.setEditable(false);\r
-               inputPanel.add(demoFileField);\r
-               demoFileChooserButton = new FileChooserButton();\r
-               inputPanel.add(demoFileChooserButton);\r
-               \r
-               //UI logic stuff\r
-               this.demoFileFC = createConfiguredFileChooser();\r
-               demoFileChooserButton.addActionListener(this);\r
-               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
-                       if (this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
-                               this.demoFileFC.setCurrentDirectory(this.job.getDemoFile());\r
-                       } else {\r
-                               this.demoFileFC.setSelectedFile(this.job.getDemoFile());\r
-                       }\r
-                       \r
-                       this.demoFileField.setText(this.job.getDemoFile().getAbsolutePath());\r
-               }\r
-               \r
-               //only specify directories for templates\r
-               if (this.dialogType == CREATE_NEW_TEMPLATE || this.dialogType == EDIT_TEMPLATE) {\r
-                       this.demoFileFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);\r
-               }\r
-       }\r
-       \r
-       private void setupStartSecond() {\r
-               //only exists for jobs, not for templates\r
-               if (this.dialogType != CREATE_NEW_JOB && this.dialogType != EDIT_JOB && this.dialogType != CREATE_JOB_FROM_TEMPLATE) {\r
-                       return;\r
-               }\r
-               \r
-               //layout stuff\r
-               inputPanel.add(new JLabel("Start second:"));\r
-               startSecondField = new JTextField();\r
-               inputPanel.add(startSecondField, "wrap");\r
-               \r
-               //UI logic stuff\r
-               if (this.dialogType == EDIT_JOB) {\r
-                       startSecondField.setText(String.valueOf( this.job.getStartSecond() ));\r
-               }\r
-       }\r
-       \r
-       private void setupEndSecond() {\r
-               //only exists for jobs, not for templates\r
-               if (this.dialogType != CREATE_NEW_JOB && this.dialogType != EDIT_JOB && this.dialogType != CREATE_JOB_FROM_TEMPLATE) {\r
-                       return;\r
-               }\r
-               \r
-               //layout stuff\r
-               inputPanel.add(new JLabel("End second:"));\r
-               endSecondField = new JTextField();\r
-               inputPanel.add(endSecondField, "wrap");\r
-               \r
-               //UI logic stuff\r
-               if (this.dialogType == EDIT_JOB) {\r
-                       endSecondField.setText(String.valueOf( this.job.getEndSecond() ));\r
-               }\r
-       }\r
-       \r
-       private void setupExecBefore() {\r
-               //layout stuff\r
-               inputPanel.add(new JLabel("Exec before capture:"));\r
-               execBeforeField = new JTextArea(3, 1);\r
-               inputPanel.add(new JScrollPane(execBeforeField), "wrap");\r
-               \r
-               //UI logic stuff\r
-               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
-                       execBeforeField.setText(this.job.getExecuteBeforeCap());\r
-               }\r
-       }\r
-       \r
-       private void setupExecAfter() {\r
-               //layout stuff\r
-               inputPanel.add(new JLabel("Exec after capture:"));\r
-               execAfterField = new JTextArea(3, 1);\r
-               inputPanel.add(new JScrollPane(execAfterField), "wrap");\r
-               \r
-               //UI logic stuff\r
-               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
-                       execAfterField.setText(this.job.getExecuteAfterCap());\r
-               }\r
-       }\r
-       \r
-       private void setupVideoDestination() {\r
-               //layout stuff\r
-               inputPanel.add(new JLabel("Video destination:"));\r
-               videoDestinationField = new JTextField();\r
-               videoDestinationField.setEditable(false);\r
-               inputPanel.add(videoDestinationField);\r
-               videoDestinationChooserButton = new FileChooserButton();\r
-               inputPanel.add(videoDestinationChooserButton, "wrap 20");\r
-               \r
-               //UI logic stuff\r
-               videoDestinationChooserButton.addActionListener(this);\r
-               this.videoDestinationFC = createConfiguredFileChooser();\r
-               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
-                       if (this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
-                               this.videoDestinationFC.setCurrentDirectory(this.job.getVideoDestination());\r
-                       } else {\r
-                               this.videoDestinationFC.setSelectedFile(this.job.getVideoDestination());\r
-                       }\r
-                       \r
-                       this.videoDestinationField.setText(this.job.getVideoDestination().getAbsolutePath());\r
-               }\r
-               if (this.dialogType == CREATE_NEW_TEMPLATE || this.dialogType == EDIT_TEMPLATE) {\r
-                       this.videoDestinationFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);\r
-               }\r
-       }\r
-       \r
-       private void setupPluginPreferences() {\r
-               for (EncoderPlugin plugin : this.appLayer.getEncoderPlugins()) {\r
-                       String pluginName = plugin.getName();\r
-                       //only display settings if the plugin actually has any...\r
-                       Properties jobSpecificDefaultPluginPreferences = plugin.getJobSpecificPreferences();\r
-                       Properties jobPluginPreferences = null;\r
-                       if (this.job != null) {\r
-                               jobPluginPreferences = this.job.getEncoderPluginSettings(plugin);\r
-                       }\r
-                       if (jobSpecificDefaultPluginPreferences.size() > 0 && plugin.isEnabled()) {\r
-                               //add heading\r
-                               JXTitledSeparator pluginHeading = new JXTitledSeparator(pluginName + " plugin settings");\r
-                               inputPanel.add(pluginHeading, "span 3,grow");\r
-                               \r
-                               for (String pluginPreferenceKey : plugin.getJobSpecificPreferencesOrder()) {\r
-                                       String value = jobSpecificDefaultPluginPreferences.getProperty(pluginPreferenceKey);\r
-                                       if (this.job != null) {\r
-                                               if (jobPluginPreferences.containsKey(pluginPreferenceKey)) {\r
-                                                       value = jobPluginPreferences.getProperty(pluginPreferenceKey);\r
-                                               }\r
-                                       }\r
-                                       \r
-                                       this.setupSinglePluginSetting(plugin, pluginPreferenceKey, value);\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-       \r
-       private void setupSinglePluginSetting(EncoderPlugin plugin, String key, String value) {\r
-               inputPanel.add(new JLabel(key + ":"));\r
-               \r
-               if (SwingGUIUtils.isBooleanValue(value)) {\r
-                       JCheckBox checkbox = new JCheckBox();\r
-                       checkbox.setSelected(Boolean.valueOf(value));\r
-                       inputPanel.add(checkbox, "wrap");\r
-                       this.pluginDialogSettings.put(NDRPreferences.getConcatenatedKey(plugin.getName(), key), checkbox);\r
-               } else if (SwingGUIUtils.isFileChooser(value)) {\r
-                       final JFileChooser fc = new JFileChooser();\r
-                       fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);\r
-                       JButton fcButton = new JButton("...");\r
-                       final JTextField filePathField = new JTextField();\r
-                       filePathField.setEditable(false);\r
-                       inputPanel.add(filePathField);\r
-                       fcButton.addActionListener(new ActionListener() {\r
-                               @Override\r
-                               public void actionPerformed(ActionEvent e) {\r
-                                       int returnValue = fc.showOpenDialog(JobDialog.this);\r
-                                       if (returnValue == JFileChooser.APPROVE_OPTION) {\r
-                                               File selectedFile = fc.getSelectedFile();\r
-                                               filePathField.setText(selectedFile.getAbsolutePath());\r
-                                       }\r
-                               }\r
-                       });\r
-                       \r
-                       try {\r
-                               File selectedFile = new File(value);\r
-                               if (selectedFile.exists()) {\r
-                                       fc.setSelectedFile(selectedFile);\r
-                                       filePathField.setText(selectedFile.getAbsolutePath());\r
-                               }\r
-                       } catch (Throwable e) {}\r
-                       this.pluginDialogSettings.put(NDRPreferences.getConcatenatedKey(plugin.getName(), key), fc);\r
-                       inputPanel.add(fcButton);\r
-               } else {\r
-                       //textfield\r
-                       JTextField textField = new JTextField();\r
-                       textField.setText(value);\r
-                       this.pluginDialogSettings.put(NDRPreferences.getConcatenatedKey(plugin.getName(), key), textField);\r
-                       inputPanel.add(textField, "wrap");\r
-               }\r
-       }\r
-\r
-       private void setupButtonPart() {\r
-               String createButtonText;\r
-               if (this.dialogType == CREATE_NEW_JOB || this.dialogType == CREATE_NEW_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
-                       createButtonText = "Create";\r
-               } else {\r
-                       createButtonText = "Save";\r
-               }\r
-               buttonPanel = new JPanel(new MigLayout("insets 0"));\r
-               createButton = new JButton(createButtonText);\r
-               createButton.addActionListener(this);\r
-               cancelButton = new JButton("Cancel");\r
-               cancelButton.addActionListener(this);\r
-               \r
-               buttonPanel.add(createButton);\r
-               buttonPanel.add(cancelButton);\r
-\r
-               getContentPane().add(buttonPanel);\r
-       }\r
-       \r
-       \r
-       public void actionPerformed(ActionEvent e) {\r
-               if (e.getSource() == enginePathChooserButton) {\r
-                       int returnValue = this.enginePathFC.showOpenDialog(this);\r
-                       if (returnValue == JFileChooser.APPROVE_OPTION) {\r
-                               File selectedFile = this.enginePathFC.getSelectedFile();\r
-                               this.enginePathField.setText(selectedFile.getAbsolutePath());\r
-                       }\r
-               } else if (e.getSource() == dpVideoDirChooserButton) {\r
-                       int returnValue = this.dpVideoDirFC.showOpenDialog(this);\r
-                       if (returnValue == JFileChooser.APPROVE_OPTION) {\r
-                               File selectedFile = this.dpVideoDirFC.getSelectedFile();\r
-                               this.dpVideoDirField.setText(selectedFile.getAbsolutePath());\r
-                       }\r
-               } else if (e.getSource() == demoFileChooserButton) {\r
-                       int returnValue = this.demoFileFC.showOpenDialog(this);\r
-                       if (returnValue == JFileChooser.APPROVE_OPTION) {\r
-                               File selectedFile = this.demoFileFC.getSelectedFile();\r
-                               if (this.dialogType == CREATE_NEW_JOB || this.dialogType == EDIT_JOB || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {\r
-                                       this.demoFileField.setText(DemoRecorderUtils.getJustFileNameOfPath(selectedFile));\r
-                               } else {\r
-                                       //template, show full path of directory\r
-                                       this.demoFileField.setText(selectedFile.getAbsolutePath());\r
-                               }\r
-                               \r
-                       }\r
-               } else if (e.getSource() == videoDestinationChooserButton) {\r
-                       int returnValue = this.videoDestinationFC.showSaveDialog(this);\r
-                       if (returnValue == JFileChooser.APPROVE_OPTION) {\r
-                               File selectedFile = this.videoDestinationFC.getSelectedFile();\r
-                               this.videoDestinationField.setText(selectedFile.getAbsolutePath());\r
-                       }\r
-               } else if (e.getSource() == createButton) {\r
-                       switch (this.dialogType) {\r
-                       case CREATE_NEW_JOB:\r
-                       case CREATE_JOB_FROM_TEMPLATE:\r
-                               this.requestNewRecordJob(); break;\r
-                       case CREATE_NEW_TEMPLATE:\r
-                               this.createNewTemplate();\r
-                               break;\r
-                       case EDIT_JOB:\r
-                               this.editJob();\r
-                               break;\r
-                       case EDIT_TEMPLATE:\r
-                               this.editTemplate();\r
-                               break;\r
-                       }\r
-               } else if (e.getSource() == cancelButton) {\r
-                       dispose();\r
-               }\r
-       }\r
-       \r
-       private void requestNewRecordJob() {\r
-               float startSecond, endSecond = -1;\r
-               try {\r
-                       startSecond = Float.valueOf(this.startSecondField.getText());\r
-                       endSecond = Float.valueOf(this.endSecondField.getText());\r
-               } catch (Exception e) {\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog("Make sure that start and end second are floating point numbers", e, true);\r
-                       return;\r
-               }\r
-               \r
-               try {\r
-                       RecordJob j = this.appLayer.createRecordJob(\r
-                               this.jobNameField.getText(),\r
-                               this.enginePathFC.getSelectedFile(),\r
-                               this.engineParameterField.getText(),\r
-                               this.demoFileFC.getSelectedFile(),\r
-                               this.relativeDemoPathField.getText(),\r
-                               this.dpVideoDirFC.getSelectedFile(),\r
-                               this.videoDestinationFC.getSelectedFile(),\r
-                               this.execBeforeField.getText(),\r
-                               this.execAfterField.getText(),\r
-                               startSecond,\r
-                               endSecond\r
-                       );\r
-                       this.saveEncoderPluginSettings(j);\r
-                       dispose();\r
-               } catch (Exception e) {\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog(e);\r
-                       return;\r
-               }\r
-               \r
-       }\r
-       \r
-       private void editJob() {\r
-               float startSecond, endSecond = -1;\r
-               try {\r
-                       startSecond = Float.valueOf(this.startSecondField.getText());\r
-                       endSecond = Float.valueOf(this.endSecondField.getText());\r
-               } catch (Exception e) {\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog("Make sure that start and end second are floating point numbers", e, true);\r
-                       return;\r
-               }\r
-               \r
-               try {\r
-                       this.job.setJobName(this.jobNameField.getText());\r
-                       this.job.setEnginePath(this.enginePathFC.getSelectedFile());\r
-                       this.job.setEngineParameters(this.engineParameterField.getText());\r
-                       this.job.setDemoFile(this.demoFileFC.getSelectedFile());\r
-                       this.job.setRelativeDemoPath(this.relativeDemoPathField.getText());\r
-                       this.job.setDpVideoPath(this.dpVideoDirFC.getSelectedFile());\r
-                       this.job.setVideoDestination(this.videoDestinationFC.getSelectedFile());\r
-                       this.job.setExecuteBeforeCap(this.execBeforeField.getText());\r
-                       this.job.setExecuteAfterCap(this.execAfterField.getText());\r
-                       this.job.setStartSecond(startSecond);\r
-                       this.job.setEndSecond(endSecond);\r
-                       this.saveEncoderPluginSettings(this.job);\r
-                       this.appLayer.fireUserInterfaceUpdate(this.job);\r
-                       dispose();\r
-               } catch (Exception e) {\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog(e);\r
-                       return;\r
-               }\r
-               \r
-       }\r
-       \r
-       private void createNewTemplate() {\r
-               try {\r
-                       RecordJobTemplate templ = new RecordJobTemplate(\r
-                               this.templateNameField.getText(),\r
-                               this.templateSummaryField.getText(),\r
-                               this.jobNameField.getText(),\r
-                               this.enginePathFC.getSelectedFile(),\r
-                               this.engineParameterField.getText(),\r
-                               this.demoFileFC.getSelectedFile(),\r
-                               this.relativeDemoPathField.getText(),\r
-                               this.dpVideoDirFC.getSelectedFile(),\r
-                               this.videoDestinationFC.getSelectedFile(),\r
-                               this.execBeforeField.getText(),\r
-                               this.execAfterField.getText()\r
-                       );\r
-                       this.saveEncoderPluginSettings(templ);\r
-                       this.tableModel.addRecordJobTemplate(templ);\r
-                       dispose();\r
-               } catch (NullPointerException e) {\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog("Make sure that you chose a file/directory in each case!", e, true);\r
-               } catch (Exception e) {\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog(e);\r
-                       return;\r
-               }\r
-       }\r
-       \r
-       private void editTemplate() {\r
-               try {\r
-                       RecordJobTemplate template = (RecordJobTemplate) this.job;\r
-                       template.setName(this.templateNameField.getText());\r
-                       template.setSummary(this.templateSummaryField.getText());\r
-                       template.setJobName(this.jobNameField.getText());\r
-                       template.setEnginePath(this.enginePathFC.getSelectedFile());\r
-                       template.setEngineParameters(this.engineParameterField.getText());\r
-                       template.setDpVideoPath(this.dpVideoDirFC.getSelectedFile());\r
-                       template.setRelativeDemoPath(this.relativeDemoPathField.getText());\r
-                       template.setDemoFile(this.demoFileFC.getSelectedFile());\r
-                       template.setExecuteBeforeCap(this.execBeforeField.getText());\r
-                       template.setExecuteAfterCap(this.execAfterField.getText());\r
-                       template.setVideoDestination(this.videoDestinationFC.getSelectedFile());\r
-                       this.saveEncoderPluginSettings(template);\r
-                       dispose();\r
-               } catch (Exception e) {\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog(e);\r
-                       return;\r
-               }\r
-       }\r
-       \r
-       private void saveEncoderPluginSettings(RecordJob job) {\r
-               Set<String> keys = this.pluginDialogSettings.keySet();\r
-               //remember, the keys are concatenated, containing both the category and actual key \r
-               for (String key : keys) {\r
-                       JComponent component = this.pluginDialogSettings.get(key);\r
-                       if (component instanceof JCheckBox) {\r
-                               JCheckBox checkbox = (JCheckBox) component;\r
-                               job.setEncoderPluginSetting(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), String.valueOf(checkbox.isSelected()));\r
-                       } else if (component instanceof JFileChooser) {\r
-                               JFileChooser fileChooser = (JFileChooser) component;\r
-                               if (fileChooser.getSelectedFile() != null) {\r
-                                       String path = fileChooser.getSelectedFile().getAbsolutePath();\r
-                                       job.setEncoderPluginSetting(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), path);\r
-                               }\r
-                       } else if (component instanceof JTextField) {\r
-                               JTextField textField = (JTextField) component;\r
-                               job.setEncoderPluginSetting(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), textField.getText());\r
-                       }\r
-               }\r
-       }\r
-       \r
-       private static class FileChooserButton extends JButton {\r
-               private static final long serialVersionUID = 1335571540372856959L;\r
-               public FileChooserButton() {\r
-                       super("...");\r
-               }\r
-       }\r
-       \r
-       private JFileChooser createConfiguredFileChooser() {\r
-               JFileChooser fc = new JFileChooser();\r
-               fc.setFileHidingEnabled(false);\r
-               fc.setFileFilter(userDirFilter);\r
-               return fc;\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.ScrollPaneConstants;
+import javax.swing.border.EmptyBorder;
+import javax.swing.filechooser.FileFilter;
+
+import net.miginfocom.swing.MigLayout;
+
+import org.jdesktop.swingx.JXTable;
+import org.jdesktop.swingx.JXTitledSeparator;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+import com.nexuiz.demorecorder.application.NDRPreferences;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+import com.nexuiz.demorecorder.ui.swinggui.tablemodels.RecordJobTemplatesTableModel;
+import com.nexuiz.demorecorder.ui.swinggui.utils.SwingGUIUtils;
+
+/**
+ * Shows the dialog that allows to create a new job, create one from a template
+ * or edit an existing job.
+ */
+
+public class JobDialog extends JDialog implements ActionListener {
+       private static final long serialVersionUID = 6926246716804560522L;
+       public static final int CREATE_NEW_JOB = 0;
+       public static final int EDIT_JOB = 1;
+       public static final int CREATE_NEW_TEMPLATE = 2;
+       public static final int EDIT_TEMPLATE = 3;
+       public static final int CREATE_JOB_FROM_TEMPLATE = 4;
+
+       private DemoRecorderApplication appLayer;
+       private RecordJobTemplatesTableModel tableModel;
+//     private JXTable templatesTable;
+       private Frame parentFrame;
+       private int dialogType;
+       private RecordJob job = null;
+       private JPanel inputPanel;
+       private JPanel buttonPanel;
+
+       private JTextField templateNameField;
+       private JTextField templateSummaryField;
+       private JTextField enginePathField;
+       private JButton enginePathChooserButton;
+       private JTextField engineParameterField;
+       private JTextField dpVideoDirField;
+       private JButton dpVideoDirChooserButton;
+       private JTextField relativeDemoPathField;
+       private JTextField jobNameField;
+       private JTextField demoFileField;
+       private JButton demoFileChooserButton;
+       private JTextField startSecondField;
+       private JTextField endSecondField;
+       private JTextArea execBeforeField;
+       private JTextArea execAfterField;
+       private JTextField videoDestinationField;
+       private JButton videoDestinationChooserButton;
+       
+       private JButton createButton;
+       private JButton cancelButton;
+       
+       //file choosers
+       private JFileChooser enginePathFC;
+       private JFileChooser dpVideoDirFC;
+       private JFileChooser demoFileFC;
+       private JFileChooser videoDestinationFC;
+       
+       private FileFilter userDirFilter = new NexuizUserDirFilter();
+       
+       private Map<String, JComponent> pluginDialogSettings = new HashMap<String, JComponent>();
+
+       /**
+        * Constructor to create a dialog when creating a new job.
+        * @param owner
+        * @param appLayer
+        */
+       public JobDialog(Frame owner, DemoRecorderApplication appLayer) {
+               super(owner, true);
+               this.parentFrame = owner;
+               this.dialogType = CREATE_NEW_JOB;
+               this.appLayer = appLayer;
+               setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+               setTitle("Create new job");
+
+               this.setupLayout();
+       }
+       
+       /**
+        * Constructor to create a dialog when creating a new template.
+        * @param owner
+        * @param dialogType
+        * @param appLayer
+        */
+       public JobDialog(Frame owner, RecordJobTemplatesTableModel tableModel, JXTable templatesTable, DemoRecorderApplication appLayer) {
+               super(owner, true);
+               this.parentFrame = owner;
+               this.dialogType = CREATE_NEW_TEMPLATE;
+               this.tableModel = tableModel;
+               this.appLayer = appLayer;
+//             this.templatesTable = templatesTable; seems we don't need it
+               setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+               setTitle("Create new template");
+
+               this.setupLayout();
+       }
+       
+       /**
+        * Constructor to use when creating a new job from a template, or when editing a template.
+        * @param owner
+        * @param template
+        * @param type either CREATE_JOB_FROM_TEMPLATE or EDIT_TEMPLATE
+        */
+       public JobDialog(Frame owner, RecordJobTemplate template, DemoRecorderApplication appLayer, int type) {
+               super(owner, true);
+               this.parentFrame = owner;
+               
+               this.job = template;
+               this.appLayer = appLayer;
+               setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+               
+               if (type != CREATE_JOB_FROM_TEMPLATE && type != EDIT_TEMPLATE) {
+                       throw new RuntimeException("Illegal paraameter \"type\"");
+               }
+               
+               this.dialogType = type;
+               if (type == CREATE_JOB_FROM_TEMPLATE) {
+                       setTitle("Create job from template");
+               } else {
+                       setTitle("Edit template");
+               }
+
+               this.setupLayout();
+       }
+       
+       /**
+        * Constructor to create a dialog to be used when editing an existing job.
+        * @param owner
+        * @param job
+        */
+       public JobDialog(Frame owner, RecordJob job, DemoRecorderApplication appLayer) {
+               super(owner, true);
+               this.parentFrame = owner;
+               this.dialogType = EDIT_JOB;
+               this.appLayer = appLayer;
+               setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+               setTitle("Edit job");
+               this.job = job;
+
+               this.setupLayout();
+       }
+       
+       
+       
+       public void showDialog() {
+               this.pack();
+               Toolkit t = Toolkit.getDefaultToolkit();
+               Dimension screenSize = t.getScreenSize();
+               if (getHeight() > screenSize.height) {
+                       Dimension newPreferredSize = getPreferredSize();
+                       newPreferredSize.height = screenSize.height - 100;
+                       setPreferredSize(newPreferredSize);
+                       this.pack();
+               }
+               this.setLocationRelativeTo(this.parentFrame);
+               this.setVisible(true);
+       }
+
+       private void setupLayout() {
+//             setLayout(new MigLayout("wrap 1", "[grow,fill]", "[]20[]"));
+               setLayout(new MigLayout("wrap 1", "[grow,fill]", "[][]"));
+               this.setupInputMask();
+               this.setupButtonPart();
+
+       }
+
+       private void setupInputMask() {
+               inputPanel = new JPanel(new MigLayout("insets 0,wrap 3", "[][250::,grow,fill][30::]"));
+               JScrollPane inputScrollPane = new JScrollPane(inputPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+               inputScrollPane.setBorder(new EmptyBorder(0,0,0,0));
+               
+               JXTitledSeparator environmentHeading = new JXTitledSeparator("Environment settings");
+               inputPanel.add(environmentHeading, "span 3,grow");
+
+               this.setupTemplateNameAndSummary();
+               this.setupEnginePath();
+               this.setupEngineParameters();
+               this.setupDPVideoDir();
+               this.setupRelativeDemoPath();
+
+               JXTitledSeparator jobSettingsHeading = new JXTitledSeparator("Job settings");
+               inputPanel.add(jobSettingsHeading, "span 3,grow");
+
+               this.setupJobName();
+               this.setupDemoFile();
+               this.setupStartSecond();
+               this.setupEndSecond();
+               this.setupExecBefore();
+               this.setupExecAfter();
+               this.setupVideoDestination();
+               
+               this.setupPluginPreferences();
+
+               getContentPane().add(inputScrollPane);
+       }
+       
+       private void setupTemplateNameAndSummary() {
+               if (this.dialogType != CREATE_NEW_TEMPLATE && this.dialogType != EDIT_TEMPLATE) {
+                       return;
+               }
+               
+               //layout stuff
+               inputPanel.add(new JLabel("Template name:"));
+               templateNameField = new JTextField();
+               inputPanel.add(templateNameField, "wrap");
+               
+               inputPanel.add(new JLabel("Summary:"));
+               templateSummaryField = new JTextField();
+               inputPanel.add(templateSummaryField, "wrap");
+               
+               //UI logic stuff
+               if (this.dialogType == EDIT_TEMPLATE) {
+                       RecordJobTemplate template = (RecordJobTemplate) this.job;
+                       templateNameField.setText(template.getName());
+                       templateSummaryField.setText(template.getSummary());
+               }
+       }
+       
+       private void setupEnginePath() {
+               //layout stuff
+               inputPanel.add(new JLabel("Engine:"));
+               enginePathField = new JTextField();
+               enginePathField.setEditable(false);
+               inputPanel.add(enginePathField);
+               enginePathChooserButton = new FileChooserButton();
+               inputPanel.add(enginePathChooserButton);
+               
+               //UI logic stuff
+               this.enginePathFC = createConfiguredFileChooser();
+               enginePathChooserButton.addActionListener(this);
+               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+                       this.enginePathFC.setSelectedFile(this.job.getEnginePath());
+                       this.enginePathField.setText(this.job.getEnginePath().getAbsolutePath());
+               }
+       }
+       
+       private void setupEngineParameters() {
+               //layout stuff
+               inputPanel.add(new JLabel("Engine parameters:"));
+               engineParameterField = new JTextField();
+               inputPanel.add(engineParameterField, "wrap");
+               
+               //UI logic stuff
+               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+                       engineParameterField.setText(this.job.getEngineParameters());
+               }
+       }
+       
+       private void setupDPVideoDir() {
+               //layout stuff
+               inputPanel.add(new JLabel("DPVideo directory:"));
+               dpVideoDirField = new JTextField();
+               dpVideoDirField.setEditable(false);
+               inputPanel.add(dpVideoDirField);
+               dpVideoDirChooserButton = new FileChooserButton();
+               inputPanel.add(dpVideoDirChooserButton);
+               
+               //UI logic stuff
+               dpVideoDirChooserButton.addActionListener(this);
+               this.dpVideoDirFC = createConfiguredFileChooser();
+               this.dpVideoDirFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+                       this.dpVideoDirFC.setSelectedFile(this.job.getDpVideoPath());
+                       this.dpVideoDirField.setText(this.job.getDpVideoPath().getAbsolutePath());
+               }
+       }
+       
+       private void setupRelativeDemoPath() {
+               //layout stuff
+               inputPanel.add(new JLabel("Relative demo path:"));
+               relativeDemoPathField = new JTextField();
+               inputPanel.add(relativeDemoPathField, "wrap 20");
+               
+               //UI logic stuff
+               if (this.dialogType == CREATE_NEW_JOB || this.dialogType == CREATE_NEW_TEMPLATE) {
+                       relativeDemoPathField.setText("demos");
+               }
+               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+                       relativeDemoPathField.setText(this.job.getRelativeDemoPath());
+               }
+       }
+       
+       private void setupJobName() {
+               inputPanel.add(new JLabel("Job name:"));
+               
+               jobNameField = new JTextField();
+               inputPanel.add(jobNameField, "wrap");
+               
+               //UI logic stuff
+               if (this.dialogType != CREATE_NEW_TEMPLATE && this.dialogType != CREATE_NEW_JOB) {
+                       jobNameField.setText(this.job.getJobName());
+               }
+       }
+       
+       private void setupDemoFile() {
+               String label;
+               if (this.dialogType == CREATE_NEW_JOB || this.dialogType == EDIT_JOB || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+                       label = "Demo file:";
+               } else {
+                       label = "Demo directory:";
+               }
+               
+               //layout stuff
+               inputPanel.add(new JLabel(label));
+               demoFileField = new JTextField();
+               demoFileField.setEditable(false);
+               inputPanel.add(demoFileField);
+               demoFileChooserButton = new FileChooserButton();
+               inputPanel.add(demoFileChooserButton);
+               
+               //UI logic stuff
+               this.demoFileFC = createConfiguredFileChooser();
+               demoFileChooserButton.addActionListener(this);
+               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+                       if (this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+                               this.demoFileFC.setCurrentDirectory(this.job.getDemoFile());
+                       } else {
+                               this.demoFileFC.setSelectedFile(this.job.getDemoFile());
+                       }
+                       
+                       this.demoFileField.setText(this.job.getDemoFile().getAbsolutePath());
+               }
+               
+               //only specify directories for templates
+               if (this.dialogType == CREATE_NEW_TEMPLATE || this.dialogType == EDIT_TEMPLATE) {
+                       this.demoFileFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+               }
+       }
+       
+       private void setupStartSecond() {
+               //only exists for jobs, not for templates
+               if (this.dialogType != CREATE_NEW_JOB && this.dialogType != EDIT_JOB && this.dialogType != CREATE_JOB_FROM_TEMPLATE) {
+                       return;
+               }
+               
+               //layout stuff
+               inputPanel.add(new JLabel("Start second:"));
+               startSecondField = new JTextField();
+               inputPanel.add(startSecondField, "wrap");
+               
+               //UI logic stuff
+               if (this.dialogType == EDIT_JOB) {
+                       startSecondField.setText(String.valueOf( this.job.getStartSecond() ));
+               }
+       }
+       
+       private void setupEndSecond() {
+               //only exists for jobs, not for templates
+               if (this.dialogType != CREATE_NEW_JOB && this.dialogType != EDIT_JOB && this.dialogType != CREATE_JOB_FROM_TEMPLATE) {
+                       return;
+               }
+               
+               //layout stuff
+               inputPanel.add(new JLabel("End second:"));
+               endSecondField = new JTextField();
+               inputPanel.add(endSecondField, "wrap");
+               
+               //UI logic stuff
+               if (this.dialogType == EDIT_JOB) {
+                       endSecondField.setText(String.valueOf( this.job.getEndSecond() ));
+               }
+       }
+       
+       private void setupExecBefore() {
+               //layout stuff
+               inputPanel.add(new JLabel("Exec before capture:"));
+               execBeforeField = new JTextArea(3, 1);
+               inputPanel.add(new JScrollPane(execBeforeField), "wrap");
+               
+               //UI logic stuff
+               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+                       execBeforeField.setText(this.job.getExecuteBeforeCap());
+               }
+       }
+       
+       private void setupExecAfter() {
+               //layout stuff
+               inputPanel.add(new JLabel("Exec after capture:"));
+               execAfterField = new JTextArea(3, 1);
+               inputPanel.add(new JScrollPane(execAfterField), "wrap");
+               
+               //UI logic stuff
+               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+                       execAfterField.setText(this.job.getExecuteAfterCap());
+               }
+       }
+       
+       private void setupVideoDestination() {
+               //layout stuff
+               inputPanel.add(new JLabel("Video destination:"));
+               videoDestinationField = new JTextField();
+               videoDestinationField.setEditable(false);
+               inputPanel.add(videoDestinationField);
+               videoDestinationChooserButton = new FileChooserButton();
+               inputPanel.add(videoDestinationChooserButton, "wrap 20");
+               
+               //UI logic stuff
+               videoDestinationChooserButton.addActionListener(this);
+               this.videoDestinationFC = createConfiguredFileChooser();
+               if (this.dialogType == EDIT_JOB || this.dialogType == EDIT_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+                       if (this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+                               this.videoDestinationFC.setCurrentDirectory(this.job.getVideoDestination());
+                       } else {
+                               this.videoDestinationFC.setSelectedFile(this.job.getVideoDestination());
+                       }
+                       
+                       this.videoDestinationField.setText(this.job.getVideoDestination().getAbsolutePath());
+               }
+               if (this.dialogType == CREATE_NEW_TEMPLATE || this.dialogType == EDIT_TEMPLATE) {
+                       this.videoDestinationFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+               }
+       }
+       
+       private void setupPluginPreferences() {
+               for (EncoderPlugin plugin : this.appLayer.getEncoderPlugins()) {
+                       String pluginName = plugin.getName();
+                       //only display settings if the plugin actually has any...
+                       Properties jobSpecificDefaultPluginPreferences = plugin.getJobSpecificPreferences();
+                       Properties jobPluginPreferences = null;
+                       if (this.job != null) {
+                               jobPluginPreferences = this.job.getEncoderPluginSettings(plugin);
+                       }
+                       if (jobSpecificDefaultPluginPreferences.size() > 0 && plugin.isEnabled()) {
+                               //add heading
+                               JXTitledSeparator pluginHeading = new JXTitledSeparator(pluginName + " plugin settings");
+                               inputPanel.add(pluginHeading, "span 3,grow");
+                               
+                               for (String pluginPreferenceKey : plugin.getJobSpecificPreferencesOrder()) {
+                                       String value = jobSpecificDefaultPluginPreferences.getProperty(pluginPreferenceKey);
+                                       if (this.job != null) {
+                                               if (jobPluginPreferences.containsKey(pluginPreferenceKey)) {
+                                                       value = jobPluginPreferences.getProperty(pluginPreferenceKey);
+                                               }
+                                       }
+                                       
+                                       this.setupSinglePluginSetting(plugin, pluginPreferenceKey, value);
+                               }
+                       }
+               }
+       }
+       
+       private void setupSinglePluginSetting(EncoderPlugin plugin, String key, String value) {
+               inputPanel.add(new JLabel(key + ":"));
+               
+               if (SwingGUIUtils.isBooleanValue(value)) {
+                       JCheckBox checkbox = new JCheckBox();
+                       checkbox.setSelected(Boolean.valueOf(value));
+                       inputPanel.add(checkbox, "wrap");
+                       this.pluginDialogSettings.put(NDRPreferences.getConcatenatedKey(plugin.getName(), key), checkbox);
+               } else if (SwingGUIUtils.isFileChooser(value)) {
+                       final JFileChooser fc = new JFileChooser();
+                       fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
+                       JButton fcButton = new JButton("...");
+                       final JTextField filePathField = new JTextField();
+                       filePathField.setEditable(false);
+                       inputPanel.add(filePathField);
+                       fcButton.addActionListener(new ActionListener() {
+                               @Override
+                               public void actionPerformed(ActionEvent e) {
+                                       int returnValue = fc.showOpenDialog(JobDialog.this);
+                                       if (returnValue == JFileChooser.APPROVE_OPTION) {
+                                               File selectedFile = fc.getSelectedFile();
+                                               filePathField.setText(selectedFile.getAbsolutePath());
+                                       }
+                               }
+                       });
+                       
+                       try {
+                               File selectedFile = new File(value);
+                               if (selectedFile.exists()) {
+                                       fc.setSelectedFile(selectedFile);
+                                       filePathField.setText(selectedFile.getAbsolutePath());
+                               }
+                       } catch (Throwable e) {}
+                       this.pluginDialogSettings.put(NDRPreferences.getConcatenatedKey(plugin.getName(), key), fc);
+                       inputPanel.add(fcButton);
+               } else {
+                       //textfield
+                       JTextField textField = new JTextField();
+                       textField.setText(value);
+                       this.pluginDialogSettings.put(NDRPreferences.getConcatenatedKey(plugin.getName(), key), textField);
+                       inputPanel.add(textField, "wrap");
+               }
+       }
+
+       private void setupButtonPart() {
+               String createButtonText;
+               if (this.dialogType == CREATE_NEW_JOB || this.dialogType == CREATE_NEW_TEMPLATE || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+                       createButtonText = "Create";
+               } else {
+                       createButtonText = "Save";
+               }
+               buttonPanel = new JPanel(new MigLayout("insets 0"));
+               createButton = new JButton(createButtonText);
+               createButton.addActionListener(this);
+               cancelButton = new JButton("Cancel");
+               cancelButton.addActionListener(this);
+               
+               buttonPanel.add(createButton);
+               buttonPanel.add(cancelButton);
+
+               getContentPane().add(buttonPanel);
+       }
+       
+       
+       public void actionPerformed(ActionEvent e) {
+               if (e.getSource() == enginePathChooserButton) {
+                       int returnValue = this.enginePathFC.showOpenDialog(this);
+                       if (returnValue == JFileChooser.APPROVE_OPTION) {
+                               File selectedFile = this.enginePathFC.getSelectedFile();
+                               this.enginePathField.setText(selectedFile.getAbsolutePath());
+                       }
+               } else if (e.getSource() == dpVideoDirChooserButton) {
+                       int returnValue = this.dpVideoDirFC.showOpenDialog(this);
+                       if (returnValue == JFileChooser.APPROVE_OPTION) {
+                               File selectedFile = this.dpVideoDirFC.getSelectedFile();
+                               this.dpVideoDirField.setText(selectedFile.getAbsolutePath());
+                       }
+               } else if (e.getSource() == demoFileChooserButton) {
+                       int returnValue = this.demoFileFC.showOpenDialog(this);
+                       if (returnValue == JFileChooser.APPROVE_OPTION) {
+                               File selectedFile = this.demoFileFC.getSelectedFile();
+                               if (this.dialogType == CREATE_NEW_JOB || this.dialogType == EDIT_JOB || this.dialogType == CREATE_JOB_FROM_TEMPLATE) {
+                                       this.demoFileField.setText(DemoRecorderUtils.getJustFileNameOfPath(selectedFile));
+                               } else {
+                                       //template, show full path of directory
+                                       this.demoFileField.setText(selectedFile.getAbsolutePath());
+                               }
+                               
+                       }
+               } else if (e.getSource() == videoDestinationChooserButton) {
+                       int returnValue = this.videoDestinationFC.showSaveDialog(this);
+                       if (returnValue == JFileChooser.APPROVE_OPTION) {
+                               File selectedFile = this.videoDestinationFC.getSelectedFile();
+                               this.videoDestinationField.setText(selectedFile.getAbsolutePath());
+                       }
+               } else if (e.getSource() == createButton) {
+                       switch (this.dialogType) {
+                       case CREATE_NEW_JOB:
+                       case CREATE_JOB_FROM_TEMPLATE:
+                               this.requestNewRecordJob(); break;
+                       case CREATE_NEW_TEMPLATE:
+                               this.createNewTemplate();
+                               break;
+                       case EDIT_JOB:
+                               this.editJob();
+                               break;
+                       case EDIT_TEMPLATE:
+                               this.editTemplate();
+                               break;
+                       }
+               } else if (e.getSource() == cancelButton) {
+                       dispose();
+               }
+       }
+       
+       private void requestNewRecordJob() {
+               float startSecond, endSecond = -1;
+               try {
+                       startSecond = Float.valueOf(this.startSecondField.getText());
+                       endSecond = Float.valueOf(this.endSecondField.getText());
+               } catch (Exception e) {
+                       DemoRecorderUtils.showNonCriticalErrorDialog("Make sure that start and end second are floating point numbers", e, true);
+                       return;
+               }
+               
+               try {
+                       RecordJob j = this.appLayer.createRecordJob(
+                               this.jobNameField.getText(),
+                               this.enginePathFC.getSelectedFile(),
+                               this.engineParameterField.getText(),
+                               this.demoFileFC.getSelectedFile(),
+                               this.relativeDemoPathField.getText(),
+                               this.dpVideoDirFC.getSelectedFile(),
+                               this.videoDestinationFC.getSelectedFile(),
+                               this.execBeforeField.getText(),
+                               this.execAfterField.getText(),
+                               startSecond,
+                               endSecond
+                       );
+                       this.saveEncoderPluginSettings(j);
+                       dispose();
+               } catch (Exception e) {
+                       DemoRecorderUtils.showNonCriticalErrorDialog(e);
+                       return;
+               }
+               
+       }
+       
+       private void editJob() {
+               float startSecond, endSecond = -1;
+               try {
+                       startSecond = Float.valueOf(this.startSecondField.getText());
+                       endSecond = Float.valueOf(this.endSecondField.getText());
+               } catch (Exception e) {
+                       DemoRecorderUtils.showNonCriticalErrorDialog("Make sure that start and end second are floating point numbers", e, true);
+                       return;
+               }
+               
+               try {
+                       this.job.setJobName(this.jobNameField.getText());
+                       this.job.setEnginePath(this.enginePathFC.getSelectedFile());
+                       this.job.setEngineParameters(this.engineParameterField.getText());
+                       this.job.setDemoFile(this.demoFileFC.getSelectedFile());
+                       this.job.setRelativeDemoPath(this.relativeDemoPathField.getText());
+                       this.job.setDpVideoPath(this.dpVideoDirFC.getSelectedFile());
+                       this.job.setVideoDestination(this.videoDestinationFC.getSelectedFile());
+                       this.job.setExecuteBeforeCap(this.execBeforeField.getText());
+                       this.job.setExecuteAfterCap(this.execAfterField.getText());
+                       this.job.setStartSecond(startSecond);
+                       this.job.setEndSecond(endSecond);
+                       this.saveEncoderPluginSettings(this.job);
+                       this.appLayer.fireUserInterfaceUpdate(this.job);
+                       dispose();
+               } catch (Exception e) {
+                       DemoRecorderUtils.showNonCriticalErrorDialog(e);
+                       return;
+               }
+               
+       }
+       
+       private void createNewTemplate() {
+               try {
+                       RecordJobTemplate templ = new RecordJobTemplate(
+                               this.templateNameField.getText(),
+                               this.templateSummaryField.getText(),
+                               this.jobNameField.getText(),
+                               this.enginePathFC.getSelectedFile(),
+                               this.engineParameterField.getText(),
+                               this.demoFileFC.getSelectedFile(),
+                               this.relativeDemoPathField.getText(),
+                               this.dpVideoDirFC.getSelectedFile(),
+                               this.videoDestinationFC.getSelectedFile(),
+                               this.execBeforeField.getText(),
+                               this.execAfterField.getText()
+                       );
+                       this.saveEncoderPluginSettings(templ);
+                       this.tableModel.addRecordJobTemplate(templ);
+                       dispose();
+               } catch (NullPointerException e) {
+                       DemoRecorderUtils.showNonCriticalErrorDialog("Make sure that you chose a file/directory in each case!", e, true);
+               } catch (Exception e) {
+                       DemoRecorderUtils.showNonCriticalErrorDialog(e);
+                       return;
+               }
+       }
+       
+       private void editTemplate() {
+               try {
+                       RecordJobTemplate template = (RecordJobTemplate) this.job;
+                       template.setName(this.templateNameField.getText());
+                       template.setSummary(this.templateSummaryField.getText());
+                       template.setJobName(this.jobNameField.getText());
+                       template.setEnginePath(this.enginePathFC.getSelectedFile());
+                       template.setEngineParameters(this.engineParameterField.getText());
+                       template.setDpVideoPath(this.dpVideoDirFC.getSelectedFile());
+                       template.setRelativeDemoPath(this.relativeDemoPathField.getText());
+                       template.setDemoFile(this.demoFileFC.getSelectedFile());
+                       template.setExecuteBeforeCap(this.execBeforeField.getText());
+                       template.setExecuteAfterCap(this.execAfterField.getText());
+                       template.setVideoDestination(this.videoDestinationFC.getSelectedFile());
+                       this.saveEncoderPluginSettings(template);
+                       dispose();
+               } catch (Exception e) {
+                       DemoRecorderUtils.showNonCriticalErrorDialog(e);
+                       return;
+               }
+       }
+       
+       private void saveEncoderPluginSettings(RecordJob job) {
+               Set<String> keys = this.pluginDialogSettings.keySet();
+               //remember, the keys are concatenated, containing both the category and actual key 
+               for (String key : keys) {
+                       JComponent component = this.pluginDialogSettings.get(key);
+                       if (component instanceof JCheckBox) {
+                               JCheckBox checkbox = (JCheckBox) component;
+                               job.setEncoderPluginSetting(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), String.valueOf(checkbox.isSelected()));
+                       } else if (component instanceof JFileChooser) {
+                               JFileChooser fileChooser = (JFileChooser) component;
+                               if (fileChooser.getSelectedFile() != null) {
+                                       String path = fileChooser.getSelectedFile().getAbsolutePath();
+                                       job.setEncoderPluginSetting(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), path);
+                               }
+                       } else if (component instanceof JTextField) {
+                               JTextField textField = (JTextField) component;
+                               job.setEncoderPluginSetting(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), textField.getText());
+                       }
+               }
+       }
+       
+       private static class FileChooserButton extends JButton {
+               private static final long serialVersionUID = 1335571540372856959L;
+               public FileChooserButton() {
+                       super("...");
+               }
+       }
+       
+       private JFileChooser createConfiguredFileChooser() {
+               JFileChooser fc = new JFileChooser();
+               fc.setFileHidingEnabled(false);
+               fc.setFileFilter(userDirFilter);
+               return fc;
+       }
+}
index e4864b28830366c626e7e08c2da9ae1b838127ed..d478e1979588217ffb8c749832de0ffcdf114ead 100644 (file)
@@ -1,31 +1,31 @@
-package com.nexuiz.demorecorder.ui.swinggui;\r
-\r
-import java.io.File;\r
-\r
-import javax.swing.filechooser.FileFilter;\r
-\r
-import com.nexuiz.demorecorder.application.DemoRecorderUtils;\r
-\r
-/**\r
- * File filter that makes sure that the hidden .nexuiz directory is being shown in the\r
- * file dialog, but other hidden directories are not.\r
- */\r
-public class NexuizUserDirFilter extends FileFilter {\r
-\r
-       @Override\r
-       public boolean accept(File f) {\r
-               if (f.isHidden()) {\r
-                       if (f.isDirectory() && DemoRecorderUtils.getJustFileNameOfPath(f).equals(".nexuiz")) {\r
-                               return true;\r
-                       }\r
-                       return false; //don't show other hidden directories/files\r
-               }\r
-               return true;\r
-       }\r
-\r
-       @Override\r
-       public String getDescription() {\r
-               return null;\r
-       }\r
-\r
-}\r
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+
+/**
+ * File filter that makes sure that the hidden .nexuiz directory is being shown in the
+ * file dialog, but other hidden directories are not.
+ */
+public class NexuizUserDirFilter extends FileFilter {
+
+       @Override
+       public boolean accept(File f) {
+               if (f.isHidden()) {
+                       if (f.isDirectory() && DemoRecorderUtils.getJustFileNameOfPath(f).equals(".nexuiz")) {
+                               return true;
+                       }
+                       return false; //don't show other hidden directories/files
+               }
+               return true;
+       }
+
+       @Override
+       public String getDescription() {
+               return null;
+       }
+
+}
index 7af4a2b4d97c88393c04c3d071dc111275b36470..cc64b461cbb3522bbbf4adac2c4908d4e499285c 100644 (file)
-package com.nexuiz.demorecorder.ui.swinggui;\r
-\r
-import java.awt.Frame;\r
-import java.awt.event.ActionEvent;\r
-import java.awt.event.ActionListener;\r
-import java.io.File;\r
-import java.util.HashMap;\r
-import java.util.Map;\r
-import java.util.Properties;\r
-import java.util.Set;\r
-\r
-import javax.swing.JButton;\r
-import javax.swing.JCheckBox;\r
-import javax.swing.JComponent;\r
-import javax.swing.JDialog;\r
-import javax.swing.JFileChooser;\r
-import javax.swing.JLabel;\r
-import javax.swing.JPanel;\r
-import javax.swing.JTextField;\r
-\r
-import net.miginfocom.swing.MigLayout;\r
-\r
-import org.jdesktop.swingx.JXTitledSeparator;\r
-\r
-import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
-import com.nexuiz.demorecorder.application.NDRPreferences;\r
-import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;\r
-import com.nexuiz.demorecorder.ui.swinggui.utils.SwingGUIUtils;\r
-\r
-public class PreferencesDialog extends JDialog implements ActionListener {\r
-\r
-       private static final long serialVersionUID = 7328399646538571333L;\r
-       private Frame parentFrame;\r
-       private DemoRecorderApplication appLayer;\r
-       private NDRPreferences preferences;\r
-       private Map<String, JComponent> dialogSettings;\r
-       \r
-       private JButton saveButton = new JButton("Save");\r
-       private JButton cancelButton = new JButton("Cancel");\r
-       \r
-       public PreferencesDialog(Frame owner, DemoRecorderApplication appLayer) {\r
-               super(owner, true);\r
-               this.parentFrame = owner;\r
-               this.appLayer = appLayer;\r
-               this.preferences = appLayer.getPreferences();\r
-               this.dialogSettings = new HashMap<String, JComponent>();\r
-               setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r
-\r
-               setTitle("Preferences");\r
-\r
-               this.setupLayout();\r
-       }\r
-\r
-       private void setupLayout() {\r
-               setLayout(new MigLayout("wrap 2", "[][::150,fill]"));\r
-               \r
-               //add heading\r
-               JXTitledSeparator applicationHeading = new JXTitledSeparator("Application settings");\r
-               getContentPane().add(applicationHeading, "span 2,grow");\r
-               \r
-               for (int i = 0; i < DemoRecorderApplication.Preferences.PREFERENCES_ORDER.length; i++) {\r
-                       String currentSetting = DemoRecorderApplication.Preferences.PREFERENCES_ORDER[i];\r
-                       if (this.preferences.getProperty(NDRPreferences.MAIN_APPLICATION, currentSetting) != null) {\r
-                               this.setupSingleSetting(NDRPreferences.MAIN_APPLICATION, currentSetting);\r
-                       }\r
-               }\r
-               \r
-               //add plugin settings\r
-               for (EncoderPlugin plugin : this.appLayer.getEncoderPlugins()) {\r
-                       String pluginName = plugin.getName();\r
-                       //only display settings if the plugin actually has any...\r
-                       Properties pluginPreferences = plugin.getGlobalPreferences();\r
-                       if (pluginPreferences.size() > 0) {\r
-                               //add heading\r
-                               JXTitledSeparator pluginHeading = new JXTitledSeparator(pluginName + " plugin settings");\r
-                               getContentPane().add(pluginHeading, "span 2,grow");\r
-                               \r
-                               for (String pluginKey : plugin.getGlobalPreferencesOrder()) {\r
-                                       if (this.preferences.getProperty(pluginName, pluginKey) != null) {\r
-                                               this.setupSingleSetting(pluginName, pluginKey);\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-               \r
-               JPanel buttonPanel = new JPanel();\r
-               buttonPanel.add(saveButton);\r
-               buttonPanel.add(cancelButton);\r
-               saveButton.addActionListener(this);\r
-               cancelButton.addActionListener(this);\r
-               getContentPane().add(buttonPanel, "span 2");\r
-       }\r
-       \r
-       private void setupSingleSetting(String category, String setting) {\r
-               getContentPane().add(new JLabel(setting + ":"));\r
-               \r
-               String value = this.preferences.getProperty(category, setting);\r
-               if (SwingGUIUtils.isBooleanValue(value)) {\r
-                       JCheckBox checkbox = new JCheckBox();\r
-                       this.dialogSettings.put(NDRPreferences.getConcatenatedKey(category, setting), checkbox);\r
-                       getContentPane().add(checkbox);\r
-               } else if (SwingGUIUtils.isFileChooser(value)) {\r
-                       final JFileChooser fc = new JFileChooser();\r
-                       fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);\r
-                       JButton fcButton = new JButton("...");\r
-                       fcButton.addActionListener(new ActionListener() {\r
-                               @Override\r
-                               public void actionPerformed(ActionEvent e) {\r
-                                       fc.showOpenDialog(PreferencesDialog.this);\r
-                               }\r
-                       });\r
-                       this.dialogSettings.put(NDRPreferences.getConcatenatedKey(category, setting), fc);\r
-                       getContentPane().add(fcButton);\r
-               } else {\r
-                       JTextField textField = new JTextField();\r
-                       this.dialogSettings.put(NDRPreferences.getConcatenatedKey(category, setting), textField);\r
-                       getContentPane().add(textField);\r
-               }\r
-       }\r
-       \r
-       \r
-       \r
-       public void showDialog() {\r
-               this.loadSettings();\r
-               this.pack();\r
-               this.setLocationRelativeTo(this.parentFrame);\r
-               setResizable(false);\r
-               this.setVisible(true);\r
-       }\r
-       \r
-       /**\r
-        * Loads the settings from the application layer (and global plug-in settings) to the form.\r
-        */\r
-       private void loadSettings() {\r
-               Set<Object> keys = this.preferences.keySet();\r
-               for (Object keyObj : keys) {\r
-                       String concatenatedKey = (String) keyObj;\r
-                       String value;\r
-                       JComponent component = null;\r
-                       if ((value = this.preferences.getProperty(concatenatedKey)) != null) {\r
-                               if (SwingGUIUtils.isBooleanValue(value)) {\r
-                                       component = this.dialogSettings.get(concatenatedKey);\r
-                                       if (component != null) {\r
-                                               ((JCheckBox) component).setSelected(Boolean.valueOf(value));\r
-                                       }\r
-                               } else if (SwingGUIUtils.isFileChooser(value)) {\r
-                                       component = this.dialogSettings.get(concatenatedKey);\r
-                                       try {\r
-                                               File selectedFile = new File(value);\r
-                                               if (selectedFile.exists() && component != null) {\r
-                                                       ((JFileChooser) component).setSelectedFile(selectedFile);\r
-                                               }\r
-                                       } catch (Throwable e) {}\r
-                                       \r
-                               } else {\r
-                                       component = this.dialogSettings.get(concatenatedKey);\r
-                                       if (component != null) {\r
-                                               ((JTextField) component).setText(value);\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-\r
-       @Override\r
-       public void actionPerformed(ActionEvent e) {\r
-               if (e.getSource() == cancelButton) {\r
-                       this.setVisible(false);\r
-               } else if (e.getSource() == saveButton) {\r
-                       this.saveSettings();\r
-               }\r
-       }\r
-\r
-       private void saveSettings() {\r
-               Set<String> keys = this.dialogSettings.keySet();\r
-               //remember, the keys are concatenated, containing both the category and actual key \r
-               for (String key : keys) {\r
-                       JComponent component = this.dialogSettings.get(key);\r
-                       if (component instanceof JCheckBox) {\r
-                               JCheckBox checkbox = (JCheckBox) component;\r
-                               this.appLayer.setPreference(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), checkbox.isSelected());\r
-                       } else if (component instanceof JFileChooser) {\r
-                               JFileChooser fileChooser = (JFileChooser) component;\r
-                               if (fileChooser.getSelectedFile() != null) {\r
-                                       String path = fileChooser.getSelectedFile().getAbsolutePath();\r
-                                       this.appLayer.setPreference(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), path);\r
-                               }\r
-                       } else if (component instanceof JTextField) {\r
-                               JTextField textField = (JTextField) component;\r
-                               this.appLayer.setPreference(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), textField.getText());\r
-                       }\r
-               }\r
-               this.setVisible(false);\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import net.miginfocom.swing.MigLayout;
+
+import org.jdesktop.swingx.JXTitledSeparator;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.NDRPreferences;
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+import com.nexuiz.demorecorder.ui.swinggui.utils.SwingGUIUtils;
+
+public class PreferencesDialog extends JDialog implements ActionListener {
+
+       private static final long serialVersionUID = 7328399646538571333L;
+       private Frame parentFrame;
+       private DemoRecorderApplication appLayer;
+       private NDRPreferences preferences;
+       private Map<String, JComponent> dialogSettings;
+       
+       private JButton saveButton = new JButton("Save");
+       private JButton cancelButton = new JButton("Cancel");
+       
+       public PreferencesDialog(Frame owner, DemoRecorderApplication appLayer) {
+               super(owner, true);
+               this.parentFrame = owner;
+               this.appLayer = appLayer;
+               this.preferences = appLayer.getPreferences();
+               this.dialogSettings = new HashMap<String, JComponent>();
+               setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+               setTitle("Preferences");
+
+               this.setupLayout();
+       }
+
+       private void setupLayout() {
+               setLayout(new MigLayout("wrap 2", "[][::150,fill]"));
+               
+               //add heading
+               JXTitledSeparator applicationHeading = new JXTitledSeparator("Application settings");
+               getContentPane().add(applicationHeading, "span 2,grow");
+               
+               for (int i = 0; i < DemoRecorderApplication.Preferences.PREFERENCES_ORDER.length; i++) {
+                       String currentSetting = DemoRecorderApplication.Preferences.PREFERENCES_ORDER[i];
+                       if (this.preferences.getProperty(NDRPreferences.MAIN_APPLICATION, currentSetting) != null) {
+                               this.setupSingleSetting(NDRPreferences.MAIN_APPLICATION, currentSetting);
+                       }
+               }
+               
+               //add plugin settings
+               for (EncoderPlugin plugin : this.appLayer.getEncoderPlugins()) {
+                       String pluginName = plugin.getName();
+                       //only display settings if the plugin actually has any...
+                       Properties pluginPreferences = plugin.getGlobalPreferences();
+                       if (pluginPreferences.size() > 0) {
+                               //add heading
+                               JXTitledSeparator pluginHeading = new JXTitledSeparator(pluginName + " plugin settings");
+                               getContentPane().add(pluginHeading, "span 2,grow");
+                               
+                               for (String pluginKey : plugin.getGlobalPreferencesOrder()) {
+                                       if (this.preferences.getProperty(pluginName, pluginKey) != null) {
+                                               this.setupSingleSetting(pluginName, pluginKey);
+                                       }
+                               }
+                       }
+               }
+               
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.add(saveButton);
+               buttonPanel.add(cancelButton);
+               saveButton.addActionListener(this);
+               cancelButton.addActionListener(this);
+               getContentPane().add(buttonPanel, "span 2");
+       }
+       
+       private void setupSingleSetting(String category, String setting) {
+               getContentPane().add(new JLabel(setting + ":"));
+               
+               String value = this.preferences.getProperty(category, setting);
+               if (SwingGUIUtils.isBooleanValue(value)) {
+                       JCheckBox checkbox = new JCheckBox();
+                       this.dialogSettings.put(NDRPreferences.getConcatenatedKey(category, setting), checkbox);
+                       getContentPane().add(checkbox);
+               } else if (SwingGUIUtils.isFileChooser(value)) {
+                       final JFileChooser fc = new JFileChooser();
+                       fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
+                       JButton fcButton = new JButton("...");
+                       fcButton.addActionListener(new ActionListener() {
+                               @Override
+                               public void actionPerformed(ActionEvent e) {
+                                       fc.showOpenDialog(PreferencesDialog.this);
+                               }
+                       });
+                       this.dialogSettings.put(NDRPreferences.getConcatenatedKey(category, setting), fc);
+                       getContentPane().add(fcButton);
+               } else {
+                       JTextField textField = new JTextField();
+                       this.dialogSettings.put(NDRPreferences.getConcatenatedKey(category, setting), textField);
+                       getContentPane().add(textField);
+               }
+       }
+       
+       
+       
+       public void showDialog() {
+               this.loadSettings();
+               this.pack();
+               this.setLocationRelativeTo(this.parentFrame);
+               setResizable(false);
+               this.setVisible(true);
+       }
+       
+       /**
+        * Loads the settings from the application layer (and global plug-in settings) to the form.
+        */
+       private void loadSettings() {
+               Set<Object> keys = this.preferences.keySet();
+               for (Object keyObj : keys) {
+                       String concatenatedKey = (String) keyObj;
+                       String value;
+                       JComponent component = null;
+                       if ((value = this.preferences.getProperty(concatenatedKey)) != null) {
+                               if (SwingGUIUtils.isBooleanValue(value)) {
+                                       component = this.dialogSettings.get(concatenatedKey);
+                                       if (component != null) {
+                                               ((JCheckBox) component).setSelected(Boolean.valueOf(value));
+                                       }
+                               } else if (SwingGUIUtils.isFileChooser(value)) {
+                                       component = this.dialogSettings.get(concatenatedKey);
+                                       try {
+                                               File selectedFile = new File(value);
+                                               if (selectedFile.exists() && component != null) {
+                                                       ((JFileChooser) component).setSelectedFile(selectedFile);
+                                               }
+                                       } catch (Throwable e) {}
+                                       
+                               } else {
+                                       component = this.dialogSettings.get(concatenatedKey);
+                                       if (component != null) {
+                                               ((JTextField) component).setText(value);
+                                       }
+                               }
+                       }
+               }
+       }
+
+       @Override
+       public void actionPerformed(ActionEvent e) {
+               if (e.getSource() == cancelButton) {
+                       this.setVisible(false);
+               } else if (e.getSource() == saveButton) {
+                       this.saveSettings();
+               }
+       }
+
+       private void saveSettings() {
+               Set<String> keys = this.dialogSettings.keySet();
+               //remember, the keys are concatenated, containing both the category and actual key 
+               for (String key : keys) {
+                       JComponent component = this.dialogSettings.get(key);
+                       if (component instanceof JCheckBox) {
+                               JCheckBox checkbox = (JCheckBox) component;
+                               this.appLayer.setPreference(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), checkbox.isSelected());
+                       } else if (component instanceof JFileChooser) {
+                               JFileChooser fileChooser = (JFileChooser) component;
+                               if (fileChooser.getSelectedFile() != null) {
+                                       String path = fileChooser.getSelectedFile().getAbsolutePath();
+                                       this.appLayer.setPreference(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), path);
+                               }
+                       } else if (component instanceof JTextField) {
+                               JTextField textField = (JTextField) component;
+                               this.appLayer.setPreference(NDRPreferences.getCategory(key), NDRPreferences.getKey(key), textField.getText());
+                       }
+               }
+               this.setVisible(false);
+       }
+}
index 89ab61ceea2d5cc1a89cbb68763cc43e80a60042..b28e0e54ee8812d44435519916602564bf061710 100644 (file)
-package com.nexuiz.demorecorder.ui.swinggui;\r
-\r
-import java.io.File;\r
-\r
-import com.nexuiz.demorecorder.application.DemoRecorderException;\r
-import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
-\r
-public class RecordJobTemplate extends RecordJob {\r
-\r
-       private static final long serialVersionUID = 8311386509410161395L;\r
-       private String templateName;\r
-       private String summary;\r
-\r
-       public RecordJobTemplate(\r
-               String templateName,\r
-               String summary,\r
-               String jobName,\r
-               File enginePath,\r
-               String engineParameters,\r
-               File demoFile,\r
-               String relativeDemoPath,\r
-               File dpVideoPath,\r
-               File videoDestination,\r
-               String executeBeforeCap,\r
-               String executeAfterCap\r
-               ) {\r
-               super();\r
-               \r
-               /*\r
-                * Differences to jobs:\r
-                * - name and summary exist\r
-                * - "Demo file:" -> "Demo directory:"\r
-                * - no start/end second\r
-                */\r
-               \r
-               if (templateName == null || summary == null || jobName == null || enginePath == null || engineParameters == null || \r
-                               demoFile == null || relativeDemoPath == null || dpVideoPath == null || videoDestination == null \r
-                               || executeBeforeCap == null || executeAfterCap == null) {\r
-                       throw new DemoRecorderException("Error: Make sure that you filled the necessary fields! (file choosers!)");\r
-               }\r
-               \r
-               this.templateName = templateName;\r
-               this.summary = summary;\r
-               this.jobName = jobName;\r
-               this.enginePath = enginePath;\r
-               this.engineParameters = engineParameters;\r
-               this.demoFile = demoFile;\r
-               this.relativeDemoPath = relativeDemoPath;\r
-               this.dpVideoPath = dpVideoPath;\r
-               this.videoDestination = videoDestination;\r
-               this.executeBeforeCap = executeBeforeCap;\r
-               this.executeAfterCap = executeAfterCap;\r
-       }\r
-\r
-       public String getName() {\r
-               return templateName;\r
-       }\r
-\r
-       public String getSummary() {\r
-               return summary;\r
-       }\r
-       \r
-       public void setName(String name) {\r
-               this.templateName = name;\r
-       }\r
-\r
-       public void setSummary(String summary) {\r
-               this.summary = summary;\r
-       }\r
-\r
-       /*\r
-        * (non-Javadoc)\r
-        * Overwrite this method because here we want to do the read/write test for the path directly\r
-        * (as this one already is the directory), and not its parent directory.\r
-        * @see com.nexuiz.demorecorder.application.jobs.RecordJob#setDemoFile(java.io.File)\r
-        */\r
-       public void setDemoFile(File demoFile) {\r
-               if (demoFile == null || !demoFile.exists()) {\r
-                       throw new DemoRecorderException("Could not locate demo file!");\r
-               }\r
-               if (!doReadWriteTest(demoFile)) {\r
-                       throw new DemoRecorderException("The directory you specified for the demo to be recorded is not writable!");\r
-               }\r
-               this.demoFile = demoFile.getAbsoluteFile();\r
-       }\r
-       \r
-       /*\r
-        * (non-Javadoc)\r
-        * Overwrite this method because here we want to do the read/write test for the path directly\r
-        * (as this one already is the directory), and not its parent directory.\r
-        * @see com.nexuiz.demorecorder.application.jobs.RecordJob#setVideoDestination(java.io.File)\r
-        */\r
-       public void setVideoDestination(File videoDestination) {\r
-               //keep in mind, here videoDestination points to the destination directory, not the destination file\r
-               if (videoDestination == null || !videoDestination.isDirectory()) {\r
-                       throw new DemoRecorderException("Could not locate the specified video destination directory");\r
-               }\r
-               \r
-               if (!this.doReadWriteTest(videoDestination)) {\r
-                       throw new DemoRecorderException("The video destination directory is not writable! It needs to be writable so that the file can be moved to its new location");\r
-               }\r
-               \r
-               this.videoDestination = videoDestination.getAbsoluteFile();\r
-       }\r
-       \r
-       public String getJobName() {\r
-               return this.jobName;\r
-       }\r
-       \r
-       public void setJobName(String jobName) {\r
-               this.jobName = jobName;\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.io.File;
+
+import com.nexuiz.demorecorder.application.DemoRecorderException;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+
+public class RecordJobTemplate extends RecordJob {
+
+       private static final long serialVersionUID = 8311386509410161395L;
+       private String templateName;
+       private String summary;
+
+       public RecordJobTemplate(
+               String templateName,
+               String summary,
+               String jobName,
+               File enginePath,
+               String engineParameters,
+               File demoFile,
+               String relativeDemoPath,
+               File dpVideoPath,
+               File videoDestination,
+               String executeBeforeCap,
+               String executeAfterCap
+               ) {
+               super();
+               
+               /*
+                * Differences to jobs:
+                * - name and summary exist
+                * - "Demo file:" -> "Demo directory:"
+                * - no start/end second
+                */
+               
+               if (templateName == null || summary == null || jobName == null || enginePath == null || engineParameters == null || 
+                               demoFile == null || relativeDemoPath == null || dpVideoPath == null || videoDestination == null 
+                               || executeBeforeCap == null || executeAfterCap == null) {
+                       throw new DemoRecorderException("Error: Make sure that you filled the necessary fields! (file choosers!)");
+               }
+               
+               this.templateName = templateName;
+               this.summary = summary;
+               this.jobName = jobName;
+               this.enginePath = enginePath;
+               this.engineParameters = engineParameters;
+               this.demoFile = demoFile;
+               this.relativeDemoPath = relativeDemoPath;
+               this.dpVideoPath = dpVideoPath;
+               this.videoDestination = videoDestination;
+               this.executeBeforeCap = executeBeforeCap;
+               this.executeAfterCap = executeAfterCap;
+       }
+
+       public String getName() {
+               return templateName;
+       }
+
+       public String getSummary() {
+               return summary;
+       }
+       
+       public void setName(String name) {
+               this.templateName = name;
+       }
+
+       public void setSummary(String summary) {
+               this.summary = summary;
+       }
+
+       /*
+        * (non-Javadoc)
+        * Overwrite this method because here we want to do the read/write test for the path directly
+        * (as this one already is the directory), and not its parent directory.
+        * @see com.nexuiz.demorecorder.application.jobs.RecordJob#setDemoFile(java.io.File)
+        */
+       public void setDemoFile(File demoFile) {
+               if (demoFile == null || !demoFile.exists()) {
+                       throw new DemoRecorderException("Could not locate demo file!");
+               }
+               if (!doReadWriteTest(demoFile)) {
+                       throw new DemoRecorderException("The directory you specified for the demo to be recorded is not writable!");
+               }
+               this.demoFile = demoFile.getAbsoluteFile();
+       }
+       
+       /*
+        * (non-Javadoc)
+        * Overwrite this method because here we want to do the read/write test for the path directly
+        * (as this one already is the directory), and not its parent directory.
+        * @see com.nexuiz.demorecorder.application.jobs.RecordJob#setVideoDestination(java.io.File)
+        */
+       public void setVideoDestination(File videoDestination) {
+               //keep in mind, here videoDestination points to the destination directory, not the destination file
+               if (videoDestination == null || !videoDestination.isDirectory()) {
+                       throw new DemoRecorderException("Could not locate the specified video destination directory");
+               }
+               
+               if (!this.doReadWriteTest(videoDestination)) {
+                       throw new DemoRecorderException("The video destination directory is not writable! It needs to be writable so that the file can be moved to its new location");
+               }
+               
+               this.videoDestination = videoDestination.getAbsoluteFile();
+       }
+       
+       public String getJobName() {
+               return this.jobName;
+       }
+       
+       public void setJobName(String jobName) {
+               this.jobName = jobName;
+       }
+}
index ed33c9ceaf9a87e91b61ecba3059f242473f4c4b..e826efde06cbb1908c8e1988bbc424330aec037e 100644 (file)
@@ -1,86 +1,86 @@
-package com.nexuiz.demorecorder.ui.swinggui;\r
-\r
-import java.awt.BorderLayout;\r
-import java.awt.Color;\r
-import java.awt.Component;\r
-import java.awt.Graphics;\r
-import java.awt.SystemColor;\r
-\r
-import javax.swing.BorderFactory;\r
-import javax.swing.Icon;\r
-import javax.swing.JLabel;\r
-import javax.swing.JPanel;\r
-\r
-public class StatusBar extends JPanel {\r
-\r
-       private static final long serialVersionUID = -1471757496863555741L;\r
-       private JLabel currentActivity = null;\r
-       \r
-       private static final String STATE_IDLE = "Idle";\r
-       private static final String STATE_WORKING = "Working";\r
-\r
-       public StatusBar() {\r
-               BorderLayout borderLayout = new BorderLayout(0, 0);\r
-               setLayout(borderLayout);\r
-               JPanel rightPanel = new JPanel(new BorderLayout());\r
-               rightPanel.add(new JLabel(new AngledLinesWindowsCornerIcon()), BorderLayout.SOUTH);\r
-               rightPanel.setOpaque(false);\r
-\r
-               add(rightPanel, BorderLayout.EAST);\r
-\r
-               this.currentActivity = new JLabel("Idle");\r
-               add(this.currentActivity, BorderLayout.WEST);\r
-               setBackground(SystemColor.control);\r
-               setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.black));\r
-       }\r
-       \r
-       /**\r
-        * Sets the state/display of the status bar to "idle" (false) or "working" (true).\r
-        * @param state\r
-        */\r
-       public void showState(boolean state) {\r
-               if (state) {\r
-                       currentActivity.setText(STATE_WORKING);\r
-               } else {\r
-                       currentActivity.setText(STATE_IDLE);\r
-               }\r
-       }\r
-\r
-       private static class AngledLinesWindowsCornerIcon implements Icon {\r
-               private static final Color WHITE_LINE_COLOR = new Color(255, 255, 255);\r
-\r
-               private static final Color GRAY_LINE_COLOR = new Color(172, 168, 153);\r
-               private static final int WIDTH = 13;\r
-\r
-               private static final int HEIGHT = 13;\r
-\r
-               public int getIconHeight() {\r
-                       return HEIGHT;\r
-               }\r
-\r
-               public int getIconWidth() {\r
-                       return WIDTH;\r
-               }\r
-\r
-               public void paintIcon(Component c, Graphics g, int x, int y) {\r
-\r
-                       g.setColor(WHITE_LINE_COLOR);\r
-                       g.drawLine(0, 12, 12, 0);\r
-                       g.drawLine(5, 12, 12, 5);\r
-                       g.drawLine(10, 12, 12, 10);\r
-\r
-                       g.setColor(GRAY_LINE_COLOR);\r
-                       g.drawLine(1, 12, 12, 1);\r
-                       g.drawLine(2, 12, 12, 2);\r
-                       g.drawLine(3, 12, 12, 3);\r
-\r
-                       g.drawLine(6, 12, 12, 6);\r
-                       g.drawLine(7, 12, 12, 7);\r
-                       g.drawLine(8, 12, 12, 8);\r
-\r
-                       g.drawLine(11, 12, 12, 11);\r
-                       g.drawLine(12, 12, 12, 12);\r
-\r
-               }\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.SystemColor;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+public class StatusBar extends JPanel {
+
+       private static final long serialVersionUID = -1471757496863555741L;
+       private JLabel currentActivity = null;
+       
+       private static final String STATE_IDLE = "Idle";
+       private static final String STATE_WORKING = "Working";
+
+       public StatusBar() {
+               BorderLayout borderLayout = new BorderLayout(0, 0);
+               setLayout(borderLayout);
+               JPanel rightPanel = new JPanel(new BorderLayout());
+               rightPanel.add(new JLabel(new AngledLinesWindowsCornerIcon()), BorderLayout.SOUTH);
+               rightPanel.setOpaque(false);
+
+               add(rightPanel, BorderLayout.EAST);
+
+               this.currentActivity = new JLabel("Idle");
+               add(this.currentActivity, BorderLayout.WEST);
+               setBackground(SystemColor.control);
+               setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.black));
+       }
+       
+       /**
+        * Sets the state/display of the status bar to "idle" (false) or "working" (true).
+        * @param state
+        */
+       public void showState(boolean state) {
+               if (state) {
+                       currentActivity.setText(STATE_WORKING);
+               } else {
+                       currentActivity.setText(STATE_IDLE);
+               }
+       }
+
+       private static class AngledLinesWindowsCornerIcon implements Icon {
+               private static final Color WHITE_LINE_COLOR = new Color(255, 255, 255);
+
+               private static final Color GRAY_LINE_COLOR = new Color(172, 168, 153);
+               private static final int WIDTH = 13;
+
+               private static final int HEIGHT = 13;
+
+               public int getIconHeight() {
+                       return HEIGHT;
+               }
+
+               public int getIconWidth() {
+                       return WIDTH;
+               }
+
+               public void paintIcon(Component c, Graphics g, int x, int y) {
+
+                       g.setColor(WHITE_LINE_COLOR);
+                       g.drawLine(0, 12, 12, 0);
+                       g.drawLine(5, 12, 12, 5);
+                       g.drawLine(10, 12, 12, 10);
+
+                       g.setColor(GRAY_LINE_COLOR);
+                       g.drawLine(1, 12, 12, 1);
+                       g.drawLine(2, 12, 12, 2);
+                       g.drawLine(3, 12, 12, 3);
+
+                       g.drawLine(6, 12, 12, 6);
+                       g.drawLine(7, 12, 12, 7);
+                       g.drawLine(8, 12, 12, 8);
+
+                       g.drawLine(11, 12, 12, 11);
+                       g.drawLine(12, 12, 12, 12);
+
+               }
+       }
+}
index 7b1d8cc0fc4db0c84e0f8c6742002b65310d7309..bb662c7c4e4af2fb2cb13faa918b3d8bcb0974f8 100644 (file)
-package com.nexuiz.demorecorder.ui.swinggui;\r
-\r
-import java.awt.Container;\r
-import java.awt.Dimension;\r
-import java.awt.Point;\r
-import java.awt.event.ActionEvent;\r
-import java.awt.event.ActionListener;\r
-import java.awt.event.MouseEvent;\r
-import java.awt.event.MouseListener;\r
-import java.awt.event.WindowEvent;\r
-import java.awt.event.WindowListener;\r
-import java.io.File;\r
-import java.io.FileInputStream;\r
-import java.io.FileOutputStream;\r
-import java.io.IOException;\r
-import java.io.InputStream;\r
-import java.io.ObjectInputStream;\r
-import java.io.ObjectOutputStream;\r
-import java.net.URL;\r
-import java.util.ArrayList;\r
-import java.util.List;\r
-\r
-import javax.help.HelpBroker;\r
-import javax.help.HelpSet;\r
-import javax.swing.BorderFactory;\r
-import javax.swing.Icon;\r
-import javax.swing.ImageIcon;\r
-import javax.swing.JButton;\r
-import javax.swing.JFileChooser;\r
-import javax.swing.JFrame;\r
-import javax.swing.JMenu;\r
-import javax.swing.JMenuBar;\r
-import javax.swing.JMenuItem;\r
-import javax.swing.JOptionPane;\r
-import javax.swing.JPanel;\r
-import javax.swing.JPopupMenu;\r
-import javax.swing.JScrollPane;\r
-import javax.swing.JTable;\r
-import javax.swing.UIManager;\r
-import javax.swing.border.TitledBorder;\r
-\r
-import net.miginfocom.swing.MigLayout;\r
-\r
-import org.jdesktop.swingx.JXTable;\r
-\r
-import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
-import com.nexuiz.demorecorder.application.DemoRecorderUtils;\r
-import com.nexuiz.demorecorder.application.NDRPreferences;\r
-import com.nexuiz.demorecorder.application.DemoRecorderApplication.Preferences;\r
-import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
-import com.nexuiz.demorecorder.application.jobs.RecordJob.State;\r
-import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;\r
-import com.nexuiz.demorecorder.ui.DemoRecorderUI;\r
-import com.nexuiz.demorecorder.ui.swinggui.tablemodels.RecordJobTemplatesTableModel;\r
-import com.nexuiz.demorecorder.ui.swinggui.tablemodels.RecordJobsTableModel;\r
-import com.nexuiz.demorecorder.ui.swinggui.utils.ShowErrorDialogExceptionHandler;\r
-import com.nexuiz.demorecorder.ui.swinggui.utils.XProperties;\r
-import com.nexuiz.demorecorder.ui.swinggui.utils.XProperties.XTableState;\r
-\r
-public class SwingGUI extends JFrame implements WindowListener, DemoRecorderUI {\r
-       \r
-       private static final long serialVersionUID = -7287303462488231068L;\r
-       public static final String JOB_TABLE_PREFERENCES_FILENAME = "jobsTable.pref";\r
-       public static final String TEMPLATE_TABLE_PREFERENCES_FILENAME = "templatesTable.pref";\r
-       public static final String TEMPLATE_TABLE_CONTENT_FILENAME = "templates.dat";\r
-\r
-       private DemoRecorderApplication appLayer;\r
-       private PreferencesDialog preferencesDialog;\r
-       \r
-       private JXTable jobsTable = null;\r
-       private JPopupMenu jobsTablePopupMenu;\r
-       private ActionListener jobButtonActionListener = new JobButtonActionListener();\r
-       private MouseListener jobsTableMouseListener = new JobsTableMouseListener();\r
-       \r
-       private JXTable templatesTable = null;\r
-       private JPopupMenu templatesTablePopupMenu;\r
-       private ActionListener templateButtonActionListener = new TemplateButtonActionListener();\r
-       private MouseListener templatesTableMouseListener = new TemplatesTableMouseListener();\r
-       \r
-       private ActionListener recordButtonActionListener = new RecordButtonActionListener();\r
-       \r
-       private static final String LABEL_JOB_CREATE = "Create";\r
-       private static final String LABEL_JOB_CREATE_FROM_TEMPL = "Create from template";\r
-       private static final String LABEL_JOB_DELETE = "Delete";\r
-       private static final String LABEL_JOB_CLEAR = "Clear";\r
-       private static final String LABEL_JOB_EDIT = "Edit job";\r
-       private static final String LABEL_JOB_DUPLICATE = "Duplicate job";\r
-       private static final String LABEL_JOB_APPLYTEMPL = "Apply template";\r
-       private static final String LABEL_JOB_START = "Start job";\r
-       private static final String LABEL_JOB_SHOWERROR = "Show error message";\r
-       private static final String LABEL_JOB_RESET_STATE_WAITING = "Reset job status to 'waiting'";\r
-       private static final String LABEL_JOB_RESET_STATE_DONE = "Reset job status to 'done'";\r
-       \r
-       private static final String LABEL_TEMPL_CREATE = "Create";\r
-       private static final String LABEL_TEMPL_CREATE_FROM_JOB = "Create from job";\r
-       private static final String LABEL_TEMPL_DELETE = "Delete";\r
-       private static final String LABEL_TEMPL_CLEAR = "Clear";\r
-       private static final String LABEL_TEMPL_EDIT = "Edit template";\r
-       private static final String LABEL_TEMPL_DUPLICATE = "Duplicate template";\r
-       \r
-       private ActionListener menuButtonActionListener = new MenuButtonActionListener();\r
-       private JMenuItem fileLoadQueue = new JMenuItem("Load job queue", getIcon("fileopen.png"));\r
-       private JMenuItem fileSaveQueue = new JMenuItem("Save job queue", getIcon("filesave.png"));\r
-       private JMenuItem fileLoadTemplates = new JMenuItem("Load templates", getIcon("fileopen.png"));\r
-       private JMenuItem fileSaveTemplates = new JMenuItem("Save templates", getIcon("filesave.png"));\r
-       private JMenuItem filePreferences = new JMenuItem("Preferences", getIcon("advanced.png"));\r
-       private JMenuItem fileExit = new JMenuItem("Exit", getIcon("exit.png"));\r
-       private JMenuItem helpHelp = new JMenuItem("Show help", getIcon("help.png"));\r
-       private JMenuItem helpAbout = new JMenuItem("About", getIcon("info.png"));\r
-       private JFileChooser jobQueueSaveAsFC = new JFileChooser();\r
-       private JFileChooser templatesSaveAsFC = new JFileChooser();\r
-       \r
-       private JButton jobs_create = new JButton(LABEL_JOB_CREATE, getIcon("edit_add.png"));\r
-       private JButton jobs_createFromTempl = new JButton(LABEL_JOB_CREATE_FROM_TEMPL, getIcon("view_right_p.png"));\r
-       private JButton jobs_delete = new JButton(LABEL_JOB_DELETE, getIcon("editdelete.png"));\r
-       private JButton jobs_clear = new JButton(LABEL_JOB_CLEAR, getIcon("editclear.png"));\r
-       private JMenuItem jobs_contextmenu_edit = new JMenuItem(LABEL_JOB_EDIT, getIcon("edit.png"));\r
-       private JMenuItem jobs_contextmenu_duplicate = new JMenuItem(LABEL_JOB_DUPLICATE, getIcon("editcopy.png"));\r
-       private JMenuItem jobs_contextmenu_applytempl = new JMenuItem(LABEL_JOB_APPLYTEMPL, getIcon("editpaste.png"));\r
-       private JMenuItem jobs_contextmenu_delete = new JMenuItem(LABEL_JOB_DELETE, getIcon("editdelete.png"));\r
-       private JMenuItem jobs_contextmenu_start = new JMenuItem(LABEL_JOB_START, getIcon("player_play.png"));\r
-       private JMenuItem jobs_contextmenu_showerror = new JMenuItem(LABEL_JOB_SHOWERROR, getIcon("status_unknown.png"));\r
-       private JMenuItem jobs_contextmenu_resetstate_waiting = new JMenuItem(LABEL_JOB_RESET_STATE_WAITING, getIcon("quick_restart.png"));\r
-       private JMenuItem jobs_contextmenu_resetstate_done = new JMenuItem(LABEL_JOB_RESET_STATE_DONE, getIcon("quick_restart_blue.png"));\r
-       private List<JMenuItem> jobs_contextmenu_runPluginMenuItems = new ArrayList<JMenuItem>();\r
-       \r
-       private JButton templ_create = new JButton(LABEL_TEMPL_CREATE, getIcon("edit_add.png"));\r
-       private JButton templ_createFromJob = new JButton(LABEL_TEMPL_CREATE_FROM_JOB, getIcon("view_right_p.png"));\r
-       private JButton templ_delete = new JButton(LABEL_TEMPL_DELETE, getIcon("editdelete.png"));\r
-       private JButton templ_clear = new JButton(LABEL_TEMPL_CLEAR, getIcon("editclear.png"));\r
-       private JMenuItem templ_contextmenu_edit = new JMenuItem(LABEL_TEMPL_EDIT, getIcon("edit.png"));\r
-       private JMenuItem templ_contextmenu_duplicate = new JMenuItem(LABEL_TEMPL_DUPLICATE, getIcon("editcopy.png"));\r
-       private JMenuItem templ_contextmenu_delete = new JMenuItem(LABEL_TEMPL_DELETE, getIcon("editdelete.png"));\r
-       \r
-       private static final String PROCESSING_START = "Start processing";\r
-       private static final String PROCESSING_STOP_NOW = "Stop processing";\r
-       private static final String PROCESSING_STOP_LATER = "Processing will stop after current job finished";\r
-       private JButton processing_start = new JButton(PROCESSING_START, getIcon("player_play.png"));\r
-       private JButton processing_stop = new JButton(PROCESSING_STOP_NOW, getIcon("player_pause.png"));\r
-       \r
-       private StatusBar statusBar = new StatusBar();\r
-       \r
-       private static HelpBroker mainHelpBroker = null;\r
-       private static final String mainHelpSetName = "help/DemoRecorderHelp.hs";\r
-\r
-       public SwingGUI(DemoRecorderApplication appLayer) {\r
-               super("Nexuiz Demo Recorder v0.3");\r
-               addWindowListener(this);\r
-\r
-               this.appLayer = appLayer;\r
-\r
-               this.setupLayout();\r
-               this.setupHelp();\r
-               this.preferencesDialog = new PreferencesDialog(this, appLayer);\r
-\r
-               setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);\r
-               // Display the window.\r
-               pack();\r
-               setVisible(true);\r
-               //now that we have the GUI we can set the parent window for the error dialog\r
-               ShowErrorDialogExceptionHandler.setParentWindow(this);\r
-       }\r
-\r
-       private void setupHelp() {\r
-               if (mainHelpBroker == null){\r
-                       HelpSet mainHelpSet = null;\r
-\r
-                       try {\r
-                               URL hsURL = HelpSet.findHelpSet(null, mainHelpSetName);\r
-                               mainHelpSet = new HelpSet(null, hsURL);\r
-                       } catch (Exception e) {\r
-                               DemoRecorderUtils.showNonCriticalErrorDialog("Could not properly create the help", e, true);\r
-                       }\r
-\r
-                       if (mainHelpSet != null)\r
-                               mainHelpBroker = mainHelpSet.createHelpBroker();\r
-                       }\r
-       }\r
-\r
-       private void setupLayout() {\r
-               setLayout(new MigLayout("wrap 1,insets 10", "[400:700:,grow,fill]",\r
-                               "[grow,fill][grow,fill][][]"));\r
-               Container contentPane = getContentPane();\r
-               setJMenuBar(this.buildMenu());\r
-\r
-               this.setupTemplatePanel();\r
-               this.setupJobPanel();\r
-               this.setupRecordPanel();\r
-\r
-               contentPane.add(statusBar, "south,height 23::");\r
-       }\r
-\r
-       private void setupTemplatePanel() {\r
-               JPanel templatePanel = new JPanel(new MigLayout("", "[500:500:,grow,fill][170!,fill,grow]", "[grow,fill]"));\r
-               TitledBorder templatePanelTitle = BorderFactory.createTitledBorder("Templates");\r
-               templatePanel.setBorder(templatePanelTitle);\r
-               getContentPane().add(templatePanel);\r
-               \r
-               this.setupTemplatesTable();\r
-               this.loadTableStates(this.templatesTable);\r
-               JScrollPane templateScrollPane = new JScrollPane(templatesTable);\r
-               templatePanel.add(templateScrollPane);\r
-               \r
-               this.templ_create.addActionListener(this.templateButtonActionListener);\r
-               this.templ_createFromJob.addActionListener(this.templateButtonActionListener);\r
-               this.templ_delete.addActionListener(this.templateButtonActionListener);\r
-               this.templ_clear.addActionListener(this.templateButtonActionListener);\r
-               \r
-               this.templ_contextmenu_edit.addActionListener(this.templateButtonActionListener);\r
-               this.templ_contextmenu_duplicate.addActionListener(this.templateButtonActionListener);\r
-               this.templ_contextmenu_delete.addActionListener(this.templateButtonActionListener);\r
-               \r
-               this.configureTableButtons();\r
-               \r
-               JPanel templateControlButtonPanel = new JPanel(new MigLayout("wrap 1", "fill,grow"));\r
-               templateControlButtonPanel.add(this.templ_create);\r
-               templateControlButtonPanel.add(this.templ_createFromJob);\r
-               templateControlButtonPanel.add(this.templ_delete);\r
-               templateControlButtonPanel.add(this.templ_clear);\r
-               templatePanel.add(templateControlButtonPanel);\r
-       }\r
-\r
-       private void setupJobPanel() {\r
-               JPanel jobPanel = new JPanel(new MigLayout("", "[500:500:,grow,fill][170!,fill,grow]", "[grow,fill]"));\r
-               TitledBorder jobPanelTitle = BorderFactory.createTitledBorder("Jobs");\r
-               jobPanel.setBorder(jobPanelTitle);\r
-               getContentPane().add(jobPanel);\r
-\r
-               this.setupJobsTable();\r
-               this.loadTableStates(this.jobsTable);\r
-               \r
-               JScrollPane jobScrollPane = new JScrollPane(jobsTable);\r
-               jobPanel.add(jobScrollPane);\r
-               \r
-               this.jobs_create.addActionListener(this.jobButtonActionListener);\r
-               this.jobs_createFromTempl.addActionListener(this.jobButtonActionListener);\r
-               this.jobs_delete.addActionListener(this.jobButtonActionListener);\r
-               this.jobs_clear.addActionListener(this.jobButtonActionListener);\r
-               \r
-               this.jobs_contextmenu_edit.addActionListener(this.jobButtonActionListener);\r
-               this.jobs_contextmenu_duplicate.addActionListener(this.jobButtonActionListener);\r
-               this.jobs_contextmenu_applytempl.addActionListener(this.jobButtonActionListener);\r
-               this.jobs_contextmenu_delete.addActionListener(this.jobButtonActionListener);\r
-               this.jobs_contextmenu_start.addActionListener(this.jobButtonActionListener);\r
-               this.jobs_contextmenu_showerror.addActionListener(this.jobButtonActionListener);\r
-               this.jobs_contextmenu_resetstate_waiting.addActionListener(this.jobButtonActionListener);\r
-               this.jobs_contextmenu_resetstate_done.addActionListener(this.jobButtonActionListener);\r
-               \r
-               //initialize button states\r
-               configureTableButtons();\r
-               \r
-               JPanel jobControlButtonPanel = new JPanel(new MigLayout("wrap 1", "fill,grow"));\r
-               jobControlButtonPanel.add(this.jobs_create);\r
-               jobControlButtonPanel.add(this.jobs_createFromTempl);\r
-               jobControlButtonPanel.add(this.jobs_delete);\r
-               jobControlButtonPanel.add(this.jobs_clear);\r
-               jobPanel.add(jobControlButtonPanel);\r
-       }\r
-       \r
-       private void setupJobsTable() {\r
-               RecordJobsTableModel tableModel = new RecordJobsTableModel(this.appLayer);\r
-               jobsTable = new JXTable(tableModel);\r
-               jobsTable.setColumnControlVisible(true);\r
-               jobsTable.setPreferredScrollableViewportSize(new Dimension(400, 100));\r
-               jobsTable.addMouseListener(this.jobsTableMouseListener);\r
-       }\r
-       \r
-       private void setupTemplatesTable() {\r
-               RecordJobTemplatesTableModel tableModel = new RecordJobTemplatesTableModel();\r
-               templatesTable = new JXTable(tableModel);\r
-               templatesTable.setColumnControlVisible(true);\r
-               templatesTable.setPreferredScrollableViewportSize(new Dimension(400, 100));\r
-               templatesTable.addMouseListener(this.templatesTableMouseListener);\r
-       }\r
-\r
-       private void setupRecordPanel() {\r
-               JPanel recButtonPanel = new JPanel(new MigLayout());\r
-               recButtonPanel.add(processing_start);\r
-               recButtonPanel.add(processing_stop);\r
-               processing_stop.setEnabled(false);\r
-               processing_start.addActionListener(recordButtonActionListener);\r
-               processing_stop.addActionListener(recordButtonActionListener);\r
-               getContentPane().add(recButtonPanel);\r
-       }\r
-\r
-       public static void setSystemLAF() {\r
-               try {\r
-                       // Set System L&F\r
-                       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());\r
-               } catch (Exception e) {\r
-               }\r
-       }\r
-       \r
-       public void RecordJobPropertiesChange(RecordJob job) {\r
-               RecordJobsTableModel jobsTableModel = (RecordJobsTableModel) this.jobsTable.getModel();\r
-               List<RecordJob> recordJobs = jobsTableModel.getRecordJobs();\r
-               int jobIndex = recordJobs.indexOf(job);\r
-               if (jobIndex == -1) {\r
-                       //new job\r
-                       recordJobs.add(job);\r
-                       //add job at the end of the table:\r
-                       int position = jobsTableModel.getRowCount() - 1;\r
-                       jobsTableModel.fireTableRowsInserted(position, position);\r
-               } else {\r
-                       //job already existed\r
-                       jobIndex = this.jobsTable.convertRowIndexToView(jobIndex); //convert due to possible view sorting\r
-                       jobsTableModel.fireTableRowsUpdated(jobIndex, jobIndex);\r
-               }\r
-       }\r
-\r
-       public void recordingFinished() {\r
-               JOptionPane.showMessageDialog(SwingGUI.this, "Finished recording all jobs", "Recording done", JOptionPane.INFORMATION_MESSAGE);\r
-               statusBar.showState(false);\r
-               processing_start.setEnabled(true);\r
-               processing_stop.setEnabled(false);\r
-               processing_stop.setText(PROCESSING_STOP_NOW);\r
-       }\r
-\r
-       private JMenuBar buildMenu() {\r
-               JMenuBar menuBar = new JMenuBar();\r
-\r
-               JMenu fileMenu = new JMenu("File");\r
-               fileMenu.add(fileLoadQueue);\r
-               fileMenu.add(fileSaveQueue);\r
-               fileMenu.add(fileLoadTemplates);\r
-               fileMenu.add(fileSaveTemplates);\r
-               fileMenu.add(filePreferences);\r
-               fileMenu.add(fileExit);\r
-               menuBar.add(fileMenu);\r
-               \r
-               fileLoadQueue.addActionListener(menuButtonActionListener);\r
-               fileSaveQueue.addActionListener(menuButtonActionListener);\r
-               fileLoadTemplates.addActionListener(menuButtonActionListener);\r
-               fileSaveTemplates.addActionListener(menuButtonActionListener);\r
-               filePreferences.addActionListener(menuButtonActionListener);\r
-               fileExit.addActionListener(menuButtonActionListener);\r
-\r
-               JMenu helpMenu = new JMenu("Help");\r
-               helpMenu.add(helpHelp);\r
-               helpMenu.add(helpAbout);\r
-               menuBar.add(helpMenu);\r
-               \r
-               helpHelp.addActionListener(menuButtonActionListener);\r
-               helpAbout.addActionListener(menuButtonActionListener);\r
-               \r
-               this.setupEncoderPluginButtons();\r
-               \r
-               this.jobsTablePopupMenu = new JPopupMenu();\r
-               this.jobsTablePopupMenu.add(jobs_contextmenu_edit);\r
-               this.jobsTablePopupMenu.add(jobs_contextmenu_duplicate);\r
-               this.jobsTablePopupMenu.add(jobs_contextmenu_applytempl);\r
-               this.jobsTablePopupMenu.add(jobs_contextmenu_delete);\r
-               this.jobsTablePopupMenu.add(jobs_contextmenu_start);\r
-               //add JMenus for plugins\r
-               for (JMenuItem menuItem : jobs_contextmenu_runPluginMenuItems) {\r
-                       this.jobsTablePopupMenu.add(menuItem);\r
-               }\r
-               this.jobsTablePopupMenu.add(jobs_contextmenu_showerror);\r
-               this.jobsTablePopupMenu.add(jobs_contextmenu_resetstate_waiting);\r
-               this.jobsTablePopupMenu.add(jobs_contextmenu_resetstate_done);\r
-               \r
-               \r
-               \r
-               \r
-               this.templatesTablePopupMenu = new JPopupMenu();\r
-               this.templatesTablePopupMenu.add(templ_contextmenu_edit);\r
-               this.templatesTablePopupMenu.add(templ_contextmenu_duplicate);\r
-               this.templatesTablePopupMenu.add(templ_contextmenu_delete);\r
-\r
-               return menuBar;\r
-       }\r
-       \r
-       private void setupEncoderPluginButtons() {\r
-               for (EncoderPlugin plugin : appLayer.getEncoderPlugins()) {\r
-                       JMenuItem pluginMenuItem = new JMenuItem("Just run " + plugin.getName() + " plugin", getIcon("package.png"));\r
-                       pluginMenuItem.addActionListener(jobButtonActionListener);\r
-                       this.jobs_contextmenu_runPluginMenuItems.add(pluginMenuItem);\r
-               }\r
-       }\r
-\r
-       private void saveTableStates(JXTable table) {\r
-               String fileName;\r
-               if (table == jobsTable) {\r
-                       fileName = JOB_TABLE_PREFERENCES_FILENAME;\r
-               } else {\r
-                       fileName = TEMPLATE_TABLE_PREFERENCES_FILENAME;\r
-               }\r
-               String exceptionMessage = "An error occurred while trying to save the table state file " + fileName;\r
-               \r
-               XProperties.XTableProperty t = new XProperties.XTableProperty();\r
-               XTableState tableState;\r
-               try {\r
-                       tableState = (XTableState) t.getSessionState(table);\r
-               } catch (Exception e) { //most likely ClassCastException\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);\r
-                       return;\r
-               }\r
-               \r
-               File tableStateFile = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, fileName);\r
-               DemoRecorderUtils.attemptFileCreation(tableStateFile);\r
-               \r
-               try {\r
-                       FileOutputStream fout = new FileOutputStream(tableStateFile);\r
-                       ObjectOutputStream oos = new ObjectOutputStream(fout);\r
-                       oos.writeObject(tableState);\r
-                       oos.close();\r
-               } catch (Exception e) {\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);\r
-               }\r
-       }\r
-       \r
-       private void loadTableStates(JXTable table) {\r
-               String fileName;\r
-               if (table == jobsTable) {\r
-                       fileName = JOB_TABLE_PREFERENCES_FILENAME;\r
-               } else {\r
-                       fileName = TEMPLATE_TABLE_PREFERENCES_FILENAME;\r
-               }\r
-               \r
-               XProperties.XTableProperty t = new XProperties.XTableProperty();\r
-               \r
-               File tableStateFile = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, fileName);\r
-               \r
-               XTableState tableState;\r
-               \r
-               try {\r
-                       FileInputStream fin = new FileInputStream(tableStateFile);\r
-                       ObjectInputStream ois = new ObjectInputStream(fin);\r
-                       tableState = (XTableState) ois.readObject();\r
-                       t.setSessionState(table, tableState);\r
-               } catch (Exception e) {\r
-                        //manually hide columns\r
-                       if (table == jobsTable) {\r
-                               //re-create table to be sure\r
-                               this.setupJobsTable();\r
-                               //manually hide some columns\r
-                               jobsTable.getColumnExt(RecordJobsTableModel.EXECUTE_AFTER_CAP).setVisible(false);\r
-                               jobsTable.getColumnExt(RecordJobsTableModel.EXECUTE_BEFORE_CAP).setVisible(false);\r
-                               jobsTable.getColumnExt(RecordJobsTableModel.VIDEO_DESTINATION_PATH).setVisible(false);\r
-                               jobsTable.getColumnExt(RecordJobsTableModel.DPVIDEO_PATH).setVisible(false);\r
-                               jobsTable.getColumnExt(RecordJobsTableModel.RELATIVE_DEMO_PATH).setVisible(false);\r
-                               jobsTable.getColumnExt(RecordJobsTableModel.ENGINE_PARAMETERS).setVisible(false);\r
-                               jobsTable.getColumnExt(RecordJobsTableModel.ENGINE_PATH).setVisible(false);\r
-                       } else {\r
-                               //re-create table to be sure\r
-                               this.setupTemplatesTable();\r
-                               //manually hide some columns\r
-                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.EXECUTE_AFTER_CAP).setVisible(false);\r
-                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.EXECUTE_BEFORE_CAP).setVisible(false);\r
-                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.VIDEO_DESTINATION_PATH).setVisible(false);\r
-                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.DPVIDEO_PATH).setVisible(false);\r
-                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.RELATIVE_DEMO_PATH).setVisible(false);\r
-                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.DEMO_FILE_PATH).setVisible(false);\r
-                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.ENGINE_PARAMETERS).setVisible(false);\r
-                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.ENGINE_PATH).setVisible(false);\r
-                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.JOB_NAME).setVisible(false);\r
-                       }\r
-               }\r
-       }\r
-       \r
-       private class MenuButtonActionListener implements ActionListener {\r
-\r
-               public void actionPerformed(ActionEvent e) {\r
-                       if (e.getSource() == fileLoadQueue) {\r
-                               int result = jobQueueSaveAsFC.showOpenDialog(SwingGUI.this);\r
-                               if (result == JFileChooser.APPROVE_OPTION) {\r
-                                       File selectedFile = jobQueueSaveAsFC.getSelectedFile();\r
-                                       if (selectedFile.isFile()) {\r
-                                               RecordJobsTableModel tableModel = (RecordJobsTableModel) jobsTable.getModel();\r
-                                               tableModel.loadNewJobQueue(SwingGUI.this, selectedFile, jobsTable);\r
-                                               configureTableButtons();\r
-                                       }\r
-                               }\r
-                               \r
-                       } else if (e.getSource() == fileSaveQueue) {\r
-                               int result = jobQueueSaveAsFC.showSaveDialog(SwingGUI.this);\r
-                               if (result == JFileChooser.APPROVE_OPTION) {\r
-                                       File selectedFile = jobQueueSaveAsFC.getSelectedFile();\r
-                                       if (!DemoRecorderUtils.getFileExtension(selectedFile).equals("queue")) {\r
-                                               //if file is not a .queue file, make it one\r
-                                               selectedFile = new File(selectedFile.getAbsoluteFile() + ".queue");\r
-                                       }\r
-                                       if (selectedFile.exists()) {\r
-                                               int confirm = JOptionPane.showConfirmDialog(SwingGUI.this, "File already exists. Are you sure you want to overwrite it?", "Confirm overwrite", JOptionPane.YES_NO_OPTION);\r
-                                               if (confirm == JOptionPane.NO_OPTION) {\r
-                                                       return;\r
-                                               }\r
-                                       }\r
-                                       appLayer.saveJobQueue(selectedFile);\r
-                               }\r
-                       } else if (e.getSource() == fileLoadTemplates) {\r
-                               int result = templatesSaveAsFC.showOpenDialog(SwingGUI.this);\r
-                               if (result == JFileChooser.APPROVE_OPTION) {\r
-                                       File selectedFile = templatesSaveAsFC.getSelectedFile();\r
-                                       if (selectedFile.isFile()) {\r
-                                               RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();\r
-                                               tableModel.loadNewTemplateList(SwingGUI.this, selectedFile, templatesTable);\r
-                                               configureTableButtons();\r
-                                       }\r
-                               }\r
-                       } else if (e.getSource() == fileSaveTemplates) {\r
-                               int result = templatesSaveAsFC.showSaveDialog(SwingGUI.this);\r
-                               if (result == JFileChooser.APPROVE_OPTION) {\r
-                                       File selectedFile = templatesSaveAsFC.getSelectedFile();\r
-                                       if (!DemoRecorderUtils.getFileExtension(selectedFile).equals("templ")) {\r
-                                               selectedFile = new File(selectedFile.getAbsoluteFile() + ".templ");\r
-                                       }\r
-                                       if (selectedFile.exists()) {\r
-                                               int confirm = JOptionPane.showConfirmDialog(SwingGUI.this, "File already exists. Are you sure you want to overwrite it?", "Confirm overwrite", JOptionPane.YES_NO_OPTION);\r
-                                               if (confirm == JOptionPane.NO_OPTION) {\r
-                                                       return;\r
-                                               }\r
-                                       }\r
-                                       RecordJobTemplatesTableModel model = (RecordJobTemplatesTableModel) templatesTable.getModel();\r
-                                       model.saveTemplateListToFile(selectedFile);\r
-                               }\r
-                       } else if (e.getSource() == filePreferences) {\r
-                               preferencesDialog.showDialog();\r
-                       } else if (e.getSource() == fileExit) {\r
-                               shutDown();\r
-                       } else if (e.getSource() == helpHelp) {\r
-                               if (mainHelpBroker != null) {\r
-                                       mainHelpBroker.setDisplayed(true);\r
-                               }\r
-                       } else if (e.getSource() == helpAbout) {\r
-                               showAboutBox();\r
-                       }\r
-               }\r
-               \r
-       }\r
-\r
-       /**\r
-        * Listens to the clicks on buttons that are in the job panel (next to the jobs table)\r
-        * or its context menu.\r
-        */\r
-       private class JobButtonActionListener implements ActionListener {\r
-\r
-               public void actionPerformed(ActionEvent e) {\r
-                       List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);\r
-                       List<RecordJob> selectedTemplates = getSelectedRecordJobs(templatesTable);\r
-                       if (e.getSource() == jobs_create) {\r
-                               JobDialog jobDialog = new JobDialog(SwingGUI.this, appLayer);\r
-                               jobDialog.showDialog();\r
-                               configureTableButtons();\r
-                       }\r
-                       else if (e.getSource() == jobs_createFromTempl) {\r
-                               if (selectedTemplates.size() != 1) {\r
-                                       return;\r
-                               }\r
-                               RecordJobTemplate template = (RecordJobTemplate) selectedTemplates.get(0);\r
-//                             JobDialog jobDialog = new JobDialog(SwingGUI.this, template, appLayer);\r
-                               JobDialog jobDialog = new JobDialog(SwingGUI.this, template, appLayer, JobDialog.CREATE_JOB_FROM_TEMPLATE);\r
-                               jobDialog.showDialog();\r
-                               configureTableButtons();\r
-                       }\r
-                       else if (e.getSource() == jobs_delete || e.getSource() == jobs_contextmenu_delete) {\r
-                               int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to delete the selected job(s)?", "Confirm delete", JOptionPane.YES_NO_OPTION);\r
-                               if (result == JOptionPane.YES_OPTION) {\r
-                                       deleteSelectedJobs(false);\r
-                                       configureTableButtons();\r
-                               }\r
-                       }\r
-                       else if (e.getSource() == jobs_clear) {\r
-                               int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to clear the job list?", "Confirm clear", JOptionPane.YES_NO_OPTION);\r
-                               if (result == JOptionPane.YES_OPTION) {\r
-                                       deleteSelectedJobs(true);\r
-                                       configureTableButtons();\r
-                               }\r
-                       } else if (e.getSource() == jobs_contextmenu_edit) {\r
-                               if (selectedJobs.size() == 1) {\r
-                                       RecordJob selectedJob = selectedJobs.get(0);\r
-                                       JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedJob, appLayer);\r
-                                       jobDialog.showDialog();\r
-                                       configureTableButtons();\r
-                               }\r
-                       } else if (e.getSource() == jobs_contextmenu_showerror) {\r
-                               if (selectedJobs.size() == 1) {\r
-                                       RecordJob selectedJob = selectedJobs.get(0);\r
-                                       DemoRecorderUtils.showNonCriticalErrorDialog(selectedJob.getLastException());\r
-                               }\r
-                       } else if (e.getSource() == jobs_contextmenu_resetstate_waiting) {\r
-                               for (RecordJob job : selectedJobs) {\r
-                                       job.setState(RecordJob.State.WAITING);\r
-                               }\r
-                       } else if (e.getSource() == jobs_contextmenu_resetstate_done) {\r
-                               for (RecordJob job : selectedJobs) {\r
-                                       if (job.getState() == State.ERROR_PLUGIN) {\r
-                                               job.setState(RecordJob.State.DONE);\r
-                                       }\r
-                               }\r
-                       } else if (e.getSource() == jobs_contextmenu_start) {\r
-                               appLayer.recordSelectedJobs(selectedJobs);\r
-                               if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {\r
-                                       processing_start.setEnabled(false);\r
-                                       processing_stop.setEnabled(true);\r
-                                       statusBar.showState(true);\r
-                               }\r
-                       } else if (e.getSource() == jobs_contextmenu_duplicate) {\r
-                               if (selectedJobs.size() > 0) {\r
-                                       this.duplicateRecordJobs(selectedJobs);\r
-                                       //select all new duplicates in the table automatically\r
-                                       jobsTable.setRowSelectionInterval(jobsTable.getRowCount() - selectedJobs.size(), jobsTable.getRowCount() - 1);\r
-                                       configureTableButtons();\r
-                               }\r
-                       } else if (e.getSource() == jobs_contextmenu_applytempl) {\r
-                               if (selectedTemplates.size() == 1 && selectedJobs.size() > 0) {\r
-                                       RecordJobTemplate template = (RecordJobTemplate) selectedTemplates.get(0);\r
-                                       ApplyTemplateDialog applyDialog = new ApplyTemplateDialog(SwingGUI.this, template, selectedJobs);\r
-                                       applyDialog.showDialog();\r
-                               }\r
-                       } else if (jobs_contextmenu_runPluginMenuItems.contains(e.getSource())) {\r
-                               int index = jobs_contextmenu_runPluginMenuItems.indexOf(e.getSource());\r
-                               EncoderPlugin selectedPlugin = appLayer.getEncoderPlugins().get(index);\r
-                               \r
-                               appLayer.executePluginForSelectedJobs(selectedPlugin, selectedJobs);\r
-                               if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {\r
-                                       processing_start.setEnabled(false);\r
-                                       processing_stop.setEnabled(true);\r
-                                       statusBar.showState(true);\r
-                               }\r
-                       }\r
-               }\r
-               \r
-               private void duplicateRecordJobs(List<RecordJob> jobs) {\r
-                       String nameSuffix = appLayer.getPreferences().getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.JOB_NAME_APPEND_DUPLICATE);\r
-                       for (RecordJob job : jobs) {\r
-                               RecordJob newJob = appLayer.createRecordJob(\r
-                                       job.getJobName() + nameSuffix,\r
-                                       job.getEnginePath(),\r
-                                       job.getEngineParameters(),\r
-                                       job.getDemoFile(),\r
-                                       job.getRelativeDemoPath(),\r
-                                       job.getDpVideoPath(),\r
-                                       job.getVideoDestination(),\r
-                                       job.getExecuteBeforeCap(),\r
-                                       job.getExecuteAfterCap(),\r
-                                       job.getStartSecond(),\r
-                                       job.getEndSecond()\r
-                               );\r
-                               newJob.setEncoderPluginSettings(job.getEncoderPluginSettings());\r
-                       }\r
-               }\r
-               \r
-       }\r
-       \r
-       private class TemplateButtonActionListener implements ActionListener {\r
-               public void actionPerformed(ActionEvent e) {\r
-                       if (e.getSource() == templ_create) {\r
-                               RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();\r
-                               JobDialog jobDialog = new JobDialog(SwingGUI.this, tableModel, templatesTable, appLayer);\r
-                               jobDialog.showDialog();\r
-                               configureTableButtons();\r
-                       }\r
-                       else if (e.getSource() == templ_createFromJob) {\r
-                               this.createTemplateFromJob();\r
-                               configureTableButtons();\r
-                       }\r
-                       else if (e.getSource() == templ_delete || e.getSource() == templ_contextmenu_delete) {\r
-                               int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to delete the selected template(s)?", "Confirm delete", JOptionPane.YES_NO_OPTION);\r
-                               if (result == JOptionPane.YES_OPTION) {\r
-                                       deleteSelectedTemplates(false);\r
-                               }\r
-                               configureTableButtons();\r
-                       }\r
-                       else if (e.getSource() == templ_clear) {\r
-                               int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to clear the template list?", "Confirm clear", JOptionPane.YES_NO_OPTION);\r
-                               if (result == JOptionPane.YES_OPTION) {\r
-                                       deleteSelectedTemplates(true);\r
-                               }\r
-                               configureTableButtons();\r
-                       }\r
-                       else if (e.getSource() == templ_contextmenu_edit) {\r
-                               List<RecordJob> selectedTemplates = getSelectedRecordJobs(templatesTable);\r
-                               if (selectedTemplates.size() == 1) {\r
-                                       RecordJobTemplate selectedTemplate = (RecordJobTemplate) selectedTemplates.get(0);\r
-                                       JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedTemplate, appLayer, JobDialog.EDIT_TEMPLATE);\r
-                                       jobDialog.showDialog();\r
-                                       configureTableButtons();\r
-                               }\r
-                       }\r
-                       else if (e.getSource() == templ_contextmenu_duplicate) {\r
-                               List<RecordJob> selectedTemplates = getSelectedRecordJobs(templatesTable);\r
-                               if (selectedTemplates.size() > 0) {\r
-                                       this.duplicateTemplates(selectedTemplates);\r
-                                       //select all new duplicates in the table automatically\r
-                                       templatesTable.setRowSelectionInterval(templatesTable.getRowCount() - selectedTemplates.size(), templatesTable.getRowCount() - 1);\r
-                                       configureTableButtons();\r
-                               }\r
-                       }\r
-               }\r
-               \r
-               private void createTemplateFromJob() {\r
-                       List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);\r
-                       if (selectedJobs.size() == 1) {\r
-                               RecordJob job = selectedJobs.get(0);\r
-                               RecordJobTemplate templ = new RecordJobTemplate(\r
-                                       "Generated from job",\r
-                                       "Generated from job",\r
-                                       job.getJobName(),\r
-                                       job.getEnginePath(),\r
-                                       job.getEngineParameters(),\r
-                                       job.getDemoFile().getParentFile(),\r
-                                       job.getRelativeDemoPath(),\r
-                                       job.getDpVideoPath(),\r
-                                       job.getVideoDestination().getParentFile(),\r
-                                       job.getExecuteBeforeCap(),\r
-                                       job.getExecuteAfterCap()\r
-                               );\r
-                               templ.setEncoderPluginSettings(job.getEncoderPluginSettings());\r
-                               \r
-                               RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();\r
-                               tableModel.addRecordJobTemplate(templ);\r
-                       }\r
-               }\r
-               \r
-               private void duplicateTemplates(List<RecordJob> selectedTemplates) {\r
-                       for (RecordJob job : selectedTemplates) {\r
-                               RecordJobTemplate template = (RecordJobTemplate) job;\r
-                               RecordJobTemplate templ = new RecordJobTemplate(\r
-                                       template.getName(),\r
-                                       template.getSummary(),\r
-                                       template.getJobName(),\r
-                                       template.getEnginePath(),\r
-                                       template.getEngineParameters(),\r
-                                       template.getDemoFile(),\r
-                                       template.getRelativeDemoPath(),\r
-                                       template.getDpVideoPath(),\r
-                                       template.getVideoDestination(),\r
-                                       template.getExecuteBeforeCap(),\r
-                                       template.getExecuteAfterCap()\r
-                               );\r
-                               templ.setEncoderPluginSettings(template.getEncoderPluginSettings());\r
-                               \r
-                               RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();\r
-                               tableModel.addRecordJobTemplate(templ);\r
-                       }\r
-               }\r
-       }\r
-       \r
-       private class RecordButtonActionListener implements ActionListener {\r
-\r
-               public void actionPerformed(ActionEvent e) {\r
-                       if (e.getSource() == processing_start) {\r
-                               appLayer.startRecording();\r
-                               if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {\r
-                                       processing_start.setEnabled(false);\r
-                                       processing_stop.setEnabled(true);\r
-                                       statusBar.showState(true);\r
-                               }\r
-                       } else if (e.getSource() == processing_stop) {\r
-                               if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {\r
-                                       appLayer.stopRecording();\r
-                                       processing_stop.setEnabled(false);\r
-                                       processing_stop.setText(PROCESSING_STOP_LATER);\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-       \r
-       private void deleteSelectedJobs(boolean deleteAllJobs) {\r
-               RecordJobsTableModel tableModel = (RecordJobsTableModel) jobsTable.getModel();\r
-               if (deleteAllJobs) {\r
-                       int rowCount = jobsTable.getRowCount();\r
-                       for (int i = rowCount - 1; i >= 0; i--) {\r
-                               int modelRowIndex = jobsTable.convertRowIndexToModel(i);\r
-                               tableModel.deleteRecordJob(modelRowIndex, i);\r
-                       }\r
-               } else {\r
-                       int[] selectedRows = jobsTable.getSelectedRows();\r
-                       for (int i = selectedRows.length - 1; i >= 0; i--) {\r
-                               int modelRowIndex = jobsTable.convertRowIndexToModel(selectedRows[i]);\r
-                               tableModel.deleteRecordJob(modelRowIndex, selectedRows[i]);\r
-                       }\r
-               }\r
-       }\r
-       \r
-       private void deleteSelectedTemplates(boolean deleteAllTemplates) {\r
-               RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();\r
-               if (deleteAllTemplates) {\r
-                       int rowCount = templatesTable.getRowCount();\r
-                       for (int i = rowCount - 1; i >= 0; i--) {\r
-                               int modelRowIndex = templatesTable.convertRowIndexToModel(i);\r
-                               tableModel.deleteRecordJobTemplate(modelRowIndex, i);\r
-                       }\r
-               } else {\r
-                       int[] selectedRows = templatesTable.getSelectedRows();\r
-                       for (int i = selectedRows.length - 1; i >= 0; i--) {\r
-                               int modelRowIndex = templatesTable.convertRowIndexToModel(selectedRows[i]);\r
-                               tableModel.deleteRecordJobTemplate(modelRowIndex, selectedRows[i]);\r
-                       }\r
-               }\r
-               //update the button state of buttons dealing with jobs\r
-               this.configureTableButtons();\r
-       }\r
-       \r
-       /**\r
-        * Iterates through all RecordJob objects (or just the selected ones) and returns true\r
-        * if at least one of them has one or more has the given state(s).\r
-        * @param state\r
-        * @param justSelectedJobs\r
-        * @return\r
-        */\r
-       private boolean checkJobStates(RecordJob.State[] state, boolean justSelectedJobs) {\r
-               boolean foundState = false;\r
-               List<RecordJob> jobsToLookAt = null;\r
-               if (!justSelectedJobs) {\r
-                       jobsToLookAt = this.appLayer.getRecordJobs();\r
-               } else {\r
-                       jobsToLookAt = getSelectedRecordJobs(jobsTable);\r
-               }\r
-               \r
-               for (RecordJob currentJob : jobsToLookAt) {\r
-                       for (int i = 0; i < state.length; i++) {\r
-                               if (currentJob.getState() == state[i]) {\r
-                                       foundState = true;\r
-                                       break;\r
-                               }\r
-                       }\r
-               }\r
-               return foundState;\r
-       }\r
-       \r
-       /**\r
-        * Returns the list of selected RecordJobs or RecordJobTemplates.\r
-        * @param table jobsTable or templatesTable\r
-        * @return list of selected RecordJobs or RecordJobTemplates\r
-        */\r
-       private List<RecordJob> getSelectedRecordJobs(JXTable table) {\r
-               List<RecordJob> list = new ArrayList<RecordJob>();\r
-               if (table.getSelectedRowCount() > 0) {\r
-                       int[] selectedRows = table.getSelectedRows();\r
-                       for (int i = 0; i < selectedRows.length; i++) {\r
-                               int modelRowIndex = table.convertRowIndexToModel(selectedRows[i]);\r
-                               if (table == jobsTable) {\r
-                                       RecordJobsTableModel tableModel = (RecordJobsTableModel) table.getModel();\r
-                                       RecordJob job = tableModel.getRecordJob(modelRowIndex);\r
-                                       if (job != null) {\r
-                                               list.add(job);\r
-                                       }\r
-                               } else {\r
-                                       RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) table.getModel();\r
-                                       RecordJobTemplate template = tableModel.getRecordJobTemplate(modelRowIndex);\r
-                                       if (template != null) {\r
-                                               list.add(template);\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-               \r
-               return list;\r
-       }\r
-       \r
-       private void configureTableButtons() {\r
-               if (jobsTable != null) {\r
-                       if (jobsTable.getRowCount() == 0) {\r
-                               jobs_clear.setEnabled(false);\r
-                               jobs_delete.setEnabled(false);\r
-                       } else {\r
-                               jobs_clear.setEnabled(true);\r
-                               jobs_delete.setEnabled(true);\r
-                               if (jobsTable.getSelectedRowCount() == 0) {\r
-                                       jobs_delete.setEnabled(false);\r
-                               } else {\r
-                                       //go through all elements and check for attributes PROCESSING\r
-                                       RecordJob.State[] lookForState = {RecordJob.State.PROCESSING};\r
-                                       boolean foundState = checkJobStates(lookForState, false);\r
-                                       if (foundState) {\r
-                                               //we have to disable the clear and delete button\r
-                                               jobs_delete.setEnabled(false);\r
-                                       }\r
-                               }\r
-                       }\r
-                       if (templatesTable.getSelectedRowCount() == 1) {\r
-                               jobs_createFromTempl.setEnabled(true);\r
-                       } else {\r
-                               jobs_createFromTempl.setEnabled(false);\r
-                       }\r
-               }\r
-               \r
-               if (templatesTable != null) {\r
-                       templ_createFromJob.setEnabled(false);\r
-                       templ_delete.setEnabled(false);\r
-                       templ_clear.setEnabled(false);\r
-                       \r
-                       if (jobsTable != null && jobsTable.getSelectedRowCount() == 1) {\r
-                               templ_createFromJob.setEnabled(true);\r
-                       }\r
-                       \r
-                       if (templatesTable.getSelectedRowCount() > 0) {\r
-                               templ_delete.setEnabled(true);\r
-                       }\r
-                       \r
-                       if (templatesTable.getRowCount() > 0) {\r
-                               templ_clear.setEnabled(true);\r
-                       }\r
-               }\r
-       }\r
-       \r
-       private class JobsTableMouseListener implements MouseListener {\r
-\r
-               public void mouseClicked(MouseEvent e) {\r
-                       if (e != null && e.getClickCount() == 2) {\r
-                               List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);\r
-                               if (selectedJobs.size() == 1) {\r
-                                       RecordJob selectedJob = selectedJobs.get(0);\r
-                                       if (selectedJob.getState() != RecordJob.State.PROCESSING) {\r
-                                               JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedJob, appLayer);\r
-                                               jobDialog.showDialog();\r
-                                       }\r
-                               }\r
-                       } else {\r
-                               configureTableButtons();\r
-                       }\r
-               }\r
-\r
-               public void mouseEntered(MouseEvent e) {}\r
-\r
-               public void mouseExited(MouseEvent e) {}\r
-\r
-               public void mousePressed(MouseEvent e) {\r
-                       this.showPopupMenu(e);\r
-               }\r
-\r
-               public void mouseReleased(MouseEvent e) {\r
-                       this.showPopupMenu(e);\r
-               }\r
-               \r
-               private void showPopupMenu(MouseEvent e) {\r
-                       if (e.isPopupTrigger()) {\r
-                               JTable table = (JTable)(e.getSource());\r
-                               Point p = e.getPoint();\r
-                               int row = table.rowAtPoint(p);\r
-                               int[] selectedRows = table.getSelectedRows();\r
-                               //figure out whether we have to reselect the current row under the pointer,\r
-                               //which is only the case if the already selected rows don't include the one under\r
-                               //the pointer yet\r
-                               boolean reSelect = true;\r
-                               for (int i = 0; i < selectedRows.length; i++) {\r
-                                       if (row == selectedRows[i]) {\r
-                                               reSelect = false;\r
-                                               break;\r
-                                       }\r
-                               }\r
-                               \r
-                               if (row != -1 && reSelect) {\r
-                                       table.setRowSelectionInterval(row, row);\r
-                               }\r
-                               \r
-                               this.configurePopupMenu();\r
-                               configureTableButtons();\r
-                               jobsTablePopupMenu.show(e.getComponent(), e.getX(), e.getY());\r
-                       }\r
-               }\r
-               \r
-               private void configurePopupMenu() {\r
-                       //Disable all buttons first\r
-                       jobs_contextmenu_edit.setEnabled(false);\r
-                       jobs_contextmenu_duplicate.setEnabled(false);\r
-                       jobs_contextmenu_applytempl.setEnabled(false);\r
-                       jobs_contextmenu_delete.setEnabled(false);\r
-                       jobs_contextmenu_resetstate_waiting.setEnabled(false);\r
-                       jobs_contextmenu_resetstate_done.setEnabled(false);\r
-                       jobs_contextmenu_showerror.setEnabled(false);\r
-                       jobs_contextmenu_start.setEnabled(false);\r
-                       for (JMenuItem pluginItem : jobs_contextmenu_runPluginMenuItems) {\r
-                               pluginItem.setEnabled(false);\r
-                       }\r
-                       \r
-                       //edit, duplicate, and show error buttons\r
-                       if (jobsTable.getSelectedRowCount() == 1) {\r
-                               jobs_contextmenu_edit.setEnabled(true);\r
-                               \r
-                               //Show error button\r
-                               List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);\r
-                               RecordJob selectedJob = selectedJobs.get(0);\r
-                               if (selectedJob.getState() == RecordJob.State.ERROR || selectedJob.getState() == RecordJob.State.ERROR_PLUGIN) {\r
-                                       jobs_contextmenu_showerror.setEnabled(true);\r
-                               }\r
-                       }\r
-                       \r
-                       if (jobsTable.getSelectedRowCount() > 0) {\r
-                               jobs_contextmenu_duplicate.setEnabled(true);\r
-                               //Delete button\r
-                               RecordJob.State[] states = {RecordJob.State.PROCESSING};\r
-                               if (!checkJobStates(states, false)) {\r
-                                       //none of the jobs is processing\r
-                                       jobs_contextmenu_delete.setEnabled(true);\r
-                                       jobs_contextmenu_resetstate_waiting.setEnabled(true);\r
-                                       \r
-                                       if (templatesTable.getSelectedRowCount() == 1) {\r
-                                               jobs_contextmenu_applytempl.setEnabled(true);\r
-                                       }\r
-                               }\r
-                               \r
-                               //Start button\r
-                               RecordJob.State[] states2 = {RecordJob.State.ERROR, RecordJob.State.DONE, RecordJob.State.PROCESSING, RecordJob.State.ERROR_PLUGIN};\r
-                               if (!checkJobStates(states2, true)) {\r
-                                       //only enable start if none of the selected jobs as any of the States above\r
-                                       //as the only job State that is not listed is "waiting", we only enable the button if all jobs are waiting\r
-                                       jobs_contextmenu_start.setEnabled(true);\r
-                               }\r
-                               \r
-                               //reset to 'done' button\r
-                               RecordJob.State[] states3 = {RecordJob.State.ERROR, RecordJob.State.WAITING, RecordJob.State.PROCESSING};\r
-                               if (!checkJobStates(states3, true)) {\r
-                                       //only enable the "reset to done" button when processes have the state DONE or ERROR_PLUGIN\r
-                                       jobs_contextmenu_resetstate_done.setEnabled(true);\r
-                               }\r
-                               \r
-                               //plugin buttons, enable only when state of the job is DONE\r
-                               RecordJob.State[] states4 = {RecordJob.State.ERROR, RecordJob.State.WAITING, RecordJob.State.PROCESSING, RecordJob.State.ERROR_PLUGIN};\r
-                               if (!checkJobStates(states4, true)) {\r
-                                       int counter = 0;\r
-                                       for (JMenuItem pluginItem : jobs_contextmenu_runPluginMenuItems) {\r
-                                               if (appLayer.getEncoderPlugins().get(counter).isEnabled()) {\r
-                                                       pluginItem.setEnabled(true);\r
-                                               }\r
-                                               counter++;\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-               \r
-       }\r
-       \r
-       private class TemplatesTableMouseListener implements MouseListener {\r
-\r
-               public void mouseClicked(MouseEvent e) {\r
-                       if (e != null && e.getClickCount() == 2) {\r
-                               List<RecordJob> selectedJobs = getSelectedRecordJobs(templatesTable);\r
-                               if (selectedJobs.size() == 1) {\r
-                                       RecordJobTemplate selectedJob = (RecordJobTemplate) selectedJobs.get(0);\r
-                                       JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedJob, appLayer, JobDialog.EDIT_TEMPLATE);\r
-                                       jobDialog.showDialog();\r
-                                       configureTableButtons();\r
-                               }\r
-                       } else {\r
-                               configureTableButtons();\r
-                       }\r
-               }\r
-\r
-               public void mouseEntered(MouseEvent e) {}\r
-\r
-               public void mouseExited(MouseEvent e) {}\r
-\r
-               public void mousePressed(MouseEvent e) {\r
-                       this.showPopupMenu(e);\r
-               }\r
-\r
-               public void mouseReleased(MouseEvent e) {\r
-                       this.showPopupMenu(e);\r
-               }\r
-               \r
-               private void showPopupMenu(MouseEvent e) {\r
-                       if (e.isPopupTrigger()) {\r
-                               JTable table = (JTable)(e.getSource());\r
-                               Point p = e.getPoint();\r
-                               int row = table.rowAtPoint(p);\r
-                               int[] selectedRows = table.getSelectedRows();\r
-                               //figure out whether we have to reselect the current row under the pointer,\r
-                               //which is only the case if the already selected rows don't include the one under\r
-                               //the pointer yet\r
-                               boolean reSelect = true;\r
-                               for (int i = 0; i < selectedRows.length; i++) {\r
-                                       if (row == selectedRows[i]) {\r
-                                               reSelect = false;\r
-                                               break;\r
-                                       }\r
-                               }\r
-                               \r
-                               if (row != -1 && reSelect) {\r
-                                       table.setRowSelectionInterval(row, row);\r
-                               }\r
-                               \r
-                               this.configurePopupMenu();\r
-                               configureTableButtons();\r
-                               templatesTablePopupMenu.show(e.getComponent(), e.getX(), e.getY());\r
-                       }\r
-               }\r
-               \r
-               private void configurePopupMenu() {\r
-                       //Various buttons\r
-                       templ_contextmenu_edit.setEnabled(false);\r
-                       templ_contextmenu_duplicate.setEnabled(false);\r
-                       templ_contextmenu_delete.setEnabled(false);\r
-                       \r
-                       //Edit button\r
-                       if (templatesTable.getSelectedRowCount() == 1) {\r
-                               templ_contextmenu_edit.setEnabled(true);\r
-                       }\r
-                       \r
-                       //Delete and duplicate button\r
-                       if (templatesTable.getSelectedRowCount() > 0) {\r
-                               templ_contextmenu_delete.setEnabled(true);\r
-                               templ_contextmenu_duplicate.setEnabled(true);\r
-                       }\r
-               }\r
-       }\r
-               \r
-       private void showAboutBox() {\r
-        try {\r
-            InputStream inStream = ClassLoader.getSystemResourceAsStream("about.html");\r
-            StringBuffer out = new StringBuffer();\r
-            byte[] b = new byte[4096];\r
-            for (int n; (n = inStream.read(b)) != -1;) {\r
-                out.append(new String(b, 0, n));\r
-            }\r
-            String htmlString = out.toString();\r
-            htmlString = htmlString.replaceAll("[\\r\\n]", "");\r
-            JOptionPane.showMessageDialog(this, htmlString, "About", JOptionPane.PLAIN_MESSAGE);\r
-        } catch (IOException ex) {\r
-            ex.printStackTrace();\r
-        }\r
-    }\r
-\r
-       public void windowActivated(WindowEvent e) {}\r
-       public void windowClosed(WindowEvent e) {}\r
-       public void windowDeactivated(WindowEvent e) {}\r
-       public void windowDeiconified(WindowEvent e) {}\r
-       public void windowIconified(WindowEvent e) {}\r
-       public void windowOpened(WindowEvent e) {}\r
-\r
-       public void windowClosing(WindowEvent e) {\r
-               this.shutDown();\r
-       }\r
-\r
-       private void shutDown() {\r
-               if (this.appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {\r
-                       int result = JOptionPane.showConfirmDialog(this, "There are still jobs being recorded. Are you sure you want to exit?", "Confirm close", JOptionPane.YES_NO_OPTION);\r
-                       if (result == JOptionPane.NO_OPTION) {\r
-                               return;\r
-                       }\r
-               }\r
-               saveTableStates(jobsTable);\r
-               saveTableStates(templatesTable);\r
-               saveTemplateTableContent();\r
-               this.appLayer.shutDown();\r
-               this.dispose();\r
-               System.exit(0);\r
-       }\r
-       \r
-       private void saveTemplateTableContent() {\r
-               File path = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, TEMPLATE_TABLE_CONTENT_FILENAME);\r
-               RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();\r
-               tableModel.saveTemplateListToFile(path);\r
-       }\r
-       \r
-       private Icon getIcon(String iconString) {\r
-               URL url = ClassLoader.getSystemResource("icons/" + iconString);\r
-               Icon i = new ImageIcon(url);\r
-               return i;\r
-       }\r
-\r
-}\r
+package com.nexuiz.demorecorder.ui.swinggui;
+
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.help.HelpBroker;
+import javax.help.HelpSet;
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.UIManager;
+import javax.swing.border.TitledBorder;
+
+import net.miginfocom.swing.MigLayout;
+
+import org.jdesktop.swingx.JXTable;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+import com.nexuiz.demorecorder.application.NDRPreferences;
+import com.nexuiz.demorecorder.application.DemoRecorderApplication.Preferences;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+import com.nexuiz.demorecorder.application.jobs.RecordJob.State;
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+import com.nexuiz.demorecorder.ui.DemoRecorderUI;
+import com.nexuiz.demorecorder.ui.swinggui.tablemodels.RecordJobTemplatesTableModel;
+import com.nexuiz.demorecorder.ui.swinggui.tablemodels.RecordJobsTableModel;
+import com.nexuiz.demorecorder.ui.swinggui.utils.ShowErrorDialogExceptionHandler;
+import com.nexuiz.demorecorder.ui.swinggui.utils.XProperties;
+import com.nexuiz.demorecorder.ui.swinggui.utils.XProperties.XTableState;
+
+public class SwingGUI extends JFrame implements WindowListener, DemoRecorderUI {
+       
+       private static final long serialVersionUID = -7287303462488231068L;
+       public static final String JOB_TABLE_PREFERENCES_FILENAME = "jobsTable.pref";
+       public static final String TEMPLATE_TABLE_PREFERENCES_FILENAME = "templatesTable.pref";
+       public static final String TEMPLATE_TABLE_CONTENT_FILENAME = "templates.dat";
+
+       private DemoRecorderApplication appLayer;
+       private PreferencesDialog preferencesDialog;
+       
+       private JXTable jobsTable = null;
+       private JPopupMenu jobsTablePopupMenu;
+       private ActionListener jobButtonActionListener = new JobButtonActionListener();
+       private MouseListener jobsTableMouseListener = new JobsTableMouseListener();
+       
+       private JXTable templatesTable = null;
+       private JPopupMenu templatesTablePopupMenu;
+       private ActionListener templateButtonActionListener = new TemplateButtonActionListener();
+       private MouseListener templatesTableMouseListener = new TemplatesTableMouseListener();
+       
+       private ActionListener recordButtonActionListener = new RecordButtonActionListener();
+       
+       private static final String LABEL_JOB_CREATE = "Create";
+       private static final String LABEL_JOB_CREATE_FROM_TEMPL = "Create from template";
+       private static final String LABEL_JOB_DELETE = "Delete";
+       private static final String LABEL_JOB_CLEAR = "Clear";
+       private static final String LABEL_JOB_EDIT = "Edit job";
+       private static final String LABEL_JOB_DUPLICATE = "Duplicate job";
+       private static final String LABEL_JOB_APPLYTEMPL = "Apply template";
+       private static final String LABEL_JOB_START = "Start job";
+       private static final String LABEL_JOB_SHOWERROR = "Show error message";
+       private static final String LABEL_JOB_RESET_STATE_WAITING = "Reset job status to 'waiting'";
+       private static final String LABEL_JOB_RESET_STATE_DONE = "Reset job status to 'done'";
+       
+       private static final String LABEL_TEMPL_CREATE = "Create";
+       private static final String LABEL_TEMPL_CREATE_FROM_JOB = "Create from job";
+       private static final String LABEL_TEMPL_DELETE = "Delete";
+       private static final String LABEL_TEMPL_CLEAR = "Clear";
+       private static final String LABEL_TEMPL_EDIT = "Edit template";
+       private static final String LABEL_TEMPL_DUPLICATE = "Duplicate template";
+       
+       private ActionListener menuButtonActionListener = new MenuButtonActionListener();
+       private JMenuItem fileLoadQueue = new JMenuItem("Load job queue", getIcon("fileopen.png"));
+       private JMenuItem fileSaveQueue = new JMenuItem("Save job queue", getIcon("filesave.png"));
+       private JMenuItem fileLoadTemplates = new JMenuItem("Load templates", getIcon("fileopen.png"));
+       private JMenuItem fileSaveTemplates = new JMenuItem("Save templates", getIcon("filesave.png"));
+       private JMenuItem filePreferences = new JMenuItem("Preferences", getIcon("advanced.png"));
+       private JMenuItem fileExit = new JMenuItem("Exit", getIcon("exit.png"));
+       private JMenuItem helpHelp = new JMenuItem("Show help", getIcon("help.png"));
+       private JMenuItem helpAbout = new JMenuItem("About", getIcon("info.png"));
+       private JFileChooser jobQueueSaveAsFC = new JFileChooser();
+       private JFileChooser templatesSaveAsFC = new JFileChooser();
+       
+       private JButton jobs_create = new JButton(LABEL_JOB_CREATE, getIcon("edit_add.png"));
+       private JButton jobs_createFromTempl = new JButton(LABEL_JOB_CREATE_FROM_TEMPL, getIcon("view_right_p.png"));
+       private JButton jobs_delete = new JButton(LABEL_JOB_DELETE, getIcon("editdelete.png"));
+       private JButton jobs_clear = new JButton(LABEL_JOB_CLEAR, getIcon("editclear.png"));
+       private JMenuItem jobs_contextmenu_edit = new JMenuItem(LABEL_JOB_EDIT, getIcon("edit.png"));
+       private JMenuItem jobs_contextmenu_duplicate = new JMenuItem(LABEL_JOB_DUPLICATE, getIcon("editcopy.png"));
+       private JMenuItem jobs_contextmenu_applytempl = new JMenuItem(LABEL_JOB_APPLYTEMPL, getIcon("editpaste.png"));
+       private JMenuItem jobs_contextmenu_delete = new JMenuItem(LABEL_JOB_DELETE, getIcon("editdelete.png"));
+       private JMenuItem jobs_contextmenu_start = new JMenuItem(LABEL_JOB_START, getIcon("player_play.png"));
+       private JMenuItem jobs_contextmenu_showerror = new JMenuItem(LABEL_JOB_SHOWERROR, getIcon("status_unknown.png"));
+       private JMenuItem jobs_contextmenu_resetstate_waiting = new JMenuItem(LABEL_JOB_RESET_STATE_WAITING, getIcon("quick_restart.png"));
+       private JMenuItem jobs_contextmenu_resetstate_done = new JMenuItem(LABEL_JOB_RESET_STATE_DONE, getIcon("quick_restart_blue.png"));
+       private List<JMenuItem> jobs_contextmenu_runPluginMenuItems = new ArrayList<JMenuItem>();
+       
+       private JButton templ_create = new JButton(LABEL_TEMPL_CREATE, getIcon("edit_add.png"));
+       private JButton templ_createFromJob = new JButton(LABEL_TEMPL_CREATE_FROM_JOB, getIcon("view_right_p.png"));
+       private JButton templ_delete = new JButton(LABEL_TEMPL_DELETE, getIcon("editdelete.png"));
+       private JButton templ_clear = new JButton(LABEL_TEMPL_CLEAR, getIcon("editclear.png"));
+       private JMenuItem templ_contextmenu_edit = new JMenuItem(LABEL_TEMPL_EDIT, getIcon("edit.png"));
+       private JMenuItem templ_contextmenu_duplicate = new JMenuItem(LABEL_TEMPL_DUPLICATE, getIcon("editcopy.png"));
+       private JMenuItem templ_contextmenu_delete = new JMenuItem(LABEL_TEMPL_DELETE, getIcon("editdelete.png"));
+       
+       private static final String PROCESSING_START = "Start processing";
+       private static final String PROCESSING_STOP_NOW = "Stop processing";
+       private static final String PROCESSING_STOP_LATER = "Processing will stop after current job finished";
+       private JButton processing_start = new JButton(PROCESSING_START, getIcon("player_play.png"));
+       private JButton processing_stop = new JButton(PROCESSING_STOP_NOW, getIcon("player_pause.png"));
+       
+       private StatusBar statusBar = new StatusBar();
+       
+       private static HelpBroker mainHelpBroker = null;
+       private static final String mainHelpSetName = "help/DemoRecorderHelp.hs";
+
+       public SwingGUI(DemoRecorderApplication appLayer) {
+               super("Nexuiz Demo Recorder v0.3");
+               addWindowListener(this);
+
+               this.appLayer = appLayer;
+
+               this.setupLayout();
+               this.setupHelp();
+               this.preferencesDialog = new PreferencesDialog(this, appLayer);
+
+               setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+               // Display the window.
+               pack();
+               setVisible(true);
+               //now that we have the GUI we can set the parent window for the error dialog
+               ShowErrorDialogExceptionHandler.setParentWindow(this);
+       }
+
+       private void setupHelp() {
+               if (mainHelpBroker == null){
+                       HelpSet mainHelpSet = null;
+
+                       try {
+                               URL hsURL = HelpSet.findHelpSet(null, mainHelpSetName);
+                               mainHelpSet = new HelpSet(null, hsURL);
+                       } catch (Exception e) {
+                               DemoRecorderUtils.showNonCriticalErrorDialog("Could not properly create the help", e, true);
+                       }
+
+                       if (mainHelpSet != null)
+                               mainHelpBroker = mainHelpSet.createHelpBroker();
+                       }
+       }
+
+       private void setupLayout() {
+               setLayout(new MigLayout("wrap 1,insets 10", "[400:700:,grow,fill]",
+                               "[grow,fill][grow,fill][][]"));
+               Container contentPane = getContentPane();
+               setJMenuBar(this.buildMenu());
+
+               this.setupTemplatePanel();
+               this.setupJobPanel();
+               this.setupRecordPanel();
+
+               contentPane.add(statusBar, "south,height 23::");
+       }
+
+       private void setupTemplatePanel() {
+               JPanel templatePanel = new JPanel(new MigLayout("", "[500:500:,grow,fill][170!,fill,grow]", "[grow,fill]"));
+               TitledBorder templatePanelTitle = BorderFactory.createTitledBorder("Templates");
+               templatePanel.setBorder(templatePanelTitle);
+               getContentPane().add(templatePanel);
+               
+               this.setupTemplatesTable();
+               this.loadTableStates(this.templatesTable);
+               JScrollPane templateScrollPane = new JScrollPane(templatesTable);
+               templatePanel.add(templateScrollPane);
+               
+               this.templ_create.addActionListener(this.templateButtonActionListener);
+               this.templ_createFromJob.addActionListener(this.templateButtonActionListener);
+               this.templ_delete.addActionListener(this.templateButtonActionListener);
+               this.templ_clear.addActionListener(this.templateButtonActionListener);
+               
+               this.templ_contextmenu_edit.addActionListener(this.templateButtonActionListener);
+               this.templ_contextmenu_duplicate.addActionListener(this.templateButtonActionListener);
+               this.templ_contextmenu_delete.addActionListener(this.templateButtonActionListener);
+               
+               this.configureTableButtons();
+               
+               JPanel templateControlButtonPanel = new JPanel(new MigLayout("wrap 1", "fill,grow"));
+               templateControlButtonPanel.add(this.templ_create);
+               templateControlButtonPanel.add(this.templ_createFromJob);
+               templateControlButtonPanel.add(this.templ_delete);
+               templateControlButtonPanel.add(this.templ_clear);
+               templatePanel.add(templateControlButtonPanel);
+       }
+
+       private void setupJobPanel() {
+               JPanel jobPanel = new JPanel(new MigLayout("", "[500:500:,grow,fill][170!,fill,grow]", "[grow,fill]"));
+               TitledBorder jobPanelTitle = BorderFactory.createTitledBorder("Jobs");
+               jobPanel.setBorder(jobPanelTitle);
+               getContentPane().add(jobPanel);
+
+               this.setupJobsTable();
+               this.loadTableStates(this.jobsTable);
+               
+               JScrollPane jobScrollPane = new JScrollPane(jobsTable);
+               jobPanel.add(jobScrollPane);
+               
+               this.jobs_create.addActionListener(this.jobButtonActionListener);
+               this.jobs_createFromTempl.addActionListener(this.jobButtonActionListener);
+               this.jobs_delete.addActionListener(this.jobButtonActionListener);
+               this.jobs_clear.addActionListener(this.jobButtonActionListener);
+               
+               this.jobs_contextmenu_edit.addActionListener(this.jobButtonActionListener);
+               this.jobs_contextmenu_duplicate.addActionListener(this.jobButtonActionListener);
+               this.jobs_contextmenu_applytempl.addActionListener(this.jobButtonActionListener);
+               this.jobs_contextmenu_delete.addActionListener(this.jobButtonActionListener);
+               this.jobs_contextmenu_start.addActionListener(this.jobButtonActionListener);
+               this.jobs_contextmenu_showerror.addActionListener(this.jobButtonActionListener);
+               this.jobs_contextmenu_resetstate_waiting.addActionListener(this.jobButtonActionListener);
+               this.jobs_contextmenu_resetstate_done.addActionListener(this.jobButtonActionListener);
+               
+               //initialize button states
+               configureTableButtons();
+               
+               JPanel jobControlButtonPanel = new JPanel(new MigLayout("wrap 1", "fill,grow"));
+               jobControlButtonPanel.add(this.jobs_create);
+               jobControlButtonPanel.add(this.jobs_createFromTempl);
+               jobControlButtonPanel.add(this.jobs_delete);
+               jobControlButtonPanel.add(this.jobs_clear);
+               jobPanel.add(jobControlButtonPanel);
+       }
+       
+       private void setupJobsTable() {
+               RecordJobsTableModel tableModel = new RecordJobsTableModel(this.appLayer);
+               jobsTable = new JXTable(tableModel);
+               jobsTable.setColumnControlVisible(true);
+               jobsTable.setPreferredScrollableViewportSize(new Dimension(400, 100));
+               jobsTable.addMouseListener(this.jobsTableMouseListener);
+       }
+       
+       private void setupTemplatesTable() {
+               RecordJobTemplatesTableModel tableModel = new RecordJobTemplatesTableModel();
+               templatesTable = new JXTable(tableModel);
+               templatesTable.setColumnControlVisible(true);
+               templatesTable.setPreferredScrollableViewportSize(new Dimension(400, 100));
+               templatesTable.addMouseListener(this.templatesTableMouseListener);
+       }
+
+       private void setupRecordPanel() {
+               JPanel recButtonPanel = new JPanel(new MigLayout());
+               recButtonPanel.add(processing_start);
+               recButtonPanel.add(processing_stop);
+               processing_stop.setEnabled(false);
+               processing_start.addActionListener(recordButtonActionListener);
+               processing_stop.addActionListener(recordButtonActionListener);
+               getContentPane().add(recButtonPanel);
+       }
+
+       public static void setSystemLAF() {
+               try {
+                       // Set System L&F
+                       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+               } catch (Exception e) {
+               }
+       }
+       
+       public void RecordJobPropertiesChange(RecordJob job) {
+               RecordJobsTableModel jobsTableModel = (RecordJobsTableModel) this.jobsTable.getModel();
+               List<RecordJob> recordJobs = jobsTableModel.getRecordJobs();
+               int jobIndex = recordJobs.indexOf(job);
+               if (jobIndex == -1) {
+                       //new job
+                       recordJobs.add(job);
+                       //add job at the end of the table:
+                       int position = jobsTableModel.getRowCount() - 1;
+                       jobsTableModel.fireTableRowsInserted(position, position);
+               } else {
+                       //job already existed
+                       jobIndex = this.jobsTable.convertRowIndexToView(jobIndex); //convert due to possible view sorting
+                       jobsTableModel.fireTableRowsUpdated(jobIndex, jobIndex);
+               }
+       }
+
+       public void recordingFinished() {
+               JOptionPane.showMessageDialog(SwingGUI.this, "Finished recording all jobs", "Recording done", JOptionPane.INFORMATION_MESSAGE);
+               statusBar.showState(false);
+               processing_start.setEnabled(true);
+               processing_stop.setEnabled(false);
+               processing_stop.setText(PROCESSING_STOP_NOW);
+       }
+
+       private JMenuBar buildMenu() {
+               JMenuBar menuBar = new JMenuBar();
+
+               JMenu fileMenu = new JMenu("File");
+               fileMenu.add(fileLoadQueue);
+               fileMenu.add(fileSaveQueue);
+               fileMenu.add(fileLoadTemplates);
+               fileMenu.add(fileSaveTemplates);
+               fileMenu.add(filePreferences);
+               fileMenu.add(fileExit);
+               menuBar.add(fileMenu);
+               
+               fileLoadQueue.addActionListener(menuButtonActionListener);
+               fileSaveQueue.addActionListener(menuButtonActionListener);
+               fileLoadTemplates.addActionListener(menuButtonActionListener);
+               fileSaveTemplates.addActionListener(menuButtonActionListener);
+               filePreferences.addActionListener(menuButtonActionListener);
+               fileExit.addActionListener(menuButtonActionListener);
+
+               JMenu helpMenu = new JMenu("Help");
+               helpMenu.add(helpHelp);
+               helpMenu.add(helpAbout);
+               menuBar.add(helpMenu);
+               
+               helpHelp.addActionListener(menuButtonActionListener);
+               helpAbout.addActionListener(menuButtonActionListener);
+               
+               this.setupEncoderPluginButtons();
+               
+               this.jobsTablePopupMenu = new JPopupMenu();
+               this.jobsTablePopupMenu.add(jobs_contextmenu_edit);
+               this.jobsTablePopupMenu.add(jobs_contextmenu_duplicate);
+               this.jobsTablePopupMenu.add(jobs_contextmenu_applytempl);
+               this.jobsTablePopupMenu.add(jobs_contextmenu_delete);
+               this.jobsTablePopupMenu.add(jobs_contextmenu_start);
+               //add JMenus for plugins
+               for (JMenuItem menuItem : jobs_contextmenu_runPluginMenuItems) {
+                       this.jobsTablePopupMenu.add(menuItem);
+               }
+               this.jobsTablePopupMenu.add(jobs_contextmenu_showerror);
+               this.jobsTablePopupMenu.add(jobs_contextmenu_resetstate_waiting);
+               this.jobsTablePopupMenu.add(jobs_contextmenu_resetstate_done);
+               
+               
+               
+               
+               this.templatesTablePopupMenu = new JPopupMenu();
+               this.templatesTablePopupMenu.add(templ_contextmenu_edit);
+               this.templatesTablePopupMenu.add(templ_contextmenu_duplicate);
+               this.templatesTablePopupMenu.add(templ_contextmenu_delete);
+
+               return menuBar;
+       }
+       
+       private void setupEncoderPluginButtons() {
+               for (EncoderPlugin plugin : appLayer.getEncoderPlugins()) {
+                       JMenuItem pluginMenuItem = new JMenuItem("Just run " + plugin.getName() + " plugin", getIcon("package.png"));
+                       pluginMenuItem.addActionListener(jobButtonActionListener);
+                       this.jobs_contextmenu_runPluginMenuItems.add(pluginMenuItem);
+               }
+       }
+
+       private void saveTableStates(JXTable table) {
+               String fileName;
+               if (table == jobsTable) {
+                       fileName = JOB_TABLE_PREFERENCES_FILENAME;
+               } else {
+                       fileName = TEMPLATE_TABLE_PREFERENCES_FILENAME;
+               }
+               String exceptionMessage = "An error occurred while trying to save the table state file " + fileName;
+               
+               XProperties.XTableProperty t = new XProperties.XTableProperty();
+               XTableState tableState;
+               try {
+                       tableState = (XTableState) t.getSessionState(table);
+               } catch (Exception e) { //most likely ClassCastException
+                       DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);
+                       return;
+               }
+               
+               File tableStateFile = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, fileName);
+               DemoRecorderUtils.attemptFileCreation(tableStateFile);
+               
+               try {
+                       FileOutputStream fout = new FileOutputStream(tableStateFile);
+                       ObjectOutputStream oos = new ObjectOutputStream(fout);
+                       oos.writeObject(tableState);
+                       oos.close();
+               } catch (Exception e) {
+                       DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);
+               }
+       }
+       
+       private void loadTableStates(JXTable table) {
+               String fileName;
+               if (table == jobsTable) {
+                       fileName = JOB_TABLE_PREFERENCES_FILENAME;
+               } else {
+                       fileName = TEMPLATE_TABLE_PREFERENCES_FILENAME;
+               }
+               
+               XProperties.XTableProperty t = new XProperties.XTableProperty();
+               
+               File tableStateFile = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, fileName);
+               
+               XTableState tableState;
+               
+               try {
+                       FileInputStream fin = new FileInputStream(tableStateFile);
+                       ObjectInputStream ois = new ObjectInputStream(fin);
+                       tableState = (XTableState) ois.readObject();
+                       t.setSessionState(table, tableState);
+               } catch (Exception e) {
+                        //manually hide columns
+                       if (table == jobsTable) {
+                               //re-create table to be sure
+                               this.setupJobsTable();
+                               //manually hide some columns
+                               jobsTable.getColumnExt(RecordJobsTableModel.EXECUTE_AFTER_CAP).setVisible(false);
+                               jobsTable.getColumnExt(RecordJobsTableModel.EXECUTE_BEFORE_CAP).setVisible(false);
+                               jobsTable.getColumnExt(RecordJobsTableModel.VIDEO_DESTINATION_PATH).setVisible(false);
+                               jobsTable.getColumnExt(RecordJobsTableModel.DPVIDEO_PATH).setVisible(false);
+                               jobsTable.getColumnExt(RecordJobsTableModel.RELATIVE_DEMO_PATH).setVisible(false);
+                               jobsTable.getColumnExt(RecordJobsTableModel.ENGINE_PARAMETERS).setVisible(false);
+                               jobsTable.getColumnExt(RecordJobsTableModel.ENGINE_PATH).setVisible(false);
+                       } else {
+                               //re-create table to be sure
+                               this.setupTemplatesTable();
+                               //manually hide some columns
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.EXECUTE_AFTER_CAP).setVisible(false);
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.EXECUTE_BEFORE_CAP).setVisible(false);
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.VIDEO_DESTINATION_PATH).setVisible(false);
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.DPVIDEO_PATH).setVisible(false);
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.RELATIVE_DEMO_PATH).setVisible(false);
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.DEMO_FILE_PATH).setVisible(false);
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.ENGINE_PARAMETERS).setVisible(false);
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.ENGINE_PATH).setVisible(false);
+                               templatesTable.getColumnExt(RecordJobTemplatesTableModel.JOB_NAME).setVisible(false);
+                       }
+               }
+       }
+       
+       private class MenuButtonActionListener implements ActionListener {
+
+               public void actionPerformed(ActionEvent e) {
+                       if (e.getSource() == fileLoadQueue) {
+                               int result = jobQueueSaveAsFC.showOpenDialog(SwingGUI.this);
+                               if (result == JFileChooser.APPROVE_OPTION) {
+                                       File selectedFile = jobQueueSaveAsFC.getSelectedFile();
+                                       if (selectedFile.isFile()) {
+                                               RecordJobsTableModel tableModel = (RecordJobsTableModel) jobsTable.getModel();
+                                               tableModel.loadNewJobQueue(SwingGUI.this, selectedFile, jobsTable);
+                                               configureTableButtons();
+                                       }
+                               }
+                               
+                       } else if (e.getSource() == fileSaveQueue) {
+                               int result = jobQueueSaveAsFC.showSaveDialog(SwingGUI.this);
+                               if (result == JFileChooser.APPROVE_OPTION) {
+                                       File selectedFile = jobQueueSaveAsFC.getSelectedFile();
+                                       if (!DemoRecorderUtils.getFileExtension(selectedFile).equals("queue")) {
+                                               //if file is not a .queue file, make it one
+                                               selectedFile = new File(selectedFile.getAbsoluteFile() + ".queue");
+                                       }
+                                       if (selectedFile.exists()) {
+                                               int confirm = JOptionPane.showConfirmDialog(SwingGUI.this, "File already exists. Are you sure you want to overwrite it?", "Confirm overwrite", JOptionPane.YES_NO_OPTION);
+                                               if (confirm == JOptionPane.NO_OPTION) {
+                                                       return;
+                                               }
+                                       }
+                                       appLayer.saveJobQueue(selectedFile);
+                               }
+                       } else if (e.getSource() == fileLoadTemplates) {
+                               int result = templatesSaveAsFC.showOpenDialog(SwingGUI.this);
+                               if (result == JFileChooser.APPROVE_OPTION) {
+                                       File selectedFile = templatesSaveAsFC.getSelectedFile();
+                                       if (selectedFile.isFile()) {
+                                               RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();
+                                               tableModel.loadNewTemplateList(SwingGUI.this, selectedFile, templatesTable);
+                                               configureTableButtons();
+                                       }
+                               }
+                       } else if (e.getSource() == fileSaveTemplates) {
+                               int result = templatesSaveAsFC.showSaveDialog(SwingGUI.this);
+                               if (result == JFileChooser.APPROVE_OPTION) {
+                                       File selectedFile = templatesSaveAsFC.getSelectedFile();
+                                       if (!DemoRecorderUtils.getFileExtension(selectedFile).equals("templ")) {
+                                               selectedFile = new File(selectedFile.getAbsoluteFile() + ".templ");
+                                       }
+                                       if (selectedFile.exists()) {
+                                               int confirm = JOptionPane.showConfirmDialog(SwingGUI.this, "File already exists. Are you sure you want to overwrite it?", "Confirm overwrite", JOptionPane.YES_NO_OPTION);
+                                               if (confirm == JOptionPane.NO_OPTION) {
+                                                       return;
+                                               }
+                                       }
+                                       RecordJobTemplatesTableModel model = (RecordJobTemplatesTableModel) templatesTable.getModel();
+                                       model.saveTemplateListToFile(selectedFile);
+                               }
+                       } else if (e.getSource() == filePreferences) {
+                               preferencesDialog.showDialog();
+                       } else if (e.getSource() == fileExit) {
+                               shutDown();
+                       } else if (e.getSource() == helpHelp) {
+                               if (mainHelpBroker != null) {
+                                       mainHelpBroker.setDisplayed(true);
+                               }
+                       } else if (e.getSource() == helpAbout) {
+                               showAboutBox();
+                       }
+               }
+               
+       }
+
+       /**
+        * Listens to the clicks on buttons that are in the job panel (next to the jobs table)
+        * or its context menu.
+        */
+       private class JobButtonActionListener implements ActionListener {
+
+               public void actionPerformed(ActionEvent e) {
+                       List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);
+                       List<RecordJob> selectedTemplates = getSelectedRecordJobs(templatesTable);
+                       if (e.getSource() == jobs_create) {
+                               JobDialog jobDialog = new JobDialog(SwingGUI.this, appLayer);
+                               jobDialog.showDialog();
+                               configureTableButtons();
+                       }
+                       else if (e.getSource() == jobs_createFromTempl) {
+                               if (selectedTemplates.size() != 1) {
+                                       return;
+                               }
+                               RecordJobTemplate template = (RecordJobTemplate) selectedTemplates.get(0);
+//                             JobDialog jobDialog = new JobDialog(SwingGUI.this, template, appLayer);
+                               JobDialog jobDialog = new JobDialog(SwingGUI.this, template, appLayer, JobDialog.CREATE_JOB_FROM_TEMPLATE);
+                               jobDialog.showDialog();
+                               configureTableButtons();
+                       }
+                       else if (e.getSource() == jobs_delete || e.getSource() == jobs_contextmenu_delete) {
+                               int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to delete the selected job(s)?", "Confirm delete", JOptionPane.YES_NO_OPTION);
+                               if (result == JOptionPane.YES_OPTION) {
+                                       deleteSelectedJobs(false);
+                                       configureTableButtons();
+                               }
+                       }
+                       else if (e.getSource() == jobs_clear) {
+                               int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to clear the job list?", "Confirm clear", JOptionPane.YES_NO_OPTION);
+                               if (result == JOptionPane.YES_OPTION) {
+                                       deleteSelectedJobs(true);
+                                       configureTableButtons();
+                               }
+                       } else if (e.getSource() == jobs_contextmenu_edit) {
+                               if (selectedJobs.size() == 1) {
+                                       RecordJob selectedJob = selectedJobs.get(0);
+                                       JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedJob, appLayer);
+                                       jobDialog.showDialog();
+                                       configureTableButtons();
+                               }
+                       } else if (e.getSource() == jobs_contextmenu_showerror) {
+                               if (selectedJobs.size() == 1) {
+                                       RecordJob selectedJob = selectedJobs.get(0);
+                                       DemoRecorderUtils.showNonCriticalErrorDialog(selectedJob.getLastException());
+                               }
+                       } else if (e.getSource() == jobs_contextmenu_resetstate_waiting) {
+                               for (RecordJob job : selectedJobs) {
+                                       job.setState(RecordJob.State.WAITING);
+                               }
+                       } else if (e.getSource() == jobs_contextmenu_resetstate_done) {
+                               for (RecordJob job : selectedJobs) {
+                                       if (job.getState() == State.ERROR_PLUGIN) {
+                                               job.setState(RecordJob.State.DONE);
+                                       }
+                               }
+                       } else if (e.getSource() == jobs_contextmenu_start) {
+                               appLayer.recordSelectedJobs(selectedJobs);
+                               if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {
+                                       processing_start.setEnabled(false);
+                                       processing_stop.setEnabled(true);
+                                       statusBar.showState(true);
+                               }
+                       } else if (e.getSource() == jobs_contextmenu_duplicate) {
+                               if (selectedJobs.size() > 0) {
+                                       this.duplicateRecordJobs(selectedJobs);
+                                       //select all new duplicates in the table automatically
+                                       jobsTable.setRowSelectionInterval(jobsTable.getRowCount() - selectedJobs.size(), jobsTable.getRowCount() - 1);
+                                       configureTableButtons();
+                               }
+                       } else if (e.getSource() == jobs_contextmenu_applytempl) {
+                               if (selectedTemplates.size() == 1 && selectedJobs.size() > 0) {
+                                       RecordJobTemplate template = (RecordJobTemplate) selectedTemplates.get(0);
+                                       ApplyTemplateDialog applyDialog = new ApplyTemplateDialog(SwingGUI.this, template, selectedJobs);
+                                       applyDialog.showDialog();
+                               }
+                       } else if (jobs_contextmenu_runPluginMenuItems.contains(e.getSource())) {
+                               int index = jobs_contextmenu_runPluginMenuItems.indexOf(e.getSource());
+                               EncoderPlugin selectedPlugin = appLayer.getEncoderPlugins().get(index);
+                               
+                               appLayer.executePluginForSelectedJobs(selectedPlugin, selectedJobs);
+                               if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {
+                                       processing_start.setEnabled(false);
+                                       processing_stop.setEnabled(true);
+                                       statusBar.showState(true);
+                               }
+                       }
+               }
+               
+               private void duplicateRecordJobs(List<RecordJob> jobs) {
+                       String nameSuffix = appLayer.getPreferences().getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.JOB_NAME_APPEND_DUPLICATE);
+                       for (RecordJob job : jobs) {
+                               RecordJob newJob = appLayer.createRecordJob(
+                                       job.getJobName() + nameSuffix,
+                                       job.getEnginePath(),
+                                       job.getEngineParameters(),
+                                       job.getDemoFile(),
+                                       job.getRelativeDemoPath(),
+                                       job.getDpVideoPath(),
+                                       job.getVideoDestination(),
+                                       job.getExecuteBeforeCap(),
+                                       job.getExecuteAfterCap(),
+                                       job.getStartSecond(),
+                                       job.getEndSecond()
+                               );
+                               newJob.setEncoderPluginSettings(job.getEncoderPluginSettings());
+                       }
+               }
+               
+       }
+       
+       private class TemplateButtonActionListener implements ActionListener {
+               public void actionPerformed(ActionEvent e) {
+                       if (e.getSource() == templ_create) {
+                               RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();
+                               JobDialog jobDialog = new JobDialog(SwingGUI.this, tableModel, templatesTable, appLayer);
+                               jobDialog.showDialog();
+                               configureTableButtons();
+                       }
+                       else if (e.getSource() == templ_createFromJob) {
+                               this.createTemplateFromJob();
+                               configureTableButtons();
+                       }
+                       else if (e.getSource() == templ_delete || e.getSource() == templ_contextmenu_delete) {
+                               int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to delete the selected template(s)?", "Confirm delete", JOptionPane.YES_NO_OPTION);
+                               if (result == JOptionPane.YES_OPTION) {
+                                       deleteSelectedTemplates(false);
+                               }
+                               configureTableButtons();
+                       }
+                       else if (e.getSource() == templ_clear) {
+                               int result = JOptionPane.showConfirmDialog(SwingGUI.this, "Are you sure you want to clear the template list?", "Confirm clear", JOptionPane.YES_NO_OPTION);
+                               if (result == JOptionPane.YES_OPTION) {
+                                       deleteSelectedTemplates(true);
+                               }
+                               configureTableButtons();
+                       }
+                       else if (e.getSource() == templ_contextmenu_edit) {
+                               List<RecordJob> selectedTemplates = getSelectedRecordJobs(templatesTable);
+                               if (selectedTemplates.size() == 1) {
+                                       RecordJobTemplate selectedTemplate = (RecordJobTemplate) selectedTemplates.get(0);
+                                       JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedTemplate, appLayer, JobDialog.EDIT_TEMPLATE);
+                                       jobDialog.showDialog();
+                                       configureTableButtons();
+                               }
+                       }
+                       else if (e.getSource() == templ_contextmenu_duplicate) {
+                               List<RecordJob> selectedTemplates = getSelectedRecordJobs(templatesTable);
+                               if (selectedTemplates.size() > 0) {
+                                       this.duplicateTemplates(selectedTemplates);
+                                       //select all new duplicates in the table automatically
+                                       templatesTable.setRowSelectionInterval(templatesTable.getRowCount() - selectedTemplates.size(), templatesTable.getRowCount() - 1);
+                                       configureTableButtons();
+                               }
+                       }
+               }
+               
+               private void createTemplateFromJob() {
+                       List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);
+                       if (selectedJobs.size() == 1) {
+                               RecordJob job = selectedJobs.get(0);
+                               RecordJobTemplate templ = new RecordJobTemplate(
+                                       "Generated from job",
+                                       "Generated from job",
+                                       job.getJobName(),
+                                       job.getEnginePath(),
+                                       job.getEngineParameters(),
+                                       job.getDemoFile().getParentFile(),
+                                       job.getRelativeDemoPath(),
+                                       job.getDpVideoPath(),
+                                       job.getVideoDestination().getParentFile(),
+                                       job.getExecuteBeforeCap(),
+                                       job.getExecuteAfterCap()
+                               );
+                               templ.setEncoderPluginSettings(job.getEncoderPluginSettings());
+                               
+                               RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();
+                               tableModel.addRecordJobTemplate(templ);
+                       }
+               }
+               
+               private void duplicateTemplates(List<RecordJob> selectedTemplates) {
+                       for (RecordJob job : selectedTemplates) {
+                               RecordJobTemplate template = (RecordJobTemplate) job;
+                               RecordJobTemplate templ = new RecordJobTemplate(
+                                       template.getName(),
+                                       template.getSummary(),
+                                       template.getJobName(),
+                                       template.getEnginePath(),
+                                       template.getEngineParameters(),
+                                       template.getDemoFile(),
+                                       template.getRelativeDemoPath(),
+                                       template.getDpVideoPath(),
+                                       template.getVideoDestination(),
+                                       template.getExecuteBeforeCap(),
+                                       template.getExecuteAfterCap()
+                               );
+                               templ.setEncoderPluginSettings(template.getEncoderPluginSettings());
+                               
+                               RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();
+                               tableModel.addRecordJobTemplate(templ);
+                       }
+               }
+       }
+       
+       private class RecordButtonActionListener implements ActionListener {
+
+               public void actionPerformed(ActionEvent e) {
+                       if (e.getSource() == processing_start) {
+                               appLayer.startRecording();
+                               if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {
+                                       processing_start.setEnabled(false);
+                                       processing_stop.setEnabled(true);
+                                       statusBar.showState(true);
+                               }
+                       } else if (e.getSource() == processing_stop) {
+                               if (appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {
+                                       appLayer.stopRecording();
+                                       processing_stop.setEnabled(false);
+                                       processing_stop.setText(PROCESSING_STOP_LATER);
+                               }
+                       }
+               }
+       }
+       
+       private void deleteSelectedJobs(boolean deleteAllJobs) {
+               RecordJobsTableModel tableModel = (RecordJobsTableModel) jobsTable.getModel();
+               if (deleteAllJobs) {
+                       int rowCount = jobsTable.getRowCount();
+                       for (int i = rowCount - 1; i >= 0; i--) {
+                               int modelRowIndex = jobsTable.convertRowIndexToModel(i);
+                               tableModel.deleteRecordJob(modelRowIndex, i);
+                       }
+               } else {
+                       int[] selectedRows = jobsTable.getSelectedRows();
+                       for (int i = selectedRows.length - 1; i >= 0; i--) {
+                               int modelRowIndex = jobsTable.convertRowIndexToModel(selectedRows[i]);
+                               tableModel.deleteRecordJob(modelRowIndex, selectedRows[i]);
+                       }
+               }
+       }
+       
+       private void deleteSelectedTemplates(boolean deleteAllTemplates) {
+               RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();
+               if (deleteAllTemplates) {
+                       int rowCount = templatesTable.getRowCount();
+                       for (int i = rowCount - 1; i >= 0; i--) {
+                               int modelRowIndex = templatesTable.convertRowIndexToModel(i);
+                               tableModel.deleteRecordJobTemplate(modelRowIndex, i);
+                       }
+               } else {
+                       int[] selectedRows = templatesTable.getSelectedRows();
+                       for (int i = selectedRows.length - 1; i >= 0; i--) {
+                               int modelRowIndex = templatesTable.convertRowIndexToModel(selectedRows[i]);
+                               tableModel.deleteRecordJobTemplate(modelRowIndex, selectedRows[i]);
+                       }
+               }
+               //update the button state of buttons dealing with jobs
+               this.configureTableButtons();
+       }
+       
+       /**
+        * Iterates through all RecordJob objects (or just the selected ones) and returns true
+        * if at least one of them has one or more has the given state(s).
+        * @param state
+        * @param justSelectedJobs
+        * @return
+        */
+       private boolean checkJobStates(RecordJob.State[] state, boolean justSelectedJobs) {
+               boolean foundState = false;
+               List<RecordJob> jobsToLookAt = null;
+               if (!justSelectedJobs) {
+                       jobsToLookAt = this.appLayer.getRecordJobs();
+               } else {
+                       jobsToLookAt = getSelectedRecordJobs(jobsTable);
+               }
+               
+               for (RecordJob currentJob : jobsToLookAt) {
+                       for (int i = 0; i < state.length; i++) {
+                               if (currentJob.getState() == state[i]) {
+                                       foundState = true;
+                                       break;
+                               }
+                       }
+               }
+               return foundState;
+       }
+       
+       /**
+        * Returns the list of selected RecordJobs or RecordJobTemplates.
+        * @param table jobsTable or templatesTable
+        * @return list of selected RecordJobs or RecordJobTemplates
+        */
+       private List<RecordJob> getSelectedRecordJobs(JXTable table) {
+               List<RecordJob> list = new ArrayList<RecordJob>();
+               if (table.getSelectedRowCount() > 0) {
+                       int[] selectedRows = table.getSelectedRows();
+                       for (int i = 0; i < selectedRows.length; i++) {
+                               int modelRowIndex = table.convertRowIndexToModel(selectedRows[i]);
+                               if (table == jobsTable) {
+                                       RecordJobsTableModel tableModel = (RecordJobsTableModel) table.getModel();
+                                       RecordJob job = tableModel.getRecordJob(modelRowIndex);
+                                       if (job != null) {
+                                               list.add(job);
+                                       }
+                               } else {
+                                       RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) table.getModel();
+                                       RecordJobTemplate template = tableModel.getRecordJobTemplate(modelRowIndex);
+                                       if (template != null) {
+                                               list.add(template);
+                                       }
+                               }
+                       }
+               }
+               
+               return list;
+       }
+       
+       private void configureTableButtons() {
+               if (jobsTable != null) {
+                       if (jobsTable.getRowCount() == 0) {
+                               jobs_clear.setEnabled(false);
+                               jobs_delete.setEnabled(false);
+                       } else {
+                               jobs_clear.setEnabled(true);
+                               jobs_delete.setEnabled(true);
+                               if (jobsTable.getSelectedRowCount() == 0) {
+                                       jobs_delete.setEnabled(false);
+                               } else {
+                                       //go through all elements and check for attributes PROCESSING
+                                       RecordJob.State[] lookForState = {RecordJob.State.PROCESSING};
+                                       boolean foundState = checkJobStates(lookForState, false);
+                                       if (foundState) {
+                                               //we have to disable the clear and delete button
+                                               jobs_delete.setEnabled(false);
+                                       }
+                               }
+                       }
+                       if (templatesTable.getSelectedRowCount() == 1) {
+                               jobs_createFromTempl.setEnabled(true);
+                       } else {
+                               jobs_createFromTempl.setEnabled(false);
+                       }
+               }
+               
+               if (templatesTable != null) {
+                       templ_createFromJob.setEnabled(false);
+                       templ_delete.setEnabled(false);
+                       templ_clear.setEnabled(false);
+                       
+                       if (jobsTable != null && jobsTable.getSelectedRowCount() == 1) {
+                               templ_createFromJob.setEnabled(true);
+                       }
+                       
+                       if (templatesTable.getSelectedRowCount() > 0) {
+                               templ_delete.setEnabled(true);
+                       }
+                       
+                       if (templatesTable.getRowCount() > 0) {
+                               templ_clear.setEnabled(true);
+                       }
+               }
+       }
+       
+       private class JobsTableMouseListener implements MouseListener {
+
+               public void mouseClicked(MouseEvent e) {
+                       if (e != null && e.getClickCount() == 2) {
+                               List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);
+                               if (selectedJobs.size() == 1) {
+                                       RecordJob selectedJob = selectedJobs.get(0);
+                                       if (selectedJob.getState() != RecordJob.State.PROCESSING) {
+                                               JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedJob, appLayer);
+                                               jobDialog.showDialog();
+                                       }
+                               }
+                       } else {
+                               configureTableButtons();
+                       }
+               }
+
+               public void mouseEntered(MouseEvent e) {}
+
+               public void mouseExited(MouseEvent e) {}
+
+               public void mousePressed(MouseEvent e) {
+                       this.showPopupMenu(e);
+               }
+
+               public void mouseReleased(MouseEvent e) {
+                       this.showPopupMenu(e);
+               }
+               
+               private void showPopupMenu(MouseEvent e) {
+                       if (e.isPopupTrigger()) {
+                               JTable table = (JTable)(e.getSource());
+                               Point p = e.getPoint();
+                               int row = table.rowAtPoint(p);
+                               int[] selectedRows = table.getSelectedRows();
+                               //figure out whether we have to reselect the current row under the pointer,
+                               //which is only the case if the already selected rows don't include the one under
+                               //the pointer yet
+                               boolean reSelect = true;
+                               for (int i = 0; i < selectedRows.length; i++) {
+                                       if (row == selectedRows[i]) {
+                                               reSelect = false;
+                                               break;
+                                       }
+                               }
+                               
+                               if (row != -1 && reSelect) {
+                                       table.setRowSelectionInterval(row, row);
+                               }
+                               
+                               this.configurePopupMenu();
+                               configureTableButtons();
+                               jobsTablePopupMenu.show(e.getComponent(), e.getX(), e.getY());
+                       }
+               }
+               
+               private void configurePopupMenu() {
+                       //Disable all buttons first
+                       jobs_contextmenu_edit.setEnabled(false);
+                       jobs_contextmenu_duplicate.setEnabled(false);
+                       jobs_contextmenu_applytempl.setEnabled(false);
+                       jobs_contextmenu_delete.setEnabled(false);
+                       jobs_contextmenu_resetstate_waiting.setEnabled(false);
+                       jobs_contextmenu_resetstate_done.setEnabled(false);
+                       jobs_contextmenu_showerror.setEnabled(false);
+                       jobs_contextmenu_start.setEnabled(false);
+                       for (JMenuItem pluginItem : jobs_contextmenu_runPluginMenuItems) {
+                               pluginItem.setEnabled(false);
+                       }
+                       
+                       //edit, duplicate, and show error buttons
+                       if (jobsTable.getSelectedRowCount() == 1) {
+                               jobs_contextmenu_edit.setEnabled(true);
+                               
+                               //Show error button
+                               List<RecordJob> selectedJobs = getSelectedRecordJobs(jobsTable);
+                               RecordJob selectedJob = selectedJobs.get(0);
+                               if (selectedJob.getState() == RecordJob.State.ERROR || selectedJob.getState() == RecordJob.State.ERROR_PLUGIN) {
+                                       jobs_contextmenu_showerror.setEnabled(true);
+                               }
+                       }
+                       
+                       if (jobsTable.getSelectedRowCount() > 0) {
+                               jobs_contextmenu_duplicate.setEnabled(true);
+                               //Delete button
+                               RecordJob.State[] states = {RecordJob.State.PROCESSING};
+                               if (!checkJobStates(states, false)) {
+                                       //none of the jobs is processing
+                                       jobs_contextmenu_delete.setEnabled(true);
+                                       jobs_contextmenu_resetstate_waiting.setEnabled(true);
+                                       
+                                       if (templatesTable.getSelectedRowCount() == 1) {
+                                               jobs_contextmenu_applytempl.setEnabled(true);
+                                       }
+                               }
+                               
+                               //Start button
+                               RecordJob.State[] states2 = {RecordJob.State.ERROR, RecordJob.State.DONE, RecordJob.State.PROCESSING, RecordJob.State.ERROR_PLUGIN};
+                               if (!checkJobStates(states2, true)) {
+                                       //only enable start if none of the selected jobs as any of the States above
+                                       //as the only job State that is not listed is "waiting", we only enable the button if all jobs are waiting
+                                       jobs_contextmenu_start.setEnabled(true);
+                               }
+                               
+                               //reset to 'done' button
+                               RecordJob.State[] states3 = {RecordJob.State.ERROR, RecordJob.State.WAITING, RecordJob.State.PROCESSING};
+                               if (!checkJobStates(states3, true)) {
+                                       //only enable the "reset to done" button when processes have the state DONE or ERROR_PLUGIN
+                                       jobs_contextmenu_resetstate_done.setEnabled(true);
+                               }
+                               
+                               //plugin buttons, enable only when state of the job is DONE
+                               RecordJob.State[] states4 = {RecordJob.State.ERROR, RecordJob.State.WAITING, RecordJob.State.PROCESSING, RecordJob.State.ERROR_PLUGIN};
+                               if (!checkJobStates(states4, true)) {
+                                       int counter = 0;
+                                       for (JMenuItem pluginItem : jobs_contextmenu_runPluginMenuItems) {
+                                               if (appLayer.getEncoderPlugins().get(counter).isEnabled()) {
+                                                       pluginItem.setEnabled(true);
+                                               }
+                                               counter++;
+                                       }
+                               }
+                       }
+               }
+               
+       }
+       
+       private class TemplatesTableMouseListener implements MouseListener {
+
+               public void mouseClicked(MouseEvent e) {
+                       if (e != null && e.getClickCount() == 2) {
+                               List<RecordJob> selectedJobs = getSelectedRecordJobs(templatesTable);
+                               if (selectedJobs.size() == 1) {
+                                       RecordJobTemplate selectedJob = (RecordJobTemplate) selectedJobs.get(0);
+                                       JobDialog jobDialog = new JobDialog(SwingGUI.this, selectedJob, appLayer, JobDialog.EDIT_TEMPLATE);
+                                       jobDialog.showDialog();
+                                       configureTableButtons();
+                               }
+                       } else {
+                               configureTableButtons();
+                       }
+               }
+
+               public void mouseEntered(MouseEvent e) {}
+
+               public void mouseExited(MouseEvent e) {}
+
+               public void mousePressed(MouseEvent e) {
+                       this.showPopupMenu(e);
+               }
+
+               public void mouseReleased(MouseEvent e) {
+                       this.showPopupMenu(e);
+               }
+               
+               private void showPopupMenu(MouseEvent e) {
+                       if (e.isPopupTrigger()) {
+                               JTable table = (JTable)(e.getSource());
+                               Point p = e.getPoint();
+                               int row = table.rowAtPoint(p);
+                               int[] selectedRows = table.getSelectedRows();
+                               //figure out whether we have to reselect the current row under the pointer,
+                               //which is only the case if the already selected rows don't include the one under
+                               //the pointer yet
+                               boolean reSelect = true;
+                               for (int i = 0; i < selectedRows.length; i++) {
+                                       if (row == selectedRows[i]) {
+                                               reSelect = false;
+                                               break;
+                                       }
+                               }
+                               
+                               if (row != -1 && reSelect) {
+                                       table.setRowSelectionInterval(row, row);
+                               }
+                               
+                               this.configurePopupMenu();
+                               configureTableButtons();
+                               templatesTablePopupMenu.show(e.getComponent(), e.getX(), e.getY());
+                       }
+               }
+               
+               private void configurePopupMenu() {
+                       //Various buttons
+                       templ_contextmenu_edit.setEnabled(false);
+                       templ_contextmenu_duplicate.setEnabled(false);
+                       templ_contextmenu_delete.setEnabled(false);
+                       
+                       //Edit button
+                       if (templatesTable.getSelectedRowCount() == 1) {
+                               templ_contextmenu_edit.setEnabled(true);
+                       }
+                       
+                       //Delete and duplicate button
+                       if (templatesTable.getSelectedRowCount() > 0) {
+                               templ_contextmenu_delete.setEnabled(true);
+                               templ_contextmenu_duplicate.setEnabled(true);
+                       }
+               }
+       }
+               
+       private void showAboutBox() {
+        try {
+            InputStream inStream = ClassLoader.getSystemResourceAsStream("about.html");
+            StringBuffer out = new StringBuffer();
+            byte[] b = new byte[4096];
+            for (int n; (n = inStream.read(b)) != -1;) {
+                out.append(new String(b, 0, n));
+            }
+            String htmlString = out.toString();
+            htmlString = htmlString.replaceAll("[\\r\\n]", "");
+            JOptionPane.showMessageDialog(this, htmlString, "About", JOptionPane.PLAIN_MESSAGE);
+        } catch (IOException ex) {
+            ex.printStackTrace();
+        }
+    }
+
+       public void windowActivated(WindowEvent e) {}
+       public void windowClosed(WindowEvent e) {}
+       public void windowDeactivated(WindowEvent e) {}
+       public void windowDeiconified(WindowEvent e) {}
+       public void windowIconified(WindowEvent e) {}
+       public void windowOpened(WindowEvent e) {}
+
+       public void windowClosing(WindowEvent e) {
+               this.shutDown();
+       }
+
+       private void shutDown() {
+               if (this.appLayer.getState() == DemoRecorderApplication.STATE_WORKING) {
+                       int result = JOptionPane.showConfirmDialog(this, "There are still jobs being recorded. Are you sure you want to exit?", "Confirm close", JOptionPane.YES_NO_OPTION);
+                       if (result == JOptionPane.NO_OPTION) {
+                               return;
+                       }
+               }
+               saveTableStates(jobsTable);
+               saveTableStates(templatesTable);
+               saveTemplateTableContent();
+               this.appLayer.shutDown();
+               this.dispose();
+               System.exit(0);
+       }
+       
+       private void saveTemplateTableContent() {
+               File path = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, TEMPLATE_TABLE_CONTENT_FILENAME);
+               RecordJobTemplatesTableModel tableModel = (RecordJobTemplatesTableModel) templatesTable.getModel();
+               tableModel.saveTemplateListToFile(path);
+       }
+       
+       private Icon getIcon(String iconString) {
+               URL url = ClassLoader.getSystemResource("icons/" + iconString);
+               Icon i = new ImageIcon(url);
+               return i;
+       }
+
+}
index f5aa069d7e89618edbc009ffd881add8ae776c9f..0e1b4fd07e90cab33a183081307824d80a397053 100644 (file)
-package com.nexuiz.demorecorder.ui.swinggui.tablemodels;\r
-\r
-import java.io.File;\r
-import java.io.FileInputStream;\r
-import java.io.FileOutputStream;\r
-import java.io.ObjectInputStream;\r
-import java.io.ObjectOutputStream;\r
-import java.util.ArrayList;\r
-import java.util.List;\r
-\r
-import javax.swing.JOptionPane;\r
-import javax.swing.table.AbstractTableModel;\r
-\r
-import org.jdesktop.swingx.JXTable;\r
-\r
-import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
-import com.nexuiz.demorecorder.application.DemoRecorderException;\r
-import com.nexuiz.demorecorder.application.DemoRecorderUtils;\r
-import com.nexuiz.demorecorder.ui.swinggui.RecordJobTemplate;\r
-import com.nexuiz.demorecorder.ui.swinggui.SwingGUI;\r
-\r
-/**\r
- * Columns:\r
- * - Job Name\r
- * - Engine path\r
- * - Engine parameters\r
- * - Demo file\r
- * - Relative demo path\r
- * - dpvideo path\r
- * - video destination\r
- * - execute before cap\r
- * - execute after cap\r
- * - start second\r
- * - end second\r
- * - status\r
- * @author Marius\r
- *\r
- */\r
-public class RecordJobTemplatesTableModel extends AbstractTableModel {\r
-       \r
-       private static final long serialVersionUID = 6541517890817708306L;\r
-       \r
-       public static final int TEMPLATE_NAME = 0;\r
-       public static final int TEMPLATE_SUMMARY = 1;\r
-       public static final int JOB_NAME = 2;\r
-       public static final int ENGINE_PATH = 3;\r
-       public static final int ENGINE_PARAMETERS = 4;\r
-       public static final int DEMO_FILE_PATH = 5;\r
-       public static final int RELATIVE_DEMO_PATH = 6;\r
-       public static final int DPVIDEO_PATH = 7;\r
-       public static final int VIDEO_DESTINATION_PATH = 8;\r
-       public static final int EXECUTE_BEFORE_CAP = 9;\r
-       public static final int EXECUTE_AFTER_CAP = 10;\r
-       \r
-       private static final int columns[] = {\r
-               TEMPLATE_NAME,\r
-               TEMPLATE_SUMMARY,\r
-               JOB_NAME,\r
-               ENGINE_PATH,\r
-               ENGINE_PARAMETERS,\r
-               DEMO_FILE_PATH,\r
-               RELATIVE_DEMO_PATH,\r
-               DPVIDEO_PATH,\r
-               VIDEO_DESTINATION_PATH,\r
-               EXECUTE_BEFORE_CAP,\r
-               EXECUTE_AFTER_CAP\r
-       };\r
-       \r
-       private List<RecordJobTemplate> templates;\r
-       \r
-       public RecordJobTemplatesTableModel() {\r
-               templates = new ArrayList<RecordJobTemplate>();\r
-               \r
-               //load table content\r
-               File path = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, SwingGUI.TEMPLATE_TABLE_CONTENT_FILENAME);\r
-               this.loadTemplateListFromFile(path, true);\r
-       }\r
-       \r
-       public void deleteRecordJobTemplate(int modelRowIndex, int viewRowIndex) {\r
-               try {\r
-                       this.templates.remove(modelRowIndex);\r
-                       fireTableRowsDeleted(viewRowIndex, viewRowIndex);\r
-               } catch (IndexOutOfBoundsException e) {\r
-                       throw new DemoRecorderException("Couldn't find correspondig template for modelRowIndex " + modelRowIndex\r
-                                       + " and viewRowIndex " + viewRowIndex, e);\r
-               }\r
-       }\r
-       \r
-       public void addRecordJobTemplate(RecordJobTemplate template) {\r
-               this.templates.add(template);\r
-               int position = this.templates.size() - 1;\r
-               fireTableRowsInserted(position, position);\r
-       }\r
-       \r
-       public RecordJobTemplate getRecordJobTemplate(int modelRowIndex) {\r
-               return this.templates.get(modelRowIndex);\r
-       }\r
-\r
-       public int getColumnCount() {\r
-               return columns.length;\r
-       }\r
-\r
-       public int getRowCount() {\r
-               return this.templates.size();\r
-       }\r
-       \r
-       public void saveTemplateListToFile(File path) {\r
-               DemoRecorderUtils.attemptFileCreation(path);\r
-               \r
-               String exceptionMessage = "Could not save the templates to file " + path.getAbsolutePath();\r
-               \r
-               if (!path.exists()) {\r
-                       DemoRecorderException ex = new DemoRecorderException(exceptionMessage);\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog(ex);\r
-                       return;\r
-               }\r
-               \r
-               try {\r
-                       FileOutputStream fout = new FileOutputStream(path);\r
-                       ObjectOutputStream oos = new ObjectOutputStream(fout);\r
-                       oos.writeObject(this.templates);\r
-                       oos.close();\r
-               } catch (Exception e) {\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);\r
-               }\r
-       }\r
-       \r
-       @SuppressWarnings("unchecked")\r
-       private int loadTemplateListFromFile(File path, boolean overwrite) {\r
-               if (!path.exists()) {\r
-                       return 0;\r
-               }\r
-               \r
-               List<RecordJobTemplate> newTemplateList;\r
-               try {\r
-                       FileInputStream fin = new FileInputStream(path);\r
-                       ObjectInputStream ois = new ObjectInputStream(fin);\r
-                       newTemplateList = (List<RecordJobTemplate>) ois.readObject();\r
-                       if (overwrite) {\r
-                               this.templates = newTemplateList;\r
-                       } else {\r
-                               this.templates.addAll(newTemplateList);\r
-                       }\r
-                       return newTemplateList.size();\r
-               } catch (Exception e) {\r
-                       DemoRecorderUtils.showNonCriticalErrorDialog("Could not load the templates from file " + path.getAbsolutePath(), e, true);\r
-                       return 0;\r
-               }\r
-               \r
-       }\r
-       \r
-       public void loadNewTemplateList(SwingGUI gui, File path, JXTable templatesTable) {\r
-               int result = JOptionPane.showConfirmDialog(gui, "Do you want to overwrite the current template list? When pressing 'no' the loaded templates will be added to the current list!", "Confirm overwrite", JOptionPane.YES_NO_OPTION);\r
-               boolean overwrite = false;\r
-               if (result == JOptionPane.YES_OPTION) {\r
-                       overwrite = true;\r
-               }\r
-               int count = loadTemplateListFromFile(path, overwrite);\r
-               fireTableDataChanged();\r
-               if (count > 0) {\r
-                       templatesTable.setRowSelectionInterval(templatesTable.getRowCount() - count, templatesTable.getRowCount() - 1);\r
-               }\r
-       }\r
-\r
-       public Object getValueAt(int rowIndex, int columnIndex) {\r
-               RecordJobTemplate template = this.templates.get(rowIndex);\r
-               if (template == null) {\r
-                       return null;\r
-               }\r
-               \r
-               if (columnIndex < 0 || columnIndex >= columns.length) {\r
-                       return null;\r
-               }\r
-               \r
-               String cellData = "UNDEF";\r
-               switch (columnIndex) {\r
-               case TEMPLATE_NAME:\r
-                       cellData = template.getName(); break;\r
-               case TEMPLATE_SUMMARY:\r
-                       cellData = template.getSummary(); break;\r
-               case JOB_NAME:\r
-                       cellData = template.getJobName(); break;\r
-               case ENGINE_PATH:\r
-                       cellData = template.getEnginePath().getAbsolutePath(); break;\r
-               case ENGINE_PARAMETERS:\r
-                       cellData = template.getEngineParameters(); break;\r
-               case DEMO_FILE_PATH:\r
-                       cellData = DemoRecorderUtils.getJustFileNameOfPath(template.getDemoFile()); break;\r
-               case RELATIVE_DEMO_PATH:\r
-                       cellData = template.getRelativeDemoPath(); break;\r
-               case DPVIDEO_PATH:\r
-                       cellData = template.getDpVideoPath().getAbsolutePath(); break;\r
-               case VIDEO_DESTINATION_PATH:\r
-                       cellData = template.getVideoDestination().getAbsolutePath(); break;\r
-               case EXECUTE_BEFORE_CAP:\r
-                       cellData = template.getExecuteBeforeCap(); break;\r
-               case EXECUTE_AFTER_CAP:\r
-                       cellData = template.getExecuteAfterCap(); break;\r
-               }\r
-               \r
-               return cellData;\r
-       }\r
-\r
-       @Override\r
-       public String getColumnName(int column) {\r
-               if (column < 0 || column >= columns.length) {\r
-                       return "";\r
-               }\r
-               \r
-               String columnName = "UNDEFINED";\r
-               switch (column) {\r
-               case TEMPLATE_NAME:\r
-                       columnName = "Name"; break;\r
-               case TEMPLATE_SUMMARY:\r
-                       columnName = "Summary"; break;\r
-               case JOB_NAME:\r
-                       columnName = "Job name"; break;\r
-               case ENGINE_PATH:\r
-                       columnName = "Engine path"; break;\r
-               case ENGINE_PARAMETERS:\r
-                       columnName = "Engine parameters"; break;\r
-               case DEMO_FILE_PATH:\r
-                       columnName = "Demo directory"; break;\r
-               case RELATIVE_DEMO_PATH:\r
-                       columnName = "Relative demo path"; break;\r
-               case DPVIDEO_PATH:\r
-                       columnName = "DPVideo path"; break;\r
-               case VIDEO_DESTINATION_PATH:\r
-                       columnName = "Video destination"; break;\r
-               case EXECUTE_BEFORE_CAP:\r
-                       columnName = "Exec before"; break;\r
-               case EXECUTE_AFTER_CAP:\r
-                       columnName = "Exec after"; break;\r
-               }\r
-               \r
-               return columnName;\r
-       }\r
-       \r
-       public List<RecordJobTemplate> getRecordJobTemplates() {\r
-               return this.templates;\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.ui.swinggui.tablemodels;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JOptionPane;
+import javax.swing.table.AbstractTableModel;
+
+import org.jdesktop.swingx.JXTable;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.DemoRecorderException;
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+import com.nexuiz.demorecorder.ui.swinggui.RecordJobTemplate;
+import com.nexuiz.demorecorder.ui.swinggui.SwingGUI;
+
+/**
+ * Columns:
+ * - Job Name
+ * - Engine path
+ * - Engine parameters
+ * - Demo file
+ * - Relative demo path
+ * - dpvideo path
+ * - video destination
+ * - execute before cap
+ * - execute after cap
+ * - start second
+ * - end second
+ * - status
+ * @author Marius
+ *
+ */
+public class RecordJobTemplatesTableModel extends AbstractTableModel {
+       
+       private static final long serialVersionUID = 6541517890817708306L;
+       
+       public static final int TEMPLATE_NAME = 0;
+       public static final int TEMPLATE_SUMMARY = 1;
+       public static final int JOB_NAME = 2;
+       public static final int ENGINE_PATH = 3;
+       public static final int ENGINE_PARAMETERS = 4;
+       public static final int DEMO_FILE_PATH = 5;
+       public static final int RELATIVE_DEMO_PATH = 6;
+       public static final int DPVIDEO_PATH = 7;
+       public static final int VIDEO_DESTINATION_PATH = 8;
+       public static final int EXECUTE_BEFORE_CAP = 9;
+       public static final int EXECUTE_AFTER_CAP = 10;
+       
+       private static final int columns[] = {
+               TEMPLATE_NAME,
+               TEMPLATE_SUMMARY,
+               JOB_NAME,
+               ENGINE_PATH,
+               ENGINE_PARAMETERS,
+               DEMO_FILE_PATH,
+               RELATIVE_DEMO_PATH,
+               DPVIDEO_PATH,
+               VIDEO_DESTINATION_PATH,
+               EXECUTE_BEFORE_CAP,
+               EXECUTE_AFTER_CAP
+       };
+       
+       private List<RecordJobTemplate> templates;
+       
+       public RecordJobTemplatesTableModel() {
+               templates = new ArrayList<RecordJobTemplate>();
+               
+               //load table content
+               File path = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.PREFERENCES_DIRNAME, SwingGUI.TEMPLATE_TABLE_CONTENT_FILENAME);
+               this.loadTemplateListFromFile(path, true);
+       }
+       
+       public void deleteRecordJobTemplate(int modelRowIndex, int viewRowIndex) {
+               try {
+                       this.templates.remove(modelRowIndex);
+                       fireTableRowsDeleted(viewRowIndex, viewRowIndex);
+               } catch (IndexOutOfBoundsException e) {
+                       throw new DemoRecorderException("Couldn't find correspondig template for modelRowIndex " + modelRowIndex
+                                       + " and viewRowIndex " + viewRowIndex, e);
+               }
+       }
+       
+       public void addRecordJobTemplate(RecordJobTemplate template) {
+               this.templates.add(template);
+               int position = this.templates.size() - 1;
+               fireTableRowsInserted(position, position);
+       }
+       
+       public RecordJobTemplate getRecordJobTemplate(int modelRowIndex) {
+               return this.templates.get(modelRowIndex);
+       }
+
+       public int getColumnCount() {
+               return columns.length;
+       }
+
+       public int getRowCount() {
+               return this.templates.size();
+       }
+       
+       public void saveTemplateListToFile(File path) {
+               DemoRecorderUtils.attemptFileCreation(path);
+               
+               String exceptionMessage = "Could not save the templates to file " + path.getAbsolutePath();
+               
+               if (!path.exists()) {
+                       DemoRecorderException ex = new DemoRecorderException(exceptionMessage);
+                       DemoRecorderUtils.showNonCriticalErrorDialog(ex);
+                       return;
+               }
+               
+               try {
+                       FileOutputStream fout = new FileOutputStream(path);
+                       ObjectOutputStream oos = new ObjectOutputStream(fout);
+                       oos.writeObject(this.templates);
+                       oos.close();
+               } catch (Exception e) {
+                       DemoRecorderUtils.showNonCriticalErrorDialog(exceptionMessage, e, true);
+               }
+       }
+       
+       @SuppressWarnings("unchecked")
+       private int loadTemplateListFromFile(File path, boolean overwrite) {
+               if (!path.exists()) {
+                       return 0;
+               }
+               
+               List<RecordJobTemplate> newTemplateList;
+               try {
+                       FileInputStream fin = new FileInputStream(path);
+                       ObjectInputStream ois = new ObjectInputStream(fin);
+                       newTemplateList = (List<RecordJobTemplate>) ois.readObject();
+                       if (overwrite) {
+                               this.templates = newTemplateList;
+                       } else {
+                               this.templates.addAll(newTemplateList);
+                       }
+                       return newTemplateList.size();
+               } catch (Exception e) {
+                       DemoRecorderUtils.showNonCriticalErrorDialog("Could not load the templates from file " + path.getAbsolutePath(), e, true);
+                       return 0;
+               }
+               
+       }
+       
+       public void loadNewTemplateList(SwingGUI gui, File path, JXTable templatesTable) {
+               int result = JOptionPane.showConfirmDialog(gui, "Do you want to overwrite the current template list? When pressing 'no' the loaded templates will be added to the current list!", "Confirm overwrite", JOptionPane.YES_NO_OPTION);
+               boolean overwrite = false;
+               if (result == JOptionPane.YES_OPTION) {
+                       overwrite = true;
+               }
+               int count = loadTemplateListFromFile(path, overwrite);
+               fireTableDataChanged();
+               if (count > 0) {
+                       templatesTable.setRowSelectionInterval(templatesTable.getRowCount() - count, templatesTable.getRowCount() - 1);
+               }
+       }
+
+       public Object getValueAt(int rowIndex, int columnIndex) {
+               RecordJobTemplate template = this.templates.get(rowIndex);
+               if (template == null) {
+                       return null;
+               }
+               
+               if (columnIndex < 0 || columnIndex >= columns.length) {
+                       return null;
+               }
+               
+               String cellData = "UNDEF";
+               switch (columnIndex) {
+               case TEMPLATE_NAME:
+                       cellData = template.getName(); break;
+               case TEMPLATE_SUMMARY:
+                       cellData = template.getSummary(); break;
+               case JOB_NAME:
+                       cellData = template.getJobName(); break;
+               case ENGINE_PATH:
+                       cellData = template.getEnginePath().getAbsolutePath(); break;
+               case ENGINE_PARAMETERS:
+                       cellData = template.getEngineParameters(); break;
+               case DEMO_FILE_PATH:
+                       cellData = DemoRecorderUtils.getJustFileNameOfPath(template.getDemoFile()); break;
+               case RELATIVE_DEMO_PATH:
+                       cellData = template.getRelativeDemoPath(); break;
+               case DPVIDEO_PATH:
+                       cellData = template.getDpVideoPath().getAbsolutePath(); break;
+               case VIDEO_DESTINATION_PATH:
+                       cellData = template.getVideoDestination().getAbsolutePath(); break;
+               case EXECUTE_BEFORE_CAP:
+                       cellData = template.getExecuteBeforeCap(); break;
+               case EXECUTE_AFTER_CAP:
+                       cellData = template.getExecuteAfterCap(); break;
+               }
+               
+               return cellData;
+       }
+
+       @Override
+       public String getColumnName(int column) {
+               if (column < 0 || column >= columns.length) {
+                       return "";
+               }
+               
+               String columnName = "UNDEFINED";
+               switch (column) {
+               case TEMPLATE_NAME:
+                       columnName = "Name"; break;
+               case TEMPLATE_SUMMARY:
+                       columnName = "Summary"; break;
+               case JOB_NAME:
+                       columnName = "Job name"; break;
+               case ENGINE_PATH:
+                       columnName = "Engine path"; break;
+               case ENGINE_PARAMETERS:
+                       columnName = "Engine parameters"; break;
+               case DEMO_FILE_PATH:
+                       columnName = "Demo directory"; break;
+               case RELATIVE_DEMO_PATH:
+                       columnName = "Relative demo path"; break;
+               case DPVIDEO_PATH:
+                       columnName = "DPVideo path"; break;
+               case VIDEO_DESTINATION_PATH:
+                       columnName = "Video destination"; break;
+               case EXECUTE_BEFORE_CAP:
+                       columnName = "Exec before"; break;
+               case EXECUTE_AFTER_CAP:
+                       columnName = "Exec after"; break;
+               }
+               
+               return columnName;
+       }
+       
+       public List<RecordJobTemplate> getRecordJobTemplates() {
+               return this.templates;
+       }
+}
index d789696a64603bdc25faf90751c4f56f96228569..839ef76131e2b825734ca5e0fd745c54994b9d69 100644 (file)
-package com.nexuiz.demorecorder.ui.swinggui.tablemodels;\r
-\r
-import java.io.File;\r
-import java.util.List;\r
-\r
-import javax.swing.JOptionPane;\r
-import javax.swing.table.AbstractTableModel;\r
-\r
-import org.jdesktop.swingx.JXTable;\r
-\r
-import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
-import com.nexuiz.demorecorder.application.DemoRecorderException;\r
-import com.nexuiz.demorecorder.application.DemoRecorderUtils;\r
-import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
-import com.nexuiz.demorecorder.ui.swinggui.SwingGUI;\r
-\r
-/**\r
- * Columns:\r
- * - Job Name\r
- * - Engine path\r
- * - Engine parameters\r
- * - Demo file\r
- * - Relative demo path\r
- * - dpvideo path\r
- * - video destination\r
- * - execute before cap\r
- * - execute after cap\r
- * - start second\r
- * - end second\r
- * - status\r
- * @author Marius\r
- *\r
- */\r
-public class RecordJobsTableModel extends AbstractTableModel {\r
-       \r
-       private static final long serialVersionUID = 5024144640874313910L;\r
-       \r
-       public static final int JOB_NAME = 0;\r
-       public static final int ENGINE_PATH = 1;\r
-       public static final int ENGINE_PARAMETERS = 2;\r
-       public static final int DEMO_FILE_PATH = 3;\r
-       public static final int RELATIVE_DEMO_PATH = 4;\r
-       public static final int DPVIDEO_PATH = 5;\r
-       public static final int VIDEO_DESTINATION_PATH = 6;\r
-       public static final int EXECUTE_BEFORE_CAP = 7;\r
-       public static final int EXECUTE_AFTER_CAP = 8;\r
-       public static final int START_SECOND = 9;\r
-       public static final int END_SECOND = 10;\r
-       public static final int STATUS = 11;\r
-       \r
-       private static final int columns[] = {\r
-               JOB_NAME,\r
-               ENGINE_PATH,\r
-               ENGINE_PARAMETERS,\r
-               DEMO_FILE_PATH,\r
-               RELATIVE_DEMO_PATH,\r
-               DPVIDEO_PATH,\r
-               VIDEO_DESTINATION_PATH,\r
-               EXECUTE_BEFORE_CAP,\r
-               EXECUTE_AFTER_CAP,\r
-               START_SECOND,\r
-               END_SECOND,\r
-               STATUS\r
-       };\r
-       \r
-       private DemoRecorderApplication appLayer;\r
-       private List<RecordJob> jobList = null;\r
-       \r
-       public RecordJobsTableModel(DemoRecorderApplication appLayer) {\r
-               this.appLayer = appLayer;\r
-               this.jobList = this.appLayer.getRecordJobs();\r
-       }\r
-       \r
-       public void deleteRecordJob(int modelRowIndex, int viewRowIndex) {\r
-               try {\r
-                       RecordJob job = this.jobList.get(modelRowIndex);\r
-                       if (this.appLayer.deleteRecordJob(job)) {\r
-                               this.jobList.remove(job);\r
-                               fireTableRowsDeleted(viewRowIndex, viewRowIndex);\r
-                       }\r
-               } catch (IndexOutOfBoundsException e) {\r
-                       throw new DemoRecorderException("Couldn't find correspondig job for modelRowIndex " + modelRowIndex\r
-                                       + " and viewRowIndex " + viewRowIndex, e);\r
-               }\r
-       }\r
-       \r
-       public void loadNewJobQueue(SwingGUI gui, File path, JXTable jobsTable) {\r
-               int result = JOptionPane.showConfirmDialog(gui, "Do you want to overwrite the current job queue? When pressing 'no' the loaded jobs will be added to the current queue!", "Confirm overwrite", JOptionPane.YES_NO_OPTION);\r
-               boolean overwrite = false;\r
-               if (result == JOptionPane.YES_OPTION) {\r
-                       overwrite = true;\r
-               }\r
-               int count = this.appLayer.loadJobQueue(path, overwrite);\r
-               this.jobList = this.appLayer.getRecordJobs();\r
-               fireTableDataChanged();\r
-               if (count > 0) {\r
-                       jobsTable.setRowSelectionInterval(jobsTable.getRowCount() - count, jobsTable.getRowCount() - 1);\r
-               }\r
-       }\r
-       \r
-       public RecordJob getRecordJob(int modelRowIndex) {\r
-               return this.jobList.get(modelRowIndex);\r
-       }\r
-\r
-       public int getColumnCount() {\r
-               return columns.length;\r
-       }\r
-\r
-       public int getRowCount() {\r
-               return this.jobList.size();\r
-       }\r
-\r
-       public Object getValueAt(int rowIndex, int columnIndex) {\r
-               RecordJob job = this.jobList.get(rowIndex);\r
-               if (job == null) {\r
-                       return null;\r
-               }\r
-               \r
-               if (columnIndex < 0 || columnIndex >= columns.length) {\r
-                       return null;\r
-               }\r
-               \r
-               String cellData = "UNDEF";\r
-               switch (columnIndex) {\r
-               case JOB_NAME:\r
-                       cellData = job.getJobName(); break;\r
-               case ENGINE_PATH:\r
-                       cellData = job.getEnginePath().getAbsolutePath(); break;\r
-               case ENGINE_PARAMETERS:\r
-                       cellData = job.getEngineParameters(); break;\r
-               case DEMO_FILE_PATH:\r
-                       cellData = DemoRecorderUtils.getJustFileNameOfPath(job.getDemoFile()); break;\r
-               case RELATIVE_DEMO_PATH:\r
-                       cellData = job.getRelativeDemoPath(); break;\r
-               case DPVIDEO_PATH:\r
-                       cellData = job.getDpVideoPath().getAbsolutePath(); break;\r
-               case VIDEO_DESTINATION_PATH:\r
-                       cellData = job.getVideoDestination().getAbsolutePath(); break;\r
-               case EXECUTE_BEFORE_CAP:\r
-                       cellData = job.getExecuteBeforeCap(); break;\r
-               case EXECUTE_AFTER_CAP:\r
-                       cellData = job.getExecuteAfterCap(); break;\r
-               case START_SECOND:\r
-                       cellData = String.valueOf(job.getStartSecond()); break;\r
-               case END_SECOND:\r
-                       cellData = String.valueOf(job.getEndSecond()); break;\r
-               case STATUS:\r
-                       if (job.getState() == RecordJob.State.DONE) {\r
-                               cellData = "done";\r
-                       } else if (job.getState() == RecordJob.State.ERROR) {\r
-                               cellData = "error";\r
-                       } else if (job.getState() == RecordJob.State.ERROR_PLUGIN) {\r
-                               cellData = "plug-in error";\r
-                       } else if (job.getState() == RecordJob.State.PROCESSING) {\r
-                               cellData = "processing";\r
-                       } else if (job.getState() == RecordJob.State.WAITING) {\r
-                               cellData = "waiting";\r
-                       }\r
-               }\r
-               \r
-               return cellData;\r
-       }\r
-\r
-       @Override\r
-       public String getColumnName(int column) {\r
-               if (column < 0 || column >= columns.length) {\r
-                       return "";\r
-               }\r
-               \r
-               String columnName = "UNDEFINED";\r
-               switch (column) {\r
-               case JOB_NAME:\r
-                       columnName = "Name"; break;\r
-               case ENGINE_PATH:\r
-                       columnName = "Engine path"; break;\r
-               case ENGINE_PARAMETERS:\r
-                       columnName = "Engine parameters"; break;\r
-               case DEMO_FILE_PATH:\r
-                       columnName = "Demo name"; break;\r
-               case RELATIVE_DEMO_PATH:\r
-                       columnName = "Relative demo path"; break;\r
-               case DPVIDEO_PATH:\r
-                       columnName = "DPVideo path"; break;\r
-               case VIDEO_DESTINATION_PATH:\r
-                       columnName = "Video destination"; break;\r
-               case EXECUTE_BEFORE_CAP:\r
-                       columnName = "Exec before"; break;\r
-               case EXECUTE_AFTER_CAP:\r
-                       columnName = "Exec after"; break;\r
-               case START_SECOND:\r
-                       columnName = "Start"; break;\r
-               case END_SECOND:\r
-                       columnName = "End"; break;\r
-               case STATUS:\r
-                       columnName = "Status"; break;\r
-               }\r
-               \r
-               return columnName;\r
-       }\r
-       \r
-       public List<RecordJob> getRecordJobs() {\r
-               return this.jobList;\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.ui.swinggui.tablemodels;
+
+import java.io.File;
+import java.util.List;
+
+import javax.swing.JOptionPane;
+import javax.swing.table.AbstractTableModel;
+
+import org.jdesktop.swingx.JXTable;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.DemoRecorderException;
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+import com.nexuiz.demorecorder.ui.swinggui.SwingGUI;
+
+/**
+ * Columns:
+ * - Job Name
+ * - Engine path
+ * - Engine parameters
+ * - Demo file
+ * - Relative demo path
+ * - dpvideo path
+ * - video destination
+ * - execute before cap
+ * - execute after cap
+ * - start second
+ * - end second
+ * - status
+ * @author Marius
+ *
+ */
+public class RecordJobsTableModel extends AbstractTableModel {
+       
+       private static final long serialVersionUID = 5024144640874313910L;
+       
+       public static final int JOB_NAME = 0;
+       public static final int ENGINE_PATH = 1;
+       public static final int ENGINE_PARAMETERS = 2;
+       public static final int DEMO_FILE_PATH = 3;
+       public static final int RELATIVE_DEMO_PATH = 4;
+       public static final int DPVIDEO_PATH = 5;
+       public static final int VIDEO_DESTINATION_PATH = 6;
+       public static final int EXECUTE_BEFORE_CAP = 7;
+       public static final int EXECUTE_AFTER_CAP = 8;
+       public static final int START_SECOND = 9;
+       public static final int END_SECOND = 10;
+       public static final int STATUS = 11;
+       
+       private static final int columns[] = {
+               JOB_NAME,
+               ENGINE_PATH,
+               ENGINE_PARAMETERS,
+               DEMO_FILE_PATH,
+               RELATIVE_DEMO_PATH,
+               DPVIDEO_PATH,
+               VIDEO_DESTINATION_PATH,
+               EXECUTE_BEFORE_CAP,
+               EXECUTE_AFTER_CAP,
+               START_SECOND,
+               END_SECOND,
+               STATUS
+       };
+       
+       private DemoRecorderApplication appLayer;
+       private List<RecordJob> jobList = null;
+       
+       public RecordJobsTableModel(DemoRecorderApplication appLayer) {
+               this.appLayer = appLayer;
+               this.jobList = this.appLayer.getRecordJobs();
+       }
+       
+       public void deleteRecordJob(int modelRowIndex, int viewRowIndex) {
+               try {
+                       RecordJob job = this.jobList.get(modelRowIndex);
+                       if (this.appLayer.deleteRecordJob(job)) {
+                               this.jobList.remove(job);
+                               fireTableRowsDeleted(viewRowIndex, viewRowIndex);
+                       }
+               } catch (IndexOutOfBoundsException e) {
+                       throw new DemoRecorderException("Couldn't find correspondig job for modelRowIndex " + modelRowIndex
+                                       + " and viewRowIndex " + viewRowIndex, e);
+               }
+       }
+       
+       public void loadNewJobQueue(SwingGUI gui, File path, JXTable jobsTable) {
+               int result = JOptionPane.showConfirmDialog(gui, "Do you want to overwrite the current job queue? When pressing 'no' the loaded jobs will be added to the current queue!", "Confirm overwrite", JOptionPane.YES_NO_OPTION);
+               boolean overwrite = false;
+               if (result == JOptionPane.YES_OPTION) {
+                       overwrite = true;
+               }
+               int count = this.appLayer.loadJobQueue(path, overwrite);
+               this.jobList = this.appLayer.getRecordJobs();
+               fireTableDataChanged();
+               if (count > 0) {
+                       jobsTable.setRowSelectionInterval(jobsTable.getRowCount() - count, jobsTable.getRowCount() - 1);
+               }
+       }
+       
+       public RecordJob getRecordJob(int modelRowIndex) {
+               return this.jobList.get(modelRowIndex);
+       }
+
+       public int getColumnCount() {
+               return columns.length;
+       }
+
+       public int getRowCount() {
+               return this.jobList.size();
+       }
+
+       public Object getValueAt(int rowIndex, int columnIndex) {
+               RecordJob job = this.jobList.get(rowIndex);
+               if (job == null) {
+                       return null;
+               }
+               
+               if (columnIndex < 0 || columnIndex >= columns.length) {
+                       return null;
+               }
+               
+               String cellData = "UNDEF";
+               switch (columnIndex) {
+               case JOB_NAME:
+                       cellData = job.getJobName(); break;
+               case ENGINE_PATH:
+                       cellData = job.getEnginePath().getAbsolutePath(); break;
+               case ENGINE_PARAMETERS:
+                       cellData = job.getEngineParameters(); break;
+               case DEMO_FILE_PATH:
+                       cellData = DemoRecorderUtils.getJustFileNameOfPath(job.getDemoFile()); break;
+               case RELATIVE_DEMO_PATH:
+                       cellData = job.getRelativeDemoPath(); break;
+               case DPVIDEO_PATH:
+                       cellData = job.getDpVideoPath().getAbsolutePath(); break;
+               case VIDEO_DESTINATION_PATH:
+                       cellData = job.getVideoDestination().getAbsolutePath(); break;
+               case EXECUTE_BEFORE_CAP:
+                       cellData = job.getExecuteBeforeCap(); break;
+               case EXECUTE_AFTER_CAP:
+                       cellData = job.getExecuteAfterCap(); break;
+               case START_SECOND:
+                       cellData = String.valueOf(job.getStartSecond()); break;
+               case END_SECOND:
+                       cellData = String.valueOf(job.getEndSecond()); break;
+               case STATUS:
+                       if (job.getState() == RecordJob.State.DONE) {
+                               cellData = "done";
+                       } else if (job.getState() == RecordJob.State.ERROR) {
+                               cellData = "error";
+                       } else if (job.getState() == RecordJob.State.ERROR_PLUGIN) {
+                               cellData = "plug-in error";
+                       } else if (job.getState() == RecordJob.State.PROCESSING) {
+                               cellData = "processing";
+                       } else if (job.getState() == RecordJob.State.WAITING) {
+                               cellData = "waiting";
+                       }
+               }
+               
+               return cellData;
+       }
+
+       @Override
+       public String getColumnName(int column) {
+               if (column < 0 || column >= columns.length) {
+                       return "";
+               }
+               
+               String columnName = "UNDEFINED";
+               switch (column) {
+               case JOB_NAME:
+                       columnName = "Name"; break;
+               case ENGINE_PATH:
+                       columnName = "Engine path"; break;
+               case ENGINE_PARAMETERS:
+                       columnName = "Engine parameters"; break;
+               case DEMO_FILE_PATH:
+                       columnName = "Demo name"; break;
+               case RELATIVE_DEMO_PATH:
+                       columnName = "Relative demo path"; break;
+               case DPVIDEO_PATH:
+                       columnName = "DPVideo path"; break;
+               case VIDEO_DESTINATION_PATH:
+                       columnName = "Video destination"; break;
+               case EXECUTE_BEFORE_CAP:
+                       columnName = "Exec before"; break;
+               case EXECUTE_AFTER_CAP:
+                       columnName = "Exec after"; break;
+               case START_SECOND:
+                       columnName = "Start"; break;
+               case END_SECOND:
+                       columnName = "End"; break;
+               case STATUS:
+                       columnName = "Status"; break;
+               }
+               
+               return columnName;
+       }
+       
+       public List<RecordJob> getRecordJobs() {
+               return this.jobList;
+       }
+}
index 41e9b78230a60217ea4fa642979c265ae1e3b1cd..733ff370a811331d94e69162c9217119b66cc01f 100644 (file)
@@ -1,21 +1,21 @@
-package com.nexuiz.demorecorder.ui.swinggui.utils;\r
-\r
-import java.awt.Component;\r
-import java.lang.Thread.UncaughtExceptionHandler;\r
-\r
-import org.jdesktop.swingx.JXErrorPane;\r
-import org.jdesktop.swingx.error.ErrorInfo;\r
-\r
-public class ShowErrorDialogExceptionHandler implements UncaughtExceptionHandler {\r
-\r
-       private static Component parentWindow = null;\r
-       \r
-       public void uncaughtException(Thread t, Throwable e) {\r
-               ErrorInfo info = new ErrorInfo("Error occurred", e.getMessage(), null, null, e, null, null);\r
-               JXErrorPane.showDialog(parentWindow, info);\r
-       }\r
-\r
-       public static void setParentWindow(Component c) {\r
-               parentWindow = c;\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.ui.swinggui.utils;
+
+import java.awt.Component;
+import java.lang.Thread.UncaughtExceptionHandler;
+
+import org.jdesktop.swingx.JXErrorPane;
+import org.jdesktop.swingx.error.ErrorInfo;
+
+public class ShowErrorDialogExceptionHandler implements UncaughtExceptionHandler {
+
+       private static Component parentWindow = null;
+       
+       public void uncaughtException(Thread t, Throwable e) {
+               ErrorInfo info = new ErrorInfo("Error occurred", e.getMessage(), null, null, e, null, null);
+               JXErrorPane.showDialog(parentWindow, info);
+       }
+
+       public static void setParentWindow(Component c) {
+               parentWindow = c;
+       }
+}
index 65462e7425f367599af453e066925c7f00159507..1a2cf40dcc33815b4e87df14867b189d600b9b93 100644 (file)
@@ -1,26 +1,26 @@
-package com.nexuiz.demorecorder.ui.swinggui.utils;\r
-\r
-import java.io.File;\r
-\r
-public class SwingGUIUtils {\r
-       public static boolean isBooleanValue(String value) {\r
-               if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {\r
-                       return true;\r
-               }\r
-               return false;\r
-       }\r
-\r
-       public static boolean isFileChooser(String value) {\r
-               if (value.equalsIgnoreCase("filechooser")) {\r
-                       return true;\r
-               }\r
-               try {\r
-                       File file = new File(value);\r
-                       if (file.exists()) {\r
-                               return true;\r
-                       }\r
-               } catch (Throwable e) {\r
-               }\r
-               return false;\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.ui.swinggui.utils;
+
+import java.io.File;
+
+public class SwingGUIUtils {
+       public static boolean isBooleanValue(String value) {
+               if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
+                       return true;
+               }
+               return false;
+       }
+
+       public static boolean isFileChooser(String value) {
+               if (value.equalsIgnoreCase("filechooser")) {
+                       return true;
+               }
+               try {
+                       File file = new File(value);
+                       if (file.exists()) {
+                               return true;
+                       }
+               } catch (Throwable e) {
+               }
+               return false;
+       }
+}
index 004b626223fad6881866333f0b12f6a64dfada14..757d802baa18351689cbd1d7b23c578baa5e7db5 100644 (file)
-/*\r
- * Created on 08.02.2007\r
- *\r
- */\r
-package com.nexuiz.demorecorder.ui.swinggui.utils;\r
-\r
-import java.awt.Component;\r
-import java.beans.DefaultPersistenceDelegate;\r
-import java.beans.XMLEncoder;\r
-import java.io.Serializable;\r
-import java.util.ArrayList;\r
-import java.util.Collections;\r
-import java.util.Comparator;\r
-import java.util.List;\r
-\r
-import javax.swing.SortOrder;\r
-import javax.swing.RowSorter.SortKey;\r
-import javax.swing.table.TableColumn;\r
-import javax.swing.table.TableColumnModel;\r
-\r
-import org.jdesktop.swingx.JXTable;\r
-import org.jdesktop.swingx.JXTaskPane;\r
-import org.jdesktop.swingx.sort.SortUtils;\r
-import org.jdesktop.swingx.table.TableColumnExt;\r
-\r
-/**\r
- * Container class for SwingX specific SessionStorage Properties. Is Factory for\r
- * custom PersistanceDelegates\r
- */\r
-public class XProperties {\r
-\r
-       /**\r
-        * \r
-        * Registers all custom PersistenceDelegates needed by contained Property\r
-        * classes.\r
-        * <p>\r
-        * \r
-        * PersistenceDelegates are effectively static properties shared by all\r
-        * encoders. In other words: Register once on an arbitrary encoder makes\r
-        * them available for all. Example usage:\r
-        * \r
-        * <pre>\r
-        * <code>\r
-        * new XProperties.registerPersistenceDelegates();\r
-        * </code>\r
-        * </pre>\r
-        * \r
-        * PENDING JW: cleanup for 1.6 sorting/filtering incomplete. Missing storage\r
-        * - multiple sort keys\r
-        * \r
-        * PENDING JW: except for comparators: didn't before and is not state that's\r
-        * configurable by users ... so probably won't, not sure, need to revisit -\r
-        * comparator (?) - filters (?) - renderers/stringvalues (?) - enhanced\r
-        * sort-related table state (?)\r
-        */\r
-       public void registerPersistenceDelegates() {\r
-               XMLEncoder encoder = new XMLEncoder(System.out);\r
-               encoder.setPersistenceDelegate(SortKeyState.class, new DefaultPersistenceDelegate(\r
-                               new String[] { "ascending", "modelIndex" }));\r
-               encoder.setPersistenceDelegate(ColumnState.class, new DefaultPersistenceDelegate(\r
-                               new String[] { "width", "preferredWidth", "modelIndex", "visible", "viewIndex" }));\r
-               encoder.setPersistenceDelegate(XTableState.class, new DefaultPersistenceDelegate(\r
-                               new String[] { "columnStates", "sortKeyState", "horizontalScrollEnabled" }));\r
-       }\r
-\r
-       /**\r
-        * Session storage support for JXTaskPane.\r
-        */\r
-       public static class XTaskPaneProperty implements Serializable {\r
-\r
-               private static final long serialVersionUID = -4069436038178318216L;\r
-\r
-               public Object getSessionState(Component c) {\r
-                       checkComponent(c);\r
-                       return new XTaskPaneState(((JXTaskPane) c).isCollapsed());\r
-               }\r
-\r
-               public void setSessionState(Component c, Object state) {\r
-                       checkComponent(c);\r
-                       if ((state != null) && !(state instanceof XTaskPaneState)) {\r
-                               throw new IllegalArgumentException("invalid state");\r
-                       }\r
-                       ((JXTaskPane) c).setCollapsed(((XTaskPaneState) state).isCollapsed());\r
-               }\r
-\r
-               private void checkComponent(Component component) {\r
-                       if (component == null) {\r
-                               throw new IllegalArgumentException("null component");\r
-                       }\r
-                       if (!(component instanceof JXTaskPane)) {\r
-                               throw new IllegalArgumentException("invalid component");\r
-                       }\r
-               }\r
-\r
-       }\r
-\r
-       public static class XTaskPaneState implements Serializable {\r
-               private static final long serialVersionUID = 3363688961112031969L;\r
-               private boolean collapsed;\r
-\r
-               public XTaskPaneState() {\r
-                       this(false);\r
-               }\r
-\r
-               /**\r
-                * @param b\r
-                */\r
-               public XTaskPaneState(boolean collapsed) {\r
-                       this.setCollapsed(collapsed);\r
-               }\r
-\r
-               /**\r
-                * @param collapsed\r
-                *            the collapsed to set\r
-                */\r
-               public void setCollapsed(boolean collapsed) {\r
-                       this.collapsed = collapsed;\r
-               }\r
-\r
-               /**\r
-                * @return the collapsed\r
-                */\r
-               public boolean isCollapsed() {\r
-                       return collapsed;\r
-               }\r
-\r
-       }\r
-\r
-       /**\r
-        * Session storage support for JXTable.\r
-        */\r
-       public static class XTableProperty implements Serializable {\r
-\r
-               private static final long serialVersionUID = -5064142292091374301L;\r
-\r
-               public Object getSessionState(Component c) {\r
-                       checkComponent(c);\r
-                       JXTable table = (JXTable) c;\r
-                       List<ColumnState> columnStates = new ArrayList<ColumnState>();\r
-                       List<TableColumn> columns = table.getColumns(true);\r
-                       List<TableColumn> visibleColumns = table.getColumns();\r
-                       for (TableColumn column : columns) {\r
-                               columnStates.add(new ColumnState((TableColumnExt) column, visibleColumns\r
-                                               .indexOf(column)));\r
-                       }\r
-                       XTableState tableState = new XTableState(columnStates\r
-                                       .toArray(new ColumnState[columnStates.size()]));\r
-                       tableState.setHorizontalScrollEnabled(table.isHorizontalScrollEnabled());\r
-                       List<? extends SortKey> sortKeys = null;\r
-                       if (table.getRowSorter() != null) {\r
-                               sortKeys = table.getRowSorter().getSortKeys();\r
-                       }\r
-                       // PENDING: store all!\r
-                       if ((sortKeys != null) && (sortKeys.size() > 0)) {\r
-                               tableState.setSortKey(sortKeys.get(0));\r
-                       }\r
-                       return tableState;\r
-               }\r
-\r
-               public void setSessionState(Component c, Object state) {\r
-                       checkComponent(c);\r
-                       JXTable table = (JXTable) c;\r
-                       XTableState tableState = ((XTableState) state);\r
-                       ColumnState[] columnState = tableState.getColumnStates();\r
-                       List<TableColumn> columns = table.getColumns(true);\r
-                       if (canRestore(columnState, columns)) {\r
-                               for (int i = 0; i < columnState.length; i++) {\r
-                                       columnState[i].configureColumn((TableColumnExt) columns.get(i));\r
-                               }\r
-                               restoreVisibleSequence(columnState, table.getColumnModel());\r
-                       }\r
-                       table.setHorizontalScrollEnabled(tableState.getHorizontalScrollEnabled());\r
-                       if (tableState.getSortKey() != null) {\r
-                               table.getRowSorter()\r
-                                               .setSortKeys(Collections.singletonList(tableState.getSortKey()));\r
-                       }\r
-               }\r
-\r
-               private void restoreVisibleSequence(ColumnState[] columnStates, TableColumnModel model) {\r
-                       List<ColumnState> visibleStates = getSortedVisibleColumnStates(columnStates);\r
-                       for (int i = 0; i < visibleStates.size(); i++) {\r
-                               TableColumn column = model.getColumn(i);\r
-                               int modelIndex = visibleStates.get(i).getModelIndex();\r
-                               if (modelIndex != column.getModelIndex()) {\r
-                                       int currentIndex = -1;\r
-                                       for (int j = i + 1; j < model.getColumnCount(); j++) {\r
-                                               TableColumn current = model.getColumn(j);\r
-                                               if (current.getModelIndex() == modelIndex) {\r
-                                                       currentIndex = j;\r
-                                                       break;\r
-                                               }\r
-                                       }\r
-                                       model.moveColumn(currentIndex, i);\r
-                               }\r
-                       }\r
-\r
-               }\r
-\r
-               private List<ColumnState> getSortedVisibleColumnStates(ColumnState[] columnStates) {\r
-                       List<ColumnState> visibleStates = new ArrayList<ColumnState>();\r
-                       for (ColumnState columnState : columnStates) {\r
-                               if (columnState.getVisible()) {\r
-                                       visibleStates.add(columnState);\r
-                               }\r
-                       }\r
-                       Collections.sort(visibleStates, new VisibleColumnIndexComparator());\r
-                       return visibleStates;\r
-               }\r
-\r
-               /**\r
-                * Returns a boolean to indicate if it's reasonably safe to restore the\r
-                * properties of columns in the list from the columnStates. Here:\r
-                * returns true if the length of both are the same and the modelIndex of\r
-                * the items at the same position are the same, otherwise returns false.\r
-                * \r
-                * @param columnState\r
-                * @param columns\r
-                * @return\r
-                */\r
-               private boolean canRestore(ColumnState[] columnState, List<TableColumn> columns) {\r
-                       if ((columnState == null) || (columnState.length != columns.size()))\r
-                               return false;\r
-                       for (int i = 0; i < columnState.length; i++) {\r
-                               if (columnState[i].getModelIndex() != columns.get(i).getModelIndex()) {\r
-                                       return false;\r
-                               }\r
-                       }\r
-                       return true;\r
-               }\r
-\r
-               private void checkComponent(Component component) {\r
-                       if (component == null) {\r
-                               throw new IllegalArgumentException("null component");\r
-                       }\r
-                       if (!(component instanceof JXTable)) {\r
-                               throw new IllegalArgumentException("invalid component - expected JXTable");\r
-                       }\r
-               }\r
-\r
-       }\r
-\r
-       public static class XTableState implements Serializable {\r
-               private static final long serialVersionUID = -3566913244872587438L;\r
-               ColumnState[] columnStates = new ColumnState[0];\r
-               boolean horizontalScrollEnabled;\r
-               SortKeyState sortKeyState;\r
-\r
-               public XTableState(ColumnState[] columnStates, SortKeyState sortKeyState,\r
-                               boolean horizontalScrollEnabled) {\r
-                       this.columnStates = copyColumnStates(columnStates);\r
-                       this.sortKeyState = sortKeyState;\r
-                       setHorizontalScrollEnabled(horizontalScrollEnabled);\r
-\r
-               }\r
-\r
-               public void setSortKey(SortKey sortKey) {\r
-                       this.sortKeyState = new SortKeyState(sortKey);\r
-\r
-               }\r
-\r
-               private SortKey getSortKey() {\r
-                       if (sortKeyState != null) {\r
-                               return sortKeyState.getSortKey();\r
-                       }\r
-                       return null;\r
-               }\r
-\r
-               public XTableState(ColumnState[] columnStates) {\r
-                       this.columnStates = copyColumnStates(columnStates);\r
-               }\r
-\r
-               public ColumnState[] getColumnStates() {\r
-                       return copyColumnStates(this.columnStates);\r
-               }\r
-\r
-               public boolean getHorizontalScrollEnabled() {\r
-                       return horizontalScrollEnabled;\r
-               }\r
-\r
-               public void setHorizontalScrollEnabled(boolean horizontalScrollEnabled) {\r
-                       this.horizontalScrollEnabled = horizontalScrollEnabled;\r
-               }\r
-\r
-               private ColumnState[] copyColumnStates(ColumnState[] states) {\r
-                       if (states == null) {\r
-                               throw new IllegalArgumentException("invalid columnWidths");\r
-                       }\r
-                       ColumnState[] copy = new ColumnState[states.length];\r
-                       System.arraycopy(states, 0, copy, 0, states.length);\r
-                       return copy;\r
-               }\r
-\r
-               public SortKeyState getSortKeyState() {\r
-                       return sortKeyState;\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Quick hack to make SortKey encodable. How to write a PersistenceDelegate\r
-        * for a SortKey? Boils down to how to write a delegate for the\r
-        * uninstantiable class (SwingX) SortOrder which does enum-mimickry (defines\r
-        * privately intantiated constants)\r
-        * \r
-        */\r
-       public static class SortKeyState implements Serializable {\r
-               private static final long serialVersionUID = 5819342622261460894L;\r
-\r
-               int modelIndex;\r
-\r
-               boolean ascending;\r
-\r
-               /**\r
-                * Constructor used by the custom PersistenceDelegate.\r
-                * \r
-                * @param ascending\r
-                * @param modelIndex\r
-                * @param comparator\r
-                */\r
-               public SortKeyState(boolean ascending, int modelIndex) {\r
-                       this.ascending = ascending;\r
-                       this.modelIndex = modelIndex;\r
-               }\r
-\r
-               /**\r
-                * Constructor used by property.\r
-                * \r
-                * @param sortKey\r
-                */\r
-               public SortKeyState(SortKey sortKey) {\r
-                       this(SortUtils.isAscending(sortKey.getSortOrder()), sortKey.getColumn());\r
-               }\r
-\r
-               protected SortKey getSortKey() {\r
-                       SortOrder sortOrder = getAscending() ? SortOrder.ASCENDING : SortOrder.DESCENDING;\r
-                       return new SortKey(getModelIndex(), sortOrder);\r
-               }\r
-\r
-               public boolean getAscending() {\r
-                       return ascending;\r
-               }\r
-\r
-               public int getModelIndex() {\r
-                       return modelIndex;\r
-               }\r
-       }\r
-\r
-       public static class ColumnState implements Serializable {\r
-               private static final long serialVersionUID = 6037947151025126049L;\r
-               private int width;\r
-               private int preferredWidth;\r
-               private int modelIndex;\r
-               private boolean visible;\r
-               private int viewIndex;\r
-\r
-               /**\r
-                * Constructor used by the custom PersistenceDelegate.\r
-                * \r
-                * @param width\r
-                * @param preferredWidth\r
-                * @param modelColumn\r
-                * @param visible\r
-                * @param viewIndex\r
-                */\r
-               public ColumnState(int width, int preferredWidth, int modelColumn, boolean visible,\r
-                               int viewIndex) {\r
-                       this.width = width;\r
-                       this.preferredWidth = preferredWidth;\r
-                       this.modelIndex = modelColumn;\r
-                       this.visible = visible;\r
-                       this.viewIndex = viewIndex;\r
-               }\r
-\r
-               /**\r
-                * Constructor used by the Property.\r
-                * \r
-                * @param columnExt\r
-                * @param viewIndex\r
-                */\r
-               public ColumnState(TableColumnExt columnExt, int viewIndex) {\r
-                       this(columnExt.getWidth(), columnExt.getPreferredWidth(), columnExt.getModelIndex(),\r
-                                       columnExt.isVisible(), viewIndex);\r
-               }\r
-\r
-               /**\r
-                * Restores column properties if the model index is the same as the\r
-                * column's model index. Does nothing otherwise.\r
-                * <p>\r
-                * \r
-                * Here the properties are: width, preferredWidth, visible.\r
-                * \r
-                * @param columnExt\r
-                *            the column to configure\r
-                */\r
-               public void configureColumn(TableColumnExt columnExt) {\r
-                       if (modelIndex != columnExt.getModelIndex())\r
-                               return;\r
-                       columnExt.setPreferredWidth(preferredWidth);\r
-                       columnExt.setWidth(width);\r
-                       columnExt.setVisible(visible);\r
-               }\r
-\r
-               public int getModelIndex() {\r
-                       return modelIndex;\r
-               }\r
-\r
-               public int getViewIndex() {\r
-                       return viewIndex;\r
-               }\r
-\r
-               public boolean getVisible() {\r
-                       return visible;\r
-               }\r
-\r
-               public int getWidth() {\r
-                       return width;\r
-               }\r
-\r
-               public int getPreferredWidth() {\r
-                       return preferredWidth;\r
-               }\r
-\r
-       }\r
-\r
-       public static class VisibleColumnIndexComparator implements Comparator<Object> {\r
-\r
-               public int compare(Object o1, Object o2) {\r
-                       return ((ColumnState) o1).getViewIndex() - ((ColumnState) o2).getViewIndex();\r
-               }\r
-\r
-       }\r
-\r
-}\r
+/*
+ * Created on 08.02.2007
+ *
+ */
+package com.nexuiz.demorecorder.ui.swinggui.utils;
+
+import java.awt.Component;
+import java.beans.DefaultPersistenceDelegate;
+import java.beans.XMLEncoder;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import javax.swing.SortOrder;
+import javax.swing.RowSorter.SortKey;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
+
+import org.jdesktop.swingx.JXTable;
+import org.jdesktop.swingx.JXTaskPane;
+import org.jdesktop.swingx.sort.SortUtils;
+import org.jdesktop.swingx.table.TableColumnExt;
+
+/**
+ * Container class for SwingX specific SessionStorage Properties. Is Factory for
+ * custom PersistanceDelegates
+ */
+public class XProperties {
+
+       /**
+        * 
+        * Registers all custom PersistenceDelegates needed by contained Property
+        * classes.
+        * <p>
+        * 
+        * PersistenceDelegates are effectively static properties shared by all
+        * encoders. In other words: Register once on an arbitrary encoder makes
+        * them available for all. Example usage:
+        * 
+        * <pre>
+        * <code>
+        * new XProperties.registerPersistenceDelegates();
+        * </code>
+        * </pre>
+        * 
+        * PENDING JW: cleanup for 1.6 sorting/filtering incomplete. Missing storage
+        * - multiple sort keys
+        * 
+        * PENDING JW: except for comparators: didn't before and is not state that's
+        * configurable by users ... so probably won't, not sure, need to revisit -
+        * comparator (?) - filters (?) - renderers/stringvalues (?) - enhanced
+        * sort-related table state (?)
+        */
+       public void registerPersistenceDelegates() {
+               XMLEncoder encoder = new XMLEncoder(System.out);
+               encoder.setPersistenceDelegate(SortKeyState.class, new DefaultPersistenceDelegate(
+                               new String[] { "ascending", "modelIndex" }));
+               encoder.setPersistenceDelegate(ColumnState.class, new DefaultPersistenceDelegate(
+                               new String[] { "width", "preferredWidth", "modelIndex", "visible", "viewIndex" }));
+               encoder.setPersistenceDelegate(XTableState.class, new DefaultPersistenceDelegate(
+                               new String[] { "columnStates", "sortKeyState", "horizontalScrollEnabled" }));
+       }
+
+       /**
+        * Session storage support for JXTaskPane.
+        */
+       public static class XTaskPaneProperty implements Serializable {
+
+               private static final long serialVersionUID = -4069436038178318216L;
+
+               public Object getSessionState(Component c) {
+                       checkComponent(c);
+                       return new XTaskPaneState(((JXTaskPane) c).isCollapsed());
+               }
+
+               public void setSessionState(Component c, Object state) {
+                       checkComponent(c);
+                       if ((state != null) && !(state instanceof XTaskPaneState)) {
+                               throw new IllegalArgumentException("invalid state");
+                       }
+                       ((JXTaskPane) c).setCollapsed(((XTaskPaneState) state).isCollapsed());
+               }
+
+               private void checkComponent(Component component) {
+                       if (component == null) {
+                               throw new IllegalArgumentException("null component");
+                       }
+                       if (!(component instanceof JXTaskPane)) {
+                               throw new IllegalArgumentException("invalid component");
+                       }
+               }
+
+       }
+
+       public static class XTaskPaneState implements Serializable {
+               private static final long serialVersionUID = 3363688961112031969L;
+               private boolean collapsed;
+
+               public XTaskPaneState() {
+                       this(false);
+               }
+
+               /**
+                * @param b
+                */
+               public XTaskPaneState(boolean collapsed) {
+                       this.setCollapsed(collapsed);
+               }
+
+               /**
+                * @param collapsed
+                *            the collapsed to set
+                */
+               public void setCollapsed(boolean collapsed) {
+                       this.collapsed = collapsed;
+               }
+
+               /**
+                * @return the collapsed
+                */
+               public boolean isCollapsed() {
+                       return collapsed;
+               }
+
+       }
+
+       /**
+        * Session storage support for JXTable.
+        */
+       public static class XTableProperty implements Serializable {
+
+               private static final long serialVersionUID = -5064142292091374301L;
+
+               public Object getSessionState(Component c) {
+                       checkComponent(c);
+                       JXTable table = (JXTable) c;
+                       List<ColumnState> columnStates = new ArrayList<ColumnState>();
+                       List<TableColumn> columns = table.getColumns(true);
+                       List<TableColumn> visibleColumns = table.getColumns();
+                       for (TableColumn column : columns) {
+                               columnStates.add(new ColumnState((TableColumnExt) column, visibleColumns
+                                               .indexOf(column)));
+                       }
+                       XTableState tableState = new XTableState(columnStates
+                                       .toArray(new ColumnState[columnStates.size()]));
+                       tableState.setHorizontalScrollEnabled(table.isHorizontalScrollEnabled());
+                       List<? extends SortKey> sortKeys = null;
+                       if (table.getRowSorter() != null) {
+                               sortKeys = table.getRowSorter().getSortKeys();
+                       }
+                       // PENDING: store all!
+                       if ((sortKeys != null) && (sortKeys.size() > 0)) {
+                               tableState.setSortKey(sortKeys.get(0));
+                       }
+                       return tableState;
+               }
+
+               public void setSessionState(Component c, Object state) {
+                       checkComponent(c);
+                       JXTable table = (JXTable) c;
+                       XTableState tableState = ((XTableState) state);
+                       ColumnState[] columnState = tableState.getColumnStates();
+                       List<TableColumn> columns = table.getColumns(true);
+                       if (canRestore(columnState, columns)) {
+                               for (int i = 0; i < columnState.length; i++) {
+                                       columnState[i].configureColumn((TableColumnExt) columns.get(i));
+                               }
+                               restoreVisibleSequence(columnState, table.getColumnModel());
+                       }
+                       table.setHorizontalScrollEnabled(tableState.getHorizontalScrollEnabled());
+                       if (tableState.getSortKey() != null) {
+                               table.getRowSorter()
+                                               .setSortKeys(Collections.singletonList(tableState.getSortKey()));
+                       }
+               }
+
+               private void restoreVisibleSequence(ColumnState[] columnStates, TableColumnModel model) {
+                       List<ColumnState> visibleStates = getSortedVisibleColumnStates(columnStates);
+                       for (int i = 0; i < visibleStates.size(); i++) {
+                               TableColumn column = model.getColumn(i);
+                               int modelIndex = visibleStates.get(i).getModelIndex();
+                               if (modelIndex != column.getModelIndex()) {
+                                       int currentIndex = -1;
+                                       for (int j = i + 1; j < model.getColumnCount(); j++) {
+                                               TableColumn current = model.getColumn(j);
+                                               if (current.getModelIndex() == modelIndex) {
+                                                       currentIndex = j;
+                                                       break;
+                                               }
+                                       }
+                                       model.moveColumn(currentIndex, i);
+                               }
+                       }
+
+               }
+
+               private List<ColumnState> getSortedVisibleColumnStates(ColumnState[] columnStates) {
+                       List<ColumnState> visibleStates = new ArrayList<ColumnState>();
+                       for (ColumnState columnState : columnStates) {
+                               if (columnState.getVisible()) {
+                                       visibleStates.add(columnState);
+                               }
+                       }
+                       Collections.sort(visibleStates, new VisibleColumnIndexComparator());
+                       return visibleStates;
+               }
+
+               /**
+                * Returns a boolean to indicate if it's reasonably safe to restore the
+                * properties of columns in the list from the columnStates. Here:
+                * returns true if the length of both are the same and the modelIndex of
+                * the items at the same position are the same, otherwise returns false.
+                * 
+                * @param columnState
+                * @param columns
+                * @return
+                */
+               private boolean canRestore(ColumnState[] columnState, List<TableColumn> columns) {
+                       if ((columnState == null) || (columnState.length != columns.size()))
+                               return false;
+                       for (int i = 0; i < columnState.length; i++) {
+                               if (columnState[i].getModelIndex() != columns.get(i).getModelIndex()) {
+                                       return false;
+                               }
+                       }
+                       return true;
+               }
+
+               private void checkComponent(Component component) {
+                       if (component == null) {
+                               throw new IllegalArgumentException("null component");
+                       }
+                       if (!(component instanceof JXTable)) {
+                               throw new IllegalArgumentException("invalid component - expected JXTable");
+                       }
+               }
+
+       }
+
+       public static class XTableState implements Serializable {
+               private static final long serialVersionUID = -3566913244872587438L;
+               ColumnState[] columnStates = new ColumnState[0];
+               boolean horizontalScrollEnabled;
+               SortKeyState sortKeyState;
+
+               public XTableState(ColumnState[] columnStates, SortKeyState sortKeyState,
+                               boolean horizontalScrollEnabled) {
+                       this.columnStates = copyColumnStates(columnStates);
+                       this.sortKeyState = sortKeyState;
+                       setHorizontalScrollEnabled(horizontalScrollEnabled);
+
+               }
+
+               public void setSortKey(SortKey sortKey) {
+                       this.sortKeyState = new SortKeyState(sortKey);
+
+               }
+
+               private SortKey getSortKey() {
+                       if (sortKeyState != null) {
+                               return sortKeyState.getSortKey();
+                       }
+                       return null;
+               }
+
+               public XTableState(ColumnState[] columnStates) {
+                       this.columnStates = copyColumnStates(columnStates);
+               }
+
+               public ColumnState[] getColumnStates() {
+                       return copyColumnStates(this.columnStates);
+               }
+
+               public boolean getHorizontalScrollEnabled() {
+                       return horizontalScrollEnabled;
+               }
+
+               public void setHorizontalScrollEnabled(boolean horizontalScrollEnabled) {
+                       this.horizontalScrollEnabled = horizontalScrollEnabled;
+               }
+
+               private ColumnState[] copyColumnStates(ColumnState[] states) {
+                       if (states == null) {
+                               throw new IllegalArgumentException("invalid columnWidths");
+                       }
+                       ColumnState[] copy = new ColumnState[states.length];
+                       System.arraycopy(states, 0, copy, 0, states.length);
+                       return copy;
+               }
+
+               public SortKeyState getSortKeyState() {
+                       return sortKeyState;
+               }
+       }
+
+       /**
+        * Quick hack to make SortKey encodable. How to write a PersistenceDelegate
+        * for a SortKey? Boils down to how to write a delegate for the
+        * uninstantiable class (SwingX) SortOrder which does enum-mimickry (defines
+        * privately intantiated constants)
+        * 
+        */
+       public static class SortKeyState implements Serializable {
+               private static final long serialVersionUID = 5819342622261460894L;
+
+               int modelIndex;
+
+               boolean ascending;
+
+               /**
+                * Constructor used by the custom PersistenceDelegate.
+                * 
+                * @param ascending
+                * @param modelIndex
+                * @param comparator
+                */
+               public SortKeyState(boolean ascending, int modelIndex) {
+                       this.ascending = ascending;
+                       this.modelIndex = modelIndex;
+               }
+
+               /**
+                * Constructor used by property.
+                * 
+                * @param sortKey
+                */
+               public SortKeyState(SortKey sortKey) {
+                       this(SortUtils.isAscending(sortKey.getSortOrder()), sortKey.getColumn());
+               }
+
+               protected SortKey getSortKey() {
+                       SortOrder sortOrder = getAscending() ? SortOrder.ASCENDING : SortOrder.DESCENDING;
+                       return new SortKey(getModelIndex(), sortOrder);
+               }
+
+               public boolean getAscending() {
+                       return ascending;
+               }
+
+               public int getModelIndex() {
+                       return modelIndex;
+               }
+       }
+
+       public static class ColumnState implements Serializable {
+               private static final long serialVersionUID = 6037947151025126049L;
+               private int width;
+               private int preferredWidth;
+               private int modelIndex;
+               private boolean visible;
+               private int viewIndex;
+
+               /**
+                * Constructor used by the custom PersistenceDelegate.
+                * 
+                * @param width
+                * @param preferredWidth
+                * @param modelColumn
+                * @param visible
+                * @param viewIndex
+                */
+               public ColumnState(int width, int preferredWidth, int modelColumn, boolean visible,
+                               int viewIndex) {
+                       this.width = width;
+                       this.preferredWidth = preferredWidth;
+                       this.modelIndex = modelColumn;
+                       this.visible = visible;
+                       this.viewIndex = viewIndex;
+               }
+
+               /**
+                * Constructor used by the Property.
+                * 
+                * @param columnExt
+                * @param viewIndex
+                */
+               public ColumnState(TableColumnExt columnExt, int viewIndex) {
+                       this(columnExt.getWidth(), columnExt.getPreferredWidth(), columnExt.getModelIndex(),
+                                       columnExt.isVisible(), viewIndex);
+               }
+
+               /**
+                * Restores column properties if the model index is the same as the
+                * column's model index. Does nothing otherwise.
+                * <p>
+                * 
+                * Here the properties are: width, preferredWidth, visible.
+                * 
+                * @param columnExt
+                *            the column to configure
+                */
+               public void configureColumn(TableColumnExt columnExt) {
+                       if (modelIndex != columnExt.getModelIndex())
+                               return;
+                       columnExt.setPreferredWidth(preferredWidth);
+                       columnExt.setWidth(width);
+                       columnExt.setVisible(visible);
+               }
+
+               public int getModelIndex() {
+                       return modelIndex;
+               }
+
+               public int getViewIndex() {
+                       return viewIndex;
+               }
+
+               public boolean getVisible() {
+                       return visible;
+               }
+
+               public int getWidth() {
+                       return width;
+               }
+
+               public int getPreferredWidth() {
+                       return preferredWidth;
+               }
+
+       }
+
+       public static class VisibleColumnIndexComparator implements Comparator<Object> {
+
+               public int compare(Object o1, Object o2) {
+                       return ((ColumnState) o1).getViewIndex() - ((ColumnState) o2).getViewIndex();
+               }
+
+       }
+
+}
index c831190be84e16fad143952a8a1da6a3735762f0..79be8db739e2e7e45b9a2647464e1f3eab147b13 100644 (file)
@@ -1,21 +1,21 @@
-<html>\r
-    <h1>Nexuiz Demo Recorder v0.3</h1>\r
-    Written by GreEn`mArine<br>\r
-    <h2>Credits</h2>\r
-    <ul>\r
-       <li>\r
-               Thanks for the used icons goes to<br>\r
-               <a href="http://www.everaldo.com/crystal/?action=downloads">http://www.everaldo.com/crystal/?action=downloads</a>\r
-               </li>\r
-               <li>\r
-                       Thanks to divVerent for the demotc.pl script without which<br>\r
-                       this project would have been impossible.\r
-               </li>\r
-    </ul>\r
-    \r
-    <h2>Usage</h2>\r
-    Open the help in order to find out how to use this software.\r
-    \r
-    <h2>License</h2>\r
-    GPL v2\r
+<html>
+    <h1>Nexuiz Demo Recorder v0.3</h1>
+    Written by GreEn`mArine<br>
+    <h2>Credits</h2>
+    <ul>
+       <li>
+               Thanks for the used icons goes to<br>
+               <a href="http://www.everaldo.com/crystal/?action=downloads">http://www.everaldo.com/crystal/?action=downloads</a>
+               </li>
+               <li>
+                       Thanks to divVerent for the demotc.pl script without which<br>
+                       this project would have been impossible.
+               </li>
+    </ul>
+    
+    <h2>Usage</h2>
+    Open the help in order to find out how to use this software.
+    
+    <h2>License</h2>
+    GPL v2
 </html>
\ No newline at end of file
index 2bd2efdd27e34f34e81d08bbe72cf8de10ad92b1..a3fa4934c16b6023cfad8296fa0e7bed16798f6a 100644 (file)
@@ -1,42 +1,42 @@
-<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>\r
-<!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.net--><!DOCTYPE helpset PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp HelpSet Version 2.0//EN" "http://java.sun.com/products/javahelp/helpset_2_0.dtd">\r
-\r
-<helpset version="1.0">\r
- <title>Demo Recorder Help </title>\r
- <maps>\r
-  <homeID>\r
-top\r
-  </homeID>\r
-  <mapref location="Map.jhm"/>\r
-\r
- </maps>\r
- <view>\r
-  <name>\r
-TOC\r
-  </name>\r
-  <label>\r
-TOC \r
-  </label>\r
-  <type>\r
-javax.help.TOCView\r
-  </type>\r
-  <data>\r
-DemoRecorderHelpTOC.xml\r
-  </data>\r
- </view>\r
\r
- <view>\r
-  <name>\r
-Search\r
-  </name>\r
-  <label>\r
-Search \r
-  </label>\r
-  <type>\r
-javax.help.SearchView\r
-  </type>\r
-  <data engine="com.sun.java.help.search.DefaultSearchEngine">\r
-JavaHelpSearch\r
-  </data>\r
- </view>\r
-</helpset>\r
+<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
+<!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.net--><!DOCTYPE helpset PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp HelpSet Version 2.0//EN" "http://java.sun.com/products/javahelp/helpset_2_0.dtd">
+
+<helpset version="1.0">
+ <title>Demo Recorder Help </title>
+ <maps>
+  <homeID>
+top
+  </homeID>
+  <mapref location="Map.jhm"/>
+
+ </maps>
+ <view>
+  <name>
+TOC
+  </name>
+  <label>
+TOC 
+  </label>
+  <type>
+javax.help.TOCView
+  </type>
+  <data>
+DemoRecorderHelpTOC.xml
+  </data>
+ </view>
+ <view>
+  <name>
+Search
+  </name>
+  <label>
+Search 
+  </label>
+  <type>
+javax.help.SearchView
+  </type>
+  <data engine="com.sun.java.help.search.DefaultSearchEngine">
+JavaHelpSearch
+  </data>
+ </view>
+</helpset>
index e2b16916fa1e89a2e993ce0ae29638fe30eece8f..e4cd27998f625e7c11184ef2e1dc309ad08fea5d 100644 (file)
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>\r
-<!DOCTYPE index PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Index Version 1.0//EN" "http://java.sun.com/products/javahelp/index_2_0.dtd"><!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.net-->\r
-\r
-<index version="1.0"/>\r
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE index PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Index Version 1.0//EN" "http://java.sun.com/products/javahelp/index_2_0.dtd"><!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.net-->
+
+<index version="1.0"/>
index d2581f4c2a9e6e7479426a2a1b6918aa03bb6c48..59826911c91b3078739acfebe9250e06ffbf76e1 100644 (file)
@@ -1,39 +1,39 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>\r
-<!DOCTYPE toc PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp TOC Version 1.0//EN" "http://java.sun.com/products/javahelp/toc_2_0.dtd"><!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.net-->\r
-\r
-<toc version="1.0">\r
- <tocitem text="Introduction " target="html.introduction"/>\r
-\r
- <tocitem text="Basic tutorial " target="html.basic_tutorial"/>\r
-\r
- <tocitem text="Templates " target="html.templates"/>\r
-\r
- <tocitem text="Applying templates " target="html.apply_templates"/>\r
-\r
- <tocitem text="Open/Save Jobs and Templates " target="html.open-save"/>\r
-\r
- <tocitem text="Preferences dialog " target="html.preferences"/>\r
-\r
- <tocitem text="Compatibility and limitations " target="html.compat-limitations"/>\r
-\r
- <tocitem text="FAQ " target="html.faq"/>\r
-\r
- <tocitem text="Advanced user topics " target="html.advanced-topics">\r
-  <tocitem text="How it works " target="html.advanced-how-it-works"/>\r
-\r
-  <tocitem text="Table settings " target="html.advanced-table-settings"/>\r
-\r
-  <tocitem text="Preliminary stop " target="html.advanced-prelim-stop"/>\r
-\r
- </tocitem>\r
- <tocitem text="Plug-in architecture " target="html.plugin-architecture">\r
-  <tocitem text="VirtualDub plug-in " target="html.plugin-virtualdub"/>\r
-\r
- </tocitem>\r
- <tocitem text="Credits " target="html.credits"/>\r
-\r
- <tocitem text="License " target="html.license"/>\r
-\r
- <tocitem text="Changelog " target="html.changelog"/>\r
-\r
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE toc PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp TOC Version 1.0//EN" "http://java.sun.com/products/javahelp/toc_2_0.dtd"><!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.net-->
+
+<toc version="1.0">
+ <tocitem text="Introduction " target="html.introduction"/>
+
+ <tocitem text="Basic tutorial " target="html.basic_tutorial"/>
+
+ <tocitem text="Templates " target="html.templates"/>
+
+ <tocitem text="Applying templates " target="html.apply_templates"/>
+
+ <tocitem text="Open/Save Jobs and Templates " target="html.open-save"/>
+
+ <tocitem text="Preferences dialog " target="html.preferences"/>
+
+ <tocitem text="Compatibility and limitations " target="html.compat-limitations"/>
+
+ <tocitem text="FAQ " target="html.faq"/>
+
+ <tocitem text="Advanced user topics " target="html.advanced-topics">
+  <tocitem text="How it works " target="html.advanced-how-it-works"/>
+
+  <tocitem text="Table settings " target="html.advanced-table-settings"/>
+
+  <tocitem text="Preliminary stop " target="html.advanced-prelim-stop"/>
+
+ </tocitem>
+ <tocitem text="Plug-in architecture " target="html.plugin-architecture">
+  <tocitem text="VirtualDub plug-in " target="html.plugin-virtualdub"/>
+
+ </tocitem>
+ <tocitem text="Credits " target="html.credits"/>
+
+ <tocitem text="License " target="html.license"/>
+
+ <tocitem text="Changelog " target="html.changelog"/>
+
 </toc>
\ No newline at end of file
index e8611a2126601520259c3a1854961f20c3e6b986..42f6bb27fa83ddf4f44d27503c6e3b6a37c53871 100644 (file)
@@ -1,32 +1,32 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>\r
-<!DOCTYPE jhelpdev PUBLIC "-//jhelpdev.sourcefore.net//JHelpDev Configuration Settings 1.2" "http://jhelpdev.sourceforge.net/dtd/jhelpdev_1_2.dtd"><!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.org-->\r
-\r
-<jhelpdev version="1.2">\r
- <config>\r
-  <project>\r
-DemoRecorderHelp\r
-  </project>\r
-  <projectdir>\r
-D:\Daten\Eclipse Projects\NexuizDemoRecorder\src\main\resources\help\r
-  </projectdir>\r
-  <startpage>\r
-html/introduction.html\r
-  </startpage>\r
-  <popupicon>\r
-\r
-  </popupicon>\r
- </config>\r
- <view>\r
-  <helptitle>\r
-Demo Recorder Help\r
-  </helptitle>\r
-  <toc showing="yes" label="Table of Contents"/>\r
-\r
-  <index showing="no" label="Index"/>\r
-\r
-  <search showing="yes" label="Search"/>\r
-\r
- </view>\r
- <encoding value="ISO-8859-1"/>\r
-\r
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE jhelpdev PUBLIC "-//jhelpdev.sourcefore.net//JHelpDev Configuration Settings 1.2" "http://jhelpdev.sourceforge.net/dtd/jhelpdev_1_2.dtd"><!--generated by JHelpDev Version: 0.63, 14 May 2008, see jhelpdev.sourceforge.org-->
+
+<jhelpdev version="1.2">
+ <config>
+  <project>
+DemoRecorderHelp
+  </project>
+  <projectdir>
+D:\Daten\Eclipse Projects\NexuizDemoRecorder\src\main\resources\help
+  </projectdir>
+  <startpage>
+html/introduction.html
+  </startpage>
+  <popupicon>
+
+  </popupicon>
+ </config>
+ <view>
+  <helptitle>
+Demo Recorder Help
+  </helptitle>
+  <toc showing="yes" label="Table of Contents"/>
+
+  <index showing="no" label="Index"/>
+
+  <search showing="yes" label="Search"/>
+
+ </view>
+ <encoding value="ISO-8859-1"/>
+
 </jhelpdev>
\ No newline at end of file
index 4597748cafaf1b6ecbc3082e2ba3c85756b4d138..6926abd397ab41b41b86deb634704900aee4df24 100644 (file)
@@ -1,77 +1,77 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\r
-\r
-<html>\r
-  <head>\r
-    <meta name="generator" content="HTML Tidy, see www.w3.org">\r
-\r
-    <title></title>\r
-  </head>\r
-\r
-  <body>\r
-    <H1>How it works</H1>\r
-<P>The following is a description of how\r
-the program processes a single job:</P>\r
-<UL>\r
-       <LI><P>The program takes your original\r
-       demo, e.g. &ldquo;C:\Nexuiz\data\demos\test.dem&rdquo; and creates a\r
-       copy of it, the <I>cut demo</I>. This file has the name\r
-       &lt;original_demo_name&gt;<B>_autocut</B>.dem. The cut demo is\r
-       different to the original demo in a way that console commands have\r
-       been <I>injected</I>, so basically the engine thinks that you had\r
-       entered them yourself (e.g. cl_capturevideo 1 to start recording).</P>\r
-       <UL>\r
-               <LI><P>Since it is possible to inject\r
-               any possible console command, the first command that is injected is\r
-               to disable rendering (r_render 0), save the value of your &ldquo;volume&rdquo;\r
-               setting and then setting it to 0 (as long as you have sound and\r
-               rendering disabled while fast-forwarding in the preferences of the\r
-               Demo Recorder). Then a slowmo 100 command (first stage value from\r
-               the Demo Recorder preferences) is injected in order to fast-forward\r
-               the demo.</P>\r
-               <LI><P>Then, when the game time in the\r
-               demo is about 1 minute less than your specified start time of the\r
-               job, slowmo is reduced to 10 (second stage value from the\r
-               preferences)</P>\r
-               <LI><P>Then, when the game time in the\r
-               demo is about 5 seconds less than your specified start time, <B>slowmo</B>\r
-               is set to <B>1</B>, rendering and sound is enabled again, and\r
-               whatever your put into the <B>exec before capture</B> field is\r
-               being injected, too. Then, the values of cl_capturevideo_nameformat\r
-               and _number are being saved to a temporary variable and are\r
-               overwritten with defined values (<B>autorec</B> and <B>1234567</B>),\r
-               so that the Nexuiz Demo Recorder will know the exact name of the\r
-               output file (which is necessary so that it can move that file to\r
-               your desired video destination/location)</P>\r
-               <LI><P>When the the start time is\r
-               reached, cl_capturevideo 1 is injected, once the end time is\r
-               reached, cl_capturevideo 0 is injected.</P>\r
-               <LI><P>Shortly after, whatever you put\r
-               into the exec after capture field is executed, and then the\r
-               original values of cl_capturevideo_nameformat and _number are being\r
-               restored.</P>\r
-               <LI><P>Then a disconnect command is\r
-               injected</P>\r
-       </UL>\r
-       <LI><P>Next, your specified Nexuiz\r
-       engine binary is launched. The parameters given to the binary are:</P>\r
-       <UL>\r
-               <LI><P>The content of the <B>engine\r
-               parameters</B> field of the job, and</P>\r
-               <LI><P><B>-demo\r
-               &lt;relative-demo-path&gt;/&lt;demo-file-name&gt;</B> (this will\r
-               start Nexuiz, e.g. <B>-demo demos/test.dem</B>, launch the <B>test.dem</B>\r
-               demo, and the engine will play the complete demo until a disconnect\r
-               is issued (which we have injected above), and then Nexuiz will\r
-               close automatically. This -demo parameter exists since Nexuiz 1.0</P>\r
-       </UL>\r
-       <LI><P>Once the Nexuiz Demo Recorder\r
-       notices that your Nexuiz engine binary closed, it will look for the\r
-       recorded video file in &lt;DPVideo-directory&gt;/autorec1234567.&lt;avi/ogv&gt;\r
-       and move it (rename it) to your desired &lt;video-destination&gt;,\r
-       keeping the original extension of the file. In case the preferences\r
-       are setup to not overwrite an existing video file, a file with\r
-       ending _copy1 (2, 3, &hellip;) will be created.</P>\r
-</UL>\r
-  </body>\r
-</html>\r
-\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>How it works</H1>
+<P>The following is a description of how
+the program processes a single job:</P>
+<UL>
+       <LI><P>The program takes your original
+       demo, e.g. &ldquo;C:\Nexuiz\data\demos\test.dem&rdquo; and creates a
+       copy of it, the <I>cut demo</I>. This file has the name
+       &lt;original_demo_name&gt;<B>_autocut</B>.dem. The cut demo is
+       different to the original demo in a way that console commands have
+       been <I>injected</I>, so basically the engine thinks that you had
+       entered them yourself (e.g. cl_capturevideo 1 to start recording).</P>
+       <UL>
+               <LI><P>Since it is possible to inject
+               any possible console command, the first command that is injected is
+               to disable rendering (r_render 0), save the value of your &ldquo;volume&rdquo;
+               setting and then setting it to 0 (as long as you have sound and
+               rendering disabled while fast-forwarding in the preferences of the
+               Demo Recorder). Then a slowmo 100 command (first stage value from
+               the Demo Recorder preferences) is injected in order to fast-forward
+               the demo.</P>
+               <LI><P>Then, when the game time in the
+               demo is about 1 minute less than your specified start time of the
+               job, slowmo is reduced to 10 (second stage value from the
+               preferences)</P>
+               <LI><P>Then, when the game time in the
+               demo is about 5 seconds less than your specified start time, <B>slowmo</B>
+               is set to <B>1</B>, rendering and sound is enabled again, and
+               whatever your put into the <B>exec before capture</B> field is
+               being injected, too. Then, the values of cl_capturevideo_nameformat
+               and _number are being saved to a temporary variable and are
+               overwritten with defined values (<B>autorec</B> and <B>1234567</B>),
+               so that the Nexuiz Demo Recorder will know the exact name of the
+               output file (which is necessary so that it can move that file to
+               your desired video destination/location)</P>
+               <LI><P>When the the start time is
+               reached, cl_capturevideo 1 is injected, once the end time is
+               reached, cl_capturevideo 0 is injected.</P>
+               <LI><P>Shortly after, whatever you put
+               into the exec after capture field is executed, and then the
+               original values of cl_capturevideo_nameformat and _number are being
+               restored.</P>
+               <LI><P>Then a disconnect command is
+               injected</P>
+       </UL>
+       <LI><P>Next, your specified Nexuiz
+       engine binary is launched. The parameters given to the binary are:</P>
+       <UL>
+               <LI><P>The content of the <B>engine
+               parameters</B> field of the job, and</P>
+               <LI><P><B>-demo
+               &lt;relative-demo-path&gt;/&lt;demo-file-name&gt;</B> (this will
+               start Nexuiz, e.g. <B>-demo demos/test.dem</B>, launch the <B>test.dem</B>
+               demo, and the engine will play the complete demo until a disconnect
+               is issued (which we have injected above), and then Nexuiz will
+               close automatically. This -demo parameter exists since Nexuiz 1.0</P>
+       </UL>
+       <LI><P>Once the Nexuiz Demo Recorder
+       notices that your Nexuiz engine binary closed, it will look for the
+       recorded video file in &lt;DPVideo-directory&gt;/autorec1234567.&lt;avi/ogv&gt;
+       and move it (rename it) to your desired &lt;video-destination&gt;,
+       keeping the original extension of the file. In case the preferences
+       are setup to not overwrite an existing video file, a file with
+       ending _copy1 (2, 3, &hellip;) will be created.</P>
+</UL>
+  </body>
+</html>
+
index dd37b2fb6a7a2f66009664bd7eb48fc8bd8267da..8ed5fd4c7f879a9507db00a53f3fad5603a1886f 100644 (file)
@@ -1,23 +1,23 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\r
-\r
-<html>\r
-  <head>\r
-    <meta name="generator" content="HTML Tidy, see www.w3.org">\r
-\r
-    <title></title>\r
-  </head>\r
-\r
-  <body>\r
-    <H1>Preliminary stop</H1>\r
-<P>If you want to preliminarily stop the\r
-recording process, the only thing you can do is to switch to the\r
-Nexuiz Demo Recorder window and click on <B>stop processing</B>.\r
-However, if Nexuiz is still open and processing a demo, you have to\r
-end it manually of wait for it to end. This means that clicking on\r
-<B>stop processing</B> button will simply remove all jobs that\r
-haven't been started yet from the internal queue of jobs that would\r
-normally be processed (of course they <B>won't</B> be removed from\r
-your jobs table).</P>\r
-  </body>\r
-</html>\r
-\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>Preliminary stop</H1>
+<P>If you want to preliminarily stop the
+recording process, the only thing you can do is to switch to the
+Nexuiz Demo Recorder window and click on <B>stop processing</B>.
+However, if Nexuiz is still open and processing a demo, you have to
+end it manually of wait for it to end. This means that clicking on
+<B>stop processing</B> button will simply remove all jobs that
+haven't been started yet from the internal queue of jobs that would
+normally be processed (of course they <B>won't</B> be removed from
+your jobs table).</P>
+  </body>
+</html>
+
index 26e28a033a5918b331382000e2e4a1cf5216baa6..b64c4bd87a4cb99c0760d686ce55c5f8bc3523bf 100644 (file)
@@ -1,26 +1,26 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\r
-\r
-<html>\r
-  <head>\r
-    <meta name="generator" content="HTML Tidy, see www.w3.org">\r
-\r
-    <title></title>\r
-  </head>\r
-\r
-  <body>\r
-    <H1>Table settings</H1>\r
-<P>Both the jobs and templates table can\r
-be customized. You can define whether to sort the jobs/templates\r
-according to a particular column, you can change the order of\r
-columns, and you can add and remove (hide) additional columns by\r
-clicking the small icon as shown here on this image:</P>\r
-<P><img src="images/customize_tables.gif" /></P>\r
-<P>These settings are being saved and\r
-automatically restored whenever you close and open the program. In\r
-case you messed the columns and want them to be reset to default, go\r
-into the settings sub-folder of the Nexuiz Demo Recorder and delete\r
-the jobsTable.pref or templatesTable.pref file (.pref for\r
-&ldquo;preferences&rdquo;)</P>\r
-  </body>\r
-</html>\r
-\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>Table settings</H1>
+<P>Both the jobs and templates table can
+be customized. You can define whether to sort the jobs/templates
+according to a particular column, you can change the order of
+columns, and you can add and remove (hide) additional columns by
+clicking the small icon as shown here on this image:</P>
+<P><img src="images/customize_tables.gif" /></P>
+<P>These settings are being saved and
+automatically restored whenever you close and open the program. In
+case you messed the columns and want them to be reset to default, go
+into the settings sub-folder of the Nexuiz Demo Recorder and delete
+the jobsTable.pref or templatesTable.pref file (.pref for
+&ldquo;preferences&rdquo;)</P>
+  </body>
+</html>
+
index 11524c30ee76a5e4324f963f32a8b9890dbd2bc8..5fd3b1c767cf5a88553a2ce7cad9f7761841a5cf 100644 (file)
@@ -1,18 +1,18 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\r
-\r
-<html>\r
-  <head>\r
-    <meta name="generator" content="HTML Tidy, see www.w3.org">\r
-\r
-    <title></title>\r
-  </head>\r
-\r
-  <body>\r
-    <H1>Advanced user topics</H1>\r
-<P>Here you find additional information\r
-that is not mandatory for you to know, but might help you\r
-understanding what and how the tool does stuff, or what you can do\r
-with it.</P>\r
-  </body>\r
-</html>\r
-\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>Advanced user topics</H1>
+<P>Here you find additional information
+that is not mandatory for you to know, but might help you
+understanding what and how the tool does stuff, or what you can do
+with it.</P>
+  </body>
+</html>
+
index 660e3648614f0d089aff19c348b6ff14bd4ff5fd..10e955b67ee32e9c9ed5095416f96d361421f952 100644 (file)
@@ -1,77 +1,77 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\r
-\r
-<html>\r
-  <head>\r
-    <meta name="generator" content="HTML Tidy, see www.w3.org">\r
-\r
-    <title></title>\r
-  </head>\r
-\r
-  <body>\r
-<H1>Applying templates</H1>\r
-<P>Since version 0.3 you are able to\r
-selectively apply settings from a template of your choice to any job\r
-you like. One reason might be that you want jobs that have already\r
-been recorded to be recorded using different config settings (e.g.\r
-other quality settings, resolution, etc.). If you wanted to do this\r
-for many jobs at once, it would be very tedious to edit each job\r
-manually, setting up changes.</P>\r
-<P>You can apply properties of templates\r
-very easily:</P>\r
-<UL>\r
-       <LI><P>Select the template you want to\r
-       use</P>\r
-       <LI><P>Select the job(s) whose\r
-       properties you want to be affected by the template</P>\r
-       <LI><P>Right-click inside the jobs\r
-       table and select &ldquo;Apply template&rdquo;</P>\r
-       <LI><P>A new dialog will open where you\r
-       will be able to select which one of the settings should be copied\r
-       from the template to the job. Please, be aware that old settings of\r
-       the job will be overwritten!</P>\r
-       <LI><P>Usually the settings will be\r
-       copied 1:1 from the template to the job. <B>There are 2 exceptions,\r
-       though</B>:</P>\r
-       <UL>\r
-               <LI><P><B>Demo directory:</B> Here\r
-               copying the value from the template to the job would not make\r
-               sense, because in a template you actually specified a <B>directory</B>,\r
-               <U>not</U> a <B>file</B>. However, a job needs to point at a <B>file</B>.\r
-               What will happen shall be demonstrated by the following example:\r
-               Let's say that the job's path to the demo file (before applying the\r
-               template) has been <B>C:\Nexuiz\data\demos\mydemo.dem</B>\r
-               and the path setup in the template has been pointing to the\r
-               directory<B> C:\Games\Nexuiz2.5.2\data\demos</B>.\r
-               The new demo file path, after the &ldquo;apply&rdquo; process is\r
-               done, would be <B>C:\Games\Nexuiz2.5.2\data\demos\mydemo.dem</B>;\r
-               this means that from the <B>job's</B>\r
-               demo path the program will just take the name of the demo by itself\r
-               (mydemo.dem), and put the path from the <B>template</B>\r
-               in front of it</P>\r
-               <LI><P><b>Video\r
-               destination:</b> Here the same thing happens like for the Demo\r
-               directory. The new video destination will be &lt;Directory set up\r
-               in the template&gt; + &lt;just the filename of the setting of the\r
-               job&gt;</P>\r
-       </UL>\r
-</UL>\r
-<P>Another example of how this feature could be useful is this one: Suppose some\r
-movie maker was to create a community movie with frags from different\r
-people, or a movie maker was to continue or finish a movie from\r
-someone else. If one person creates a queue of jobs (which naturally\r
-includes the demo's names, start second and end second) and then\r
-decides not to record the frags herself but send it to some other\r
-movie maker, all that needs to be done is to save the job queue and\r
-send it to the other movie maker, together with the demos. The other\r
-movie maker can then copy the demos into his own demo directory and\r
-import the job queue. Of course the problem would then be that all\r
-these imported jobs could not really be executed right away, because\r
-all these paths might be incorrect, I mean, would simply not exist or\r
-match to the system of the movie maker who wants to record the jobs\r
-(e.g. path to Nexuiz engine or demo directory). But this can easily\r
-be fixed. The movie maker just needs to select the imported jobs and\r
-apply one of her own templates to them, and by doing so, fix all\r
-incorrect paths.</P>\r
-  </body>\r
-</html>\r
-\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+<H1>Applying templates</H1>
+<P>Since version 0.3 you are able to
+selectively apply settings from a template of your choice to any job
+you like. One reason might be that you want jobs that have already
+been recorded to be recorded using different config settings (e.g.
+other quality settings, resolution, etc.). If you wanted to do this
+for many jobs at once, it would be very tedious to edit each job
+manually, setting up changes.</P>
+<P>You can apply properties of templates
+very easily:</P>
+<UL>
+       <LI><P>Select the template you want to
+       use</P>
+       <LI><P>Select the job(s) whose
+       properties you want to be affected by the template</P>
+       <LI><P>Right-click inside the jobs
+       table and select &ldquo;Apply template&rdquo;</P>
+       <LI><P>A new dialog will open where you
+       will be able to select which one of the settings should be copied
+       from the template to the job. Please, be aware that old settings of
+       the job will be overwritten!</P>
+       <LI><P>Usually the settings will be
+       copied 1:1 from the template to the job. <B>There are 2 exceptions,
+       though</B>:</P>
+       <UL>
+               <LI><P><B>Demo directory:</B> Here
+               copying the value from the template to the job would not make
+               sense, because in a template you actually specified a <B>directory</B>,
+               <U>not</U> a <B>file</B>. However, a job needs to point at a <B>file</B>.
+               What will happen shall be demonstrated by the following example:
+               Let's say that the job's path to the demo file (before applying the
+               template) has been <B>C:\Nexuiz\data\demos\mydemo.dem</B>
+               and the path setup in the template has been pointing to the
+               directory<B> C:\Games\Nexuiz2.5.2\data\demos</B>.
+               The new demo file path, after the &ldquo;apply&rdquo; process is
+               done, would be <B>C:\Games\Nexuiz2.5.2\data\demos\mydemo.dem</B>;
+               this means that from the <B>job's</B>
+               demo path the program will just take the name of the demo by itself
+               (mydemo.dem), and put the path from the <B>template</B>
+               in front of it</P>
+               <LI><P><b>Video
+               destination:</b> Here the same thing happens like for the Demo
+               directory. The new video destination will be &lt;Directory set up
+               in the template&gt; + &lt;just the filename of the setting of the
+               job&gt;</P>
+       </UL>
+</UL>
+<P>Another example of how this feature could be useful is this one: Suppose some
+movie maker was to create a community movie with frags from different
+people, or a movie maker was to continue or finish a movie from
+someone else. If one person creates a queue of jobs (which naturally
+includes the demo's names, start second and end second) and then
+decides not to record the frags herself but send it to some other
+movie maker, all that needs to be done is to save the job queue and
+send it to the other movie maker, together with the demos. The other
+movie maker can then copy the demos into his own demo directory and
+import the job queue. Of course the problem would then be that all
+these imported jobs could not really be executed right away, because
+all these paths might be incorrect, I mean, would simply not exist or
+match to the system of the movie maker who wants to record the jobs
+(e.g. path to Nexuiz engine or demo directory). But this can easily
+be fixed. The movie maker just needs to select the imported jobs and
+apply one of her own templates to them, and by doing so, fix all
+incorrect paths.</P>
+  </body>
+</html>
+
index 4f96ca4abd0d5ca81badb8eb944a2d5763093af5..d63821d5585be16fd4e0d95289af897a174f02f6 100644 (file)
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\r
-\r
-<html>\r
-  <head>\r
-    <meta name="generator" content="HTML Tidy, see www.w3.org">\r
-\r
-    <title></title>\r
-  </head>\r
-\r
-  <body>\r
-   <H1>Basic tutorial</H1>\r
-<P>The following images shows the main window:</P>\r
-<img src="images/main_window.gif" />\r
-<P>You will notice that the main window is separated into\r
-4 areas:</P>\r
-<UL>\r
-       <LI><P>The menu bar (File, Help)</P>\r
-       <LI><P>The &quot;Templates&quot; area which has a table\r
-       and 4 buttons on the right</P>\r
-       <LI><P>The &quot;Jobs&quot; area which also has a table\r
-       and 4 buttons on the right</P>\r
-       <LI><P>The Start/stop processing buttons</P>\r
-</UL>\r
-<H2>Step 1</H2>\r
-<P>Minimize this demo recorder application for a moment\r
-and figure out which demo(s) you actually want to record. Once you\r
-know the names of the demo files, you have to figure out the start\r
-time and end time (when you want the recording process to start and\r
-end). You have to be aware that the Nexuiz demo recorder needs a\r
-value in seconds. The problem is that there are 2 different kinds of\r
-values.\r
-</P>\r
-<UL>\r
-       <LI><P>The first one is the one you see in your HUD in\r
-       the top right corner. This value is not useful for the Nexuiz demo\r
-       recorder, because the time is shown in the &lt;minutes:seconds&gt;\r
-       format. Also this value will be changed/reset to 0 in case the\r
-       ready-restart feature was using in the game, which resets the time.</P>\r
-       <LI><P>On the other hand there is the <B>absolute time\r
-       value</B>. It represents the amount of time that passed on the\r
-       server since the map was loaded. This value will always increase\r
-       each second, and won't be reset to 0 after a ready-restart call\r
-       either. This is the value you need for this tool. You can obtain it\r
-       the following ways</P>\r
-       <UL>\r
-               <LI><P>1: There is a sub-folder &quot;tools&quot; in\r
-               this package which contains a .patch file that allows you to patch\r
-               the source-code of the Nexuiz 2.5.2 engine. What this patch does is\r
-               to add a new variable &quot;showgametime&quot;. Once you patched\r
-               your source-code and compiled the engine, you can set this variable\r
-               to 1, and then in the lower right corner you will see the current\r
-               game time. Please note that I cannot help you with patching the\r
-               sources and compiling the engine. For Microsoft Windows users I put\r
-               the Nexuiz binary (darkplaces.exe) into the package as well, so you\r
-               can use this one (copy it into your Nexuiz directory)</P>\r
-               <LI><P>2: If the demo was recorded with Nexuiz 2.5 or\r
-               newer you can obtain the time values by opening the console and\r
-               entering this: prvm_global client time</P>\r
-               <LI><P>3: If the demo was recorded with a Nexuiz version\r
-               older than 2.5, just pause the demo at the desired moment, open the\r
-               scoreboard (by default: hold tabulator key). The scoreboard will\r
-               show you the current time in &lt;minutes&gt;:&lt;seconds&gt;.\r
-               Calculate the time using a calculator (time = minutes * 60 +\r
-               seconds)</P>\r
-       </UL>\r
-</UL>\r
-<P>Make sure that you know the demo file name, start and\r
-end time, then open the Nexuiz demo recorder tool again</P>\r
-<H2>Step 2</H2>\r
-<P><a name="jobs_create">In the &quot;Jobs&quot; panel</a>, click on the +Create\r
-button. A new dialog will open which asks you for all different kinds\r
-of things:</P>\r
-<img src="images/create_job.gif" />\r
-<UL>\r
-       <LI><P><b>Engine:</b> click on the ... button and specify the\r
-       Nexuiz engine that should be used for recording</P>\r
-       <LI><P><b>Engine parameters:</b> Only fill this out if you already know what it\r
-       means. Examples include to execute a particular config (+exec\r
-       someConfig.cfg), or set a special directory (-userdir option) so\r
-       that your Engine will use a different set of configs</P>\r
-       <LI><P><b>DPVideo directory:</b> here you have to specify the\r
-       path where your Nexuiz usually creates new .avi or .ogv files. This\r
-       is one of the items where you will see that it is required that you\r
-       already have recorded demos previously. Usually the path will be\r
-       Nexuiz/data/video (on Windows) or ~/.nexuiz/data/video</P>\r
-       <LI><P><b>Relative demo path:</b> This is the path that is being\r
-       used within the virtual file-system of Nexuiz in order to find your\r
-       demo. Normally, when starting a demo by hand, you would enter\r
-       something like &quot;playdemo demos/stormkeep_demo.dem&quot;,\r
-       because usually demos are stored in the directory &quot;demos&quot;.\r
-       In case you changed that (e.g. If the demo is in a sub-directory of\r
-       the demos directory), make sure to put this into the field. But\r
-       usually you <B>won't</B> have to change the value (demos) to\r
-       anything else. Just leave it as it is.</P>\r
-       <LI><P><b>Job name:</b> Here you can specify a name for this job. If\r
-       you don't specify any name, a name will be automatically chosen, using\r
-       the format "Job x", where <b>x</b> is a index number.</P>\r
-       <LI><P><b>Demo file:</b> obviously you have to specify the demo\r
-       file here</P>\r
-       <LI><P><b>Start second</b> and <b>end second</b> are self-explanatory</P>\r
-       <LI><P><b>Exec before capture:</b> Here you have the chance to\r
-       enter console commands (that is, commands that you could otherwise\r
-       also enter into the console of the game). One example could be to\r
-       execute a config here that will make sure that the HUD is hidden. Or\r
-       you might want to change the FPS for the rendered video by setting\r
-       cl_capturevideo_fps to another value. If you want to put several\r
-       commands in here you can either separate them by a new-line, or by ;\r
-       (semi-colon)</P>\r
-       <LI><P><b>Exec after capture:</b> works like the &quot;exec\r
-       before capture&quot; field. Can be used for anything, e.g. Restoring\r
-       settings that you altered in the exec before field.</P>\r
-       <LI><P><b>Video destination:</b> Here you have to specify where\r
-       you want the recorded video file to be moved once the recording\r
-       process is done. Please note that you have to enter a name for the\r
-       file, however you should not enter the extension (.avi or .ogv). You\r
-       don't have to, because the Nexuiz demo recorder will automatically\r
-       figure out whether the Nexuiz engine generated an avi or ogv file,\r
-       and will keep its ending when moving the file to its destination.</P>\r
-</UL>\r
-<P>Once you filled out all fields, click on create. In\r
-case you did something wrong with one of the fields, you will\r
-hopefully get an error message immediately, which allows you to\r
-correct the mistake. If nothing was filled out incorrectly the dialog\r
-should close and you should now see a &quot;Job 1&quot; in the job\r
-queue.</P>\r
-<P>In order to edit a job, either double-click on it, or\r
-right-click it and click on Edit</P>\r
-<H2>Step 3</H2>\r
-<P>Now that you have setup your first job, click on the\r
-<B>Start processing</B> button. After a short moment you should see\r
-Nexuiz opening. Don't be surprised if the &quot;Loading&quot; image\r
-appears for a long time. What you don't see is that Nexuiz is\r
-fast-forwarding your demo to the start time you specified, which can\r
-take some time. After that, once Nexuiz started and finished\r
-recording the stuff you wanted it to record, it should close\r
-automatically. The demo recorder should show you a new message dialog\r
-that it finished recording all jobs. The &quot;Status&quot; column of\r
-your first job should now be showing &quot;done&quot;.</P>\r
-<H2>Trouble-shooting:</H2>\r
-<P>In case something went wrong, the status column of your job will\r
-show &quot;error&quot; (in the jobs table). In order to find out what the problem was,\r
-right-click on the job and click on &quot;Show error message&quot;.\r
-The message dialog that pops up will hopefully help you to figure out\r
-what went wrong. If it is something that you think you can fix, do so\r
-(by editing the job settings), then right-click the job again and click on\r
-&quot;Reset job status to waiting&quot;. This will set the status of the job to\r
-&quot;waiting&quot; again.</P>\r
-<H2>Job queue processing</H2>\r
-<P>You need to be aware that once you click on the <b>Start\r
-processing</b> button only the jobs with status &quot;waiting&quot; will be\r
-put into a queue and will be executed one after another. All other jobs\r
-(with status &quot;error&quot; or &quot;done&quot; will not be started.\r
-If an error occurs while processing one of the jobs this job will set its state to\r
-&quot;error&quot; and the next job in queue will be executed. That is, the behavior\r
-of the Nexuiz demo recorder is to continue working even if one or more individual\r
-jobs failed.</P>\r
-<P>In case you already have a job list with several\r
-jobs but you want to just record one particular job (even though all\r
-other jobs also have the state &quot;waiting&quot;), just right-click\r
-on the particular job and click on &quot;Start job&quot;. Note that this only\r
-works if the Nexuiz demo recorder is currently not working on any other jobs.</P>\r
-<H2>Further reading</H2>\r
-<P>Congratulations, you managed your\r
-first steps using this program. You should read the other help\r
-chapters as well in order to be able to fully utilize the\r
-functionality of the program.</P>\r
-  </body>\r
-</html>\r
-\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+   <H1>Basic tutorial</H1>
+<P>The following images shows the main window:</P>
+<img src="images/main_window.gif" />
+<P>You will notice that the main window is separated into
+4 areas:</P>
+<UL>
+       <LI><P>The menu bar (File, Help)</P>
+       <LI><P>The &quot;Templates&quot; area which has a table
+       and 4 buttons on the right</P>
+       <LI><P>The &quot;Jobs&quot; area which also has a table
+       and 4 buttons on the right</P>
+       <LI><P>The Start/stop processing buttons</P>
+</UL>
+<H2>Step 1</H2>
+<P>Minimize this demo recorder application for a moment
+and figure out which demo(s) you actually want to record. Once you
+know the names of the demo files, you have to figure out the start
+time and end time (when you want the recording process to start and
+end). You have to be aware that the Nexuiz demo recorder needs a
+value in seconds. The problem is that there are 2 different kinds of
+values.
+</P>
+<UL>
+       <LI><P>The first one is the one you see in your HUD in
+       the top right corner. This value is not useful for the Nexuiz demo
+       recorder, because the time is shown in the &lt;minutes:seconds&gt;
+       format. Also this value will be changed/reset to 0 in case the
+       ready-restart feature was using in the game, which resets the time.</P>
+       <LI><P>On the other hand there is the <B>absolute time
+       value</B>. It represents the amount of time that passed on the
+       server since the map was loaded. This value will always increase
+       each second, and won't be reset to 0 after a ready-restart call
+       either. This is the value you need for this tool. You can obtain it
+       the following ways</P>
+       <UL>
+               <LI><P>1: There is a sub-folder &quot;tools&quot; in
+               this package which contains a .patch file that allows you to patch
+               the source-code of the Nexuiz 2.5.2 engine. What this patch does is
+               to add a new variable &quot;showgametime&quot;. Once you patched
+               your source-code and compiled the engine, you can set this variable
+               to 1, and then in the lower right corner you will see the current
+               game time. Please note that I cannot help you with patching the
+               sources and compiling the engine. For Microsoft Windows users I put
+               the Nexuiz binary (darkplaces.exe) into the package as well, so you
+               can use this one (copy it into your Nexuiz directory)</P>
+               <LI><P>2: If the demo was recorded with Nexuiz 2.5 or
+               newer you can obtain the time values by opening the console and
+               entering this: prvm_global client time</P>
+               <LI><P>3: If the demo was recorded with a Nexuiz version
+               older than 2.5, just pause the demo at the desired moment, open the
+               scoreboard (by default: hold tabulator key). The scoreboard will
+               show you the current time in &lt;minutes&gt;:&lt;seconds&gt;.
+               Calculate the time using a calculator (time = minutes * 60 +
+               seconds)</P>
+       </UL>
+</UL>
+<P>Make sure that you know the demo file name, start and
+end time, then open the Nexuiz demo recorder tool again</P>
+<H2>Step 2</H2>
+<P><a name="jobs_create">In the &quot;Jobs&quot; panel</a>, click on the +Create
+button. A new dialog will open which asks you for all different kinds
+of things:</P>
+<img src="images/create_job.gif" />
+<UL>
+       <LI><P><b>Engine:</b> click on the ... button and specify the
+       Nexuiz engine that should be used for recording</P>
+       <LI><P><b>Engine parameters:</b> Only fill this out if you already know what it
+       means. Examples include to execute a particular config (+exec
+       someConfig.cfg), or set a special directory (-userdir option) so
+       that your Engine will use a different set of configs</P>
+       <LI><P><b>DPVideo directory:</b> here you have to specify the
+       path where your Nexuiz usually creates new .avi or .ogv files. This
+       is one of the items where you will see that it is required that you
+       already have recorded demos previously. Usually the path will be
+       Nexuiz/data/video (on Windows) or ~/.nexuiz/data/video</P>
+       <LI><P><b>Relative demo path:</b> This is the path that is being
+       used within the virtual file-system of Nexuiz in order to find your
+       demo. Normally, when starting a demo by hand, you would enter
+       something like &quot;playdemo demos/stormkeep_demo.dem&quot;,
+       because usually demos are stored in the directory &quot;demos&quot;.
+       In case you changed that (e.g. If the demo is in a sub-directory of
+       the demos directory), make sure to put this into the field. But
+       usually you <B>won't</B> have to change the value (demos) to
+       anything else. Just leave it as it is.</P>
+       <LI><P><b>Job name:</b> Here you can specify a name for this job. If
+       you don't specify any name, a name will be automatically chosen, using
+       the format "Job x", where <b>x</b> is a index number.</P>
+       <LI><P><b>Demo file:</b> obviously you have to specify the demo
+       file here</P>
+       <LI><P><b>Start second</b> and <b>end second</b> are self-explanatory</P>
+       <LI><P><b>Exec before capture:</b> Here you have the chance to
+       enter console commands (that is, commands that you could otherwise
+       also enter into the console of the game). One example could be to
+       execute a config here that will make sure that the HUD is hidden. Or
+       you might want to change the FPS for the rendered video by setting
+       cl_capturevideo_fps to another value. If you want to put several
+       commands in here you can either separate them by a new-line, or by ;
+       (semi-colon)</P>
+       <LI><P><b>Exec after capture:</b> works like the &quot;exec
+       before capture&quot; field. Can be used for anything, e.g. Restoring
+       settings that you altered in the exec before field.</P>
+       <LI><P><b>Video destination:</b> Here you have to specify where
+       you want the recorded video file to be moved once the recording
+       process is done. Please note that you have to enter a name for the
+       file, however you should not enter the extension (.avi or .ogv). You
+       don't have to, because the Nexuiz demo recorder will automatically
+       figure out whether the Nexuiz engine generated an avi or ogv file,
+       and will keep its ending when moving the file to its destination.</P>
+</UL>
+<P>Once you filled out all fields, click on create. In
+case you did something wrong with one of the fields, you will
+hopefully get an error message immediately, which allows you to
+correct the mistake. If nothing was filled out incorrectly the dialog
+should close and you should now see a &quot;Job 1&quot; in the job
+queue.</P>
+<P>In order to edit a job, either double-click on it, or
+right-click it and click on Edit</P>
+<H2>Step 3</H2>
+<P>Now that you have setup your first job, click on the
+<B>Start processing</B> button. After a short moment you should see
+Nexuiz opening. Don't be surprised if the &quot;Loading&quot; image
+appears for a long time. What you don't see is that Nexuiz is
+fast-forwarding your demo to the start time you specified, which can
+take some time. After that, once Nexuiz started and finished
+recording the stuff you wanted it to record, it should close
+automatically. The demo recorder should show you a new message dialog
+that it finished recording all jobs. The &quot;Status&quot; column of
+your first job should now be showing &quot;done&quot;.</P>
+<H2>Trouble-shooting:</H2>
+<P>In case something went wrong, the status column of your job will
+show &quot;error&quot; (in the jobs table). In order to find out what the problem was,
+right-click on the job and click on &quot;Show error message&quot;.
+The message dialog that pops up will hopefully help you to figure out
+what went wrong. If it is something that you think you can fix, do so
+(by editing the job settings), then right-click the job again and click on
+&quot;Reset job status to waiting&quot;. This will set the status of the job to
+&quot;waiting&quot; again.</P>
+<H2>Job queue processing</H2>
+<P>You need to be aware that once you click on the <b>Start
+processing</b> button only the jobs with status &quot;waiting&quot; will be
+put into a queue and will be executed one after another. All other jobs
+(with status &quot;error&quot; or &quot;done&quot; will not be started.
+If an error occurs while processing one of the jobs this job will set its state to
+&quot;error&quot; and the next job in queue will be executed. That is, the behavior
+of the Nexuiz demo recorder is to continue working even if one or more individual
+jobs failed.</P>
+<P>In case you already have a job list with several
+jobs but you want to just record one particular job (even though all
+other jobs also have the state &quot;waiting&quot;), just right-click
+on the particular job and click on &quot;Start job&quot;. Note that this only
+works if the Nexuiz demo recorder is currently not working on any other jobs.</P>
+<H2>Further reading</H2>
+<P>Congratulations, you managed your
+first steps using this program. You should read the other help
+chapters as well in order to be able to fully utilize the
+functionality of the program.</P>
+  </body>
+</html>
+
index f15c319ecf74c9daf2cb9430bee05de175a0145d..a981818d3249ad365bc88f88018c97f80a15d367 100644 (file)
@@ -1,52 +1,52 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\r
-\r
-<html>\r
-  <head>\r
-    <meta name="generator" content="HTML Tidy, see www.w3.org">\r
-\r
-    <title></title>\r
-  </head>\r
-\r
-  <body>\r
-<H1>Changelog</H1>\r
-<H2>v0.2 -&gt; v0.3</H2>\r
-<H3>Compatibility changes</H3>\r
-<P>None. Job and template queues are completely compatible between v0.2 and v0.3</P>\r
-<H3>Functionality changes and fixes</H3>\r
-<UL>\r
-       <LI><P>New feature: loading and saving of the template list to file is now\r
-       possible</P>\r
-       <LI><P>Open dialogs for jobs or templates allow you to select whether to overwrite\r
-       the current list/queue or not (if not, the loaded items will be added to the\r
-       current list/queue)</P>\r
-       <LI><P>New feature: <a href="apply_templates.html">Applying properties of a template </a>\r
-       selectively to one or more selected jobs</P>\r
-</UL>\r
-\r
-\r
-<H2>v0.1 -&gt; v0.2</H2>\r
-<H3>Compatibility changes</H3>\r
-<UL>\r
-       <LI><P>Table preferences, job queues and template lists are not compatible\r
-       between v0.1 and 0.2! Make sure you install v0.2 in a new direcory!</P>\r
-       <LI><P>NDR v0.2 requires Java SE 6 now (instead of Java SE 5 which was\r
-       required for v0.1)</P>\r
-</UL>\r
-<H3>Functionality changes and fixes</H3>\r
-<UL>\r
-       <LI><P><a href="plugin-architecture.html">Plugin architecture</a> that allows other Java developers to include\r
-       their own plug-ins that can encode the recorded job to\r
-       another format (e.g. H.264)</P>\r
-       <LI><P>Ships with <a href="plugin-virtualdub.html">encoder plug-in for VirtualDub</a> (MS Windows,\r
-       www.virtualdub.org)</P>\r
-       <LI><P>Duplicates can now be created with <b>more than one job</b> selected,\r
-       duplicating <b>all</b> selected ones at once</P>\r
-       <LI><P><b>Templates</b> can now be duplicated</P>\r
-       <LI><P>Fixed bug where deleting selected rows in the tables caused\r
-       a weird exception</P>\r
-       <LI><P>Jobs can be given individual names (don't have to be "Job 1",\r
-       "Job 2", ... anymore)</P>\r
-</UL>\r
-  </body>\r
-</html>\r
-\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+<H1>Changelog</H1>
+<H2>v0.2 -&gt; v0.3</H2>
+<H3>Compatibility changes</H3>
+<P>None. Job and template queues are completely compatible between v0.2 and v0.3</P>
+<H3>Functionality changes and fixes</H3>
+<UL>
+       <LI><P>New feature: loading and saving of the template list to file is now
+       possible</P>
+       <LI><P>Open dialogs for jobs or templates allow you to select whether to overwrite
+       the current list/queue or not (if not, the loaded items will be added to the
+       current list/queue)</P>
+       <LI><P>New feature: <a href="apply_templates.html">Applying properties of a template </a>
+       selectively to one or more selected jobs</P>
+</UL>
+
+
+<H2>v0.1 -&gt; v0.2</H2>
+<H3>Compatibility changes</H3>
+<UL>
+       <LI><P>Table preferences, job queues and template lists are not compatible
+       between v0.1 and 0.2! Make sure you install v0.2 in a new direcory!</P>
+       <LI><P>NDR v0.2 requires Java SE 6 now (instead of Java SE 5 which was
+       required for v0.1)</P>
+</UL>
+<H3>Functionality changes and fixes</H3>
+<UL>
+       <LI><P><a href="plugin-architecture.html">Plugin architecture</a> that allows other Java developers to include
+       their own plug-ins that can encode the recorded job to
+       another format (e.g. H.264)</P>
+       <LI><P>Ships with <a href="plugin-virtualdub.html">encoder plug-in for VirtualDub</a> (MS Windows,
+       www.virtualdub.org)</P>
+       <LI><P>Duplicates can now be created with <b>more than one job</b> selected,
+       duplicating <b>all</b> selected ones at once</P>
+       <LI><P><b>Templates</b> can now be duplicated</P>
+       <LI><P>Fixed bug where deleting selected rows in the tables caused
+       a weird exception</P>
+       <LI><P>Jobs can be given individual names (don't have to be "Job 1",
+       "Job 2", ... anymore)</P>
+</UL>
+  </body>
+</html>
+
index 8a27e16b05b522e26225258204e97079d676cdda..0390ae0c7d9779401c320128a8c13f31cc877f34 100644 (file)
@@ -1,55 +1,55 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\r
-\r
-<html>\r
-  <head>\r
-    <meta name="generator" content="HTML Tidy, see www.w3.org">\r
-\r
-    <title></title>\r
-  </head>\r
-\r
-  <body>\r
-   <H1>Compatibility and limitations</H1>\r
-<P><BR><BR>\r
-</P>\r
-<H1>Compatibility</H1>\r
-<H2>Operating system</H2>\r
-<P>Since this tool is written in Java\r
-you can use it on Windows, Linux and Mac OS. This means that it can\r
-be used on all platforms supported by Nexuiz. You do need Java 6.</P>\r
-<H2>Nexuiz engine version</H2>\r
-<P>In order to use this tool you will\r
-need to use a Nexuiz engine of version 2.5 or newer. The reason is\r
-that the engine needs to have the variables\r
-cl_capturevideo_nameformat and cl_capturevideo_number. If you use\r
-older engines, such as the 2.3 engine, recording will work, but the\r
-tool will be unable to move the avi clip rendered by the engine to\r
-your desired location. However, you can manually move it and simply\r
-ignore the fact that the job finished with an &ldquo;error&rdquo;.</P>\r
-<H2>Demo file version</H2>\r
-<P>There is not really any limitation\r
-about the Nexuiz version that was used when recording the demo. That\r
-means that you should be able to use the Nexuiz demo recorder with\r
-demos from previous Nexuiz versions (they can be older than 2.5). You\r
-just have to make sure that the engine binary you use for recording\r
-them is v2.5 and newer. Keep in mind that using a newer-generation\r
-engine such as the 2.5 one for older demos should usually work\r
-without problems. You might have to move the dataXXXXXX.pk3 files\r
-from the Nexuiz version that corresponds to the demo version into the\r
-data directory.</P>\r
-<H2>Nexuiz Demo Recorder job queues</H2>\r
-<P>Table preferences, job queues and template lists are not compatible\r
-between v0.1 and newer versions! Make sure you install this in a different\r
-direcory than v0.1!</P>\r
-<H1>Limitations</H1>\r
-<P>This tool only works with 1-map demos\r
-(such as the one being automatically recorded by Nexuiz when using\r
-the cl_autodemo feature). If you manually recorded a demo that\r
-contains several maps, you can only specify a start- and endtime for\r
-the first map. However, you can try to use the demosplit.pl script\r
-from svn (trunk/misc/tools/demosplit.pl) to split demos having\r
-several maps into several 1-map demos. However, note that this tool\r
-will probably only work properly for older demos (up to Nexuiz v2.4.2\r
-demos).</P>\r
-  </body>\r
-</html>\r
-\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+   <H1>Compatibility and limitations</H1>
+<P><BR><BR>
+</P>
+<H1>Compatibility</H1>
+<H2>Operating system</H2>
+<P>Since this tool is written in Java
+you can use it on Windows, Linux and Mac OS. This means that it can
+be used on all platforms supported by Nexuiz. You do need Java 6.</P>
+<H2>Nexuiz engine version</H2>
+<P>In order to use this tool you will
+need to use a Nexuiz engine of version 2.5 or newer. The reason is
+that the engine needs to have the variables
+cl_capturevideo_nameformat and cl_capturevideo_number. If you use
+older engines, such as the 2.3 engine, recording will work, but the
+tool will be unable to move the avi clip rendered by the engine to
+your desired location. However, you can manually move it and simply
+ignore the fact that the job finished with an &ldquo;error&rdquo;.</P>
+<H2>Demo file version</H2>
+<P>There is not really any limitation
+about the Nexuiz version that was used when recording the demo. That
+means that you should be able to use the Nexuiz demo recorder with
+demos from previous Nexuiz versions (they can be older than 2.5). You
+just have to make sure that the engine binary you use for recording
+them is v2.5 and newer. Keep in mind that using a newer-generation
+engine such as the 2.5 one for older demos should usually work
+without problems. You might have to move the dataXXXXXX.pk3 files
+from the Nexuiz version that corresponds to the demo version into the
+data directory.</P>
+<H2>Nexuiz Demo Recorder job queues</H2>
+<P>Table preferences, job queues and template lists are not compatible
+between v0.1 and newer versions! Make sure you install this in a different
+direcory than v0.1!</P>
+<H1>Limitations</H1>
+<P>This tool only works with 1-map demos
+(such as the one being automatically recorded by Nexuiz when using
+the cl_autodemo feature). If you manually recorded a demo that
+contains several maps, you can only specify a start- and endtime for
+the first map. However, you can try to use the demosplit.pl script
+from svn (trunk/misc/tools/demosplit.pl) to split demos having
+several maps into several 1-map demos. However, note that this tool
+will probably only work properly for older demos (up to Nexuiz v2.4.2
+demos).</P>
+  </body>
+</html>
+
index 4baabcd3ad68990a7faee8b8175084317e8c83e9..36b1cca7cf6eae39a0ffdd2a061f5cf32b0002f6 100644 (file)
@@ -1,27 +1,27 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\r
-\r
-<html>\r
-  <head>\r
-    <meta name="generator" content="HTML Tidy, see www.w3.org">\r
-\r
-    <title></title>\r
-  </head>\r
-\r
-  <body>\r
-    <H1>Credits</H1>\r
-<P>Credits to the <A HREF="http://www.everaldo.com/crystal/">the\r
-Crystal project</A> (<A HREF="http://www.everaldo.com/crystal/">http://www.everaldo.com/crystal/</A>)\r
-for the icons. Thanks to the development teams of\r
-<A HREF="https://javahelp.dev.java.net/" target="_blank">JavaHelp</A>\r
-(<A HREF="https://javahelp.dev.java.net/" target="_blank">https://javahelp.dev.java.net/</A>,\r
-<A HREF="https://swingx.dev.java.net/">SwingX</A> (<A HREF="https://swingx.dev.java.net/">https://swingx.dev.java.net/</A>)\r
-and <A HREF="http://www.miglayout.com/">MigLayout</A> (<A HREF="http://www.miglayout.com/">http://www.miglayout.com/</A>).\r
-Thanks to the <A HREF="http://jhelpdev.sourceforge.net/">JHelpDev</A> team\r
-(<A HREF="http://jhelpdev.sourceforge.net/">http://jhelpdev.sourceforge.net/</A>) for developing the\r
-tool that allowed me to create this documentation without much hassle.\r
-Thanks to divverent (div0) for the <b>demotc</b> perl script. Thanks to merlijn for\r
-some suggestions and hints. Thanks to Dave who offered some "useless" patches :P.\r
-Thank <B>you</B> for using the Nexuiz Demo Recorder.</P>\r
-  </body>\r
-</html>\r
-\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>Credits</H1>
+<P>Credits to the <A HREF="http://www.everaldo.com/crystal/">the
+Crystal project</A> (<A HREF="http://www.everaldo.com/crystal/">http://www.everaldo.com/crystal/</A>)
+for the icons. Thanks to the development teams of
+<A HREF="https://javahelp.dev.java.net/" target="_blank">JavaHelp</A>
+(<A HREF="https://javahelp.dev.java.net/" target="_blank">https://javahelp.dev.java.net/</A>,
+<A HREF="https://swingx.dev.java.net/">SwingX</A> (<A HREF="https://swingx.dev.java.net/">https://swingx.dev.java.net/</A>)
+and <A HREF="http://www.miglayout.com/">MigLayout</A> (<A HREF="http://www.miglayout.com/">http://www.miglayout.com/</A>).
+Thanks to the <A HREF="http://jhelpdev.sourceforge.net/">JHelpDev</A> team
+(<A HREF="http://jhelpdev.sourceforge.net/">http://jhelpdev.sourceforge.net/</A>) for developing the
+tool that allowed me to create this documentation without much hassle.
+Thanks to divverent (div0) for the <b>demotc</b> perl script. Thanks to merlijn for
+some suggestions and hints. Thanks to Dave who offered some "useless" patches :P.
+Thank <B>you</B> for using the Nexuiz Demo Recorder.</P>
+  </body>
+</html>
+
index 7e8a6eda6f651a28e5cde66b17885621887fe560..dfddec07346714b3498605ff24a4723a5fbe44dd 100644 (file)
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\r
-\r
-<html>\r
-  <head>\r
-    <meta name="generator" content="HTML Tidy, see www.w3.org">\r
-\r
-    <title></title>\r
-  </head>\r
-\r
-  <body>\r
-    <H1>FAQ</H1>\r
-<P><I>Question: Is it possible to have a\r
-job being recorded with one of the Nexuiz presets (Low, medium,\r
-normal, high, ultra), without affecting my Nexuiz installation in\r
-general (so that this configuration is just active during capturing\r
-the current demo?</I></P>\r
-<P>Yes it is. In Nexuiz there are config\r
-files for this, which are called:</P>\r
-<UL>\r
-       <LI><P>effects-low.cfg</P>\r
-       <LI><P>effects-med.cfg</P>\r
-       <LI><P>effects-normal.cfg</P>\r
-       <LI><P>effects-high.cfg</P>\r
-       <LI><P>effects-ultra.cfg</P>\r
-       <LI><P>effects-ultimate.cfg</P>\r
-       <LI><P>effects-omg.cfg</P>\r
-</UL>\r
-<P>In order to use them, there are 2\r
-approaches</P>\r
-<P><B>Approach 1:</B> You should\r
-consider to use a new directory for each new set of effect configs\r
-you want to try out. This will simply help you to avoid loss of your\r
-original config, or any unwanted modification to it. For example,\r
-setup Nexuiz manually by starting it with some userdir, e.g. <B>-userdir\r
-effectTestUltimate</B>, start Nexuiz, go into the menu and set the\r
-effects to ultimate (e<I>xec effects-ultimate.cfg</I> in the\r
-console). Maybe fine-tune settings as you like. Then put the <B>-userdir\r
-effectTestUltimate</B> directive into the <B>engine parameters</B>\r
-field of the job.</P>\r
-<P><b>Approach 2:</b> For this approach it is\r
-recommended that you do a backup of your config somewhere else, even\r
-though everything should work fine and your original config should\r
-not be modified at all after the demo was recorded.</P>\r
-<P>Add these 4 lines to the <b>exec before\r
-capture</b> field:</P>\r
-<P><B>saveconfig backup.cfg</B></P>\r
-<P><B>cvar_resettodefaults_all</B></P>\r
-<P><B>exec effects-ultimate.cfg</B></P>\r
-<P><B>r_restart</B></P>\r
-<P>The first line will backup your\r
-current config to &ldquo;backup.cfg&rdquo;. The second line resets\r
-all variables (you can remove this if you want, but sometimes these\r
-effect-xyz.cfg config files will not set every single variable to the\r
-new value, but assume that some values have their default value,\r
-which might not be the case in your config. So resetting everything\r
-to defaults is recommended. Of course you can choose to use another\r
-effects config than <B>effects-ultimate.cfg</B>. After executing that\r
-effects config a restart of the renderer (<B>r_restart</B>) is\r
-mandatory, because of some of the changes of the effects config not\r
-being applied without a renderer-restart. Also, keep in mind that the\r
-cvar_resettodefaults_all will have changed your Nexuiz screen\r
-resolution (vid_height, vid_width). The r_restart command however\r
-won't take these changes into account (it will still use your old resolution),\r
-only <B>vid_restart</B> would.\r
-This just means that you do not have to change these 2 variables\r
-manually to &ldquo;keep&rdquo; your old screen resolution, as long as\r
-you don't use vid_restart.</P>\r
-<P>Also add this to your <b>exec after\r
-capture</b> field:</P>\r
-<P><B>cvar_resettodefaults_all</B></P>\r
-<P><B>exec backup.cfg</B></P>\r
-<P>Please note: in case you want to\r
-setup other special things, e.g. whether to record in the AVI instead\r
-of OGV format, the video FPS, etc, you'd have to put these settings\r
-some place <B>after</B> the <B>cvar_resettodefaults_all</B> command\r
-in the <B>exec before capture</B> field.</P>\r
-<P><BR><BR>\r
-</P>\r
-<P><I>Question: Is it possible to record\r
-a job in a particular screen resolution?</I></P>\r
-<P>Yes it is. The height and width are\r
-configured by setting the value vid_height and vid_width to your\r
-desired value. For example to record a demo in the HD resolution,\r
-which is 1280x720, put this into the <B>exec before capture</B>\r
-field:</P>\r
-<P ALIGN=LEFT><B>vid_width 1280</B></P>\r
-<P ALIGN=LEFT><B>vid_height 720</B></P>\r
-<P ALIGN=LEFT><B>vid_restart</B></P>\r
-<P><BR><BR>\r
-</P>\r
-<P><I>Question: I have found a bug, or I\r
-encountered some other problem, what do I do? </I>\r
-</P>\r
-<P>Well, truth to be told, I cannot\r
-guarantee for any support of this tool. You can put questions or\r
-comments into the forum thread of the AlienTrap forums where you\r
-downloaded the tool from.</P>\r
-  </body>\r
-</html>\r
-\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>FAQ</H1>
+<P><I>Question: Is it possible to have a
+job being recorded with one of the Nexuiz presets (Low, medium,
+normal, high, ultra), without affecting my Nexuiz installation in
+general (so that this configuration is just active during capturing
+the current demo?</I></P>
+<P>Yes it is. In Nexuiz there are config
+files for this, which are called:</P>
+<UL>
+       <LI><P>effects-low.cfg</P>
+       <LI><P>effects-med.cfg</P>
+       <LI><P>effects-normal.cfg</P>
+       <LI><P>effects-high.cfg</P>
+       <LI><P>effects-ultra.cfg</P>
+       <LI><P>effects-ultimate.cfg</P>
+       <LI><P>effects-omg.cfg</P>
+</UL>
+<P>In order to use them, there are 2
+approaches</P>
+<P><B>Approach 1:</B> You should
+consider to use a new directory for each new set of effect configs
+you want to try out. This will simply help you to avoid loss of your
+original config, or any unwanted modification to it. For example,
+setup Nexuiz manually by starting it with some userdir, e.g. <B>-userdir
+effectTestUltimate</B>, start Nexuiz, go into the menu and set the
+effects to ultimate (e<I>xec effects-ultimate.cfg</I> in the
+console). Maybe fine-tune settings as you like. Then put the <B>-userdir
+effectTestUltimate</B> directive into the <B>engine parameters</B>
+field of the job.</P>
+<P><b>Approach 2:</b> For this approach it is
+recommended that you do a backup of your config somewhere else, even
+though everything should work fine and your original config should
+not be modified at all after the demo was recorded.</P>
+<P>Add these 4 lines to the <b>exec before
+capture</b> field:</P>
+<P><B>saveconfig backup.cfg</B></P>
+<P><B>cvar_resettodefaults_all</B></P>
+<P><B>exec effects-ultimate.cfg</B></P>
+<P><B>r_restart</B></P>
+<P>The first line will backup your
+current config to &ldquo;backup.cfg&rdquo;. The second line resets
+all variables (you can remove this if you want, but sometimes these
+effect-xyz.cfg config files will not set every single variable to the
+new value, but assume that some values have their default value,
+which might not be the case in your config. So resetting everything
+to defaults is recommended. Of course you can choose to use another
+effects config than <B>effects-ultimate.cfg</B>. After executing that
+effects config a restart of the renderer (<B>r_restart</B>) is
+mandatory, because of some of the changes of the effects config not
+being applied without a renderer-restart. Also, keep in mind that the
+cvar_resettodefaults_all will have changed your Nexuiz screen
+resolution (vid_height, vid_width). The r_restart command however
+won't take these changes into account (it will still use your old resolution),
+only <B>vid_restart</B> would.
+This just means that you do not have to change these 2 variables
+manually to &ldquo;keep&rdquo; your old screen resolution, as long as
+you don't use vid_restart.</P>
+<P>Also add this to your <b>exec after
+capture</b> field:</P>
+<P><B>cvar_resettodefaults_all</B></P>
+<P><B>exec backup.cfg</B></P>
+<P>Please note: in case you want to
+setup other special things, e.g. whether to record in the AVI instead
+of OGV format, the video FPS, etc, you'd have to put these settings
+some place <B>after</B> the <B>cvar_resettodefaults_all</B> command
+in the <B>exec before capture</B> field.</P>
+<P><BR><BR>
+</P>
+<P><I>Question: Is it possible to record
+a job in a particular screen resolution?</I></P>
+<P>Yes it is. The height and width are
+configured by setting the value vid_height and vid_width to your
+desired value. For example to record a demo in the HD resolution,
+which is 1280x720, put this into the <B>exec before capture</B>
+field:</P>
+<P ALIGN=LEFT><B>vid_width 1280</B></P>
+<P ALIGN=LEFT><B>vid_height 720</B></P>
+<P ALIGN=LEFT><B>vid_restart</B></P>
+<P><BR><BR>
+</P>
+<P><I>Question: I have found a bug, or I
+encountered some other problem, what do I do? </I>
+</P>
+<P>Well, truth to be told, I cannot
+guarantee for any support of this tool. You can put questions or
+comments into the forum thread of the AlienTrap forums where you
+downloaded the tool from.</P>
+  </body>
+</html>
+
index f678b337168bb2113c6b3321a2dd4e94cd4b7871..ac4ea8be58afd97d909554de7273b26f568163ea 100644 (file)
@@ -1,28 +1,28 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\r
-\r
-<html>\r
-  <head>\r
-    <meta name="generator" content="HTML Tidy, see www.w3.org">\r
-\r
-    <title></title>\r
-  </head>\r
-\r
-  <body>\r
-    <H1>Introduction</H1>\r
-<P>Welcome to the Nexuiz demo recorder. This tool is made\r
-for people <B>who are already familiar</B> with the manual demo\r
-recording process and are looking for a way to automate this process.\r
-In simple words, this tool allows you to create jobs that are being\r
-put into a job queue, so that they can be executed one after another,\r
-at a point in time when you don't need to access your computer\r
-anymore. For each job you have to specify the demo you want to\r
-record, the second when the record is supposed to start and end, as\r
-well as the location where to save the final video file to. You can\r
-also use one or more of the available plug-ins to encode the resulting\r
-video file to another format.</P>\r
-<P>To get started, take a look at the <a href="basic_tutorial.html">basic tutorial</a></P>\r
-<P>Click <a href="changelog.html">here</a> to learn about the new features of\r
-this release.</P>\r
-  </body>\r
-</html>\r
-\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>Introduction</H1>
+<P>Welcome to the Nexuiz demo recorder. This tool is made
+for people <B>who are already familiar</B> with the manual demo
+recording process and are looking for a way to automate this process.
+In simple words, this tool allows you to create jobs that are being
+put into a job queue, so that they can be executed one after another,
+at a point in time when you don't need to access your computer
+anymore. For each job you have to specify the demo you want to
+record, the second when the record is supposed to start and end, as
+well as the location where to save the final video file to. You can
+also use one or more of the available plug-ins to encode the resulting
+video file to another format.</P>
+<P>To get started, take a look at the <a href="basic_tutorial.html">basic tutorial</a></P>
+<P>Click <a href="changelog.html">here</a> to learn about the new features of
+this release.</P>
+  </body>
+</html>
+
index fbab77d122f6bdb9769025231f1bd8a928f8b37c..90ee7d0eb467f0d16787d38aba2bb368423f6714 100644 (file)
@@ -1,15 +1,15 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\r
-\r
-<html>\r
-  <head>\r
-    <meta name="generator" content="HTML Tidy, see www.w3.org">\r
-\r
-    <title></title>\r
-  </head>\r
-\r
-  <body>\r
-    <H1>License</H1>\r
-<P>This tool is released under the <b>GPL v2</b> (same license as Nexuiz).</P>\r
-  </body>\r
-</html>\r
-\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>License</H1>
+<P>This tool is released under the <b>GPL v2</b> (same license as Nexuiz).</P>
+  </body>
+</html>
+
index 9502af4e0b4f7eeb78b750da49d483bc056968f0..45fb4ae52b90e4ed032eae76ab1427e67680cdb3 100644 (file)
@@ -1,27 +1,27 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\r
-\r
-<html>\r
-  <head>\r
-    <meta name="generator" content="HTML Tidy, see www.w3.org">\r
-\r
-    <title></title>\r
-  </head>\r
-\r
-  <body>\r
-   <H1>Open/Save Jobs and Templates</H1>\r
-<P>First of all, all your templates and\r
-jobs will automatically be saved when closing the program (and\r
-automatically loaded when starting it again). You can find these\r
-files in the sub-directory &ldquo;settings&rdquo;of the program, the\r
-files are called <B>templates.dat</B> and <B>jobs.dat</B>. Please\r
-note that the job queue will also be saved each time a job finished\r
-its execution (so that you don't lose the progress of your jobs in\r
-case your computer crashes while processing).</P>\r
-<P>You have the possibility to manually\r
-save and load other job queues (this might be handy if you are\r
-working on different projects). For this simply use the <B>File menu</B>\r
-and click on <B>Load job queue</B> or <B>Save job queue</B>\r
-respectively. The same applies to templates.</P>\r
-  </body>\r
-</html>\r
-\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+   <H1>Open/Save Jobs and Templates</H1>
+<P>First of all, all your templates and
+jobs will automatically be saved when closing the program (and
+automatically loaded when starting it again). You can find these
+files in the sub-directory &ldquo;settings&rdquo;of the program, the
+files are called <B>templates.dat</B> and <B>jobs.dat</B>. Please
+note that the job queue will also be saved each time a job finished
+its execution (so that you don't lose the progress of your jobs in
+case your computer crashes while processing).</P>
+<P>You have the possibility to manually
+save and load other job queues (this might be handy if you are
+working on different projects). For this simply use the <B>File menu</B>
+and click on <B>Load job queue</B> or <B>Save job queue</B>
+respectively. The same applies to templates.</P>
+  </body>
+</html>
+
index 35f950716581299e2ffcb497e4be77f85f364241..453232cdcca7ec6334c06912832833b32dab9f57 100644 (file)
@@ -1,81 +1,81 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\r
-\r
-<html>\r
-  <head>\r
-    <meta name="generator" content="HTML Tidy, see www.w3.org">\r
-\r
-    <title></title>\r
-  </head>\r
-\r
-  <body>\r
-<H1>Plug-in architecture</H1>\r
-<H2>Introduction</H2>\r
-<P>While the Nexuiz Demo Recorder (NDR)\r
-saves you a lot of time, what many video editors still need to do is\r
-to transcode/convert/encode/you-name-it the resulting huge avi clip\r
-to another (lossless) format. Or, if you are not an editor but just a\r
-player who wants to distribute the recorded clip, unless you had\r
-Nexuiz encode the clip in the Ogg Theora codec (cl_capturevideo_ogg\r
-1), you might want to encode it in XviD, H.264 or some other lossy\r
-codec. For encoding people use all different kinds of tools. Some\r
-have a GUI, some don't. Some are available on just one platform, some\r
-are cross-platform to some extent. However, since this release the\r
-NDR is now able to also encode clips to whatever format you like by\r
-the use of <B>encoder plug-ins</B>. For this the NDR has been\r
-extended with a plug-in architecture that will look for plug-ins in\r
-the &ldquo;plugins&rdquo; folder. NDR plug-ins are basically just a\r
-bridge between the NDR application and an actual encoder. Examples\r
-for actual encoders are <B>VirtualDub</B> for MS Windows, or <B>mencoder</B>\r
-for Linux. What a plug-in will do is to assemble a command line that\r
-will execute this encoder and hand it the required parameters.</P>\r
-<H2>Extension is easy</H2>\r
-<P>In this release the NDR ships with\r
-just one plug-in: the VirtualDub plug-in. However, if you are a Java\r
-developer it is really easy for you to write your own plug-ins that\r
-will be able to handle whatever encoder you want to use. If you are\r
-interested, have a look into the <B>src</B> folder at the <B>Sample\r
-Plugin</B>. All you need to do is to implement an interface, edit\r
-some meta-information files and build a jar file that is being put\r
-into the <B>plugins</B> directory of the NDR.</P>\r
-<H2>Plug-in settings</H2>\r
-<P>Each plug-in allows the user to\r
-interact with it, by offering settings (or &ldquo;preferences&rdquo;).\r
-There are 2 kinds of settings:</P>\r
-<UL>\r
-       <LI><P>Global settings, which will be\r
-       shown in the File &rarr; Preferences dialog</P>\r
-       <LI><P>Job/template-specific settings,\r
-       which will be shown in the dialog you use when creating or editing\r
-       jobs or templates</P>\r
-</UL>\r
-<H2>Use plug-ins later on</H2>\r
-<P>In order to invoke the functionality\r
-of the plug-in you are not required to set up everything at the very\r
-beginning (before the clip has actually been recorded). Of course,\r
-you can do so, and in this case the job would be recorded and, right\r
-after that, be encoded as well. But you can also choose to have jobs\r
-recorded first, and then later when you see the need to encode them,\r
-edit the job settings (add your plug-in settings there), and then\r
-right-click the job and select &ldquo;just run the xyz plug-in&rdquo;.\r
-Basically this menu item will work on any job with status = <B>done</B>.</P>\r
-<P>Of course you may not move the\r
-recorded clip from the destination that you set up, because otherwise\r
-the plug-in would not find the file to encode it.</P>\r
-<H2>Error handling</H2>\r
-<P>In case something went wrong during\r
-the execution of the plug-in itself, you will see a &ldquo;plug-in\r
-error&rdquo; in the <B>status</B> column of the jobs table. Like for\r
-normal &ldquo;errors&rdquo;, you can right-click the job and click on\r
-&ldquo;show error message&rdquo; in order to see the error message of\r
-the plug-in. In case you can repair whatever went wrong, do so, then\r
-right-click the job and click on &ldquo;reset job status to <B>done</B>&rdquo;.\r
-Then you can run the plug-in again.</P>\r
-<P>If a plug-in appeared to do its job\r
-just fine but you suspect that something went wrong, you might want\r
-to have a look at the <B>logs</B> directory in the NDR folder.\r
-Depending on whether the one who created the plug-in implemented it,\r
-you might see log files which are basically text files containing the\r
-output of the encoder itself.</P>\r
-  </body>\r
-</html>\r
-\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+<H1>Plug-in architecture</H1>
+<H2>Introduction</H2>
+<P>While the Nexuiz Demo Recorder (NDR)
+saves you a lot of time, what many video editors still need to do is
+to transcode/convert/encode/you-name-it the resulting huge avi clip
+to another (lossless) format. Or, if you are not an editor but just a
+player who wants to distribute the recorded clip, unless you had
+Nexuiz encode the clip in the Ogg Theora codec (cl_capturevideo_ogg
+1), you might want to encode it in XviD, H.264 or some other lossy
+codec. For encoding people use all different kinds of tools. Some
+have a GUI, some don't. Some are available on just one platform, some
+are cross-platform to some extent. However, since this release the
+NDR is now able to also encode clips to whatever format you like by
+the use of <B>encoder plug-ins</B>. For this the NDR has been
+extended with a plug-in architecture that will look for plug-ins in
+the &ldquo;plugins&rdquo; folder. NDR plug-ins are basically just a
+bridge between the NDR application and an actual encoder. Examples
+for actual encoders are <B>VirtualDub</B> for MS Windows, or <B>mencoder</B>
+for Linux. What a plug-in will do is to assemble a command line that
+will execute this encoder and hand it the required parameters.</P>
+<H2>Extension is easy</H2>
+<P>In this release the NDR ships with
+just one plug-in: the VirtualDub plug-in. However, if you are a Java
+developer it is really easy for you to write your own plug-ins that
+will be able to handle whatever encoder you want to use. If you are
+interested, have a look into the <B>src</B> folder at the <B>Sample
+Plugin</B>. All you need to do is to implement an interface, edit
+some meta-information files and build a jar file that is being put
+into the <B>plugins</B> directory of the NDR.</P>
+<H2>Plug-in settings</H2>
+<P>Each plug-in allows the user to
+interact with it, by offering settings (or &ldquo;preferences&rdquo;).
+There are 2 kinds of settings:</P>
+<UL>
+       <LI><P>Global settings, which will be
+       shown in the File &rarr; Preferences dialog</P>
+       <LI><P>Job/template-specific settings,
+       which will be shown in the dialog you use when creating or editing
+       jobs or templates</P>
+</UL>
+<H2>Use plug-ins later on</H2>
+<P>In order to invoke the functionality
+of the plug-in you are not required to set up everything at the very
+beginning (before the clip has actually been recorded). Of course,
+you can do so, and in this case the job would be recorded and, right
+after that, be encoded as well. But you can also choose to have jobs
+recorded first, and then later when you see the need to encode them,
+edit the job settings (add your plug-in settings there), and then
+right-click the job and select &ldquo;just run the xyz plug-in&rdquo;.
+Basically this menu item will work on any job with status = <B>done</B>.</P>
+<P>Of course you may not move the
+recorded clip from the destination that you set up, because otherwise
+the plug-in would not find the file to encode it.</P>
+<H2>Error handling</H2>
+<P>In case something went wrong during
+the execution of the plug-in itself, you will see a &ldquo;plug-in
+error&rdquo; in the <B>status</B> column of the jobs table. Like for
+normal &ldquo;errors&rdquo;, you can right-click the job and click on
+&ldquo;show error message&rdquo; in order to see the error message of
+the plug-in. In case you can repair whatever went wrong, do so, then
+right-click the job and click on &ldquo;reset job status to <B>done</B>&rdquo;.
+Then you can run the plug-in again.</P>
+<P>If a plug-in appeared to do its job
+just fine but you suspect that something went wrong, you might want
+to have a look at the <B>logs</B> directory in the NDR folder.
+Depending on whether the one who created the plug-in implemented it,
+you might see log files which are basically text files containing the
+output of the encoder itself.</P>
+  </body>
+</html>
+
index 9824ee7ab7fb7c1dc630fbea2a4e3595c8fc8d80..49df2e0794f4c12645e5424669939dd79759c0b2 100644 (file)
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\r
-\r
-<html>\r
-  <head>\r
-    <meta name="generator" content="HTML Tidy, see www.w3.org">\r
-\r
-    <title></title>\r
-  </head>\r
-\r
-  <body>\r
-<H1>VirtualDub plug-in</H1>\r
-<H2>Introduction</H2>\r
-<P><B><U>Important:</U> Make sure that\r
-you read this documentation carefully in order to avoid mistakes!</B></P>\r
-<P>The prerequisite for using this\r
-plug-in is that you already familiar with the &ldquo;manual&rdquo;\r
-process of encoding clips with VirtualDub (<A HREF="http://www.virtualdub.org/">www.virtualdub.org</A>).\r
-The great thing about VirtualDub is that it offers both a graphical\r
-user interface (GUI), accessible via <B>virtualdub.exe</B>, and a\r
-command line interface (CLI), accessible via <B>vdub.exe</B>.</P>\r
-<H2>Step 1: create VDub configuration\r
-files (VCF)</H2>\r
-<P>Let's say we had a clip that we\r
-wanted to encode not only once, but twice &ndash; into different\r
-formats. Let's say that the first goal is to encode it to a loss-less\r
-Huffyuv clip, and the second goal is to encode it to a quality-based\r
-1-pass XviD clip using MP3 sound.</P>\r
-<P>What you need to do first is to open\r
-VirtualDub, set up the settings for one of the goals (by going into\r
-the video compression and audio compression dialogs) and then click\r
-on File &rarr; Save processing settings (CTRL+S). You will end up\r
-with saving a .vcf file somewhere on your hard drive &hellip;\r
-remember where you put it! After that, you'd set up the settings for\r
-your second goal and save these processing settings as well, as a new\r
-.vcf file.</P>\r
-<H2>Step 2: Enable and set up the VDub\r
-plug-in</H2>\r
-<P>First we need to set up the global\r
-settings of the plug-in. Go into the preferences dialog and enable\r
-the plug-in.</P>\r
-<P><img src="images/preferences_global_vdub.gif"/></P>\r
-<P>Use the &ldquo;...&rdquo; button to\r
-specify the path to the vdub.exe file. The setting <B>max number of\r
-VCFs per job</B> is important, because the number you put in there\r
-will influence how many VCF files you will be able to select in the\r
-<B>job dialog</B> (the dialog where you create or edit a\r
-job/template). For our example we need to set it to 2, but it doesn't\r
-matter if you choose a higher value. Since VDub will encode <B>two</B>\r
-files for us, we will have to tell VDub under which filenames to save\r
-them. You can select which approach you want to use by configuring\r
-the setting <B>output as suffix(0) or file(1)</B>. If you <B>don't</B>\r
-check the checkbox this means that you will enter a small part of the\r
-file name, the <B>suffix</B>, that will be put at the end of the\r
-original file name. E.g. if your original filename was\r
-&ldquo;C:\render\myVideoFile.avi&rdquo;, and the suffix is\r
-&ldquo;_huffyuv&rdquo;, the resulting file would be\r
-&ldquo;C:\render\myVideoFile_huffyuv.avi&rdquo;. However, if you\r
-checked that checkbox, you would instead get a &ldquo;...&rdquo;\r
-button that allows you to specify the filename for the resulting file\r
-manually (this is handy if you wanted to save the file to another\r
-folder for example).</P>\r
-<P>The <B>show extra options</B> setting\r
-will be explained at the end of this page.</P>\r
-<P>Let's now have a look at the dialog\r
-when editing a job</P>\r
-<H2>Step 3: Set up the job-specific\r
-settings</H2>\r
-<P>Open the job dialog.</P>\r
-<P><img src="images/edit_job_vdub.gif" />/P>\r
-<P>Let's ignore the 2 checkboxes at the\r
-top for a moment. What you will need to do is to specify the path to\r
-the 2 .vcf files you created previously in VirtualDub in step 1. Then\r
-you need to specify the suffix (or the file using the file-chooser)\r
-so that VDub knows where to save the encoded videos.</P>\r
-<H2>Step 4: Run the plug-in</H2>\r
-<UL>\r
-       <LI><P>If you had your job recorded\r
-       (without using the plug-in) then the status of the job will already\r
-       be &ldquo;<B>done&rdquo;</B> and you can right-click the job and run\r
-       the VirtualDub plug-in</P>\r
-       <LI><P>If the job has <B>not</B> been\r
-       recorded yet, just click on Start processing and wait for the result</P>\r
-</UL>\r
-<H2>Additional information</H2>\r
-<H3>Empty VCF paths</H3>\r
-<P>As you know you can specify any\r
-number in the <B>preferences dialog</B> for the setting <B>max number\r
-of VCFs per job</B>. In case of our example that consists of 2 jobs,\r
-if you selected 3 (or more) as max. number, and then opened the <B>job\r
-dialog</B>, you would see 3 fields each for the <B>path to VCF file</B>\r
-and <B>output file</B> setting. This, however, is not a problem. The\r
-plug-in will only execute those VCF jobs for which both the path to\r
-the VCF and the output file fields are filled with proper\r
-information.</P>\r
-<H3>VDub job control</H3>\r
-<P>You still don't know what the 2\r
-checkboxes in the <B>job dialog</B> at the top of the VDub settings\r
-mean. What you need to know is that VirtualDub is having its own &ldquo;job\r
-queue&rdquo;, which, however, is called <B>&ldquo;job control&rdquo;</B>.</P>\r
-<P>The following scenarios can make\r
-sense:</P>\r
-<UL>\r
-       <LI><P>Having <B>both</B> checkboxes\r
-       <B>enabled</B>: Each time a new NDR job is executed which has\r
-       VirtualDub jobs, the <B>job control</B> of VirtualDub will be\r
-       cleared. This means that if you had other, old things in there\r
-       (possibly when working with VirtualDub outside of the NDR) that\r
-       these will be deleted from VirtualDub's job control, before the new\r
-       jobs are added there. Checking the second checkbox means that after\r
-       adding one of the VirtualDub jobs from within the the NDR,\r
-       VirtualDub will actually encode them right away (which you will\r
-       want, at least under normal circumstances).</P>\r
-       <LI><P>Having <B>both</B> checkboxes\r
-       <B>disabled</B>: In this case the only thing the NDR would do would\r
-       be to add the two jobs to the job control of VirtualDub, but\r
-       VirtualDub won't be encoding them. They are just put into the queue.\r
-       This means that if you were to start the the graphical user\r
-       interface of VirtualDub after the NDR completed processing, and open\r
-       the Job Control (F4), you'd see these jobs &ldquo;waiting&rdquo;,\r
-       and you could render them by pressing the Start button in Virtual\r
-       Dub.</P>\r
-</UL>\r
-<P>The reason why these checkboxes exist\r
-is flexibility. I often want the NDR to record stuff over night,\r
-because during the recording process my PC is virtually unusable.\r
-However, encoding clips in VirtualDub is a task that, depending on\r
-the codec, can be done in the background and allows me to use the PC\r
-for other things. In this case I'd disable both checkboxes, have the\r
-NDR record all clips and add them to VirtualDub's job control, and\r
-then on the next day, I decide when and which jobs to encode into\r
-different formats from within the VirtualDub GUI.</P>\r
-<P>And btw, just to be clear, the <B>second</B>\r
-checkbox means that, <B>when enabled</B>, VirtualDub would actually\r
-start encoding <U><B>all</B></U> clips that have already been in\r
-VirtualDub's job control and that have not been encoded yet.</P>\r
-<H3>Extra options</H3>\r
-<P>If you selected the <B>show extra\r
-options</B> checkbox in the <B>preferences dialog</B>, you will\r
-notice that, in the <B>job dialog</B>, you got 2 additional\r
-settings/checkboxes for each VCF file. Their names are pretty much\r
-self-explanatory. I added these checkboxes because, after a scene was\r
-rendered, I usually have the clip encoded into a loss-less format\r
-(e.g. Huffyuv) right away. Since it is no problem to encode further\r
-things (e.g. compressed XviD movies) based on the Huffyuv clip\r
-(instead of always using the original avi file), enabling both these\r
-checkboxes for VCF 1 would mean that the 2<SUP>nd</SUP> (3<SUP>rd</SUP>,\r
-4<SUP>th</SUP> &hellip;) VCF jobs will be based on the\r
-huffyuv-encoded file, and I got rid of original, big video as well,\r
-saving hard disk space.</P>\r
-<H3>Trouble shooting</H3>\r
-<P>In case you suspect that the\r
-expected, encoded file(s) is somehow incorrect (or even missing),\r
-have a look at the <B>logs</B> directory in the NDR folder. You will\r
-see log-files with the following format:</P>\r
-<P>VirtualDub_NameOfTheJob_vcf1.log</P>\r
-<P>Instead of &ldquo;vcf1&rdquo; you\r
-might also find &ldquo;vcf2&rdquo;, &ldquo;vcf3&rdquo;... it\r
-specifies for which VCF file of that job the log file stands for.</P>\r
-<P><BR><BR>\r
-</P>\r
-  </body>\r
-</html>\r
-\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+<H1>VirtualDub plug-in</H1>
+<H2>Introduction</H2>
+<P><B><U>Important:</U> Make sure that
+you read this documentation carefully in order to avoid mistakes!</B></P>
+<P>The prerequisite for using this
+plug-in is that you already familiar with the &ldquo;manual&rdquo;
+process of encoding clips with VirtualDub (<A HREF="http://www.virtualdub.org/">www.virtualdub.org</A>).
+The great thing about VirtualDub is that it offers both a graphical
+user interface (GUI), accessible via <B>virtualdub.exe</B>, and a
+command line interface (CLI), accessible via <B>vdub.exe</B>.</P>
+<H2>Step 1: create VDub configuration
+files (VCF)</H2>
+<P>Let's say we had a clip that we
+wanted to encode not only once, but twice &ndash; into different
+formats. Let's say that the first goal is to encode it to a loss-less
+Huffyuv clip, and the second goal is to encode it to a quality-based
+1-pass XviD clip using MP3 sound.</P>
+<P>What you need to do first is to open
+VirtualDub, set up the settings for one of the goals (by going into
+the video compression and audio compression dialogs) and then click
+on File &rarr; Save processing settings (CTRL+S). You will end up
+with saving a .vcf file somewhere on your hard drive &hellip;
+remember where you put it! After that, you'd set up the settings for
+your second goal and save these processing settings as well, as a new
+.vcf file.</P>
+<H2>Step 2: Enable and set up the VDub
+plug-in</H2>
+<P>First we need to set up the global
+settings of the plug-in. Go into the preferences dialog and enable
+the plug-in.</P>
+<P><img src="images/preferences_global_vdub.gif"/></P>
+<P>Use the &ldquo;...&rdquo; button to
+specify the path to the vdub.exe file. The setting <B>max number of
+VCFs per job</B> is important, because the number you put in there
+will influence how many VCF files you will be able to select in the
+<B>job dialog</B> (the dialog where you create or edit a
+job/template). For our example we need to set it to 2, but it doesn't
+matter if you choose a higher value. Since VDub will encode <B>two</B>
+files for us, we will have to tell VDub under which filenames to save
+them. You can select which approach you want to use by configuring
+the setting <B>output as suffix(0) or file(1)</B>. If you <B>don't</B>
+check the checkbox this means that you will enter a small part of the
+file name, the <B>suffix</B>, that will be put at the end of the
+original file name. E.g. if your original filename was
+&ldquo;C:\render\myVideoFile.avi&rdquo;, and the suffix is
+&ldquo;_huffyuv&rdquo;, the resulting file would be
+&ldquo;C:\render\myVideoFile_huffyuv.avi&rdquo;. However, if you
+checked that checkbox, you would instead get a &ldquo;...&rdquo;
+button that allows you to specify the filename for the resulting file
+manually (this is handy if you wanted to save the file to another
+folder for example).</P>
+<P>The <B>show extra options</B> setting
+will be explained at the end of this page.</P>
+<P>Let's now have a look at the dialog
+when editing a job</P>
+<H2>Step 3: Set up the job-specific
+settings</H2>
+<P>Open the job dialog.</P>
+<P><img src="images/edit_job_vdub.gif" />/P>
+<P>Let's ignore the 2 checkboxes at the
+top for a moment. What you will need to do is to specify the path to
+the 2 .vcf files you created previously in VirtualDub in step 1. Then
+you need to specify the suffix (or the file using the file-chooser)
+so that VDub knows where to save the encoded videos.</P>
+<H2>Step 4: Run the plug-in</H2>
+<UL>
+       <LI><P>If you had your job recorded
+       (without using the plug-in) then the status of the job will already
+       be &ldquo;<B>done&rdquo;</B> and you can right-click the job and run
+       the VirtualDub plug-in</P>
+       <LI><P>If the job has <B>not</B> been
+       recorded yet, just click on Start processing and wait for the result</P>
+</UL>
+<H2>Additional information</H2>
+<H3>Empty VCF paths</H3>
+<P>As you know you can specify any
+number in the <B>preferences dialog</B> for the setting <B>max number
+of VCFs per job</B>. In case of our example that consists of 2 jobs,
+if you selected 3 (or more) as max. number, and then opened the <B>job
+dialog</B>, you would see 3 fields each for the <B>path to VCF file</B>
+and <B>output file</B> setting. This, however, is not a problem. The
+plug-in will only execute those VCF jobs for which both the path to
+the VCF and the output file fields are filled with proper
+information.</P>
+<H3>VDub job control</H3>
+<P>You still don't know what the 2
+checkboxes in the <B>job dialog</B> at the top of the VDub settings
+mean. What you need to know is that VirtualDub is having its own &ldquo;job
+queue&rdquo;, which, however, is called <B>&ldquo;job control&rdquo;</B>.</P>
+<P>The following scenarios can make
+sense:</P>
+<UL>
+       <LI><P>Having <B>both</B> checkboxes
+       <B>enabled</B>: Each time a new NDR job is executed which has
+       VirtualDub jobs, the <B>job control</B> of VirtualDub will be
+       cleared. This means that if you had other, old things in there
+       (possibly when working with VirtualDub outside of the NDR) that
+       these will be deleted from VirtualDub's job control, before the new
+       jobs are added there. Checking the second checkbox means that after
+       adding one of the VirtualDub jobs from within the the NDR,
+       VirtualDub will actually encode them right away (which you will
+       want, at least under normal circumstances).</P>
+       <LI><P>Having <B>both</B> checkboxes
+       <B>disabled</B>: In this case the only thing the NDR would do would
+       be to add the two jobs to the job control of VirtualDub, but
+       VirtualDub won't be encoding them. They are just put into the queue.
+       This means that if you were to start the the graphical user
+       interface of VirtualDub after the NDR completed processing, and open
+       the Job Control (F4), you'd see these jobs &ldquo;waiting&rdquo;,
+       and you could render them by pressing the Start button in Virtual
+       Dub.</P>
+</UL>
+<P>The reason why these checkboxes exist
+is flexibility. I often want the NDR to record stuff over night,
+because during the recording process my PC is virtually unusable.
+However, encoding clips in VirtualDub is a task that, depending on
+the codec, can be done in the background and allows me to use the PC
+for other things. In this case I'd disable both checkboxes, have the
+NDR record all clips and add them to VirtualDub's job control, and
+then on the next day, I decide when and which jobs to encode into
+different formats from within the VirtualDub GUI.</P>
+<P>And btw, just to be clear, the <B>second</B>
+checkbox means that, <B>when enabled</B>, VirtualDub would actually
+start encoding <U><B>all</B></U> clips that have already been in
+VirtualDub's job control and that have not been encoded yet.</P>
+<H3>Extra options</H3>
+<P>If you selected the <B>show extra
+options</B> checkbox in the <B>preferences dialog</B>, you will
+notice that, in the <B>job dialog</B>, you got 2 additional
+settings/checkboxes for each VCF file. Their names are pretty much
+self-explanatory. I added these checkboxes because, after a scene was
+rendered, I usually have the clip encoded into a loss-less format
+(e.g. Huffyuv) right away. Since it is no problem to encode further
+things (e.g. compressed XviD movies) based on the Huffyuv clip
+(instead of always using the original avi file), enabling both these
+checkboxes for VCF 1 would mean that the 2<SUP>nd</SUP> (3<SUP>rd</SUP>,
+4<SUP>th</SUP> &hellip;) VCF jobs will be based on the
+huffyuv-encoded file, and I got rid of original, big video as well,
+saving hard disk space.</P>
+<H3>Trouble shooting</H3>
+<P>In case you suspect that the
+expected, encoded file(s) is somehow incorrect (or even missing),
+have a look at the <B>logs</B> directory in the NDR folder. You will
+see log-files with the following format:</P>
+<P>VirtualDub_NameOfTheJob_vcf1.log</P>
+<P>Instead of &ldquo;vcf1&rdquo; you
+might also find &ldquo;vcf2&rdquo;, &ldquo;vcf3&rdquo;... it
+specifies for which VCF file of that job the log file stands for.</P>
+<P><BR><BR>
+</P>
+  </body>
+</html>
+
index 00a9c1228dcfb4ab00f5ace1fd33bd503fe1f390..d35505806f08a6f692e4e21ced9a5300a5069a6d 100644 (file)
@@ -1,64 +1,64 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\r
-\r
-<html>\r
-  <head>\r
-    <meta name="generator" content="HTML Tidy, see www.w3.org">\r
-\r
-    <title></title>\r
-  </head>\r
-\r
-  <body>\r
-    <H1>Preferences dialog</H1>\r
-<P>The preferences dialog allows you to\r
-change the behavior of the program in certain situaitons.</P>\r
-<P><img src="images/preferences_dialog.gif" /></P>\r
-<UL>\r
-       <LI><P><b>Overwrite final video\r
-       destination file if it exists:</b> This checkbox allows you to determine\r
-       whether the final destination video file (e.g.\r
-       <B>C:\render\fragOfTheMonth.avi</B>) should be overwritten by a job\r
-       if that file already existed from your harddrive. By default this\r
-       option is disabled. The program will, in this case, not overwrite\r
-       any existing files, but save the video file with a &ldquo;copyX&rdquo;\r
-       suffix, for example <B>C:\render\fragOfTheMonth_copy1.avi</B></P>\r
-       <LI><P><b>Disable rendering while\r
-       fast-forwarding:</b> As the Nexuiz demo recorder creates a cutted demo\r
-       that has slowmo-commands in it to fast-forward it to the desired\r
-       start time, the fast-forwarding process is often slowed down due to\r
-       the action taking place on your screen (and your graphic card\r
-       choking with the mere speed of the demo, e.g. during a &ldquo;slowmo\r
-       100&rdquo; period). However, there is a variable called r_render in\r
-       the engine which can be set to 0, which will disable any render\r
-       updates. This will speed up the fast-forwarding process massively.\r
-       Of course, r_render is being set to 1 a few seconds before start\r
-       time is reached.</P>\r
-       <LI><P><b>Disable sound while\r
-       fast-forwarding:</b> The value of the variable volume is saved and the\r
-       set to 0 right after the demo was loaded. The reason is that it can\r
-       be annoying to hear the in-game sounds while fast-forwarding.</P>\r
-       <LI><P><b>Fast-forward speed (first stage\r
-       or second stage):</b> The demo is fast-forwarded using two different\r
-       fast-forward speeds. In the first stage, when the start time is\r
-       still about a minute away, the &ldquo;first stage&rdquo; value is\r
-       used. Then, until 5 seconds before the start time, the &ldquo;second\r
-       stage&rdquo; value is used. You will only need to manipulate these\r
-       values if you discover that the final recorded video is inaccurate\r
-       when it comes to the time when the recording starts (e.g. when then\r
-       video recording starts too late, reduce these 2 values for first and\r
-       second stage a bit &ndash; you will have to experiment)</P>\r
-       <LI><P><b>Do not delete cut demos:</b> The\r
-       file name of the automatically created cut demo is like the original\r
-       name of your demo, but ends with &ldquo;_autocut.dem&rdquo;.\r
-       Normally this demo is created, being recorded from, and then deleted\r
-       again. If you enable this option the demo file is not deleted, and\r
-       you can inspect it with a Hex editor or whatever else you want to do\r
-       with it (maybe send it to a friend to have him record it?)</P>\r
-       <LI><P><b>Append this suffix to job-name when duplicating jobs:</b>\r
-       As you now have customizable job names it would probably be a bad idea to\r
-       give duplicated jobs the same name as the original jobs. This is why you can\r
-       set up a suffix that will be appended to the original job name.</P>\r
-\r
-</UL>\r
-  </body>\r
-</html>\r
-\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+    <H1>Preferences dialog</H1>
+<P>The preferences dialog allows you to
+change the behavior of the program in certain situaitons.</P>
+<P><img src="images/preferences_dialog.gif" /></P>
+<UL>
+       <LI><P><b>Overwrite final video
+       destination file if it exists:</b> This checkbox allows you to determine
+       whether the final destination video file (e.g.
+       <B>C:\render\fragOfTheMonth.avi</B>) should be overwritten by a job
+       if that file already existed from your harddrive. By default this
+       option is disabled. The program will, in this case, not overwrite
+       any existing files, but save the video file with a &ldquo;copyX&rdquo;
+       suffix, for example <B>C:\render\fragOfTheMonth_copy1.avi</B></P>
+       <LI><P><b>Disable rendering while
+       fast-forwarding:</b> As the Nexuiz demo recorder creates a cutted demo
+       that has slowmo-commands in it to fast-forward it to the desired
+       start time, the fast-forwarding process is often slowed down due to
+       the action taking place on your screen (and your graphic card
+       choking with the mere speed of the demo, e.g. during a &ldquo;slowmo
+       100&rdquo; period). However, there is a variable called r_render in
+       the engine which can be set to 0, which will disable any render
+       updates. This will speed up the fast-forwarding process massively.
+       Of course, r_render is being set to 1 a few seconds before start
+       time is reached.</P>
+       <LI><P><b>Disable sound while
+       fast-forwarding:</b> The value of the variable volume is saved and the
+       set to 0 right after the demo was loaded. The reason is that it can
+       be annoying to hear the in-game sounds while fast-forwarding.</P>
+       <LI><P><b>Fast-forward speed (first stage
+       or second stage):</b> The demo is fast-forwarded using two different
+       fast-forward speeds. In the first stage, when the start time is
+       still about a minute away, the &ldquo;first stage&rdquo; value is
+       used. Then, until 5 seconds before the start time, the &ldquo;second
+       stage&rdquo; value is used. You will only need to manipulate these
+       values if you discover that the final recorded video is inaccurate
+       when it comes to the time when the recording starts (e.g. when then
+       video recording starts too late, reduce these 2 values for first and
+       second stage a bit &ndash; you will have to experiment)</P>
+       <LI><P><b>Do not delete cut demos:</b> The
+       file name of the automatically created cut demo is like the original
+       name of your demo, but ends with &ldquo;_autocut.dem&rdquo;.
+       Normally this demo is created, being recorded from, and then deleted
+       again. If you enable this option the demo file is not deleted, and
+       you can inspect it with a Hex editor or whatever else you want to do
+       with it (maybe send it to a friend to have him record it?)</P>
+       <LI><P><b>Append this suffix to job-name when duplicating jobs:</b>
+       As you now have customizable job names it would probably be a bad idea to
+       give duplicated jobs the same name as the original jobs. This is why you can
+       set up a suffix that will be appended to the original job name.</P>
+
+</UL>
+  </body>
+</html>
+
index 7385cc69c4528de3ec0438f7e675b831ec0f1d0a..debf2022e9f9b448eefbb0460f4550bc61762857 100644 (file)
@@ -1,69 +1,69 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\r
-\r
-<html>\r
-  <head>\r
-    <meta name="generator" content="HTML Tidy, see www.w3.org">\r
-\r
-    <title></title>\r
-  </head>\r
-\r
-  <body>\r
-<H1>Templates</H1>\r
-<P>The Nexuiz demo recorder offers you\r
-templates that you can use to create new jobs (which are based on a\r
-template) more quickly and efficiently. Imagine these &quot;templates&quot;\r
-didn't exist ... you would always have to specify the complete path\r
-to the engine, the video file, the demo file, and the final video\r
-file &ndash; each single time when creating a job, from scratch. This\r
-would take a long time and would be inefficient.</P>\r
-<P>Instead you are encouraged to create\r
-templates. Templates can be created either from scratch (click on the\r
-<B>+ Create</B> button next to the template table) or from an\r
-existing job (select the job, then click on the button <B>Create from\r
-job</B>).</P>\r
-<P>The latter will just add a new entry\r
-in the templates table, without prompting you for any further\r
-information. The template's name and summary will be &quot;Generated\r
-from job&quot;, and you can change this by double-clicking this\r
-generated template, renaming it and giving it a meaningful summary,\r
-then click save. You will notice that all other values have been\r
-taken from the job and don't need to be filled out by you anymore.</P>\r
-<P>Here you can see the template dialog\r
-when creating a new template:</P>\r
-<P><img src="images/create_template.gif" /></P>\r
-<P>The dialog looks very much alike the\r
-&quot;create job&quot; dialog presented to you in the <a href="basic_tutorial.html#jobs_create">Basic\r
-tutorial</a>. There are a few differences, however:</P>\r
-<UL>\r
-       <LI><P>The dialog shows you a <B>template\r
-       name</B> and <B>description</B></P>\r
-       <LI><P>Instead of specifying a <B>demo\r
-       file</B> you are now specifying a <B>demo directory</B></P>\r
-       <LI><P>Instead of specifying a <B>video\r
-       destination file</B>, you now just specify the <B>directory</B></P>\r
-       <LI><P>You\r
-       don't specify a <B>start time</B> or <B>end time</B> for templates,\r
-       because these 2 input parameters are specific for each job and don't\r
-       make sense to be saved for a template</P>\r
-</UL>\r
-<P>Once you have a template you can\r
-create new jobs easily by selecting a template in the templates table\r
-and then click on the <B>Create from template</B> button in the jobs\r
-panel.\r
-</P>\r
-<H2>What could templates also be good\r
-for?</H2>\r
-<UL>\r
-       <LI><P>Using different configs for\r
-       recording (you can set a config to be used either in the &ldquo;engine\r
-       parameters&rdquo; field, e.g. <B>+exec myconfig.cfg</B>, or you\r
-       could put the <B>exec myconfig.cfg</B> into the &ldquo;exec before&rdquo;\r
-       field.</P>\r
-       <LI><P>Recording with different Nexuiz\r
-       engines to compare the visual quality</P>\r
-       <LI><P>Inspire me &hellip; what do you\r
-       use templates for? Post it to the forum!</P>\r
-</UL>\r
-  </body>\r
-</html>\r
-\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
+
+<html>
+  <head>
+    <meta name="generator" content="HTML Tidy, see www.w3.org">
+
+    <title></title>
+  </head>
+
+  <body>
+<H1>Templates</H1>
+<P>The Nexuiz demo recorder offers you
+templates that you can use to create new jobs (which are based on a
+template) more quickly and efficiently. Imagine these &quot;templates&quot;
+didn't exist ... you would always have to specify the complete path
+to the engine, the video file, the demo file, and the final video
+file &ndash; each single time when creating a job, from scratch. This
+would take a long time and would be inefficient.</P>
+<P>Instead you are encouraged to create
+templates. Templates can be created either from scratch (click on the
+<B>+ Create</B> button next to the template table) or from an
+existing job (select the job, then click on the button <B>Create from
+job</B>).</P>
+<P>The latter will just add a new entry
+in the templates table, without prompting you for any further
+information. The template's name and summary will be &quot;Generated
+from job&quot;, and you can change this by double-clicking this
+generated template, renaming it and giving it a meaningful summary,
+then click save. You will notice that all other values have been
+taken from the job and don't need to be filled out by you anymore.</P>
+<P>Here you can see the template dialog
+when creating a new template:</P>
+<P><img src="images/create_template.gif" /></P>
+<P>The dialog looks very much alike the
+&quot;create job&quot; dialog presented to you in the <a href="basic_tutorial.html#jobs_create">Basic
+tutorial</a>. There are a few differences, however:</P>
+<UL>
+       <LI><P>The dialog shows you a <B>template
+       name</B> and <B>description</B></P>
+       <LI><P>Instead of specifying a <B>demo
+       file</B> you are now specifying a <B>demo directory</B></P>
+       <LI><P>Instead of specifying a <B>video
+       destination file</B>, you now just specify the <B>directory</B></P>
+       <LI><P>You
+       don't specify a <B>start time</B> or <B>end time</B> for templates,
+       because these 2 input parameters are specific for each job and don't
+       make sense to be saved for a template</P>
+</UL>
+<P>Once you have a template you can
+create new jobs easily by selecting a template in the templates table
+and then click on the <B>Create from template</B> button in the jobs
+panel.
+</P>
+<H2>What could templates also be good
+for?</H2>
+<UL>
+       <LI><P>Using different configs for
+       recording (you can set a config to be used either in the &ldquo;engine
+       parameters&rdquo; field, e.g. <B>+exec myconfig.cfg</B>, or you
+       could put the <B>exec myconfig.cfg</B> into the &ldquo;exec before&rdquo;
+       field.</P>
+       <LI><P>Recording with different Nexuiz
+       engines to compare the visual quality</P>
+       <LI><P>Inspire me &hellip; what do you
+       use templates for? Post it to the forum!</P>
+</UL>
+  </body>
+</html>
+
index 58547953f17d716039f11f4fd8664cae0f0e1265..3522b0137c8b14f31157c578032d3e6a6837776d 100644 (file)
@@ -1,35 +1,35 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
-       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">\r
-       <modelVersion>4.0.0</modelVersion>\r
-       <groupId>NexuizDemoRecorder</groupId>\r
-       <artifactId>SamplePlugin</artifactId>\r
-       <version>0.0.1-SNAPSHOT</version>\r
-       <dependencies>\r
-               <dependency>\r
-                       <groupId>NexuizDemoRecorder</groupId>\r
-                       <artifactId>NexuizDemoRecorder</artifactId>\r
-                       <version>0.3</version>\r
-                       <scope>provided</scope>\r
-               </dependency>\r
-       </dependencies>\r
-       <build>\r
-               <resources>\r
-                       <resource>\r
-                               <directory>src/main/resources</directory>\r
-                       </resource>\r
-               </resources>\r
-               <plugins>\r
-                       <plugin>\r
-                               <groupId>org.apache.maven.plugins</groupId>\r
-                               <artifactId>maven-compiler-plugin</artifactId>\r
-                               <version>2.0.2</version>\r
-                               <configuration>\r
-                                       <source>1.6</source>\r
-                                       <target>1.6</target>\r
-                               </configuration>\r
-                       </plugin>\r
-\r
-\r
-               </plugins>\r
-       </build>\r
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <groupId>NexuizDemoRecorder</groupId>
+       <artifactId>SamplePlugin</artifactId>
+       <version>0.0.1-SNAPSHOT</version>
+       <dependencies>
+               <dependency>
+                       <groupId>NexuizDemoRecorder</groupId>
+                       <artifactId>NexuizDemoRecorder</artifactId>
+                       <version>0.3</version>
+                       <scope>provided</scope>
+               </dependency>
+       </dependencies>
+       <build>
+               <resources>
+                       <resource>
+                               <directory>src/main/resources</directory>
+                       </resource>
+               </resources>
+               <plugins>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-compiler-plugin</artifactId>
+                               <version>2.0.2</version>
+                               <configuration>
+                                       <source>1.6</source>
+                                       <target>1.6</target>
+                               </configuration>
+                       </plugin>
+
+
+               </plugins>
+       </build>
 </project>
\ No newline at end of file
index 72b0f7306ec14fef4783212209dc8f69c77db9f9..e0596a8a365277d6a082736135f10580e1df16df 100644 (file)
-package com.nexuiz.demorecorder.application.plugins.impl.sample;\r
-\r
-import java.util.Properties;\r
-\r
-import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
-import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
-import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;\r
-import com.nexuiz.demorecorder.application.plugins.EncoderPluginException;\r
-\r
-/**\r
- * This is a sample plug-in implementation. It does not really do anything, but it\r
- * is supposed to show you how to implement a plug-in and where to do what.\r
- * \r
- * First of all, it is important that your final jar file (you can have Maven create\r
- * it for you) contains the META-INF folder (it will have that one anyway), and within\r
- * that folder you must have the folder "services", in which you must have a file called\r
- * com.nexuiz.demorecorder.application.plugins.EncoderPlugin (this is the fully\r
- * qualified name of the interface you need to implement, EncoderPlugin).\r
- * This file needs to contain just one line: the fully qualified name to your\r
- * implementation class!\r
- * \r
- * Okay. The Nexuiz Demo Recorder (NDR) gives your plug-in 2 kinds of possibilities to\r
- * configure it ("set it up") from within the NDR. Configuring the plug-in is also \r
- * referred to as "setting preferences". There are \r
- * - Global preferences: these will be shown in the "Preferences" dialog of the NDR\r
- * - Job-specific preferences: these will be shown in the dialog you get when creating\r
- *   new jobs or templates, or when editing them\r
- * \r
- * Once the NDR loaded your plug-in, the first thing it will do is to call \r
- * setApplicationLayer(), handing your plug-in the reference to the app-layer. Make sure that\r
- * you save it in a private member variable!\r
- * \r
- * NDR will ask your plug-in to tell it about its global and job-specific preferences that exist.\r
- * For each of these 2 kinds of preferences it will also ask you for the order in which you want\r
- * these settings to appear in dialogs.\r
- * \r
- * The methods that ask you to return a Properties object: create a new Properties object and fill\r
- * it with KEYS (that identify the setting), and VALUES (reasonable default values). The app-layer\r
- * will save these "new" settings in the app_preferences.xml in the "settings" folder once NDR\r
- * is closed (this applies only to the global settings!). That just means that, later on, to figure\r
- * out whether the user changed settings from their default value, you need to ask the app-layer\r
- * for its preferences object (that might have been manipulated by the user using the GUI) and look\r
- * for "your" settings in that Properties object. A good example is the isEnabled() method.\r
- */\r
-public class SamplePlugin implements EncoderPlugin {\r
-       \r
-       /**\r
-        *  Do not put the word "plug-in" in here, that would be redundant.\r
-        */\r
-       private static final String PLUGIN_NAME = "Sample";\r
-       \r
-       /**\r
-        * Here we store our preferences. It is not necessary that these are in a inner-class, do it in\r
-        * your way if you want.\r
-        */\r
-       private static class Preferences {\r
-               /*\r
-                * Lets start with GLOBAL settings which will be seen in the Preferences dialog of the NDR\r
-                */\r
-               public static final String ENABLED = "Enabled"; //we will need this! "Enabled" means that\r
-                                                                       //that the preferences dialog will show the exact word "Enabled"\r
-               \r
-               public static final String SAMPLE_SETTING = "Some sample setting";\r
-               \r
-               /*\r
-                * Now we define the order in which we want these to be shown.\r
-                */\r
-               public static final String[] GLOBAL_PREFERENCES_ORDER = {\r
-                       ENABLED,\r
-                       SAMPLE_SETTING\r
-               };\r
-               \r
-               //job-specific preferences\r
-               public static final String IN_USE_FOR_THIS_JOB = "Do something for this job";\r
-               \r
-               /*\r
-                * OK, so far we have actually only created labels. But we also need default values\r
-                * So let's have a function that sets the default values up.\r
-                */\r
-               public static Properties globalDefaultPreferences = new Properties();\r
-               public static void createPreferenceDefaultValues() {\r
-                       globalDefaultPreferences.setProperty(ENABLED, "false");\r
-                       globalDefaultPreferences.setProperty(SAMPLE_SETTING, "filechooser");\r
-                       /*\r
-                        * Note that the values for the defaults can be:\r
-                        * - "true" or "false", in this case the GUI will show a check-box\r
-                        * - "filechooser", in this case the GUI will show a button that allows the user to select\r
-                        *    a file\r
-                        * - anything else (also empty string if you like): will show a text field in the GUI\r
-                        *   (you are in charge of parsing it)\r
-                        */\r
-               }\r
-               \r
-       }\r
-       \r
-       private DemoRecorderApplication appLayer;\r
-       \r
-       /**\r
-        * You must only have a default constructor without parameters!\r
-        */\r
-       public SamplePlugin() {\r
-               Preferences.createPreferenceDefaultValues();\r
-       }\r
-\r
-       \r
-\r
-       @Override\r
-       public Properties getGlobalPreferences() {\r
-               return Preferences.globalDefaultPreferences;\r
-       }\r
-\r
-       @Override\r
-       public String[] getGlobalPreferencesOrder() {\r
-               return Preferences.GLOBAL_PREFERENCES_ORDER;\r
-       }\r
-\r
-       @Override\r
-       public Properties getJobSpecificPreferences() {\r
-               /*\r
-                * This method is called whenever the dialog to create new jobs/templates (or edit them)\r
-                * is opened. This means that you can dynamically create the returned Properties object\r
-                * if you like, or you could of course also return something static.\r
-                */\r
-               Properties preferences = new Properties();\r
-               preferences.setProperty(Preferences.IN_USE_FOR_THIS_JOB, "true");\r
-               return preferences;\r
-       }\r
-\r
-       @Override\r
-       public String[] getJobSpecificPreferencesOrder() {\r
-               String[] order = {Preferences.IN_USE_FOR_THIS_JOB};\r
-               return order;\r
-       }\r
-\r
-       @Override\r
-       public String getName() {\r
-               return PLUGIN_NAME;\r
-       }\r
-       \r
-       @Override\r
-       public void setApplicationLayer(DemoRecorderApplication appLayer) {\r
-               this.appLayer = appLayer;\r
-       }\r
-\r
-       @Override\r
-       public boolean isEnabled() {\r
-               /*\r
-                * Here we get the Properties object of the app-layer. Notice that this is actually a\r
-                * NDRPreferences object. It has a new method getProperty(String category, String key).\r
-                * The category is the name of our plug-in. The key is obviously our own ENABLED key.\r
-                */\r
-               String enabledString = this.appLayer.getPreferences().getProperty(PLUGIN_NAME, Preferences.ENABLED);\r
-               return Boolean.valueOf(enabledString);\r
-       }\r
-       \r
-       @Override\r
-       public void executeEncoder(RecordJob job) throws EncoderPluginException {\r
-               /*\r
-                * This is where the party gets started.\r
-                * Of course you need to check whether your plug-in is enabled by the user, and whether the\r
-                * job-specific settings are set correctly. So let's do this now:\r
-                */\r
-               if (!this.isEnabled()) {\r
-                       return;\r
-               }\r
-               \r
-               if (job.getActualVideoDestination() == null) {\r
-                       //should never happen... but just to make sure!\r
-                       throw new EncoderPluginException("Actual video destination is not set (should have been set when processing the job)");\r
-               }\r
-               \r
-               if (!job.getActualVideoDestination().exists()) {\r
-                       throw new EncoderPluginException("Could not locate recorded video file (source) at location "\r
-                                       + job.getActualVideoDestination().getAbsolutePath());\r
-               }\r
-               \r
-               //check for a job-specific setting ... this time we need it from the job:\r
-               Properties jobSpecificSettings = job.getEncoderPluginSettings(this);\r
-               String isEnabled = jobSpecificSettings.getProperty(Preferences.IN_USE_FOR_THIS_JOB);\r
-               if (!Boolean.valueOf(isEnabled)) {\r
-                       //the job does not want our plug-in to be executed, d'oh\r
-                       throw new EncoderPluginException("We are not enabled to do anything for this job :-(");\r
-                       //of course in a real implementation, instead of throwing an exception we'd just "return;"\r
-               }\r
-               \r
-               /*\r
-                * Now we can start doing the work. What you'll normally do is to construct a big string that you then have executed\r
-                * Have a look at the VirtualDub plug-in implementation to see how I did it.\r
-                * \r
-                * IMPORTANT: unless you parse the output of the console when executing a shell command (to check whether\r
-                * the encoder threw error messages at you), it is recommended that you create a log file of each job.\r
-                * The VirtualDub plug-in also provides an example of how to do that.\r
-                * \r
-                * Also notice the use of the EncoderPluginException. Whenever something goes wrong, throw this exception.\r
-                * Note that there is also another constructor EncoderPluginException(String message, Throwable t) where you\r
-                * can attach the original exception.\r
-                */\r
-       }\r
-\r
-       \r
-\r
-}\r
+package com.nexuiz.demorecorder.application.plugins.impl.sample;
+
+import java.util.Properties;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+import com.nexuiz.demorecorder.application.plugins.EncoderPluginException;
+
+/**
+ * This is a sample plug-in implementation. It does not really do anything, but it
+ * is supposed to show you how to implement a plug-in and where to do what.
+ * 
+ * First of all, it is important that your final jar file (you can have Maven create
+ * it for you) contains the META-INF folder (it will have that one anyway), and within
+ * that folder you must have the folder "services", in which you must have a file called
+ * com.nexuiz.demorecorder.application.plugins.EncoderPlugin (this is the fully
+ * qualified name of the interface you need to implement, EncoderPlugin).
+ * This file needs to contain just one line: the fully qualified name to your
+ * implementation class!
+ * 
+ * Okay. The Nexuiz Demo Recorder (NDR) gives your plug-in 2 kinds of possibilities to
+ * configure it ("set it up") from within the NDR. Configuring the plug-in is also 
+ * referred to as "setting preferences". There are 
+ * - Global preferences: these will be shown in the "Preferences" dialog of the NDR
+ * - Job-specific preferences: these will be shown in the dialog you get when creating
+ *   new jobs or templates, or when editing them
+ * 
+ * Once the NDR loaded your plug-in, the first thing it will do is to call 
+ * setApplicationLayer(), handing your plug-in the reference to the app-layer. Make sure that
+ * you save it in a private member variable!
+ * 
+ * NDR will ask your plug-in to tell it about its global and job-specific preferences that exist.
+ * For each of these 2 kinds of preferences it will also ask you for the order in which you want
+ * these settings to appear in dialogs.
+ * 
+ * The methods that ask you to return a Properties object: create a new Properties object and fill
+ * it with KEYS (that identify the setting), and VALUES (reasonable default values). The app-layer
+ * will save these "new" settings in the app_preferences.xml in the "settings" folder once NDR
+ * is closed (this applies only to the global settings!). That just means that, later on, to figure
+ * out whether the user changed settings from their default value, you need to ask the app-layer
+ * for its preferences object (that might have been manipulated by the user using the GUI) and look
+ * for "your" settings in that Properties object. A good example is the isEnabled() method.
+ */
+public class SamplePlugin implements EncoderPlugin {
+       
+       /**
+        *  Do not put the word "plug-in" in here, that would be redundant.
+        */
+       private static final String PLUGIN_NAME = "Sample";
+       
+       /**
+        * Here we store our preferences. It is not necessary that these are in a inner-class, do it in
+        * your way if you want.
+        */
+       private static class Preferences {
+               /*
+                * Lets start with GLOBAL settings which will be seen in the Preferences dialog of the NDR
+                */
+               public static final String ENABLED = "Enabled"; //we will need this! "Enabled" means that
+                                                                       //that the preferences dialog will show the exact word "Enabled"
+               
+               public static final String SAMPLE_SETTING = "Some sample setting";
+               
+               /*
+                * Now we define the order in which we want these to be shown.
+                */
+               public static final String[] GLOBAL_PREFERENCES_ORDER = {
+                       ENABLED,
+                       SAMPLE_SETTING
+               };
+               
+               //job-specific preferences
+               public static final String IN_USE_FOR_THIS_JOB = "Do something for this job";
+               
+               /*
+                * OK, so far we have actually only created labels. But we also need default values
+                * So let's have a function that sets the default values up.
+                */
+               public static Properties globalDefaultPreferences = new Properties();
+               public static void createPreferenceDefaultValues() {
+                       globalDefaultPreferences.setProperty(ENABLED, "false");
+                       globalDefaultPreferences.setProperty(SAMPLE_SETTING, "filechooser");
+                       /*
+                        * Note that the values for the defaults can be:
+                        * - "true" or "false", in this case the GUI will show a check-box
+                        * - "filechooser", in this case the GUI will show a button that allows the user to select
+                        *    a file
+                        * - anything else (also empty string if you like): will show a text field in the GUI
+                        *   (you are in charge of parsing it)
+                        */
+               }
+               
+       }
+       
+       private DemoRecorderApplication appLayer;
+       
+       /**
+        * You must only have a default constructor without parameters!
+        */
+       public SamplePlugin() {
+               Preferences.createPreferenceDefaultValues();
+       }
+
+       
+
+       @Override
+       public Properties getGlobalPreferences() {
+               return Preferences.globalDefaultPreferences;
+       }
+
+       @Override
+       public String[] getGlobalPreferencesOrder() {
+               return Preferences.GLOBAL_PREFERENCES_ORDER;
+       }
+
+       @Override
+       public Properties getJobSpecificPreferences() {
+               /*
+                * This method is called whenever the dialog to create new jobs/templates (or edit them)
+                * is opened. This means that you can dynamically create the returned Properties object
+                * if you like, or you could of course also return something static.
+                */
+               Properties preferences = new Properties();
+               preferences.setProperty(Preferences.IN_USE_FOR_THIS_JOB, "true");
+               return preferences;
+       }
+
+       @Override
+       public String[] getJobSpecificPreferencesOrder() {
+               String[] order = {Preferences.IN_USE_FOR_THIS_JOB};
+               return order;
+       }
+
+       @Override
+       public String getName() {
+               return PLUGIN_NAME;
+       }
+       
+       @Override
+       public void setApplicationLayer(DemoRecorderApplication appLayer) {
+               this.appLayer = appLayer;
+       }
+
+       @Override
+       public boolean isEnabled() {
+               /*
+                * Here we get the Properties object of the app-layer. Notice that this is actually a
+                * NDRPreferences object. It has a new method getProperty(String category, String key).
+                * The category is the name of our plug-in. The key is obviously our own ENABLED key.
+                */
+               String enabledString = this.appLayer.getPreferences().getProperty(PLUGIN_NAME, Preferences.ENABLED);
+               return Boolean.valueOf(enabledString);
+       }
+       
+       @Override
+       public void executeEncoder(RecordJob job) throws EncoderPluginException {
+               /*
+                * This is where the party gets started.
+                * Of course you need to check whether your plug-in is enabled by the user, and whether the
+                * job-specific settings are set correctly. So let's do this now:
+                */
+               if (!this.isEnabled()) {
+                       return;
+               }
+               
+               if (job.getActualVideoDestination() == null) {
+                       //should never happen... but just to make sure!
+                       throw new EncoderPluginException("Actual video destination is not set (should have been set when processing the job)");
+               }
+               
+               if (!job.getActualVideoDestination().exists()) {
+                       throw new EncoderPluginException("Could not locate recorded video file (source) at location "
+                                       + job.getActualVideoDestination().getAbsolutePath());
+               }
+               
+               //check for a job-specific setting ... this time we need it from the job:
+               Properties jobSpecificSettings = job.getEncoderPluginSettings(this);
+               String isEnabled = jobSpecificSettings.getProperty(Preferences.IN_USE_FOR_THIS_JOB);
+               if (!Boolean.valueOf(isEnabled)) {
+                       //the job does not want our plug-in to be executed, d'oh
+                       throw new EncoderPluginException("We are not enabled to do anything for this job :-(");
+                       //of course in a real implementation, instead of throwing an exception we'd just "return;"
+               }
+               
+               /*
+                * Now we can start doing the work. What you'll normally do is to construct a big string that you then have executed
+                * Have a look at the VirtualDub plug-in implementation to see how I did it.
+                * 
+                * IMPORTANT: unless you parse the output of the console when executing a shell command (to check whether
+                * the encoder threw error messages at you), it is recommended that you create a log file of each job.
+                * The VirtualDub plug-in also provides an example of how to do that.
+                * 
+                * Also notice the use of the EncoderPluginException. Whenever something goes wrong, throw this exception.
+                * Note that there is also another constructor EncoderPluginException(String message, Throwable t) where you
+                * can attach the original exception.
+                */
+       }
+
+       
+
+}
index 6f95540c284e7df862a0b6980bf618ef273cf86e..ddea72a4cb63b3d706f0fb2d0c91ac85210473bb 100644 (file)
-package com.nexuiz.demorecorder.application.plugins.impl.virtualdub;\r
-\r
-import java.io.BufferedReader;\r
-import java.io.BufferedWriter;\r
-import java.io.File;\r
-import java.io.FileWriter;\r
-import java.io.IOException;\r
-import java.io.InputStreamReader;\r
-import java.util.ArrayList;\r
-import java.util.List;\r
-import java.util.Properties;\r
-\r
-import com.nexuiz.demorecorder.application.DemoRecorderApplication;\r
-import com.nexuiz.demorecorder.application.DemoRecorderException;\r
-import com.nexuiz.demorecorder.application.DemoRecorderUtils;\r
-import com.nexuiz.demorecorder.application.jobs.RecordJob;\r
-import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;\r
-import com.nexuiz.demorecorder.application.plugins.EncoderPluginException;\r
-\r
-public class VirtualDubPlugin implements EncoderPlugin {\r
-       \r
-       private static final String PLUGIN_NAME = "Virtual Dub";\r
-       \r
-       private static class Preferences {\r
-               public static final String ENABLED = "Enabled";\r
-               public static final String VIRTUAL_DUB_BINARY_PATH = "Path to vdub.exe";\r
-               public static final String VCF_PER_JOB_LIMIT = "Max. number of VCFs per job";\r
-               public static final String OUTPUT_FILE_MODE = "Output as suffix (0) or file (1)";\r
-               public static final String EXTRA_OPTIONS = "Show extra options";\r
-               \r
-               public static final String[] GLOBAL_PREFERENCES_ORDER = {\r
-                       ENABLED,\r
-                       VIRTUAL_DUB_BINARY_PATH,\r
-                       VCF_PER_JOB_LIMIT,\r
-                       OUTPUT_FILE_MODE,\r
-                       EXTRA_OPTIONS\r
-               };\r
-               \r
-               //job-specific preferences\r
-               public static final String CLEAR_JOBCONTROL = "Clear VDub job control on first VCF";\r
-               public static final String RENDER_OUTPUT = "VDub renders queued jobs";\r
-               public static final String VCF_PATH = "Path to VCF file "; //x will be attached, e.g. "Path to VCF file 1"\r
-               public static final String OUTPUT_SUFFIX = "Suffix for output file "; //x will be attached, e.g. "Suffix for output file 1"\r
-               public static final String OUTPUT_FILE = "Output file "; //x will be attached\r
-               public static final String USE_ENCODED_VIDEO = "<HTML><BODY>Use encoded video from VCF "; //x will be attached\r
-               public static final String USE_ENCODED_VIDEO_2 = "<BR>for consecutive VCFs</BODY></HTML>";\r
-               public static final String DELETE_ORIG_FILE = "Delete orig. file after processing VCF "; //x will be attached\r
-       }\r
-       \r
-       private DemoRecorderApplication appLayer = null;\r
-       private Properties globalDefaultPreferences = new Properties();\r
-       \r
-       public VirtualDubPlugin() {\r
-               this.createPreferenceDefaultValues();\r
-       }\r
-\r
-       @Override\r
-       public void executeEncoder(RecordJob job) throws EncoderPluginException {\r
-               this.checkAppLayer();\r
-               if (!this.isEnabled()) {\r
-                       return;\r
-               }\r
-               \r
-               if (job.getActualVideoDestination() == null) {\r
-                       //should never happen... but just to make sure!\r
-                       throw new EncoderPluginException("Actual video destination is not set (should have been set when processing the job)");\r
-               }\r
-               \r
-               if (!job.getActualVideoDestination().exists()) {\r
-                       throw new EncoderPluginException("Could not locate video file (source) at location "\r
-                                       + job.getActualVideoDestination().getAbsolutePath());\r
-               }\r
-               \r
-               String limitStr = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VCF_PER_JOB_LIMIT);\r
-               int vcfCounter;\r
-               try {\r
-                       vcfCounter = Integer.valueOf(limitStr);\r
-               } catch (NumberFormatException e) {\r
-                       throw new EncoderPluginException("Invalid value \"" + limitStr + "\" for setting " + Preferences.VCF_PER_JOB_LIMIT);\r
-               }\r
-               \r
-               //check vdub.exe\r
-               String vDubBinary = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VIRTUAL_DUB_BINARY_PATH);\r
-               File vDubBinaryFile = new File(vDubBinary);\r
-               if (!vDubBinaryFile.exists() || !vDubBinaryFile.canExecute()) {\r
-                       throw new EncoderPluginException("Invalid location for the vdub.exe: " + vDubBinary);\r
-               }\r
-               \r
-               this.doEncoding(job, vcfCounter);\r
-       }\r
-\r
-       @Override\r
-       public Properties getGlobalPreferences() {\r
-               return this.globalDefaultPreferences;\r
-       }\r
-\r
-       @Override\r
-       public String[] getGlobalPreferencesOrder() {\r
-               return Preferences.GLOBAL_PREFERENCES_ORDER;\r
-       }\r
-\r
-       @Override\r
-       public Properties getJobSpecificPreferences() {\r
-               this.checkAppLayer();\r
-               Properties jobSpecificPreferences = new Properties();\r
-               \r
-               //static properties\r
-               jobSpecificPreferences.setProperty(Preferences.CLEAR_JOBCONTROL, "true");\r
-               jobSpecificPreferences.setProperty(Preferences.RENDER_OUTPUT, "true");\r
-               \r
-               //dynamic properties\r
-               String limitStr = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VCF_PER_JOB_LIMIT);\r
-               try {\r
-                       int limit = Integer.valueOf(limitStr);\r
-                       if (limit > 0) {\r
-                               for (int i = 1; i <= limit; i++) {\r
-                                       jobSpecificPreferences.setProperty(Preferences.VCF_PATH + i, "filechooser");\r
-                                       if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) {\r
-                                               //filechooser\r
-                                               jobSpecificPreferences.setProperty(Preferences.OUTPUT_FILE + i, "filechooser");\r
-                                       } else {\r
-                                               //suffix\r
-                                               jobSpecificPreferences.setProperty(Preferences.OUTPUT_SUFFIX + i, "_vdub" + i);\r
-                                       }\r
-                                       \r
-                                       if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.EXTRA_OPTIONS))) {\r
-                                               String useEncStringKey = Preferences.USE_ENCODED_VIDEO + i + Preferences.USE_ENCODED_VIDEO_2;\r
-                                               jobSpecificPreferences.setProperty(useEncStringKey, "false");\r
-                                               jobSpecificPreferences.setProperty(Preferences.DELETE_ORIG_FILE + i, "false");\r
-                                       }\r
-                               }\r
-                       }\r
-               } catch (NumberFormatException e) {\r
-                       throw new DemoRecorderException("Invalid value \"" + limitStr + "\" for setting " + Preferences.VCF_PER_JOB_LIMIT);\r
-               }\r
-               \r
-               return jobSpecificPreferences;\r
-       }\r
-       \r
-       @Override\r
-       public String[] getJobSpecificPreferencesOrder() {\r
-               this.checkAppLayer();\r
-               List<String> preferencesOrderList = new ArrayList<String>();\r
-               \r
-               //static properties\r
-               preferencesOrderList.add(Preferences.CLEAR_JOBCONTROL);\r
-               preferencesOrderList.add(Preferences.RENDER_OUTPUT);\r
-               \r
-               //dynamic properties\r
-               String limitStr = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VCF_PER_JOB_LIMIT);\r
-               try {\r
-                       int limit = Integer.valueOf(limitStr);\r
-                       if (limit > 0) {\r
-                               for (int i = 1; i <= limit; i++) {\r
-                                       preferencesOrderList.add(Preferences.VCF_PATH + i);\r
-                                       if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) {\r
-                                               //filechooser\r
-                                               preferencesOrderList.add(Preferences.OUTPUT_FILE + i);\r
-                                       } else {\r
-                                               //suffix\r
-                                               preferencesOrderList.add(Preferences.OUTPUT_SUFFIX + i);\r
-                                       }\r
-                                       \r
-                                       if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.EXTRA_OPTIONS))) {\r
-                                               String useEncStringKey = Preferences.USE_ENCODED_VIDEO + i + Preferences.USE_ENCODED_VIDEO_2;\r
-                                               preferencesOrderList.add(useEncStringKey);\r
-                                               preferencesOrderList.add(Preferences.DELETE_ORIG_FILE + i);\r
-                                       }\r
-                               }\r
-                       }\r
-               } catch (NumberFormatException e) {\r
-                       throw new DemoRecorderException("Invalid value \"" + limitStr + "\" for setting " + Preferences.VCF_PER_JOB_LIMIT);\r
-               }\r
-               \r
-               Object[] arr = preferencesOrderList.toArray();\r
-               String[] stringArr = new String[arr.length];\r
-               for (int i = 0; i < arr.length; i++) {\r
-                       stringArr[i] = (String) arr[i];\r
-               }\r
-               \r
-               return stringArr;\r
-       }\r
-\r
-       @Override\r
-       public String getName() {\r
-               return PLUGIN_NAME;\r
-       }\r
-\r
-       @Override\r
-       public boolean isEnabled() {\r
-               this.checkAppLayer();\r
-               String enabledString = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.ENABLED);\r
-               return Boolean.valueOf(enabledString);\r
-       }\r
-\r
-       @Override\r
-       public void setApplicationLayer(DemoRecorderApplication appLayer) {\r
-               this.appLayer = appLayer;\r
-       }\r
-       \r
-       private void checkAppLayer() {\r
-               if (this.appLayer == null) {\r
-                       throw new DemoRecorderException("Error in plugin " + PLUGIN_NAME + "! Application layer not set!");\r
-               }\r
-       }\r
-       \r
-       private void createPreferenceDefaultValues() {\r
-               this.globalDefaultPreferences.setProperty(Preferences.ENABLED, "false");\r
-               this.globalDefaultPreferences.setProperty(Preferences.VIRTUAL_DUB_BINARY_PATH, "filechooser");\r
-               this.globalDefaultPreferences.setProperty(Preferences.VCF_PER_JOB_LIMIT, "1");\r
-               this.globalDefaultPreferences.setProperty(Preferences.OUTPUT_FILE_MODE, "false");\r
-               this.globalDefaultPreferences.setProperty(Preferences.EXTRA_OPTIONS, "false");\r
-       }\r
-       \r
-       private void doEncoding(RecordJob job, int vcfCounter) throws EncoderPluginException {\r
-               boolean firstValidVCF = true;\r
-               for (int i = 1; i <= vcfCounter; i++) {\r
-                       Properties jobSpecificSettings = job.getEncoderPluginSettings(this);\r
-                       String path = jobSpecificSettings.getProperty(Preferences.VCF_PATH + i);\r
-                       if (path != null) {\r
-                               File vcfFile = new File(path);\r
-                               if (vcfFile.exists()) {\r
-                                       if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) {\r
-                                               //filechooser\r
-                                               String outputPath = jobSpecificSettings.getProperty(Preferences.OUTPUT_FILE + i, "filechooser");\r
-                                               if (outputPath == null || outputPath.equals("") || outputPath.equals("filechoose")) {\r
-                                                       //user has not yet selected a file\r
-                                                       continue;\r
-                                               }\r
-                                       } else {\r
-                                               //suffix\r
-                                               String suffix = jobSpecificSettings.getProperty(Preferences.OUTPUT_SUFFIX + i);\r
-                                               if (suffix == null || suffix.equals("")) {\r
-                                                       continue;\r
-                                               }\r
-                                       }\r
-                                       BufferedWriter logWriter = this.getLogWriter(job.getJobName(), i);\r
-                                       this.executeVDub(job, i, firstValidVCF, logWriter);\r
-                                       firstValidVCF = false;\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-       \r
-       private void executeVDub(RecordJob job, int index, boolean firstValidVCF, BufferedWriter logWriter) throws EncoderPluginException {\r
-               String shellString = "";\r
-               Properties jobSpecificSettings = job.getEncoderPluginSettings(this);\r
-               File vcfFile = new File(jobSpecificSettings.getProperty(Preferences.VCF_PATH + index));\r
-               File sourceFile = job.getActualVideoDestination();\r
-               \r
-               String vDubBinary = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VIRTUAL_DUB_BINARY_PATH);\r
-               shellString += '"' + vDubBinary.trim() + '"';\r
-               \r
-               shellString += " /s " + '"' + vcfFile.getAbsolutePath() + '"';\r
-               \r
-               boolean clearJobControl = Boolean.valueOf(jobSpecificSettings.getProperty(Preferences.CLEAR_JOBCONTROL, "true"));\r
-               if (clearJobControl && firstValidVCF) {\r
-                       shellString += " /c";\r
-               }\r
-               \r
-               String outputFilePath = this.getOutputFilePath(job, index);\r
-               File outputFile = new File(outputFilePath);\r
-               shellString += " /p " + '"' + sourceFile.getAbsolutePath() + '"';\r
-               shellString += " " + '"' + outputFilePath + '"';\r
-               \r
-               boolean renderOutput = Boolean.valueOf(jobSpecificSettings.getProperty(Preferences.RENDER_OUTPUT, "true"));\r
-               if (renderOutput) {\r
-                       shellString += " /r";\r
-               }\r
-               \r
-               shellString += " /x";\r
-               \r
-               try {\r
-                       logWriter.write("Executing commandline: " + shellString);\r
-                       logWriter.newLine();\r
-                       File vdubDir = new File(vDubBinary).getParentFile();\r
-                       Process vDubProc;\r
-                       vDubProc = Runtime.getRuntime().exec(shellString, null, vdubDir);\r
-                       vDubProc.getOutputStream();\r
-                       InputStreamReader isr = new InputStreamReader(vDubProc.getInputStream());\r
-                       BufferedReader bufferedInputStream = new BufferedReader(isr);\r
-                       String currentLine;\r
-                       while ((currentLine = bufferedInputStream.readLine()) != null) {\r
-                               logWriter.write(currentLine);\r
-                               logWriter.newLine();\r
-                       }\r
-                       InputStreamReader isrErr = new InputStreamReader(vDubProc.getErrorStream());\r
-                       BufferedReader bufferedInputStreamErr = new BufferedReader(isrErr);\r
-                       while ((currentLine = bufferedInputStreamErr.readLine()) != null) {\r
-                               logWriter.write(currentLine);\r
-                               logWriter.newLine();\r
-                       }\r
-                       logWriter.close();\r
-                       \r
-               } catch (IOException e) {\r
-                       throw new EncoderPluginException("I/O Exception occurred when trying to execute the VDub binary or logging output", e);\r
-               }\r
-               \r
-               //extra options: replace original video with encoded one, possibly delete original one\r
-               if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.EXTRA_OPTIONS))) {\r
-                       String useEncStringKey = Preferences.USE_ENCODED_VIDEO + index + Preferences.USE_ENCODED_VIDEO_2;\r
-                       String useEncVideo = jobSpecificSettings.getProperty(useEncStringKey);\r
-                       File origFile = job.getActualVideoDestination();\r
-                       if (useEncVideo != null && Boolean.valueOf(useEncVideo)) {\r
-                               job.setActualVideoDestination(outputFile);\r
-                       }\r
-                       \r
-                       String deleteOrigFile = jobSpecificSettings.getProperty(Preferences.DELETE_ORIG_FILE + index);\r
-                       if (deleteOrigFile != null && Boolean.valueOf(deleteOrigFile)) {\r
-                               //only delete the original file if the encoded one exists:\r
-                               if (outputFile.exists() && outputFile.length() > 0) {\r
-                                       origFile.delete();\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-       \r
-       private String getOutputFilePath(RecordJob job, int index) {\r
-               File sourceFile = job.getActualVideoDestination();\r
-               String ext = DemoRecorderUtils.getFileExtension(sourceFile);\r
-               String outputFilePath;\r
-               Properties jobSpecificSettings = job.getEncoderPluginSettings(this);\r
-               if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) {\r
-                       //filechooser\r
-                       outputFilePath = jobSpecificSettings.getProperty(Preferences.OUTPUT_FILE + index);\r
-               } else {\r
-                       //suffix\r
-                       outputFilePath = sourceFile.getAbsolutePath();\r
-                       String suffix = jobSpecificSettings.getProperty(Preferences.OUTPUT_SUFFIX + index);\r
-                       int idx = outputFilePath.indexOf("." + ext);\r
-                       outputFilePath = outputFilePath.substring(0, idx);\r
-                       outputFilePath += suffix + "." + ext;\r
-               }\r
-               \r
-               return outputFilePath;\r
-       }\r
-       \r
-       private BufferedWriter getLogWriter(String jobName, int vcfIndex) throws EncoderPluginException {\r
-               File logDir = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.LOGS_DIRNAME, "");\r
-               if (jobName == null || jobName.equals("")) {\r
-                       jobName = "unnamed_job";\r
-               }\r
-               String path = logDir.getAbsolutePath() + File.separator + PLUGIN_NAME + '_' + jobName + '_' + "vcf" + vcfIndex + ".log";\r
-               File logFile = new File(path);\r
-               if (!DemoRecorderUtils.attemptFileCreation(logFile)) {\r
-                       throw new EncoderPluginException("Could not create log file for VDub job at location: " + path);\r
-               }\r
-               try {\r
-                       FileWriter fileWriter = new FileWriter(logFile);\r
-                       return new BufferedWriter(fileWriter);\r
-               } catch (IOException e) {\r
-                       throw new EncoderPluginException("Could not create log file for VDub job at location: " + path, e);\r
-               }\r
-       }\r
-}\r
+package com.nexuiz.demorecorder.application.plugins.impl.virtualdub;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import com.nexuiz.demorecorder.application.DemoRecorderApplication;
+import com.nexuiz.demorecorder.application.DemoRecorderException;
+import com.nexuiz.demorecorder.application.DemoRecorderUtils;
+import com.nexuiz.demorecorder.application.jobs.RecordJob;
+import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
+import com.nexuiz.demorecorder.application.plugins.EncoderPluginException;
+
+public class VirtualDubPlugin implements EncoderPlugin {
+       
+       private static final String PLUGIN_NAME = "Virtual Dub";
+       
+       private static class Preferences {
+               public static final String ENABLED = "Enabled";
+               public static final String VIRTUAL_DUB_BINARY_PATH = "Path to vdub.exe";
+               public static final String VCF_PER_JOB_LIMIT = "Max. number of VCFs per job";
+               public static final String OUTPUT_FILE_MODE = "Output as suffix (0) or file (1)";
+               public static final String EXTRA_OPTIONS = "Show extra options";
+               
+               public static final String[] GLOBAL_PREFERENCES_ORDER = {
+                       ENABLED,
+                       VIRTUAL_DUB_BINARY_PATH,
+                       VCF_PER_JOB_LIMIT,
+                       OUTPUT_FILE_MODE,
+                       EXTRA_OPTIONS
+               };
+               
+               //job-specific preferences
+               public static final String CLEAR_JOBCONTROL = "Clear VDub job control on first VCF";
+               public static final String RENDER_OUTPUT = "VDub renders queued jobs";
+               public static final String VCF_PATH = "Path to VCF file "; //x will be attached, e.g. "Path to VCF file 1"
+               public static final String OUTPUT_SUFFIX = "Suffix for output file "; //x will be attached, e.g. "Suffix for output file 1"
+               public static final String OUTPUT_FILE = "Output file "; //x will be attached
+               public static final String USE_ENCODED_VIDEO = "<HTML><BODY>Use encoded video from VCF "; //x will be attached
+               public static final String USE_ENCODED_VIDEO_2 = "<BR>for consecutive VCFs</BODY></HTML>";
+               public static final String DELETE_ORIG_FILE = "Delete orig. file after processing VCF "; //x will be attached
+       }
+       
+       private DemoRecorderApplication appLayer = null;
+       private Properties globalDefaultPreferences = new Properties();
+       
+       public VirtualDubPlugin() {
+               this.createPreferenceDefaultValues();
+       }
+
+       @Override
+       public void executeEncoder(RecordJob job) throws EncoderPluginException {
+               this.checkAppLayer();
+               if (!this.isEnabled()) {
+                       return;
+               }
+               
+               if (job.getActualVideoDestination() == null) {
+                       //should never happen... but just to make sure!
+                       throw new EncoderPluginException("Actual video destination is not set (should have been set when processing the job)");
+               }
+               
+               if (!job.getActualVideoDestination().exists()) {
+                       throw new EncoderPluginException("Could not locate video file (source) at location "
+                                       + job.getActualVideoDestination().getAbsolutePath());
+               }
+               
+               String limitStr = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VCF_PER_JOB_LIMIT);
+               int vcfCounter;
+               try {
+                       vcfCounter = Integer.valueOf(limitStr);
+               } catch (NumberFormatException e) {
+                       throw new EncoderPluginException("Invalid value \"" + limitStr + "\" for setting " + Preferences.VCF_PER_JOB_LIMIT);
+               }
+               
+               //check vdub.exe
+               String vDubBinary = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VIRTUAL_DUB_BINARY_PATH);
+               File vDubBinaryFile = new File(vDubBinary);
+               if (!vDubBinaryFile.exists() || !vDubBinaryFile.canExecute()) {
+                       throw new EncoderPluginException("Invalid location for the vdub.exe: " + vDubBinary);
+               }
+               
+               this.doEncoding(job, vcfCounter);
+       }
+
+       @Override
+       public Properties getGlobalPreferences() {
+               return this.globalDefaultPreferences;
+       }
+
+       @Override
+       public String[] getGlobalPreferencesOrder() {
+               return Preferences.GLOBAL_PREFERENCES_ORDER;
+       }
+
+       @Override
+       public Properties getJobSpecificPreferences() {
+               this.checkAppLayer();
+               Properties jobSpecificPreferences = new Properties();
+               
+               //static properties
+               jobSpecificPreferences.setProperty(Preferences.CLEAR_JOBCONTROL, "true");
+               jobSpecificPreferences.setProperty(Preferences.RENDER_OUTPUT, "true");
+               
+               //dynamic properties
+               String limitStr = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VCF_PER_JOB_LIMIT);
+               try {
+                       int limit = Integer.valueOf(limitStr);
+                       if (limit > 0) {
+                               for (int i = 1; i <= limit; i++) {
+                                       jobSpecificPreferences.setProperty(Preferences.VCF_PATH + i, "filechooser");
+                                       if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) {
+                                               //filechooser
+                                               jobSpecificPreferences.setProperty(Preferences.OUTPUT_FILE + i, "filechooser");
+                                       } else {
+                                               //suffix
+                                               jobSpecificPreferences.setProperty(Preferences.OUTPUT_SUFFIX + i, "_vdub" + i);
+                                       }
+                                       
+                                       if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.EXTRA_OPTIONS))) {
+                                               String useEncStringKey = Preferences.USE_ENCODED_VIDEO + i + Preferences.USE_ENCODED_VIDEO_2;
+                                               jobSpecificPreferences.setProperty(useEncStringKey, "false");
+                                               jobSpecificPreferences.setProperty(Preferences.DELETE_ORIG_FILE + i, "false");
+                                       }
+                               }
+                       }
+               } catch (NumberFormatException e) {
+                       throw new DemoRecorderException("Invalid value \"" + limitStr + "\" for setting " + Preferences.VCF_PER_JOB_LIMIT);
+               }
+               
+               return jobSpecificPreferences;
+       }
+       
+       @Override
+       public String[] getJobSpecificPreferencesOrder() {
+               this.checkAppLayer();
+               List<String> preferencesOrderList = new ArrayList<String>();
+               
+               //static properties
+               preferencesOrderList.add(Preferences.CLEAR_JOBCONTROL);
+               preferencesOrderList.add(Preferences.RENDER_OUTPUT);
+               
+               //dynamic properties
+               String limitStr = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VCF_PER_JOB_LIMIT);
+               try {
+                       int limit = Integer.valueOf(limitStr);
+                       if (limit > 0) {
+                               for (int i = 1; i <= limit; i++) {
+                                       preferencesOrderList.add(Preferences.VCF_PATH + i);
+                                       if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) {
+                                               //filechooser
+                                               preferencesOrderList.add(Preferences.OUTPUT_FILE + i);
+                                       } else {
+                                               //suffix
+                                               preferencesOrderList.add(Preferences.OUTPUT_SUFFIX + i);
+                                       }
+                                       
+                                       if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.EXTRA_OPTIONS))) {
+                                               String useEncStringKey = Preferences.USE_ENCODED_VIDEO + i + Preferences.USE_ENCODED_VIDEO_2;
+                                               preferencesOrderList.add(useEncStringKey);
+                                               preferencesOrderList.add(Preferences.DELETE_ORIG_FILE + i);
+                                       }
+                               }
+                       }
+               } catch (NumberFormatException e) {
+                       throw new DemoRecorderException("Invalid value \"" + limitStr + "\" for setting " + Preferences.VCF_PER_JOB_LIMIT);
+               }
+               
+               Object[] arr = preferencesOrderList.toArray();
+               String[] stringArr = new String[arr.length];
+               for (int i = 0; i < arr.length; i++) {
+                       stringArr[i] = (String) arr[i];
+               }
+               
+               return stringArr;
+       }
+
+       @Override
+       public String getName() {
+               return PLUGIN_NAME;
+       }
+
+       @Override
+       public boolean isEnabled() {
+               this.checkAppLayer();
+               String enabledString = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.ENABLED);
+               return Boolean.valueOf(enabledString);
+       }
+
+       @Override
+       public void setApplicationLayer(DemoRecorderApplication appLayer) {
+               this.appLayer = appLayer;
+       }
+       
+       private void checkAppLayer() {
+               if (this.appLayer == null) {
+                       throw new DemoRecorderException("Error in plugin " + PLUGIN_NAME + "! Application layer not set!");
+               }
+       }
+       
+       private void createPreferenceDefaultValues() {
+               this.globalDefaultPreferences.setProperty(Preferences.ENABLED, "false");
+               this.globalDefaultPreferences.setProperty(Preferences.VIRTUAL_DUB_BINARY_PATH, "filechooser");
+               this.globalDefaultPreferences.setProperty(Preferences.VCF_PER_JOB_LIMIT, "1");
+               this.globalDefaultPreferences.setProperty(Preferences.OUTPUT_FILE_MODE, "false");
+               this.globalDefaultPreferences.setProperty(Preferences.EXTRA_OPTIONS, "false");
+       }
+       
+       private void doEncoding(RecordJob job, int vcfCounter) throws EncoderPluginException {
+               boolean firstValidVCF = true;
+               for (int i = 1; i <= vcfCounter; i++) {
+                       Properties jobSpecificSettings = job.getEncoderPluginSettings(this);
+                       String path = jobSpecificSettings.getProperty(Preferences.VCF_PATH + i);
+                       if (path != null) {
+                               File vcfFile = new File(path);
+                               if (vcfFile.exists()) {
+                                       if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) {
+                                               //filechooser
+                                               String outputPath = jobSpecificSettings.getProperty(Preferences.OUTPUT_FILE + i, "filechooser");
+                                               if (outputPath == null || outputPath.equals("") || outputPath.equals("filechoose")) {
+                                                       //user has not yet selected a file
+                                                       continue;
+                                               }
+                                       } else {
+                                               //suffix
+                                               String suffix = jobSpecificSettings.getProperty(Preferences.OUTPUT_SUFFIX + i);
+                                               if (suffix == null || suffix.equals("")) {
+                                                       continue;
+                                               }
+                                       }
+                                       BufferedWriter logWriter = this.getLogWriter(job.getJobName(), i);
+                                       this.executeVDub(job, i, firstValidVCF, logWriter);
+                                       firstValidVCF = false;
+                               }
+                       }
+               }
+       }
+       
+       private void executeVDub(RecordJob job, int index, boolean firstValidVCF, BufferedWriter logWriter) throws EncoderPluginException {
+               String shellString = "";
+               Properties jobSpecificSettings = job.getEncoderPluginSettings(this);
+               File vcfFile = new File(jobSpecificSettings.getProperty(Preferences.VCF_PATH + index));
+               File sourceFile = job.getActualVideoDestination();
+               
+               String vDubBinary = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VIRTUAL_DUB_BINARY_PATH);
+               shellString += '"' + vDubBinary.trim() + '"';
+               
+               shellString += " /s " + '"' + vcfFile.getAbsolutePath() + '"';
+               
+               boolean clearJobControl = Boolean.valueOf(jobSpecificSettings.getProperty(Preferences.CLEAR_JOBCONTROL, "true"));
+               if (clearJobControl && firstValidVCF) {
+                       shellString += " /c";
+               }
+               
+               String outputFilePath = this.getOutputFilePath(job, index);
+               File outputFile = new File(outputFilePath);
+               shellString += " /p " + '"' + sourceFile.getAbsolutePath() + '"';
+               shellString += " " + '"' + outputFilePath + '"';
+               
+               boolean renderOutput = Boolean.valueOf(jobSpecificSettings.getProperty(Preferences.RENDER_OUTPUT, "true"));
+               if (renderOutput) {
+                       shellString += " /r";
+               }
+               
+               shellString += " /x";
+               
+               try {
+                       logWriter.write("Executing commandline: " + shellString);
+                       logWriter.newLine();
+                       File vdubDir = new File(vDubBinary).getParentFile();
+                       Process vDubProc;
+                       vDubProc = Runtime.getRuntime().exec(shellString, null, vdubDir);
+                       vDubProc.getOutputStream();
+                       InputStreamReader isr = new InputStreamReader(vDubProc.getInputStream());
+                       BufferedReader bufferedInputStream = new BufferedReader(isr);
+                       String currentLine;
+                       while ((currentLine = bufferedInputStream.readLine()) != null) {
+                               logWriter.write(currentLine);
+                               logWriter.newLine();
+                       }
+                       InputStreamReader isrErr = new InputStreamReader(vDubProc.getErrorStream());
+                       BufferedReader bufferedInputStreamErr = new BufferedReader(isrErr);
+                       while ((currentLine = bufferedInputStreamErr.readLine()) != null) {
+                               logWriter.write(currentLine);
+                               logWriter.newLine();
+                       }
+                       logWriter.close();
+                       
+               } catch (IOException e) {
+                       throw new EncoderPluginException("I/O Exception occurred when trying to execute the VDub binary or logging output", e);
+               }
+               
+               //extra options: replace original video with encoded one, possibly delete original one
+               if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.EXTRA_OPTIONS))) {
+                       String useEncStringKey = Preferences.USE_ENCODED_VIDEO + index + Preferences.USE_ENCODED_VIDEO_2;
+                       String useEncVideo = jobSpecificSettings.getProperty(useEncStringKey);
+                       File origFile = job.getActualVideoDestination();
+                       if (useEncVideo != null && Boolean.valueOf(useEncVideo)) {
+                               job.setActualVideoDestination(outputFile);
+                       }
+                       
+                       String deleteOrigFile = jobSpecificSettings.getProperty(Preferences.DELETE_ORIG_FILE + index);
+                       if (deleteOrigFile != null && Boolean.valueOf(deleteOrigFile)) {
+                               //only delete the original file if the encoded one exists:
+                               if (outputFile.exists() && outputFile.length() > 0) {
+                                       origFile.delete();
+                               }
+                       }
+               }
+       }
+       
+       private String getOutputFilePath(RecordJob job, int index) {
+               File sourceFile = job.getActualVideoDestination();
+               String ext = DemoRecorderUtils.getFileExtension(sourceFile);
+               String outputFilePath;
+               Properties jobSpecificSettings = job.getEncoderPluginSettings(this);
+               if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) {
+                       //filechooser
+                       outputFilePath = jobSpecificSettings.getProperty(Preferences.OUTPUT_FILE + index);
+               } else {
+                       //suffix
+                       outputFilePath = sourceFile.getAbsolutePath();
+                       String suffix = jobSpecificSettings.getProperty(Preferences.OUTPUT_SUFFIX + index);
+                       int idx = outputFilePath.indexOf("." + ext);
+                       outputFilePath = outputFilePath.substring(0, idx);
+                       outputFilePath += suffix + "." + ext;
+               }
+               
+               return outputFilePath;
+       }
+       
+       private BufferedWriter getLogWriter(String jobName, int vcfIndex) throws EncoderPluginException {
+               File logDir = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.LOGS_DIRNAME, "");
+               if (jobName == null || jobName.equals("")) {
+                       jobName = "unnamed_job";
+               }
+               String path = logDir.getAbsolutePath() + File.separator + PLUGIN_NAME + '_' + jobName + '_' + "vcf" + vcfIndex + ".log";
+               File logFile = new File(path);
+               if (!DemoRecorderUtils.attemptFileCreation(logFile)) {
+                       throw new EncoderPluginException("Could not create log file for VDub job at location: " + path);
+               }
+               try {
+                       FileWriter fileWriter = new FileWriter(logFile);
+                       return new BufferedWriter(fileWriter);
+               } catch (IOException e) {
+                       throw new EncoderPluginException("Could not create log file for VDub job at location: " + path, e);
+               }
+       }
+}
index 6625a3bd03fb8b9a54e162eb381d41ca2fcc0dc5..09f3a378a332ba08a15a29ea27a6de9c44e50194 100644 (file)
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\r
-        "http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd">\r
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\r
-<head>\r
-<meta http-equiv="content-type" content="text/html; charset=utf-8" />\r
-<meta name="author" content="Tyler Mulligan of www.detrition.net a.k.a -z- of www.nexuizninjaz.com" />\r
-<meta name="Description" content="Xonotic is a free open-source fast paced first person shooter (FPS) that runs on Windows, Linux and OSX.  Xonotic utilizes the darkplaces engine modeled after the Quake series." />\r
-<meta name="Keywords" content="Xonotic, Nexiuz, first person shooter, darkplaces, open-source game, open source game, free game, linux game, deathmatch, death match, ctf, quake, alientrap, alien trap, ninjaz" />\r
-<title>Xonotic - A free open-source fast paced first person shooter (FPS) for Windows, Linux and OSX</title>\r
-<link rel="stylesheet" href="Docs/htmlfiles/style.css" type="text/css"></link>\r
-<link rel="shortcut icon" href="favicon.ico"></link>\r
-</head>\r
-<body>\r
-<div id="container">\r
-       <div id="left">\r
-               <div id="logo">\r
-\r
-                       <a href="http://www.xonotic.org" title="Xonotic - Simple, fast, intense and completely free"><img src="Docs/htmlfiles/img/nexuiz_logo.jpg" alt="Xonotic is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="201" height="193" border="0" title="Xonotic is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." /></a>\r
-               </div>\r
-               <!-- Sidebar -->\r
-         <div id="sidebar">\r
-                       <a href="news.php" title="Latest Xonotic News"><img src="Docs/htmlfiles/img/headers/latest_news.gif" alt="Latest News" width="168" height="16" border="0" class="imgHeader" style="margin-top:0;" title="Latest Xonotic News" /></a>\r
-                       <div id="news">\r
-                         <div class="newsPost">\r
-                                               <h1><a href="http://xonotic.org/news.php" title="Two Xonotic 1v1 Tourneys">xonotic.org/news - for xonotic news updates</a></h1>\r
-                         </div>\r
-               </div>\r
-                       <!-- Downloads -->\r
-                       <a href="downloads.php" title="Xonotic Downloads (Game, Maps)"><img src="Docs/htmlfiles/img/headers/downloads.gif" alt="Downloads" width="145" height="15" border="0" class="imgHeader" title="Downloads" /></a>\r
-                       <a id="download_xonotic" href="http://www.sourceforge.net/projects/xonotic/" title="Download Xonotic from Source Forge - Left Click">&nbsp;</a>\r
-                       <!--<a id="download_q3_mappack" href="http://downloads.sourceforge.net/xonotic/nexmappack_r2.zip" title="Download Xonotic Q3 Mappack from Source\r
-                       Forge - Left Click">&nbsp;</a>-->\r
-                       <h2 class="page"><a href="http://xonotic.org/downloads.php" title="Xonotic Download Mirrors">Mirrors -&gt;</a></h2>\r
-                       <!-- Help Wanted -->\r
-                       \r
-                       <img src="Docs/htmlfiles/img/headers/help_wanted.gif" alt="Help Wanted" width="165" height="15" class="imgHeader" title="Help Wanted" />                        \r
-                       <form action="https://www.paypal.com/cgi-bin/webscr" method="post" name="donate_form"> <input name="cmd" value="_s-xclick" type="hidden" />\r
-                       <a id="donate" href="javascript:document.donate_form.submit();" title="Help The Aliens, Please Donate">&nbsp;</a>\r
-                       <noscript>\r
-                               <!-- It not an elegant solution but I don't expect many users to arrive with JS turned off -->\r
-                <input name="submit" type="image" src="Docs/htmlfiles/img/donate_button_noscript.jpg" alt="Help this project - Donate to the aliens" width="166" height="43" />\r
-</noscript>\r
-                       <h2 class="page"><a href="http://xonotic.org/donators.php" title="Those who have donated to Xonotic">Donators -&gt;</a></h2>\r
-                       <input name="encrypted" value="-----BEGIN PKCS7-----MIIHBgYJKoZIhvcNAQcEoIIG9zCCBvMCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYBfp40KRLmnxKPY06C4gjvEiWZchbxK6bgD7ZdjhdWO5Vbwo4T4Ro+HE041PVVqIxPlJgO80l3aQpBtfhC66FfM2kIF1BjLs1zzhQM89XoPGViS3e4kbmzxkMnpdiZFmOsR5Fs5NJYiVaMnVGcoQ+K3+KsyOehZGket7GwUeNFMRzELMAkGBSsOAwIaBQAwgYMGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIb1QeJqVIc5SAYOPcI23W84XMGt8mSfHE1Gf0/GZAM7NvqLiHF0BeaecRW1Bc85O0tL9OduZiraGf7WVnAmP5kp1D0irXsA5+N2l15WADxwNQ/GoCAU293l0dAQ7Qy4F3vh6eSii18MaH2KCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA1MDUxMjE5MDQ0OFowIwYJKoZIhvcNAQkEMRYEFC7mlfdaA7Pg2eBhxI5xQTe7ydTtMA0GCSqGSIb3DQEBAQUABIGAfR38tX84huxk9JjvhggcSMxzHbmDxpxInBU6/lbyqAu7iT5KJn7rcJgaH5ZVyKNoNQLGp9IxweBrcMiYUQNVxShm9+hunXhQmj5r7AMGaxNZ0mE8mQRW1ZTaz7TLz1HkDFA+R0Dm8HYyDQA4L505cBiWNEsKC17VwNK1G7CEVvA=-----END PKCS7-----" type="hidden" /></form>\r
-                       \r
-                 <!-- Created By -->\r
-                 <img src="Docs/htmlfiles/img/headers/created_by.gif" alt="Created By" width="145" height="15" class="imgHeader" title="Created By" />\r
-\r
-         <a id="alien_trap" href="http://www.alientrap.org" title="Developed by Alien Trap" target="_blank">&nbsp;</a>   </div>\r
-               <!-- end sidebar -->    </div><!-- end left -->\r
-       <div id="right">\r
-               <div id="header">\r
-                       <a href="http://xonotic.org" title="Xonotic - Simple, fast, intense and completely free"><img src="Docs/htmlfiles/img/nexuiz_header.jpg" alt="Xonotic is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="766" height="107" border="0" title="Xonotic - Simple, fast, intense and completely free" /></a>\r
-               </div>\r
-               <div id="menu">\r
-                       <ul>\r
-\r
-                               <li class="first"><a href="readme.html" title="Xonotic - Latest News">About </a></li>\r
-                               <li><a href="Docs/basics.html" title="Xonotic - Media (Screenshots, videos and graphics)">Xonotic Basics</a></li>\r
-                               <li><a href="Docs/say-esc.html" title="Xonotic Information  (General, System Requirements)">Say Escapes</a></li>\r
-                               <li><a href="Docs/irc.html" title="Nexuiz - Downloads (Game, Maps)">IRC</a></li>\r
-                               <li><a href="Docs/faq.html" title="Official Nexuiz FAQ">FAQ</a></li>\r
-                               <li class="first"></li>\r
-                 </ul>         </div>\r
-               <p id="tagline">Nexuiz is a <b>free</b> open-source first person shooter that runs on <b>Windows</b>, <b>Linux</b> and <b>OSX</b>.</p>\r
-         <div id="content">\r
-                 <p>Nexuiz is  a fast paced  3d deathmatch game  project created online by a team of developers called <a href="http://www.alientrap.org" title="Alientrap - Online video game creation and collaboration" target="_blank">Alientrap</a>. It is available for download for Windows, Mac, and Linux (all the same archive).</p>\r
-               <p>The first version was released May 31st 2005, released entirely GPL and free over the net, a first for a project of its kind. Since then it has been downloaded over 1.5 million times, and the game is still being updated and developed, currently at version 2.4 and new releases being developed.</p>\r
-                       <p>With an advanced UI the user can select between over a dozen different player models with an average of two skins for each and can connect to our master server to play people from all over the world. The game's content and source are released under the <a href="http://www.gnu.org/"><span class="linktext">The GNU General Public License (GPL) is one of the most common open-source licenses and was the license id Software chose to release the Quake1 engine under </span>GPL</a>, meaning it is entirely free and any of it can be used in other free projects, even if modified. This is a first for any large game project of its type. We hope this will support the free game community and encourage more GPL projects.</p>\r
-         <p>If you would like to support the project please see its website for donating:<br /> \r
-           <a href="https://www.paypal.com/ca/cgi-bin/webscr?cmd=_flow&SESSION=IkbSiO6w4rlHaKCfSGGBCFlYXxl9q4yyRG5SnQe6yiMr9KHfUGyEUtYu_Jm&dispatch=5885d80a13c0db1f38432c9462fe731381a7a80e09148cd4af2c34b16e5a6822"><span class="linktext">PayPal - Donate to the aliens!</span></a></p>\r
-               <p><br style="clear:left" />\r
-        </p>\r
-         </div>\r
-         <!-- end content -->\r
-               <div id="footer">\r
-                       <p id="ninja"><a href="http://www.detrition.net" title="Tyler Mulligan's Working Portfolio" target="_blank">page created by</a> <a href="http://www.nexuizninjaz.com" title="Nexuiz Ninjaz - Practicing the ninja arts of Nexuiz" target="_blank">a ninja</a></p>\r
-                       <ul>\r
-\r
-                               <li class="first"><a href="http://alientrap.org/nexuiz/news.php" title="Nexuiz - Latest News">News</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/media.php" title="Nexuiz - Media (Screenshots, videos and graphics)">Media</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/information.php" title="Nexuiz Information  (General, System Requirements)">Info</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/team.php" title="The Nexuiz Team">Team</a></li>\r
-                               <li><a href="http://alientrap.org/nexuiz/links.php" title="Nexuiz Links">Links</a></li>\r
-                               <li><a href="http://alientrap.org/forum/index.php?c=3" title="Official Nexuiz Forums" target="_blank">Forums</a></li>\r
-                               <li><a href="http://planetnexuiz.com/ladder/" title="Nexuiz Ladder" target="_blank">Ladder</a></li>\r
-                               <li><a href="http://planetnexuiz.com/tourney/" title="Nexuiz Tournaments" target="_blank">Tournament</a></li>\r
-                               <li class="first"></li>\r
-                               </ul>           </div><!-- end footer -->\r
-               <p class="subFooter">Come to the Nexuiz IRC channel: <a href="irc://irc.quakenet.org/nexuiz" title="#nexuiz on irc.quakenet.org">#nexuiz on irc.quakenet.org</a> or the team channel: <a href="irc://irc.anynet.org/alientrap" title="#alientrap on irc.anynet.org">#alientrap on irc.anynet.org</a></p>\r
-\r
-               <div id="valid">\r
-                       <a id="valid_css" href="http://jigsaw.w3.org/css-validator/validator?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>\r
-                       <a id="valid_xhtml" href="http://validator.w3.org/check?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>\r
-                       <p>&copy; 2004-2008 Nexuiz.com</p>\r
-               </div>\r
-       </div><!-- end right -->\r
-</div>\r
-</body>\r
-</html>\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<meta name="author" content="Tyler Mulligan of www.detrition.net a.k.a -z- of www.nexuizninjaz.com" />
+<meta name="Description" content="Xonotic is a free open-source fast paced first person shooter (FPS) that runs on Windows, Linux and OSX.  Xonotic utilizes the darkplaces engine modeled after the Quake series." />
+<meta name="Keywords" content="Xonotic, Nexiuz, first person shooter, darkplaces, open-source game, open source game, free game, linux game, deathmatch, death match, ctf, quake, alientrap, alien trap, ninjaz" />
+<title>Xonotic - A free open-source fast paced first person shooter (FPS) for Windows, Linux and OSX</title>
+<link rel="stylesheet" href="Docs/htmlfiles/style.css" type="text/css"></link>
+<link rel="shortcut icon" href="favicon.ico"></link>
+</head>
+<body>
+<div id="container">
+       <div id="left">
+               <div id="logo">
+
+                       <a href="http://www.xonotic.org" title="Xonotic - Simple, fast, intense and completely free"><img src="Docs/htmlfiles/img/nexuiz_logo.jpg" alt="Xonotic is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="201" height="193" border="0" title="Xonotic is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." /></a>
+               </div>
+               <!-- Sidebar -->
+         <div id="sidebar">
+                       <a href="news.php" title="Latest Xonotic News"><img src="Docs/htmlfiles/img/headers/latest_news.gif" alt="Latest News" width="168" height="16" border="0" class="imgHeader" style="margin-top:0;" title="Latest Xonotic News" /></a>
+                       <div id="news">
+                         <div class="newsPost">
+                                               <h1><a href="http://xonotic.org/news.php" title="Two Xonotic 1v1 Tourneys">xonotic.org/news - for xonotic news updates</a></h1>
+                         </div>
+               </div>
+                       <!-- Downloads -->
+                       <a href="downloads.php" title="Xonotic Downloads (Game, Maps)"><img src="Docs/htmlfiles/img/headers/downloads.gif" alt="Downloads" width="145" height="15" border="0" class="imgHeader" title="Downloads" /></a>
+                       <a id="download_xonotic" href="http://www.sourceforge.net/projects/xonotic/" title="Download Xonotic from Source Forge - Left Click">&nbsp;</a>
+                       <!--<a id="download_q3_mappack" href="http://downloads.sourceforge.net/xonotic/nexmappack_r2.zip" title="Download Xonotic Q3 Mappack from Source
+                       Forge - Left Click">&nbsp;</a>-->
+                       <h2 class="page"><a href="http://xonotic.org/downloads.php" title="Xonotic Download Mirrors">Mirrors -&gt;</a></h2>
+                       <!-- Help Wanted -->
+                       
+                       <img src="Docs/htmlfiles/img/headers/help_wanted.gif" alt="Help Wanted" width="165" height="15" class="imgHeader" title="Help Wanted" />                        
+                       <form action="https://www.paypal.com/cgi-bin/webscr" method="post" name="donate_form"> <input name="cmd" value="_s-xclick" type="hidden" />
+                       <a id="donate" href="javascript:document.donate_form.submit();" title="Help The Aliens, Please Donate">&nbsp;</a>
+                       <noscript>
+                               <!-- It not an elegant solution but I don't expect many users to arrive with JS turned off -->
+                <input name="submit" type="image" src="Docs/htmlfiles/img/donate_button_noscript.jpg" alt="Help this project - Donate to the aliens" width="166" height="43" />
+</noscript>
+                       <h2 class="page"><a href="http://xonotic.org/donators.php" title="Those who have donated to Xonotic">Donators -&gt;</a></h2>
+                       <input name="encrypted" value="-----BEGIN PKCS7-----MIIHBgYJKoZIhvcNAQcEoIIG9zCCBvMCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYBfp40KRLmnxKPY06C4gjvEiWZchbxK6bgD7ZdjhdWO5Vbwo4T4Ro+HE041PVVqIxPlJgO80l3aQpBtfhC66FfM2kIF1BjLs1zzhQM89XoPGViS3e4kbmzxkMnpdiZFmOsR5Fs5NJYiVaMnVGcoQ+K3+KsyOehZGket7GwUeNFMRzELMAkGBSsOAwIaBQAwgYMGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIb1QeJqVIc5SAYOPcI23W84XMGt8mSfHE1Gf0/GZAM7NvqLiHF0BeaecRW1Bc85O0tL9OduZiraGf7WVnAmP5kp1D0irXsA5+N2l15WADxwNQ/GoCAU293l0dAQ7Qy4F3vh6eSii18MaH2KCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA1MDUxMjE5MDQ0OFowIwYJKoZIhvcNAQkEMRYEFC7mlfdaA7Pg2eBhxI5xQTe7ydTtMA0GCSqGSIb3DQEBAQUABIGAfR38tX84huxk9JjvhggcSMxzHbmDxpxInBU6/lbyqAu7iT5KJn7rcJgaH5ZVyKNoNQLGp9IxweBrcMiYUQNVxShm9+hunXhQmj5r7AMGaxNZ0mE8mQRW1ZTaz7TLz1HkDFA+R0Dm8HYyDQA4L505cBiWNEsKC17VwNK1G7CEVvA=-----END PKCS7-----" type="hidden" /></form>
+                       
+                 <!-- Created By -->
+                 <img src="Docs/htmlfiles/img/headers/created_by.gif" alt="Created By" width="145" height="15" class="imgHeader" title="Created By" />
+
+         <a id="alien_trap" href="http://www.alientrap.org" title="Developed by Alien Trap" target="_blank">&nbsp;</a>   </div>
+               <!-- end sidebar -->    </div><!-- end left -->
+       <div id="right">
+               <div id="header">
+                       <a href="http://xonotic.org" title="Xonotic - Simple, fast, intense and completely free"><img src="Docs/htmlfiles/img/nexuiz_header.jpg" alt="Xonotic is a free open-source fast paced first person shooter that runs on Windows, Linux and OSX." width="766" height="107" border="0" title="Xonotic - Simple, fast, intense and completely free" /></a>
+               </div>
+               <div id="menu">
+                       <ul>
+
+                               <li class="first"><a href="readme.html" title="Xonotic - Latest News">About </a></li>
+                               <li><a href="Docs/basics.html" title="Xonotic - Media (Screenshots, videos and graphics)">Xonotic Basics</a></li>
+                               <li><a href="Docs/say-esc.html" title="Xonotic Information  (General, System Requirements)">Say Escapes</a></li>
+                               <li><a href="Docs/irc.html" title="Nexuiz - Downloads (Game, Maps)">IRC</a></li>
+                               <li><a href="Docs/faq.html" title="Official Nexuiz FAQ">FAQ</a></li>
+                               <li class="first"></li>
+                 </ul>         </div>
+               <p id="tagline">Nexuiz is a <b>free</b> open-source first person shooter that runs on <b>Windows</b>, <b>Linux</b> and <b>OSX</b>.</p>
+         <div id="content">
+                 <p>Nexuiz is  a fast paced  3d deathmatch game  project created online by a team of developers called <a href="http://www.alientrap.org" title="Alientrap - Online video game creation and collaboration" target="_blank">Alientrap</a>. It is available for download for Windows, Mac, and Linux (all the same archive).</p>
+               <p>The first version was released May 31st 2005, released entirely GPL and free over the net, a first for a project of its kind. Since then it has been downloaded over 1.5 million times, and the game is still being updated and developed, currently at version 2.4 and new releases being developed.</p>
+                       <p>With an advanced UI the user can select between over a dozen different player models with an average of two skins for each and can connect to our master server to play people from all over the world. The game's content and source are released under the <a href="http://www.gnu.org/"><span class="linktext">The GNU General Public License (GPL) is one of the most common open-source licenses and was the license id Software chose to release the Quake1 engine under </span>GPL</a>, meaning it is entirely free and any of it can be used in other free projects, even if modified. This is a first for any large game project of its type. We hope this will support the free game community and encourage more GPL projects.</p>
+         <p>If you would like to support the project please see its website for donating:<br /> 
+           <a href="https://www.paypal.com/ca/cgi-bin/webscr?cmd=_flow&SESSION=IkbSiO6w4rlHaKCfSGGBCFlYXxl9q4yyRG5SnQe6yiMr9KHfUGyEUtYu_Jm&dispatch=5885d80a13c0db1f38432c9462fe731381a7a80e09148cd4af2c34b16e5a6822"><span class="linktext">PayPal - Donate to the aliens!</span></a></p>
+               <p><br style="clear:left" />
+        </p>
+         </div>
+         <!-- end content -->
+               <div id="footer">
+                       <p id="ninja"><a href="http://www.detrition.net" title="Tyler Mulligan's Working Portfolio" target="_blank">page created by</a> <a href="http://www.nexuizninjaz.com" title="Nexuiz Ninjaz - Practicing the ninja arts of Nexuiz" target="_blank">a ninja</a></p>
+                       <ul>
+
+                               <li class="first"><a href="http://alientrap.org/nexuiz/news.php" title="Nexuiz - Latest News">News</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/media.php" title="Nexuiz - Media (Screenshots, videos and graphics)">Media</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/information.php" title="Nexuiz Information  (General, System Requirements)">Info</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/team.php" title="The Nexuiz Team">Team</a></li>
+                               <li><a href="http://alientrap.org/nexuiz/links.php" title="Nexuiz Links">Links</a></li>
+                               <li><a href="http://alientrap.org/forum/index.php?c=3" title="Official Nexuiz Forums" target="_blank">Forums</a></li>
+                               <li><a href="http://planetnexuiz.com/ladder/" title="Nexuiz Ladder" target="_blank">Ladder</a></li>
+                               <li><a href="http://planetnexuiz.com/tourney/" title="Nexuiz Tournaments" target="_blank">Tournament</a></li>
+                               <li class="first"></li>
+                               </ul>           </div><!-- end footer -->
+               <p class="subFooter">Come to the Nexuiz IRC channel: <a href="irc://irc.quakenet.org/nexuiz" title="#nexuiz on irc.quakenet.org">#nexuiz on irc.quakenet.org</a> or the team channel: <a href="irc://irc.anynet.org/alientrap" title="#alientrap on irc.anynet.org">#alientrap on irc.anynet.org</a></p>
+
+               <div id="valid">
+                       <a id="valid_css" href="http://jigsaw.w3.org/css-validator/validator?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>
+                       <a id="valid_xhtml" href="http://validator.w3.org/check?uri=http%3A%2F%2Falientrap.org%2Fnexuiz%2Findex.php">&nbsp;</a>
+                       <p>&copy; 2004-2008 Nexuiz.com</p>
+               </div>
+       </div><!-- end right -->
+</div>
+</body>
+</html>
index 7fdc1a9e0fbbee06e089dbbc00c9d60eee19d480..50b44c17de80e32fe2f57c02ab662b14bc00552b 100644 (file)
@@ -1,52 +1,52 @@
-This subdirectory contains scripts and a config file that can be used\r
-to start a dedicated Xonotic server on linux, mac or windows systems\r
-and also some tools that help with maintaining your server.\r
-\r
-You will need to copy the right script for your system into your\r
-Xonotic main directory, where the normal binaries are.  You then need\r
-to copy and ADJUST the config file which is called server.cfg.  You\r
-can copy it either into the Xonotic/data directory where the big\r
-data*.pk3 file is or when running on linux or mac you can as well copy\r
-it into a special directory named '~/.xonotic/data'.  After you have\r
-setup everything and have adjusted the config file you can start the\r
-server by running the server script.\r
-\r
-Please make sure your server is always uptodate!  Just signup the\r
-Xonotic release mailinglist to get informed about new releases.\r
-https://lists.sourceforge.net/lists/listinfo/xonotic-releases\r
-\r
-An important thing is to make sure that your firewall does allow\r
-players to connect to your server.  This typicly means you will have\r
-to open or forward the port (see the line that sets the variable port\r
-in your config.cfg for the right port number, default is 26000) to the\r
-computer running your server.  How to do this does depend on your\r
-computer and network setup.\r
-\r
-If you plan to install custom maps on your server you should read the\r
-file Xonotic/Docs/mapdownload.txt to learn how to setup automatic map\r
-download.\r
-\r
-In case you want to rename the server.cfg file, e.g. because you want\r
-to run several servers on one machine, you have to edit the script and\r
-change the name there too.\r
-\r
-A very useful tool for running and controlling a server is the\r
-application 'gnu screen'.  It should be available for all usual\r
-operating systems.  You can find some hints about its usage here:\r
-http://jmcpherson.org/screen.html\r
-\r
-The options in the config file are only the most interesting and\r
-important ones.  You can get a list of all available commands and\r
-variables with 'cmdlist' and 'cvarlist' in the server console.\r
-\r
-rcon.pl is a perl script that implements rcon which can be used to\r
-remotely control your server.\r
-\r
-rcon2irc is a Xonotic server to irc gateway.  It allows you to watch\r
-and communicate with active players on your server via irc.  Read its\r
-rcon2irc.txt to learn how to setup and use it!\r
-\r
-help.cfg is a Xonotic config file providing a simple help message\r
-system.  It prints all the messages from a list, one after the other\r
-with an configureable delay between them.  Great to provide beginners\r
-with some hints.\r
+This subdirectory contains scripts and a config file that can be used
+to start a dedicated Xonotic server on linux, mac or windows systems
+and also some tools that help with maintaining your server.
+
+You will need to copy the right script for your system into your
+Xonotic main directory, where the normal binaries are.  You then need
+to copy and ADJUST the config file which is called server.cfg.  You
+can copy it either into the Xonotic/data directory where the big
+data*.pk3 file is or when running on linux or mac you can as well copy
+it into a special directory named '~/.xonotic/data'.  After you have
+setup everything and have adjusted the config file you can start the
+server by running the server script.
+
+Please make sure your server is always uptodate!  Just signup the
+Xonotic release mailinglist to get informed about new releases.
+https://lists.sourceforge.net/lists/listinfo/xonotic-releases
+
+An important thing is to make sure that your firewall does allow
+players to connect to your server.  This typicly means you will have
+to open or forward the port (see the line that sets the variable port
+in your config.cfg for the right port number, default is 26000) to the
+computer running your server.  How to do this does depend on your
+computer and network setup.
+
+If you plan to install custom maps on your server you should read the
+file Xonotic/Docs/mapdownload.txt to learn how to setup automatic map
+download.
+
+In case you want to rename the server.cfg file, e.g. because you want
+to run several servers on one machine, you have to edit the script and
+change the name there too.
+
+A very useful tool for running and controlling a server is the
+application 'gnu screen'.  It should be available for all usual
+operating systems.  You can find some hints about its usage here:
+http://jmcpherson.org/screen.html
+
+The options in the config file are only the most interesting and
+important ones.  You can get a list of all available commands and
+variables with 'cmdlist' and 'cvarlist' in the server console.
+
+rcon.pl is a perl script that implements rcon which can be used to
+remotely control your server.
+
+rcon2irc is a Xonotic server to irc gateway.  It allows you to watch
+and communicate with active players on your server via irc.  Read its
+rcon2irc.txt to learn how to setup and use it!
+
+help.cfg is a Xonotic config file providing a simple help message
+system.  It prints all the messages from a list, one after the other
+with an configureable delay between them.  Great to provide beginners
+with some hints.
index 9de5639551927509fa24e2f4eca1844c257dbe80..9de6f514231e567d05dde0ace16afeb93573d318 100644 (file)
-// A setting needs to be surrounded with " if it contains special\r
-// characters like space, semicolon (seperates commands) or starts\r
-// with // (begins a comment for the rest of the line or next\r
-// semicolon).  While just "" means 'no/empty setting'.\r
-\r
-/////////////////////////////////////////////////////////////////////\r
-// Everything has some sensible defaults so you do NOT really need to\r
-// change anything.  But here are some settings you SHOULD change to\r
-// make it 'YOUR' server.  IF so remove the // from the line start and\r
-// change them to your liking.\r
-\r
-//sv_public 1  // 0 if the server should not be on the public servers list\r
-//sv_status_privacy 1  // 1 hides IP addresses from "status" replies shown to clients, 0 shows them. Enables players to identify wellknown punks on your server OR is a thread to anonymity.. depending on your point of view :)\r
-//hostname "Xonotic $g_xonoticversion Server"  // this name will appear on the server list (the $g_xonoticversion gets replaced with the current version)\r
-//sv_motd ""   // this message (unless "") is displayed to players on connect\r
-//maxplayers 8 // number of players allowed on the server\r
-//port 26000   // the port used by the server\r
-//log_file "server.log"        // server console output will also be copied to this file. This: "${serverconfig}.log" uses variable substitution to make the logfile have the same name as your config file with .log appended which is useful if you run several servers\r
-\r
-// List of maps to play on the server.  Set to "" to autodetect (which would enumerate ALL maps.  Maps that don't support the current game mode will be skipped, so you don't need to remove them here.\r
-//g_maplist "accident aggressor aneurysm basement basementctf bleach bloodprison bloodprisonctf bluesky cyberparcour01 darkzone desertfactory dieselpower downer eggandbacon evilspace farewell final_rage nr_piece-o-cake ons-reborn racetrack reslimed ruiner runningman runningman_1on1remix runningmanctf silvercity skyway slimepit soylent starship stormkeep2 strength toxic warfare"\r
-//g_maplist_shuffle 1  // 0 always selects the next map out of g_maplist (but then better set g_maplist_votable 0 below to prevent repetitive votes), 1 will select random maps each time\r
-//g_maplist_mostrecent_count 3 // number of maps that have to be played before a map can be repeated\r
-\r
-// LIMITS: how long a match will last before the next map starts (-1 means use the map default, 0 = unlimited, please note that the variables fraglimit/timelimit can NOT be globally used for this but only DURING a match is being played)\r
-//timelimit_override -1        // global timelimit for all maps and gametypes\r
-//fraglimit_override -1        // global frag/point limit for all maps and gametypes\r
-//g_ctf_capture_limit -1       // frag/capture limit for CTF\r
-//capturelimit_override -1     // capture limit for CTF\r
-//g_domination_point_limit -1  // frag/point limit for DOM\r
-//g_runematch_point_limit -1   // frag/point limit for RUNE\r
-//g_keyhunt_point_limit -1     // frag/point limit for KH\r
-//g_lms_lives_override -1      // lives for LMS\r
-//g_nexball_goallimit -1       // goals for NEXBALL\r
-\r
-//g_ctf_win_mode 0     // CTF win mode: 0 = caps only, 1 = caps + points as tie breaker, 2 = points only\r
-//g_ctf_ignore_frags 1 // set to 1 to ignore kills except for FC kills\r
-\r
-// TEAMS for key hunt (change this to 2, 3, 4 to set a fixed keyhunt style)\r
-//g_keyhunt_teams_override -1  // teams for KH\r
-\r
-// GAME MODE: what type of server do you want to host?\r
-// possible values: dm (deathmatch), tdm (team deathmatch), dom (domination), ctf (ctf), rune (runematch), lms (last man standing), arena (arena), kh (keyhunt), as (assault), ons (onslaught), race (race), nexball (nexball)\r
-//gametype dm\r
-\r
-\r
-// enable some mutators you'd like\r
-//g_cloaked 0  // set to 1 for transparent hard to see players\r
-//g_footsteps 0        // set to 1 to enable footsteps\r
-//g_grappling_hook 0   // set to 1 to enable the hook\r
-//g_laserguided_missile 0      // set to 1 for laser guided missiles in the RL\r
-//g_midair 0   // set to 1 to make only midair damage count\r
-//g_vampire 0  // set to 1 to give players the damage they cause as health\r
-//sv_gravity 800       // for low gravity, set this somewhere between 50 and 400\r
-\r
-// select AT MOST one of these arena mutators\r
-//g_minstagib 0        // set to 1 for MinstaGib\r
-//g_nixnex 0   // set to 1 for No Items Xonotic\r
-//g_nixnex_with_laser 0        // set to 1 to always give players the laser in NixNex\r
-//g_rocketarena 0      // set to 1 to make the rocket launcher the only weapon\r
-\r
-/////////////////////////////////////////////////////////////////////\r
-// Some settings you MIGHT want to change.  Again IF so remove the //\r
-// from the line start and change them to your liking.\r
-\r
-//net_address 0.0.0.0  // ONLY change this if you have problems with your server. Specifies the network interface used by the engine.  Set it you one of you internal IPs\r
-\r
-//rcon_password ""     // you can use rcon if this password is set.  you need to set the same password in your client if you want to issue rcon commands\r
-//rcon_restricted_password ""  // put here the password for a "restricted" rcon user (see below)\r
-\r
-//bot_number 0 // number of bots to add\r
-//skill 1      // the bots skill level\r
-//minplayers 0 // add bots if less then that number of players playing. MAKE SURE ITS LESS THEN maxplayers or people will get blocked by bots!\r
-//bot_prefix [BOT]     // prepend this to all botnames\r
-//bot_suffix ""        // append this to all botnames\r
-//g_maplist_check_waypoints 0  // set this to 1 to skip maps without waypoints if there aren't enough players to play without bots\r
-\r
-//g_maplist_votable 6  // number of maps to vote between.  set to 0 to disable the map voting screen (please enable g_maplist_shuffle above when this is enabled, or votes will be repetitive)\r
-//g_maplist_votable_suggestions 2      // number of suggestions to accept using the suggestmap command\r
-//g_maplist_votable_abstain 0  // when 1, people get a "don't care" selection in the vote screen\r
-//g_maplist_votable_nodetail 1 // when 1, people can't see how many voted for what (to thwart abusive "influential" first votes)\r
-\r
-//sv_vote_commands "restart fraglimit chmap gotomap nextmap endmatch reducematchtime extendmatchtime allready kick cointoss movetoteam_auto"   // players can vote for those commands or use them if they are masters. You canm also add 'g_grappling_hook' for hook voting, and 'sv_fbskin_green sv_fbskin_red sv_fbskin_orange sv_fbskin_off' for fbskin voting.\r
-//sv_vote_master_commands "movetoteam_red movetoteam_blue movetoteam_yellow movetoteam_pink"   // add commands masters can use if logged in or elected. You may want to put 'kickban' here, so masters can keep out punks. It may be good to also put "sv_status_privacy 0" then...\r
-//rcon_restricted_commands "restart fraglimit chmap gotomap endmatch reducematchtime extendmatchtime allready kick kickban \"sv_cmd bans\" \"sv_cmd unban\" status \"sv_cmd teamstatus\" movetoteam_auto movetoteam_red movetoteam_blue movetoteam_yellow movetoteam_pink"     // commands for the (stronger) rcon restricted\r
-\r
-//sv_vote_call 1       // 0 will disable the normal voting\r
-//sv_vote_master 1     // 0 will disable voting to become master, good if you prefer to use the master password instead\r
-//sv_vote_master_password ""   // when set, vlogin PWD will allow people to become master to run master commands directly using vdo\r
-//sv_vote_majority_factor 0.5  // 0.666 will require a 2/3 majority instead of a regular 1/2 one\r
-//sv_vote_simple_majority_factor 0     // 0.666 will win votes by a 2/3 majority of the VOTERS (not the players!)\r
-//note: to JUST support simple majorities, set these two factors equal\r
-\r
-/////////////////////////////////////////////////////////////////////\r
-// Some more advanced settings.  You probably are not interested in changing them at all.\r
-\r
-// select one of these:\r
-//g_balance_rocketlauncher_detonatedelay 0     // rocket detonation at any time\r
-//g_balance_rocketlauncher_detonatedelay 0.2   // rocket detonation at least 0.2s after it was shot (reduces rocket boosting)\r
-//g_balance_rocketlauncher_detonatedelay -1    // rocket detonation only if the rocket is out of range of the owner (prevents rocket boosting, makes rockets "safer")\r
-\r
-//sv_ready_restart 0   // 1 to allow players to restart the map when all of them press the ready button\r
-//sv_autoscreenshot 0  // 1 will force a screenshot for all clients when the map ends.  Useful for competitions or the ladder.\r
-\r
-//g_antilag 2  // 0 to disable the antilag feature, 1 and 2 use different methods\r
-\r
-//sys_ticrate 0.05     // how long a server frame is.  0.05 = 20 fps, 0.02 = 50 fps.  Lower settings makes things smoother but create much more traffic (known good values include: 0.05, 0.046875, 0.03125)\r
-//sv_maxrate 1000000   // limits client connections to this rate in bytes/seconds\r
-\r
-// see Docs/mapdownload.txt for more info\r
-//sv_curl_defaulturl http://www.xonotic.com/contentdownload/getmap.php?file=   //fallback download URL\r
-\r
-//sv_defaultcharacter 0        // 1 will force a specific model for all players\r
-//sv_defaultplayermodel models/player/nexus.zym        // this model will be used\r
-//sv_defaultplayerskin 0       // this skin number will be forced for all players\r
-//g_fullbrightplayers 0        // 1 to make players fullbright\r
-//g_fullbrightitems 0  // 1 to make items fullbright\r
-\r
-//g_balance_teams 1    // 0 will show players the team selection menu after joining instead of automaticly putting them on the smaller team\r
-//g_balance_teams_force 0      // 1 to automaticly balance teams even during a game\r
-\r
-//set g_ban_sync_uri "http://94.23.21.40/~xonotic/bans/" // sync bans using this ban list provider (disabled by default, uncomment this line to enable)\r
-//set g_ban_sync_trusted_servers "" // accept bans that were initially set on the server IPs listed here (if not set, your bans are just sent to the sync URIs, but no bans are retrieved from there)\r
+// A setting needs to be surrounded with " if it contains special
+// characters like space, semicolon (seperates commands) or starts
+// with // (begins a comment for the rest of the line or next
+// semicolon).  While just "" means 'no/empty setting'.
+
+/////////////////////////////////////////////////////////////////////
+// Everything has some sensible defaults so you do NOT really need to
+// change anything.  But here are some settings you SHOULD change to
+// make it 'YOUR' server.  IF so remove the // from the line start and
+// change them to your liking.
+
+//sv_public 1  // 0 if the server should not be on the public servers list
+//sv_status_privacy 1  // 1 hides IP addresses from "status" replies shown to clients, 0 shows them. Enables players to identify wellknown punks on your server OR is a thread to anonymity.. depending on your point of view :)
+//hostname "Xonotic $g_xonoticversion Server"  // this name will appear on the server list (the $g_xonoticversion gets replaced with the current version)
+//sv_motd ""   // this message (unless "") is displayed to players on connect
+//maxplayers 8 // number of players allowed on the server
+//port 26000   // the port used by the server
+//log_file "server.log"        // server console output will also be copied to this file. This: "${serverconfig}.log" uses variable substitution to make the logfile have the same name as your config file with .log appended which is useful if you run several servers
+
+// List of maps to play on the server.  Set to "" to autodetect (which would enumerate ALL maps.  Maps that don't support the current game mode will be skipped, so you don't need to remove them here.
+//g_maplist "accident aggressor aneurysm basement basementctf bleach bloodprison bloodprisonctf bluesky cyberparcour01 darkzone desertfactory dieselpower downer eggandbacon evilspace farewell final_rage nr_piece-o-cake ons-reborn racetrack reslimed ruiner runningman runningman_1on1remix runningmanctf silvercity skyway slimepit soylent starship stormkeep2 strength toxic warfare"
+//g_maplist_shuffle 1  // 0 always selects the next map out of g_maplist (but then better set g_maplist_votable 0 below to prevent repetitive votes), 1 will select random maps each time
+//g_maplist_mostrecent_count 3 // number of maps that have to be played before a map can be repeated
+
+// LIMITS: how long a match will last before the next map starts (-1 means use the map default, 0 = unlimited, please note that the variables fraglimit/timelimit can NOT be globally used for this but only DURING a match is being played)
+//timelimit_override -1        // global timelimit for all maps and gametypes
+//fraglimit_override -1        // global frag/point limit for all maps and gametypes
+//g_ctf_capture_limit -1       // frag/capture limit for CTF
+//capturelimit_override -1     // capture limit for CTF
+//g_domination_point_limit -1  // frag/point limit for DOM
+//g_runematch_point_limit -1   // frag/point limit for RUNE
+//g_keyhunt_point_limit -1     // frag/point limit for KH
+//g_lms_lives_override -1      // lives for LMS
+//g_nexball_goallimit -1       // goals for NEXBALL
+
+//g_ctf_win_mode 0     // CTF win mode: 0 = caps only, 1 = caps + points as tie breaker, 2 = points only
+//g_ctf_ignore_frags 1 // set to 1 to ignore kills except for FC kills
+
+// TEAMS for key hunt (change this to 2, 3, 4 to set a fixed keyhunt style)
+//g_keyhunt_teams_override -1  // teams for KH
+
+// GAME MODE: what type of server do you want to host?
+// possible values: dm (deathmatch), tdm (team deathmatch), dom (domination), ctf (ctf), rune (runematch), lms (last man standing), arena (arena), kh (keyhunt), as (assault), ons (onslaught), race (race), nexball (nexball)
+//gametype dm
+
+
+// enable some mutators you'd like
+//g_cloaked 0  // set to 1 for transparent hard to see players
+//g_footsteps 0        // set to 1 to enable footsteps
+//g_grappling_hook 0   // set to 1 to enable the hook
+//g_laserguided_missile 0      // set to 1 for laser guided missiles in the RL
+//g_midair 0   // set to 1 to make only midair damage count
+//g_vampire 0  // set to 1 to give players the damage they cause as health
+//sv_gravity 800       // for low gravity, set this somewhere between 50 and 400
+
+// select AT MOST one of these arena mutators
+//g_minstagib 0        // set to 1 for MinstaGib
+//g_nixnex 0   // set to 1 for No Items Xonotic
+//g_nixnex_with_laser 0        // set to 1 to always give players the laser in NixNex
+//g_rocketarena 0      // set to 1 to make the rocket launcher the only weapon
+
+/////////////////////////////////////////////////////////////////////
+// Some settings you MIGHT want to change.  Again IF so remove the //
+// from the line start and change them to your liking.
+
+//net_address 0.0.0.0  // ONLY change this if you have problems with your server. Specifies the network interface used by the engine.  Set it you one of you internal IPs
+
+//rcon_password ""     // you can use rcon if this password is set.  you need to set the same password in your client if you want to issue rcon commands
+//rcon_restricted_password ""  // put here the password for a "restricted" rcon user (see below)
+
+//bot_number 0 // number of bots to add
+//skill 1      // the bots skill level
+//minplayers 0 // add bots if less then that number of players playing. MAKE SURE ITS LESS THEN maxplayers or people will get blocked by bots!
+//bot_prefix [BOT]     // prepend this to all botnames
+//bot_suffix ""        // append this to all botnames
+//g_maplist_check_waypoints 0  // set this to 1 to skip maps without waypoints if there aren't enough players to play without bots
+
+//g_maplist_votable 6  // number of maps to vote between.  set to 0 to disable the map voting screen (please enable g_maplist_shuffle above when this is enabled, or votes will be repetitive)
+//g_maplist_votable_suggestions 2      // number of suggestions to accept using the suggestmap command
+//g_maplist_votable_abstain 0  // when 1, people get a "don't care" selection in the vote screen
+//g_maplist_votable_nodetail 1 // when 1, people can't see how many voted for what (to thwart abusive "influential" first votes)
+
+//sv_vote_commands "restart fraglimit chmap gotomap nextmap endmatch reducematchtime extendmatchtime allready kick cointoss movetoteam_auto"   // players can vote for those commands or use them if they are masters. You canm also add 'g_grappling_hook' for hook voting, and 'sv_fbskin_green sv_fbskin_red sv_fbskin_orange sv_fbskin_off' for fbskin voting.
+//sv_vote_master_commands "movetoteam_red movetoteam_blue movetoteam_yellow movetoteam_pink"   // add commands masters can use if logged in or elected. You may want to put 'kickban' here, so masters can keep out punks. It may be good to also put "sv_status_privacy 0" then...
+//rcon_restricted_commands "restart fraglimit chmap gotomap endmatch reducematchtime extendmatchtime allready kick kickban \"sv_cmd bans\" \"sv_cmd unban\" status \"sv_cmd teamstatus\" movetoteam_auto movetoteam_red movetoteam_blue movetoteam_yellow movetoteam_pink"     // commands for the (stronger) rcon restricted
+
+//sv_vote_call 1       // 0 will disable the normal voting
+//sv_vote_master 1     // 0 will disable voting to become master, good if you prefer to use the master password instead
+//sv_vote_master_password ""   // when set, vlogin PWD will allow people to become master to run master commands directly using vdo
+//sv_vote_majority_factor 0.5  // 0.666 will require a 2/3 majority instead of a regular 1/2 one
+//sv_vote_simple_majority_factor 0     // 0.666 will win votes by a 2/3 majority of the VOTERS (not the players!)
+//note: to JUST support simple majorities, set these two factors equal
+
+/////////////////////////////////////////////////////////////////////
+// Some more advanced settings.  You probably are not interested in changing them at all.
+
+// select one of these:
+//g_balance_rocketlauncher_detonatedelay 0     // rocket detonation at any time
+//g_balance_rocketlauncher_detonatedelay 0.2   // rocket detonation at least 0.2s after it was shot (reduces rocket boosting)
+//g_balance_rocketlauncher_detonatedelay -1    // rocket detonation only if the rocket is out of range of the owner (prevents rocket boosting, makes rockets "safer")
+
+//sv_ready_restart 0   // 1 to allow players to restart the map when all of them press the ready button
+//sv_autoscreenshot 0  // 1 will force a screenshot for all clients when the map ends.  Useful for competitions or the ladder.
+
+//g_antilag 2  // 0 to disable the antilag feature, 1 and 2 use different methods
+
+//sys_ticrate 0.05     // how long a server frame is.  0.05 = 20 fps, 0.02 = 50 fps.  Lower settings makes things smoother but create much more traffic (known good values include: 0.05, 0.046875, 0.03125)
+//sv_maxrate 1000000   // limits client connections to this rate in bytes/seconds
+
+// see Docs/mapdownload.txt for more info
+//sv_curl_defaulturl http://www.xonotic.com/contentdownload/getmap.php?file=   //fallback download URL
+
+//sv_defaultcharacter 0        // 1 will force a specific model for all players
+//sv_defaultplayermodel models/player/nexus.zym        // this model will be used
+//sv_defaultplayerskin 0       // this skin number will be forced for all players
+//g_fullbrightplayers 0        // 1 to make players fullbright
+//g_fullbrightitems 0  // 1 to make items fullbright
+
+//g_balance_teams 1    // 0 will show players the team selection menu after joining instead of automaticly putting them on the smaller team
+//g_balance_teams_force 0      // 1 to automaticly balance teams even during a game
+
+//set g_ban_sync_uri "http://94.23.21.40/~xonotic/bans/" // sync bans using this ban list provider (disabled by default, uncomment this line to enable)
+//set g_ban_sync_trusted_servers "" // accept bans that were initially set on the server IPs listed here (if not set, your bans are just sent to the sync URIs, but no bans are retrieved from there)